SEBA-672 model update commands

Change-Id: I7fef3a4c1ee5ccb8c33a01f37a772142a82249c1
diff --git a/commands/orm.go b/commands/orm.go
index 8a4108e..65304a2 100644
--- a/commands/orm.go
+++ b/commands/orm.go
@@ -51,21 +51,21 @@
 //    "==foo"  --> "EQUAL", "foo"
 func DecodeOperator(query string) (string, string, bool, error) {
 	if strings.HasPrefix(query, "!=") {
-		return query[2:], "EQUAL", true, nil
+		return strings.TrimSpace(query[2:]), "EQUAL", true, nil
 	} else if strings.HasPrefix(query, "==") {
 		return "", "", false, errors.New("Operator == is now allowed. Suggest using = instead.")
 	} else if strings.HasPrefix(query, "=") {
-		return query[1:], "EQUAL", false, nil
+		return strings.TrimSpace(query[1:]), "EQUAL", false, nil
 	} else if strings.HasPrefix(query, ">=") {
-		return query[2:], "GREATER_THAN_OR_EQUAL", false, nil
+		return strings.TrimSpace(query[2:]), "GREATER_THAN_OR_EQUAL", false, nil
 	} else if strings.HasPrefix(query, ">") {
-		return query[1:], "GREATER_THAN", false, nil
+		return strings.TrimSpace(query[1:]), "GREATER_THAN", false, nil
 	} else if strings.HasPrefix(query, "<=") {
-		return query[2:], "LESS_THAN_OR_EQUAL", false, nil
+		return strings.TrimSpace(query[2:]), "LESS_THAN_OR_EQUAL", false, nil
 	} else if strings.HasPrefix(query, "<") {
-		return query[1:], "LESS_THAN", false, nil
+		return strings.TrimSpace(query[1:]), "LESS_THAN", false, nil
 	} else {
-		return query, "EQUAL", false, nil
+		return strings.TrimSpace(query), "EQUAL", false, nil
 	}
 }
 
@@ -95,15 +95,32 @@
 
 		nm := dynamic.NewMessage(elements_mt)
 
-		field_type := h.Model.FindFieldByName(field_name).GetType()
-		if field_type == descriptor.FieldDescriptorProto_TYPE_INT32 {
-			i, _ := strconv.ParseInt(value, 10, 32)
+		field_descriptor := h.Model.FindFieldByName(field_name)
+		if field_descriptor == nil {
+			return fmt.Errorf("Field %s does not exist", field_name)
+		}
+
+		field_type := field_descriptor.GetType()
+		switch field_type {
+		case descriptor.FieldDescriptorProto_TYPE_INT32:
+			var i int64
+			i, err = strconv.ParseInt(value, 10, 32)
 			nm.SetFieldByName("iValue", int32(i))
-		} else if field_type == descriptor.FieldDescriptorProto_TYPE_UINT32 {
-			i, _ := strconv.ParseInt(value, 10, 32)
+		case descriptor.FieldDescriptorProto_TYPE_UINT32:
+			var i int64
+			i, err = strconv.ParseInt(value, 10, 32)
 			nm.SetFieldByName("iValue", uint32(i))
-		} else {
+		case descriptor.FieldDescriptorProto_TYPE_FLOAT:
+			err = errors.New("Floating point filters are unsupported")
+		case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
+			err = errors.New("Floating point filters are unsupported")
+		default:
 			nm.SetFieldByName("sValue", value)
+			err = nil
+		}
+
+		if err != nil {
+			return err
 		}
 
 		nm.SetFieldByName("name", field_name)
@@ -120,33 +137,83 @@
 }
 
 // Take a string list of queries and turns it into a map of queries
