Repository: ovn-org/libovsdb
Pull request #111 (2021-05-13 19:54:01)

Add OVSDB Condition Notation
This commit adds a Go type for an OVSDB Condition
along with relevent JSON marshal/unmarshal functions.

It updates the client to use this new notation
when dealing with Conditions

Related to #99 


Patch:
diff --git a/client/api.go b/client/api.go
index ba549c9..19ed60a 100644
--- a/client/api.go
+++ b/client/api.go
@@ -323,16 +323,17 @@ func (a api) Mutate(model Model, mutationObjs []Mutation) ([]ovsdb.Operation, er
 		}
 		mutations = append(mutations, mutation)
 	}
-
 	for _, condition := range conditions {
 		operations = append(operations,
 			ovsdb.Operation{
 				Op:        opMutate,
 				Table:     tableName,
 				Mutations: mutations,
-				Where:     condition,
-			})
+				Where:     []ovsdb.Condition{condition},
+			},
+		)
 	}
+
 	return operations, nil
 }
 
@@ -356,12 +357,14 @@ func (a api) Update(model Model, fields ...interface{}) ([]ovsdb.Operation, erro
 	}
 
 	for _, condition := range conditions {
-		operations = append(operations, ovsdb.Operation{
-			Op:    opUpdate,
-			Table: table,
-			Row:   row,
-			Where: condition,
-		})
+		operations = append(operations,
+			ovsdb.Operation{
+				Op:    opUpdate,
+				Table: table,
+				Row:   row,
+				Where: []ovsdb.Condition{condition},
+			},
+		)
 	}
 	return operations, nil
 }
@@ -375,11 +378,13 @@ func (a api) Delete() ([]ovsdb.Operation, error) {
 	}
 
 	for _, condition := range conditions {
-		operations = append(operations, ovsdb.Operation{
-			Op:    opDelete,
-			Table: a.cond.Table(),
-			Where: condition,
-		})
+		operations = append(operations,
+			ovsdb.Operation{
+				Op:    opDelete,
+				Table: a.cond.Table(),
+				Where: []ovsdb.Condition{condition},
+			},
+		)
 	}
 
 	return operations, nil
