Repository: ovn-org/libovsdb
Pull request #139 (2021-05-25 16:48:35)

notation: Correct Row notation
We had ResultRow and Row which were the same thing. The latter
placed the contents in an intermediate map[string]interface{} called
Fields while the former was just an alias for map[string]interface{}.

This commit uses an alias for map[string]interface{} everywhere for
consistency and ensures that we use this everywhere we'd expect a
Row. This allows arbitraty Row creation by casting a map directly.

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>

coveralls: ## Pull Request Test Coverage Report for [Build 875690488](https://coveralls.io/builds/40000125)

* **19** of **22**   **(86.36%)**  changed or added relevant lines in **4** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.4%**) to **70.97%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [ovsdb/row.go](https://coveralls.io/builds/40000125/source?filename=ovsdb%2Frow.go#L24) | 0 | 3 | 0.0%
<!-- | **Total:** | **19** | **22** | **86.36%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/40000125/badge)](https://coveralls.io/builds/40000125) |
| :-- | --: |
| Change from base [Build 875164410](https://coveralls.io/builds/39992641): |  0.4% |
| Covered Lines: | 1858 |
| Relevant Lines: | 2618 |

---
##### 💛  - [Coveralls](https://coveralls.io)

amorenoz: Very much needed indeed!
LGTM

Patch:
diff --git a/client/cache.go b/client/cache.go
index 847da59..ce6689a 100644
--- a/client/cache.go
+++ b/client/cache.go
@@ -182,14 +182,14 @@ func (t *TableCache) populate(tableUpdates ovsdb.TableUpdates) {
 		tCache.mutex.Lock()
 		for uuid, row := range updates {
 			if row.New != nil {
-				newModel, err := t.createModel(table, row.New, uuid)
+				newModel, err := t.CreateModel(table, row.New, uuid)
 				if err != nil {
 					panic(err)
 				}
 				if existing, ok := tCache.cache[uuid]; ok {
 					if !reflect.DeepEqual(newModel, existing) {
 						tCache.cache[uuid] = newModel
-						oldModel, err := t.createModel(table, row.Old, uuid)
+						oldModel, err := t.CreateModel(table, row.Old, uuid)
 						if err != nil {
 							panic(err)
 						}
@@ -202,7 +202,7 @@ func (t *TableCache) populate(tableUpdates ovsdb.TableUpdates) {
 				t.eventProcessor.AddEvent(addEvent, table, nil, newModel)
 				continue
 			} else {
-				oldModel, err := t.createModel(table, row.Old, uuid)
+				oldModel, err := t.CreateModel(table, row.Old, uuid)
 				if err != nil {
 					panic(err)
 				}
@@ -307,7 +307,7 @@ func (e *eventProcessor) Run(stopCh <-chan struct{}) {
 }
 
 // createModel creates a new Model instance based on the Row information
-func (t *TableCache) createModel(tableName string, row *ovsdb.Row, uuid string) (model.Model, error) {
+func (t *TableCache) CreateModel(tableName string, row *ovsdb.Row, uuid string) (model.Model, error) {
 	table := t.mapper.Schema.Table(tableName)
 	if table == nil {
 		return nil, fmt.Errorf("table %s not found", tableName)
diff --git a/mapper/mapper.go b/mapper/mapper.go
index dd958d9..f75a117 100644
--- a/mapper/mapper.go
+++ b/mapper/mapper.go
@@ -64,13 +64,13 @@ func (m Mapper) GetRowData(tableName string, row *ovsdb.Row, result interface{})
 	if row == nil {
 		return nil
 	}
-	return m.getData(tableName, row.Fields, result)
+	return m.getData(tableName, *row, result)
 }
 
 // getData transforms a map[string]interface{} containing OvS types (e.g: a ResultRow
 // has this format) to orm struct
 // The result object must be given as pointer to an object with the right tags
-func (m Mapper) getData(tableName string, ovsData map[string]interface{}, result interface{}) error {
+func (m Mapper) getData(tableName string, ovsData ovsdb.Row, result interface{}) error {
 	table := m.Schema.Table(tableName)
 	if table == nil {
 		return newErrNoTable(tableName)
@@ -109,7 +109,7 @@ func (m Mapper) getData(tableName string, ovsData map[string]interface{}, result
 // NewRow transforms an orm struct to a map[string] interface{} that can be used as libovsdb.Row
 // By default, default or null values are skipped. This behaviour can be modified by specifying
 // a list of fields (pointers to fields in the struct) to be added to the row
-func (m Mapper) NewRow(tableName string, data interface{}, fields ...interface{}) (map[string]interface{}, error) {
+func (m Mapper) NewRow(tableName string, data interface{}, fields ...interface{}) (ovsdb.Row, error) {
 	table := m.Schema.Table(tableName)
 	if table == nil {
 		return nil, newErrNoTable(tableName)
diff --git a/ovsdb/notation.go b/ovsdb/notation.go
index ddb2ffd..90f0375 100644
--- a/ovsdb/notation.go
+++ b/ovsdb/notation.go
@@ -19,19 +19,19 @@ const (
 
 // Operation represents an operation according to RFC7047 section 5.2
 type Operation struct {
-	Op        string                   `json:"op"`
-	Table     string                   `json:"table"`
-	Row       map[string]interface{}   `json:"row,omitempty"`
-	Rows      []map[string]interface{} `json:"rows,omitempty"`
-	Columns   []string                 `json:"columns,omitempty"`
-	Mutations []Mutation               `json:"mutations,omitempty"`
-	Timeout   int                      `json:"timeout,omitempty"`
-	Where     []Condition              `json:"where,omitempty"`
-	Until     string                   `json:"until,omitempty"`
-	Durable   *bool                    `json:"durable,omitempty"`
-	Comment   *string                  `json:"comment,omitempty"`
-	Lock      *string                  `json:"lock,omitempty"`
-	UUIDName  string                   `json:"uuid-name,omitempty"`
+	Op        string      `json:"op"`
+	Table     string      `json:"table"`
+	Row       Row         `json:"row,omitempty"`
+	Rows      []Row       `json:"rows,omitempty"`
+	Columns   []string    `json:"columns,omitempty"`
+	Mutations []Mutation  `json:"mutations,omitempty"`
+	Timeout   int         `json:"timeout,omitempty"`
+	Where     []Condition `json:"where,omitempty"`
+	Until     string      `json:"until,omitempty"`
+	Durable   *bool       `json:"durable,omitempty"`
+	Comment   *string     `json:"comment,omitempty"`
+	Lock      *string     `json:"lock,omitempty"`
+	UUIDName  string      `json:"uuid-name,omitempty"`
 }
 
 // MarshalJSON marshalls 'Operation' to a byte array
@@ -89,11 +89,11 @@ type TransactResponse struct {
 
 // OperationResult is the result of an Operation
 type OperationResult struct {
-	Count   int         `json:"count,omitempty"`
-	Error   string      `json:"error,omitempty"`
-	Details string      `json:"details,omitempty"`
-	UUID    UUID        `json:"uuid,omitempty"`
-	Rows    []ResultRow `json:"rows,omitempty"`
+	Count   int    `json:"count,omitempty"`
+	Error   string `json:"error,omitempty"`
+	Details string `json:"details,omitempty"`
+	UUID    UUID   `json:"uuid,omitempty"`
+	Rows    []Row  `json:"rows,omitempty"`
 }
 
 func ovsSliceToGoNotation(val interface{}) (interface{}, error) {
diff --git a/ovsdb/row.go b/ovsdb/row.go
index 57f722b..9a253f7 100644
--- a/ovsdb/row.go
+++ b/ovsdb/row.go
@@ -3,34 +3,10 @@ package ovsdb
 import "encoding/json"
 
 // Row is a table Row according to RFC7047
-type Row struct {
-	Fields map[string]interface{}
-}
+type Row map[string]interface{}
 
 // UnmarshalJSON unmarshalls a byte array to an OVSDB Row
 func (r *Row) UnmarshalJSON(b []byte) (err error) {
-	r.Fields = make(map[string]interface{})
-	var raw map[string]interface{}
-	err = json.Unmarshal(b, &raw)
-	for key, val := range raw {
-		val, err = ovsSliceToGoNotation(val)
-		if err != nil {
-			return err
-		}
-		r.Fields[key] = val
-	}
-	return err
-}
-
-func (r Row) MarshalJSON() ([]byte, error) {
-	return json.Marshal(r.Fields)
-}
-
-// ResultRow is an properly unmarshalled row returned by Transact
-type ResultRow map[string]interface{}
-
-// UnmarshalJSON unmarshalls a byte array to an OVSDB Row
-func (r *ResultRow) UnmarshalJSON(b []byte) (err error) {
 	*r = make(map[string]interface{})
 	var raw map[string]interface{}
 	err = json.Unmarshal(b, &raw)
@@ -43,3 +19,8 @@ func (r *ResultRow) UnmarshalJSON(b []byte) (err error) {
 	}
 	return err
 }
+
+// NewRow returns a new empty row
+func NewRow() Row {
+	return Row(make(map[string]interface{}))
+}
diff --git a/ovsdb/updates.go b/ovsdb/updates.go
index 8e3b62f..f6af9de 100644
--- a/ovsdb/updates.go
+++ b/ovsdb/updates.go
@@ -39,6 +39,22 @@ type RowUpdate struct {
 	Old *Row `json:"old,omitempty"`
 }
 
+// newRowUpdate creates a *RowUpdate using the provided rows
+func newRowUpdate(old, new Row) *RowUpdate {
+	r := &RowUpdate{}
+	if old != nil {
+		r.Old = &old
+	} else {
+		r.Old = nil
+	}
+	if new != nil {
+		r.New = &new
+	} else {
+		r.New = nil
+	}
+	return r
+}
+
 // Insert returns true if this is an update for an insert operation
 func (r RowUpdate) Insert() bool {
 	return r.New != nil && r.Old == nil


Test patch:
diff --git a/client/api_test.go b/client/api_test.go
index e1f0526..8367cd8 100644
--- a/client/api_test.go
+++ b/client/api_test.go
@@ -522,6 +522,8 @@ func TestAPICreate(t *testing.T) {
 	cache.cache["Logical_Switch"] = &RowCache{cache: lsCache}
 	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspCache}
 
+	rowFoo := ovsdb.Row(map[string]interface{}{"name": "foo"})
+	rowBar := ovsdb.Row(map[string]interface{}{"name": "bar"})
 	test := []struct {
 		name   string
 		input  []model.Model
@@ -534,7 +536,7 @@ func TestAPICreate(t *testing.T) {
 			result: []ovsdb.Operation{{
 				Op:       "insert",
 				Table:    "Logical_Switch",
-				Row:      map[string]interface{}{},
+				Row:      ovsdb.Row{},
 				UUIDName: "",
 			}},
 			err: false,
@@ -547,7 +549,7 @@ func TestAPICreate(t *testing.T) {
 			result: []ovsdb.Operation{{
 				Op:       "insert",
 				Table:    "Logical_Switch",
-				Row:      map[string]interface{}{"name": "foo"},
+				Row:      rowFoo,
 				UUIDName: "",
 			}},
 			err: false,
@@ -560,7 +562,7 @@ func TestAPICreate(t *testing.T) {
 			result: []ovsdb.Operation{{
 				Op:       "insert",
 				Table:    "Logical_Switch",
-				Row:      map[string]interface{}{},
+				Row:      ovsdb.Row{},
 				UUIDName: "foo",
 			}},
 			err: false,
@@ -580,12 +582,12 @@ func TestAPICreate(t *testing.T) {
 			result: []ovsdb.Operation{{
 				Op:       "insert",
 				Table:    "Logical_Switch",
-				Row:      map[string]interface{}{"name": "foo"},
+				Row:      rowFoo,
 				UUIDName: "fooUUID",
 			}, {
 				Op:       "insert",
 				Table:    "Logical_Switch",
-				Row:      map[string]interface{}{"name": "bar"},
+				Row:      rowBar,
 				UUIDName: "barUUID",
 			}},
 			err: false,
@@ -802,7 +804,8 @@ func TestAPIUpdate(t *testing.T) {
 	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspCache}
 
 	testObj := testLogicalSwitchPort{}
-
+	testRow := ovsdb.Row(map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})})
+	tagRow := ovsdb.Row(map[string]interface{}{"tag": testOvsSet(t, []int{6})})
 	test := []struct {
 		name      string
 		condition func(API) ConditionalAPI
@@ -825,7 +828,7 @@ func TestAPIUpdate(t *testing.T) {
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
-					Row:   map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})},
+					Row:   testRow,
 					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 			},
@@ -846,7 +849,7 @@ func TestAPIUpdate(t *testing.T) {
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
-					Row:   map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})},
+					Row:   testRow,
 					Where: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "lsp1"}},
 				},
 			},
@@ -872,7 +875,7 @@ func TestAPIUpdate(t *testing.T) {
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
-					Row:   map[string]interface{}{"tag": testOvsSet(t, []int{6})},
+					Row:   tagRow,
 					Where: []ovsdb.Condition{{Column: "type", Function: ovsdb.ConditionEqual, Value: "sometype"}},
 				},
 			},
@@ -901,13 +904,13 @@ func TestAPIUpdate(t *testing.T) {
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
-					Row:   map[string]interface{}{"tag": testOvsSet(t, []int{6})},
+					Row:   tagRow,
 					Where: []ovsdb.Condition{{Column: "type", Function: ovsdb.ConditionEqual, Value: "sometype"}},
 				},
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
-					Row:   map[string]interface{}{"tag": testOvsSet(t, []int{6})},
+					Row:   tagRow,
 					Where: []ovsdb.Condition{{Column: "enabled", Function: ovsdb.ConditionIncludes, Value: testOvsSet(t, []bool{true})}},
 				},
 			},
@@ -936,7 +939,7 @@ func TestAPIUpdate(t *testing.T) {
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
-					Row:   map[string]interface{}{"tag": testOvsSet(t, []int{6})},
+					Row:   tagRow,
 					Where: []ovsdb.Condition{
 						{Column: "type", Function: ovsdb.ConditionEqual, Value: "sometype"},
 						{Column: "enabled", Function: ovsdb.ConditionIncludes, Value: testOvsSet(t, []bool{true})},
@@ -965,7 +968,7 @@ func TestAPIUpdate(t *testing.T) {
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
-					Row:   map[string]interface{}{"tag": testOvsSet(t, []int{6})},
+					Row:   tagRow,
 					Where: []ovsdb.Condition{{Column: "type", Function: ovsdb.ConditionNotEqual, Value: "sometype"}},
 				},
 			},
@@ -986,13 +989,13 @@ func TestAPIUpdate(t *testing.T) {
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
-					Row:   map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})},
+					Row:   testRow,
 					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})},
+					Row:   testRow,
 					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID1}}},
 				},
 			},
diff --git a/client/cache_test.go b/client/cache_test.go
index a210fad..108da14 100644
--- a/client/cache_test.go
+++ b/client/cache_test.go
@@ -310,13 +310,13 @@ func TestTableCache_populate(t *testing.T) {
 	tc, err := newTableCache(&schema, db)
 	assert.Nil(t, err)
 
-	testRow := &ovsdb.Row{Fields: map[string]interface{}{"_uuid": "test", "foo": "bar"}}
+	testRow := ovsdb.Row(map[string]interface{}{"_uuid": "test", "foo": "bar"})
 	testRowModel := &testModel{UUID: "test", Foo: "bar"}
 	updates := ovsdb.TableUpdates{
 		"Open_vSwitch": {
 			"test": &ovsdb.RowUpdate{
 				Old: nil,
-				New: testRow,
+				New: &testRow,
 			},
 		},
 	}
@@ -326,11 +326,11 @@ func TestTableCache_populate(t *testing.T) {
 	assert.Equal(t, testRowModel, got)
 
 	t.Log("Update")
-	updatedRow := &ovsdb.Row{Fields: map[string]interface{}{"_uuid": "test", "foo": "quux"}}
+	updatedRow := ovsdb.Row(map[string]interface{}{"_uuid": "test", "foo": "quux"})
 	updatedRowModel := &testModel{UUID: "test", Foo: "quux"}
 	updates["Open_vSwitch"]["test"] = &ovsdb.RowUpdate{
-		Old: testRow,
-		New: updatedRow,
+		Old: &testRow,
+		New: &updatedRow,
 	}
 	tc.populate(updates)
 
@@ -339,7 +339,7 @@ func TestTableCache_populate(t *testing.T) {
 
 	t.Log("Delete")
 	updates["Open_vSwitch"]["test"] = &ovsdb.RowUpdate{
-		Old: updatedRow,
+		Old: &updatedRow,
 		New: nil,
 	}
 
diff --git a/client/ovs_integration_test.go b/client/ovs_integration_test.go
index eaebd4f..66f3f85 100644
--- a/client/ovs_integration_test.go
+++ b/client/ovs_integration_test.go
@@ -369,13 +369,10 @@ func TestTableSchemaValidationIntegration(t *testing.T) {
 		t.Fatalf("Failed to Connect. error: %s", err)
 	}
 
-	bridge := make(map[string]interface{})
-	bridge["name"] = "docker-ovs"
-
 	operation := ovsdb.Operation{
 		Op:    "insert",
 		Table: "InvalidTable",
-		Row:   bridge,
+		Row:   ovsdb.Row(map[string]interface{}{"name": "docker-ovs"}),
 	}
 	_, err = ovs.Transact(operation)
 
@@ -397,14 +394,10 @@ func TestColumnSchemaInRowValidationIntegration(t *testing.T) {
 		t.Fatalf("Failed to Connect. error: %s", err)
 	}
 
-	bridge := make(map[string]interface{})
-	bridge["name"] = "docker-ovs"
-	bridge["invalid_column"] = "invalid_column"
-
 	operation := ovsdb.Operation{
 		Op:    "insert",
 		Table: "Bridge",
-		Row:   bridge,
+		Row:   ovsdb.Row(map[string]interface{}{"name": "docker-ovs", "invalid_column": "invalid_column"}),
 	}
 
 	_, err = ovs.Transact(operation)
@@ -427,16 +420,10 @@ func TestColumnSchemaInMultipleRowsValidationIntegration(t *testing.T) {
 		t.Fatalf("Failed to Connect. error: %s", err)
 	}
 
-	rows := make([]map[string]interface{}, 2)
-
-	invalidBridge := make(map[string]interface{})
-	invalidBridge["invalid_column"] = "invalid_column"
-
-	bridge := make(map[string]interface{})
-	bridge["name"] = "docker-ovs"
+	invalidBridge := ovsdb.Row(map[string]interface{}{"invalid_column": "invalid_column"})
+	bridge := ovsdb.Row(map[string]interface{}{"name": "docker-ovs"})
+	rows := []ovsdb.Row{invalidBridge, bridge}
 
-	rows[0] = invalidBridge
-	rows[1] = bridge
 	operation := ovsdb.Operation{
 		Op:    "insert",
 		Table: "Bridge",
diff --git a/mapper/mapper_test.go b/mapper/mapper_test.go
index 967d72d..7eb6fb0 100644
--- a/mapper/mapper_test.go
+++ b/mapper/mapper_test.go
@@ -153,31 +153,31 @@ var testSchema = []byte(`{
 }`)
 
 func getOvsTestRow(t *testing.T) ovsdb.Row {
-	ovsRow := ovsdb.Row{Fields: make(map[string]interface{})}
-	ovsRow.Fields["aString"] = aString
-	ovsRow.Fields["aSet"] = *testOvsSet(t, aSet)
+	ovsRow := ovsdb.NewRow()
+	ovsRow["aString"] = aString
+	ovsRow["aSet"] = *testOvsSet(t, aSet)
 	// Set's can hold the value if they have len == 1
-	ovsRow.Fields["aSingleSet"] = aString
+	ovsRow["aSingleSet"] = aString
 
 	us := make([]ovsdb.UUID, 0)
 	for _, u := range aUUIDSet {
 		us = append(us, ovsdb.UUID{GoUUID: u})
 	}
-	ovsRow.Fields["aUUIDSet"] = *testOvsSet(t, us)
+	ovsRow["aUUIDSet"] = *testOvsSet(t, us)
 
-	ovsRow.Fields["aUUID"] = ovsdb.UUID{GoUUID: aUUID0}
+	ovsRow["aUUID"] = ovsdb.UUID{GoUUID: aUUID0}
 
-	ovsRow.Fields["aIntSet"] = *testOvsSet(t, aIntSet)
+	ovsRow["aIntSet"] = *testOvsSet(t, aIntSet)
 
-	ovsRow.Fields["aFloat"] = aFloat
+	ovsRow["aFloat"] = aFloat
 
-	ovsRow.Fields["aFloatSet"] = *testOvsSet(t, aFloatSet)
+	ovsRow["aFloatSet"] = *testOvsSet(t, aFloatSet)
 
-	ovsRow.Fields["aEmptySet"] = *testOvsSet(t, []string{})
+	ovsRow["aEmptySet"] = *testOvsSet(t, []string{})
 
-	ovsRow.Fields["aEnum"] = aEnum
+	ovsRow["aEnum"] = aEnum
 
-	ovsRow.Fields["aMap"] = *testOvsMap(t, aMap)
+	ovsRow["aMap"] = *testOvsMap(t, aMap)
 
 	return ovsRow
 }
@@ -242,7 +242,7 @@ func TestMapperNewRow(t *testing.T) {
 	tests := []struct {
 		name        string
 		objInput    interface{}
-		expectedRow map[string]interface{}
+		expectedRow ovsdb.Row
 		shoulderr   bool
 	}{{
 		name: "string",
@@ -251,7 +251,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			AString: aString,
 		},
-		expectedRow: map[string]interface{}{"aString": aString},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aString": aString}),
 	}, {
 		name: "set",
 		objInput: &struct {
@@ -259,7 +259,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			SomeSet: aSet,
 		},
-		expectedRow: map[string]interface{}{"aSet": testOvsSet(t, aSet)},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aSet": testOvsSet(t, aSet)}),
 	}, {
 		name: "emptySet with no column specification",
 		objInput: &struct {
@@ -267,7 +267,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			EmptySet: []string{},
 		},
-		expectedRow: map[string]interface{}{},
+		expectedRow: ovsdb.Row(map[string]interface{}{}),
 	}, {
 		name: "UUID",
 		objInput: &struct {
@@ -275,7 +275,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			MyUUID: aUUID0,
 		},
-		expectedRow: map[string]interface{}{"aUUID": ovsdb.UUID{GoUUID: aUUID0}},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aUUID": ovsdb.UUID{GoUUID: aUUID0}}),
 	}, {
 		name: "aUUIDSet",
 		objInput: &struct {
@@ -283,7 +283,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			MyUUIDSet: []string{aUUID0, aUUID1},
 		},
-		expectedRow: map[string]interface{}{"aUUIDSet": testOvsSet(t, []ovsdb.UUID{{GoUUID: aUUID0}, {GoUUID: aUUID1}})},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aUUIDSet": testOvsSet(t, []ovsdb.UUID{{GoUUID: aUUID0}, {GoUUID: aUUID1}})}),
 	}, {
 		name: "aIntSet",
 		objInput: &struct {
@@ -291,7 +291,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			MyIntSet: []int{0, 42},
 		},
-		expectedRow: map[string]interface{}{"aIntSet": testOvsSet(t, []int{0, 42})},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aIntSet": testOvsSet(t, []int{0, 42})}),
 	}, {
 		name: "aFloat",
 		objInput: &struct {
@@ -299,7 +299,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			MyFloat: 42.42,
 		},
-		expectedRow: map[string]interface{}{"aFloat": 42.42},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aFloat": 42.42}),
 	}, {
 		name: "aFloatSet",
 		objInput: &struct {
@@ -307,7 +307,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			MyFloatSet: aFloatSet,
 		},
-		expectedRow: map[string]interface{}{"aFloatSet": testOvsSet(t, aFloatSet)},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aFloatSet": testOvsSet(t, aFloatSet)}),
 	}, {
 		name: "Enum",
 		objInput: &struct {
@@ -315,7 +315,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			MyEnum: aEnum,
 		},
-		expectedRow: map[string]interface{}{"aEnum": aEnum},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aEnum": aEnum}),
 	}, {
 		name: "untagged fields should not affect row",
 		objInput: &struct {
@@ -325,7 +325,7 @@ func TestMapperNewRow(t *testing.T) {
 			AString: aString,
 			MyStuff: map[string]string{"this is": "private"},
 		},
-		expectedRow: map[string]interface{}{"aString": aString},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aString": aString}),
 	}, {
 		name: "Maps",
 		objInput: &struct {
@@ -333,7 +333,7 @@ func TestMapperNewRow(t *testing.T) {
 		}{
 			MyMap: aMap,
 		},
-		expectedRow: map[string]interface{}{"aMap": testOvsMap(t, aMap)},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aMap": testOvsMap(t, aMap)}),
 	},
 	}
 	for _, test := range tests {
@@ -367,7 +367,7 @@ func TestMapperNewRowFields(t *testing.T) {
 	tests := []struct {
 		name        string
 		prepare     func(*obj)
-		expectedRow map[string]interface{}
+		expectedRow ovsdb.Row
 		fields      []interface{}
 		err         bool
 	}{{
@@ -375,38 +375,38 @@ func TestMapperNewRowFields(t *testing.T) {
 		prepare: func(o *obj) {
 			o.MyString = aString
 		},
-		expectedRow: map[string]interface{}{"aString": aString},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aString": aString}),
 	}, {
 		name: "empty string with field specification",
 		prepare: func(o *obj) {
 			o.MyString = ""
 		},
 		fields:      []interface{}{&testObj.MyString},
-		expectedRow: map[string]interface{}{"aString": ""},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aString": ""}),
 	}, {
 		name: "empty set without field specification",
 		prepare: func(o *obj) {
 		},
-		expectedRow: map[string]interface{}{},
+		expectedRow: ovsdb.Row(map[string]interface{}{}),
 	}, {
 		name: "empty set without field specification",
 		prepare: func(o *obj) {
 		},
 		fields:      []interface{}{&testObj.MySet},
-		expectedRow: map[string]interface{}{"aSet": testOvsSet(t, []string{})},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aSet": testOvsSet(t, []string{})}),
 	}, {
 		name: "empty maps",
 		prepare: func(o *obj) {
 			o.MyString = "foo"
 		},
-		expectedRow: map[string]interface{}{"aString": aString},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aString": aString}),
 	}, {
 		name: "empty maps with field specification",
 		prepare: func(o *obj) {
 			o.MyString = "foo"
 		},
 		fields:      []interface{}{&testObj.MyMap},
-		expectedRow: map[string]interface{}{"aMap": testOvsMap(t, map[string]string{})},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aMap": testOvsMap(t, map[string]string{})}),
 	}, {
 		name: "Complex object with field selection",
 		prepare: func(o *obj) {
@@ -416,7 +416,7 @@ func TestMapperNewRowFields(t *testing.T) {
 			o.MyFloat = aFloat
 		},
 		fields:      []interface{}{&testObj.MyMap, &testObj.MySet},
-		expectedRow: map[string]interface{}{"aMap": testOvsMap(t, aMap), "aSet": testOvsSet(t, aSet)},
+		expectedRow: ovsdb.Row(map[string]interface{}{"aMap": testOvsMap(t, aMap), "aSet": testOvsSet(t, aSet)}),
 	},
 	}
 
diff --git a/ovsdb/notation_test.go b/ovsdb/notation_test.go
index 7c3e1a3..59f0ab1 100644
--- a/ovsdb/notation_test.go
+++ b/ovsdb/notation_test.go
@@ -11,17 +11,24 @@ func TestOpRowSerialization(t *testing.T) {
 		Op:    "insert",
 		Table: "Bridge",
 	}
-
-	operation.Row = make(map[string]interface{})
-	operation.Row["name"] = "docker-ovs"
-
 	str, err := json.Marshal(operation)
+	if err != nil {
+		log.Fatal("serialization error:", err)
+	}
+	expected := `{"op":"insert","table":"Bridge"}`
+	if string(str) != expected {
+		t.Error("Expected: ", expected, "Got", string(str))
+	}
 
+	row := Row(map[string]interface{}{"name": "docker-ovs"})
+	operation.Row = row
+
+	str, err = json.Marshal(operation)
 	if err != nil {
 		log.Fatal("serialization error:", err)
 	}
 
-	expected := `{"op":"insert","table":"Bridge","row":{"name":"docker-ovs"}}`
+	expected = `{"op":"insert","table":"Bridge","row":{"name":"docker-ovs"}}`
 
 	if string(str) != expected {
 		t.Error("Expected: ", expected, "Got", string(str))
@@ -34,17 +41,18 @@ func TestOpRowsSerialization(t *testing.T) {
 		Table: "Interface",
 	}
 
-	iface1 := make(map[string]interface{})
-	iface1["name"] = "test-iface1"
-	iface1["mac"] = "0000ffaaaa"
-	iface1["ofport"] = 1
-
-	iface2 := make(map[string]interface{})
-	iface2["name"] = "test-iface2"
-	iface2["mac"] = "0000ffaabb"
-	iface2["ofport"] = 2
+	iface1 := Row(map[string]interface{}{
+		"name":   "test-iface1",
+		"mac":    "0000ffaaaa",
+		"ofport": 1,
+	})
 
-	operation.Rows = []map[string]interface{}{iface1, iface2}
+	iface2 := Row(map[string]interface{}{
+		"name":   "test-iface2",
+		"mac":    "0000ffaabb",
+		"ofport": 2,
+	})
+	operation.Rows = []Row{iface1, iface2}
 
 	str, err := json.Marshal(operation)
 
diff --git a/ovsdb/updates_test.go b/ovsdb/updates_test.go
index 9d4098c..bb8f10d 100644
--- a/ovsdb/updates_test.go
+++ b/ovsdb/updates_test.go
@@ -56,27 +56,21 @@ func TestAddRowUpdate(t *testing.T) {
 			"new row",
 			TableUpdate{},
 			"foo",
-			&RowUpdate{},
+			newRowUpdate(nil, nil),
 			TableUpdate{"foo": {}},
 		},
 		{
 			"update existing row",
 			TableUpdate{
-				"foo": {
-					Old: nil,
-					New: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-				},
+				"foo": newRowUpdate(nil, Row(map[string]interface{}{"foo": "bar"})),
 			},
 			"foo",
-			&RowUpdate{
-				Old: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-				New: &Row{Fields: map[string]interface{}{"foo": "baz"}},
-			},
+			newRowUpdate(
+				Row(map[string]interface{}{"foo": "bar"}),
+				Row(map[string]interface{}{"foo": "baz"}),
+			),
 			TableUpdate{
-				"foo": {
-					Old: nil,
-					New: &Row{Fields: map[string]interface{}{"foo": "baz"}},
-				},
+				"foo": newRowUpdate(nil, Row(map[string]interface{}{"foo": "baz"})),
 			},
 		},
 	}
@@ -97,60 +91,42 @@ func TestAddRowUpdateMerge(t *testing.T) {
 	}{
 		{
 			"insert then modify",
-			&RowUpdate{
-				Old: nil,
-				New: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-			},
-			&RowUpdate{
-				Old: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-				New: &Row{Fields: map[string]interface{}{"foo": "baz"}},
-			},
-			&RowUpdate{
-				Old: nil,
-				New: &Row{Fields: map[string]interface{}{"foo": "baz"}},
-			},
+			newRowUpdate(nil, Row(map[string]interface{}{"foo": "bar"})),
+			newRowUpdate(
+				Row(map[string]interface{}{"foo": "bar"}),
+				Row(map[string]interface{}{"foo": "baz"}),
+			),
+			newRowUpdate(nil, Row(map[string]interface{}{"foo": "baz"})),
 		},
 		{
 			"insert then delete",
-			&RowUpdate{
-				New: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-			},
-			&RowUpdate{
-				Old: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-			},
-			&RowUpdate{
-				Old: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-				New: nil,
-			},
+			newRowUpdate(nil, Row(map[string]interface{}{"foo": "bar"})),
+			newRowUpdate(Row(map[string]interface{}{"foo": "bar"}), nil),
+			newRowUpdate(Row(map[string]interface{}{"foo": "bar"}), nil),
 		},
 		{
 			"modify then delete",
-			&RowUpdate{
-				New: &Row{Fields: map[string]interface{}{"foo": "baz"}},
-				Old: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-			},
-			&RowUpdate{
-				Old: &Row{Fields: map[string]interface{}{"foo": "baz"}},
-			},
-			&RowUpdate{
-				Old: &Row{Fields: map[string]interface{}{"foo": "baz"}},
-				New: nil,
-			},
+			newRowUpdate(
+				Row(map[string]interface{}{"foo": "bar"}),
+				Row(map[string]interface{}{"foo": "baz"}),
+			),
+			newRowUpdate(Row(map[string]interface{}{"foo": "baz"}), nil),
+			newRowUpdate(Row(map[string]interface{}{"foo": "baz"}), nil),
 		},
 		{
 			"modify then modify",
-			&RowUpdate{
-				New: &Row{Fields: map[string]interface{}{"foo": "baz"}},
-				Old: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-			},
-			&RowUpdate{
-				New: &Row{Fields: map[string]interface{}{"foo": "quux"}},
-				Old: &Row{Fields: map[string]interface{}{"foo": "baz"}},
-			},
-			&RowUpdate{
-				New: &Row{Fields: map[string]interface{}{"foo": "quux"}},
-				Old: &Row{Fields: map[string]interface{}{"foo": "bar"}},
-			},
+			newRowUpdate(
+				Row(map[string]interface{}{"foo": "bar"}),
+				Row(map[string]interface{}{"foo": "baz"}),
+			),
+			newRowUpdate(
+				Row(map[string]interface{}{"foo": "baz"}),
+				Row(map[string]interface{}{"foo": "quux"}),
+			),
+			newRowUpdate(
+				Row(map[string]interface{}{"foo": "bar"}),
+				Row(map[string]interface{}{"foo": "quux"}),
+			),
 		},
 	}
 	for _, tt := range tests {