-func QueryStringsToMap(query_args []string) (map[string]string, error) {
+func QueryStringsToMap(query_args []string, allow_inequality bool) (map[string]string, error) {
 	queries := make(map[string]string)
 	for _, query_str := range query_args {
-		query_str := strings.Trim(query_str, " ")
+		query_str := strings.TrimSpace(query_str)
 		operator_pos := -1
 		for i, ch := range query_str {
-			if (ch == '!') || (ch == '=') || (ch == '>') || (ch == '<') {
-				operator_pos = i
-				break
+			if allow_inequality {
+				if (ch == '!') || (ch == '=') || (ch == '>') || (ch == '<') {
+					operator_pos = i
+					break
+				}
+			} else {
+				if ch == '=' {
+					operator_pos = i
+					break
+				}
 			}
 		}
 		if operator_pos == -1 {
-			return nil, fmt.Errorf("Illegal query string %s", query_str)
+			return nil, fmt.Errorf("Illegal operator/value string %s", query_str)
 		}
-		queries[query_str[:operator_pos]] = query_str[operator_pos:]
+		queries[strings.TrimSpace(query_str[:operator_pos])] = query_str[operator_pos:]
 	}
 	return queries, nil
 }
 
 // Take a string of comma-separated queries and turn it into a map of queries
-func CommaSeparatedQueryToMap(query_str string) (map[string]string, error) {
+func CommaSeparatedQueryToMap(query_str string, allow_inequality bool) (map[string]string, error) {
 	if query_str == "" {
 		return nil, nil
 	}
 
 	query_strings := strings.Split(query_str, ",")
-	return QueryStringsToMap(query_strings)
+	return QueryStringsToMap(query_strings, allow_inequality)
+}
+
+// Convert a string into the appropriate gRPC type for a given field
+func TypeConvert(source grpcurl.DescriptorSource, modelName string, field_name string, v string) (interface{}, error) {
+	model_descriptor, err := source.FindSymbol("xos." + modelName)
+	if err != nil {
+		return nil, err
+	}
+	model_md, ok := model_descriptor.(*desc.MessageDescriptor)
+	if !ok {
+		return nil, fmt.Errorf("Failed to convert model %s to a messagedescriptor", modelName)
+	}
+	field_descriptor := model_md.FindFieldByName(field_name)
+	if field_descriptor == nil {
+		return nil, fmt.Errorf("Field %s does not exist in model %s", field_name, modelName)
+	}
+	field_type := field_descriptor.GetType()
+
+	var result interface{}
+
+	switch field_type {
+	case descriptor.FieldDescriptorProto_TYPE_INT32:
+		var i int64
+		i, err = strconv.ParseInt(v, 10, 32)
+		result = int32(i)
+	case descriptor.FieldDescriptorProto_TYPE_UINT32:
+		var i int64
+		i, err = strconv.ParseInt(v, 10, 32)
+		result = uint32(i)
+	case descriptor.FieldDescriptorProto_TYPE_FLOAT:
+		var f float64
+		f, err = strconv.ParseFloat(v, 32)
+		result = float32(f)
+	case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
+		var f float64
+		f, err = strconv.ParseFloat(v, 64)
+		result = f
+	default:
+		result = v
+		err = nil
+	}
+
+	return result, err
 }
 
 // Return a list of all available model names
@@ -211,6 +278,34 @@
 	return nil
 }
 
+// Update a model in XOS given a map of fields
+func UpdateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	defer cancel()
+
+	headers := GenerateHeaders()
+
+	h := &RpcEventHandler{
+		Fields: map[string]map[string]interface{}{"xos." + modelName: fields},
+	}
+	err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Update"+modelName, headers, h, h.GetParams)
+	if err != nil {
+		return err
+	} else if h.Status != nil && h.Status.Err() != nil {
+		return h.Status.Err()
+	}
+
+	resp, err := dynamic.AsDynamicMessage(h.Response)
+	if err != nil {
+		return err
+	}
+
+	// TODO: Do we need to do anything with the response?
+	_ = resp
+
+	return nil
+}
+
 // Get a model from XOS given its ID
 func GetModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) (*dynamic.Message, error) {
 	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
@@ -385,6 +480,15 @@
 	return ItemsToDynamicMessageList(items), nil
 }
 
+// Call ListModels or FilterModels as appropriate
+func ListOrFilterModels(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) ([]*dynamic.Message, error) {
+	if len(queries) == 0 {
+		return ListModels(conn, descriptor, modelName)
+	} else {
+		return FilterModels(conn, descriptor, modelName, queries)
+	}
+}
+
 // Get a model from XOS given a fieldName/fieldValue
 func FindModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) (*dynamic.Message, error) {
 	models, err := FilterModels(conn, descriptor, modelName, queries)