diff --git a/client/condition.go b/client/condition.go
index 24b120c..7f11f18 100644
--- a/client/condition.go
+++ b/client/condition.go
@@ -3,13 +3,15 @@ package client
 import (
 	"fmt"
 	"reflect"
+
+	"github.com/ovn-org/libovsdb/ovsdb"
 )
 
 // Condition is the interface used by the ConditionalAPI to match on cache objects
 // and generate operation conditions
 type Condition interface {
 	// Generate returns a list of conditions to be used in Operations
-	Generate() ([][]interface{}, error)
+	Generate() ([]ovsdb.Condition, error)
 	// matches returns true if a model matches the condition
 	Matches(m Model) (bool, error)
 	// returns the table that this condition is associated with
@@ -35,12 +37,12 @@ func (c *indexCond) Table() string {
 }
 
 // Generate returns a condition based on the model and the field pointers
-func (c *indexCond) Generate() ([][]interface{}, error) {
+func (c *indexCond) Generate() ([]ovsdb.Condition, error) {
 	condition, err := c.orm.newCondition(c.tableName, c.model, c.fields...)
 	if err != nil {
 		return nil, err
 	}
-	return [][]interface{}{condition}, nil
+	return condition, nil
 }
 
 // newIndexCondition creates a new indexCond
@@ -74,8 +76,8 @@ func (c *predicateCond) Table() string {
 
 // generate returns a list of conditions that match, by _uuid equality, all the objects that
 // match the predicate
-func (c *predicateCond) Generate() ([][]interface{}, error) {
-	allConditions := make([][]interface{}, 0)
+func (c *predicateCond) Generate() ([]ovsdb.Condition, error) {
+	allConditions := make([]ovsdb.Condition, 0)
 	tableCache := c.cache.Table(c.tableName)
 	if tableCache == nil {
 		return nil, ErrNotFound
@@ -91,7 +93,7 @@ func (c *predicateCond) Generate() ([][]interface{}, error) {
 			if err != nil {
 				return nil, err
 			}
-			allConditions = append(allConditions, elemCond)
+			allConditions = append(allConditions, elemCond...)
 		}
 	}
 	return allConditions, nil
@@ -120,7 +122,7 @@ func (e *errorCondition) Table() string {
 	return ""
 }
 
-func (e *errorCondition) Generate() ([][]interface{}, error) {
+func (e *errorCondition) Generate() ([]ovsdb.Condition, error) {
 	return nil, e.err
 }
 
diff --git a/client/orm.go b/client/orm.go
index a33b707..72dc3f5 100644
--- a/client/orm.go
+++ b/client/orm.go
@@ -166,8 +166,8 @@ func (o orm) newRow(tableName string, data interface{}, fields ...interface{}) (
 // object has valid data. The order in which they are traversed matches the order defined
 // in the schema.
 // By `valid data` we mean non-default data.
-func (o orm) newCondition(tableName string, data interface{}, fields ...interface{}) ([]interface{}, error) {
-	var conditions []interface{}
+func (o orm) newCondition(tableName string, data interface{}, fields ...interface{}) ([]ovsdb.Condition, error) {
+	var conditions []ovsdb.Condition
 	var condIndex [][]string
 
 	table := o.schema.Table(tableName)
@@ -218,7 +218,7 @@ func (o orm) newCondition(tableName string, data interface{}, fields ...interfac
 		if err != nil {
 			return nil, err
 		}
-		conditions = append(conditions, []interface{}{col, "==", ovsVal})
+		conditions = append(conditions, ovsdb.NewCondition(col, ovsdb.ConditionEqual, ovsVal))
 	}
 	return conditions, nil
 }
diff --git a/ovsdb/condition.go b/ovsdb/condition.go
new file mode 100644
index 0000000..6c7ba3f
--- /dev/null
+++ b/ovsdb/condition.go
@@ -0,0 +1,70 @@
+package ovsdb
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+type ConditionFunction string
+
+const (
+	ConditionLessThan           ConditionFunction = "<"
+	ConditionLessThanOrEqual    ConditionFunction = "<="
+	ConditionEqual              ConditionFunction = "=="
+	ConditionNotEqual           ConditionFunction = "!="
+	ConditionGreaterThan        ConditionFunction = ">"
+	ConditionGreaterThanOrEqual ConditionFunction = ">="
+	ConditionIncludes           ConditionFunction = "includes"
+	ConditionExcludes           ConditionFunction = "excludes"
+)
+
+// Condition is described in RFC 7047: 5.1
+type Condition struct {
+	Column   string
+	Function ConditionFunction
+	Value    interface{}
+}
+
+// NewCondition returns a new condition
+func NewCondition(column string, function ConditionFunction, value interface{}) Condition {
+	return Condition{
+		Column:   column,
+		Function: function,
+		Value:    value,
+	}
+}
+
+// MarshalJSON marshals a condition to a 3 element JSON array
+func (c Condition) MarshalJSON() ([]byte, error) {
+	v := []interface{}{c.Column, c.Function, c.Value}
+	return json.Marshal(v)
+}
+
+// UnmarshalJSON converts a 3 element JSON array to a Condition
+func (c Condition) UnmarshalJSON(b []byte) error {
+	var v []interface{}
+	err := json.Unmarshal(b, &v)
+	if err != nil {
+		return err
+	}
+	if len(v) != 3 {
+		return fmt.Errorf("expected a 3 element json array. there are %d elements", len(v))
+	}
+	c.Column = v[0].(string)
+	function := ConditionFunction(v[1].(string))
+	switch function {
+	case ConditionEqual:
+	case ConditionNotEqual:
+	case ConditionIncludes:
+	case ConditionExcludes:
+	case ConditionGreaterThan:
+	case ConditionGreaterThanOrEqual:
+	case ConditionLessThan:
+	case ConditionLessThanOrEqual:
+		c.Function = function
+	default:
+		return fmt.Errorf("%s is not a valid function", function)
+	}
+	c.Value = v[2]
+	return nil
+}
diff --git a/ovsdb/notation.go b/ovsdb/notation.go
index ebaf4ee..fc9a051 100644
--- a/ovsdb/notation.go
+++ b/ovsdb/notation.go
@@ -11,7 +11,7 @@ type Operation struct {
 	Columns   []string                 `json:"columns,omitempty"`
 	Mutations []interface{}            `json:"mutations,omitempty"`
 	Timeout   int                      `json:"timeout,omitempty"`
-	Where     []interface{}            `json:"where,omitempty"`
+	Where     []Condition              `json:"where,omitempty"`
 	Until     string                   `json:"until,omitempty"`
 	UUIDName  string                   `json:"uuid-name,omitempty"`
 }
@@ -25,10 +25,10 @@ func (o Operation) MarshalJSON() ([]byte, error) {
 	case "select":
 		where := o.Where
 		if where == nil {
-			where = make([]interface{}, 0)
+			where = make([]Condition, 0)
 		}
 		return json.Marshal(&struct {
-			Where []interface{} `json:"where"`
+			Where []Condition `json:"where"`
 			OpAlias
 		}{
 			Where:   where,
@@ -91,11 +91,6 @@ type OvsdbError struct {
 	Details string `json:"details,omitempty"`
 }
 
-// NewCondition creates a new condition as specified in RFC7047
-func NewCondition(column string, function string, value interface{}) []interface{} {
-	return []interface{}{column, function, value}
-}
-
 // NewMutation creates a new mutation as specified in RFC7047
 func NewMutation(column string, mutator string, value interface{}) []interface{} {
 	return []interface{}{column, mutator, value}


Test patch:
diff --git a/client/api_test.go b/client/api_test.go
index f1cd984..ed45baf 100644
--- a/client/api_test.go
+++ b/client/api_test.go
@@ -676,7 +676,7 @@ func TestAPIMutate(t *testing.T) {
 					Op:        opMutate,
 					Table:     "Logical_Switch_Port",
 					Mutations: []interface{}{[]interface{}{"tag", ovsdb.MutateOperationInsert, testOvsSet(t, []int{5})}},
-					Where:     []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
+					Where:     []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 			},
 			err: false,
@@ -700,7 +700,7 @@ func TestAPIMutate(t *testing.T) {
 					Op:        opMutate,
 					Table:     "Logical_Switch_Port",
 					Mutations: []interface{}{[]interface{}{"external_ids", ovsdb.MutateOperationDelete, testOvsSet(t, []string{"foo"})}},
-					Where:     []interface{}{[]interface{}{"name", "==", "lsp2"}},
+					Where:     []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "lsp2"}},
 				},
 			},
 			err: false,
@@ -724,7 +724,7 @@ func TestAPIMutate(t *testing.T) {
 					Op:        opMutate,
 					Table:     "Logical_Switch_Port",
 					Mutations: []interface{}{[]interface{}{"external_ids", ovsdb.MutateOperationInsert, testOvsMap(t, map[string]string{"bar": "baz"})}},
-					Where:     []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID2}}},
+					Where:     []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID2}}},
 				},
 			},
 			err: false,
@@ -748,13 +748,13 @@ func TestAPIMutate(t *testing.T) {
 					Op:        opMutate,
 					Table:     "Logical_Switch_Port",
 					Mutations: []interface{}{[]interface{}{"external_ids", ovsdb.MutateOperationInsert, testOvsMap(t, map[string]string{"bar": "baz"})}},
-					Where:     []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
+					Where:     []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 				{
 					Op:        opMutate,
 					Table:     "Logical_Switch_Port",
 					Mutations: []interface{}{[]interface{}{"external_ids", ovsdb.MutateOperationInsert, testOvsMap(t, map[string]string{"bar": "baz"})}},
-					Where:     []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID1}}},
+					Where:     []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID1}}},
 				},
 			},
 			err: false,
@@ -831,7 +831,7 @@ func TestAPIUpdate(t *testing.T) {
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
 					Row:   map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})},
-					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
+					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 			},
 			err: false,
@@ -853,7 +853,7 @@ func TestAPIUpdate(t *testing.T) {
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
 					Row:   map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})},
-					Where: []interface{}{[]interface{}{"name", "==", "lsp1"}},
+					Where: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "lsp1"}},
 				},
 			},
 			err: false,
@@ -876,7 +876,7 @@ func TestAPIUpdate(t *testing.T) {
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
 					Row:   map[string]interface{}{"tag": testOvsSet(t, []int{6})},
-					Where: []interface{}{[]interface{}{"type", "==", "sometype"}},
+					Where: []ovsdb.Condition{{Column: "type", Function: ovsdb.ConditionEqual, Value: "sometype"}},
 				},
 			},
 			err: false,
@@ -898,13 +898,13 @@ func TestAPIUpdate(t *testing.T) {
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
 					Row:   map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})},
-					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
+					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
 					Row:   map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})},
-					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID1}}},
+					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID1}}},
 				},
 			},
 			err: false,
@@ -974,7 +974,7 @@ func TestAPIDelete(t *testing.T) {
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch",
-					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
+					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 			},
 			err: false,
@@ -990,7 +990,7 @@ func TestAPIDelete(t *testing.T) {
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch_Port",
-					Where: []interface{}{[]interface{}{"name", "==", "lsp1"}},
+					Where: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "lsp1"}},
 				},
 			},
 			err: false,
@@ -1008,7 +1008,7 @@ func TestAPIDelete(t *testing.T) {
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch_Port",
-					Where: []interface{}{[]interface{}{"type", "==", "sometype"}},
+					Where: []ovsdb.Condition{{Column: "type", Function: ovsdb.ConditionEqual, Value: "sometype"}},
 				},
 			},
 			err: false,
@@ -1024,12 +1024,12 @@ func TestAPIDelete(t *testing.T) {
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch_Port",
-					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
+					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch_Port",
-					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID1}}},
+					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID1}}},
 				},
 			},
 			err: false,
diff --git a/client/orm_test.go b/client/orm_test.go
index d40e1bd..7abf5f7 100644
--- a/client/orm_test.go
+++ b/client/orm_test.go
@@ -492,7 +492,7 @@ func TestORMCondition(t *testing.T) {
 	type Test struct {
 		name     string
 		prepare  func(*testType)
-		expected []interface{}
+		expected []ovsdb.Condition
 		index    []interface{}
 		err      bool
 	}
@@ -509,7 +509,7 @@ func TestORMCondition(t *testing.T) {
 				t.Comp2 = ""
 			},
 			index:    []interface{}{},
-			expected: []interface{}{[]interface{}{"name", "==", "foo"}},
+			expected: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "foo"}},
 			err:      false,
 		},
 		{
@@ -522,7 +522,7 @@ func TestORMCondition(t *testing.T) {
 				t.Comp2 = ""
 			},
 			index:    []interface{}{},
-			expected: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
+			expected: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
 			err:      false,
 		},
 		{
@@ -535,7 +535,7 @@ func TestORMCondition(t *testing.T) {
 				t.Comp2 = ""
 			},
 			index:    []interface{}{&testObj.MyName},
-			expected: []interface{}{[]interface{}{"name", "==", "foo"}},
+			expected: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "foo"}},
 			err:      false,
 		},
 		{
@@ -547,8 +547,9 @@ func TestORMCondition(t *testing.T) {
 				t.Comp1 = "foo"
 				t.Comp2 = "bar"
 			},
-			expected: []interface{}{[]interface{}{"composed_1", "==", "foo"},
-				[]interface{}{"composed_2", "==", "bar"}},
+			expected: []ovsdb.Condition{
+				{Column: "composed_1", Function: ovsdb.ConditionEqual, Value: "foo"},
+				{Column: "composed_2", Function: ovsdb.ConditionEqual, Value: "bar"}},
 			index: []interface{}{},
 			err:   false,
 		},
@@ -561,7 +562,7 @@ func TestORMCondition(t *testing.T) {
 				t.Comp1 = "foo"
 				t.Comp2 = "bar"
 			},
-			expected: []interface{}{[]interface{}{"name", "==", "something"}},
+			expected: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "something"}},
 			index:    []interface{}{},
 			err:      false,
 		},
diff --git a/ovsdb/condition_test.go b/ovsdb/condition_test.go
new file mode 100644
index 0000000..e1940ec
--- /dev/null
+++ b/ovsdb/condition_test.go
@@ -0,0 +1,133 @@
+package ovsdb
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestConditionMarshalJSON(t *testing.T) {
+	type fields struct {
+		Column   string
+		Function ConditionFunction
+		Value    interface{}
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		want    string
+		wantErr bool
+	}{
+		{
+			"test <",
+			fields{"foo", ConditionLessThan, "bar"},
+			`[ "foo", "<", "bar" ]`,
+			false,
+		},
+		{
+			"test <=",
+			fields{"foo", ConditionLessThanOrEqual, "bar"},
+			`[ "foo", "<=", "bar" ]`,
+			false,
+		},
+		{
+			"test >",
+			fields{"foo", ConditionGreaterThan, "bar"},
+			`[ "foo", ">", "bar" ]`,
+			false,
+		},
+		{
+			"test >=",
+			fields{"foo", ConditionGreaterThanOrEqual, "bar"},
+			`[ "foo", ">=", "bar" ]`,
+			false,
+		},
+		{
+			"test ==",
+			fields{"foo", ConditionEqual, "bar"},
+			`[ "foo", "==", "bar" ]`,
+			false,
+		},
+		{
+			"test !=",
+			fields{"foo", ConditionNotEqual, "bar"},
+			`[ "foo", "!=", "bar" ]`,
+			false,
+		},
+		{
+			"test includes",
+			fields{"foo", ConditionIncludes, "bar"},
+			`[ "foo", "includes", "bar" ]`,
+			false,
+		},
+		{
+			"test excludes",
+			fields{"foo", ConditionExcludes, "bar"},
+			`[ "foo", "excludes", "bar" ]`,
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			c := Condition{
+				Column:   tt.fields.Column,
+				Function: tt.fields.Function,
+				Value:    tt.fields.Value,
+			}
+			got, err := c.MarshalJSON()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Condition.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			assert.JSONEq(t, tt.want, string(got))
+		})
+	}
+}
+
+func TestCondition_UnmarshalJSON(t *testing.T) {
+	type fields struct {
+		Column   string
+		Function ConditionFunction
+		Value    interface{}
+	}
+	type args struct {
+		b []byte
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		{
+			"success",
+			fields{"foo", ConditionEqual, "bar"},
+			args{[]byte(`[ "foo", "==", "bar" ]`)},
+			false,
+		},
+		{
+			"bad function",
+			fields{},
+			args{[]byte(`[ "foo", "baz", "bar" ]`)},
+			true,
+		},
+		{
+			"too many elements",
+			fields{},
+			args{[]byte(`[ "foo", "bar", "baz", "quuz" ]`)},
+			true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			c := Condition{
+				Column:   tt.fields.Column,
+				Function: tt.fields.Function,
+				Value:    tt.fields.Value,
+			}
+			if err := c.UnmarshalJSON(tt.args.b); (err != nil) != tt.wantErr {
+				t.Errorf("Condition.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
diff --git a/ovsdb/notation_test.go b/ovsdb/notation_test.go
index a8ad879..7c3e1a3 100644
--- a/ovsdb/notation_test.go
+++ b/ovsdb/notation_test.go
@@ -156,15 +156,6 @@ func TestNewNamedUUID(t *testing.T) {
 	}
 }
 
-func TestNewCondition(t *testing.T) {
-	cond := NewCondition("uuid", "==", "550e8400-e29b-41d4-a716-446655440000")
-	condStr, _ := json.Marshal(cond)
-	expected := `["uuid","==","550e8400-e29b-41d4-a716-446655440000"]`
-	if string(condStr) != expected {
-		t.Error("condition is not correctly formatted")
-	}
-}
-
 func TestNewMutation(t *testing.T) {
 	mutation := NewMutation("column", "+=", 1)
 	mutationStr, _ := json.Marshal(mutation)