Repository: ovn-org/libovsdb
Pull request #106 (2021-05-12 16:11:15)

api: allow Create()ing multiple Models per call
Apart from convenient, it has the added benefit of unifyint the API to
return []ovsdb.Operation for all the calls

Fixes: #101 #98 
Signed-off-by: Adrian Moreno <amorenoz@redhat.com>


Patch:
diff --git a/client/api.go b/client/api.go
index 55a913d..bafbb52 100644
--- a/client/api.go
+++ b/client/api.go
@@ -43,11 +43,11 @@ type API interface {
 	// preferred way is Where({condition}).List()
 	Get(Model) error
 
-	// Create returns the operation needed to add a model to the Database
+	// Create returns the operation needed to add the model(s) to the Database
 	// Only fields with non-default values will be added to the transaction
 	// If the field associated with column "_uuid" has some content, it will be
 	// treated as named-uuid
-	Create(Model) (*ovsdb.Operation, error)
+	Create(...Model) ([]ovsdb.Operation, error)
 }
 
 // ConditionalAPI is an interface used to perform operations that require / use Conditions
@@ -250,39 +250,44 @@ func (a api) Get(model Model) error {
 
 // Create is a generic function capable of creating any row in the DB
 // A valud Model (pointer to object) must be provided.
-func (a api) Create(model Model) (*ovsdb.Operation, error) {
-	var namedUUID string
-	var err error
+func (a api) Create(models ...Model) ([]ovsdb.Operation, error) {
+	var operations []ovsdb.Operation
 
-	tableName, err := a.getTableFromModel(model)
-	if err != nil {
-		return nil, err
-	}
-	table := a.cache.orm.schema.Table(tableName)
+	for _, model := range models {
+		var namedUUID string
+		var err error
 
-	// Read _uuid field, and use it as named-uuid
-	info, err := newORMInfo(table, model)
-	if err != nil {
-		return nil, err
-	}
-	if uuid, err := info.fieldByColumn("_uuid"); err == nil {
-		namedUUID = uuid.(string)
-	} else {
-		return nil, err
-	}
+		tableName, err := a.getTableFromModel(model)
+		if err != nil {
+			return nil, err
+		}
 
-	row, err := a.cache.orm.newRow(tableName, model)
-	if err != nil {
-		return nil, err
-	}
+		table := a.cache.orm.schema.Table(tableName)
+
+		// Read _uuid field, and use it as named-uuid
+		info, err := newORMInfo(table, model)
+		if err != nil {
+			return nil, err
+		}
+		if uuid, err := info.fieldByColumn("_uuid"); err == nil {
+			namedUUID = uuid.(string)
+		} else {
+			return nil, err
+		}
+
+		row, err := a.cache.orm.newRow(tableName, model)
+		if err != nil {
+			return nil, err
+		}
 
-	insertOp := ovsdb.Operation{
-		Op:       opInsert,
-		Table:    tableName,
-		Row:      row,
-		UUIDName: namedUUID,
+		operations = append(operations, ovsdb.Operation{
+			Op:       opInsert,
+			Table:    tableName,
+			Row:      row,
+			UUIDName: namedUUID,
+		})
 	}
-	return &insertOp, nil
+	return operations, nil
 }
 
 // Mutate returns the operations needed to transform the one Model into another one
diff --git a/cmd/modelgen/dbmodel.go b/cmd/modelgen/dbmodel.go
index 2bd8603..28948f8 100644
--- a/cmd/modelgen/dbmodel.go
+++ b/cmd/modelgen/dbmodel.go
@@ -13,12 +13,12 @@ const MODEL_TEMPLATE = `
 package {{ .PackageName }}
 
 import (
-	"github.com/ovn-org/libovsdb/client"
+	goovn "github.com/ovn-org/libovsdb"
 )
 
 // FullDatabaseModel() returns the DatabaseModel object to be used in libovsdb
-func FullDatabaseModel() (*client.DBModel, error) {
-	return client.NewDBModel("{{ .DatabaseName }}", map[string]client.Model{
+func FullDatabaseModel() (*goovn.DBModel, error) {
+	return goovn.NewDBModel("{{ .DatabaseName }}", map[string]goovn.Model{
     {{ range $tableName, $structName := .Tables }} "{{ $tableName }}" : &{{ $structName }}{}, 
     {{ end }}
 	})
diff --git a/cmd/modelgen/main.go b/cmd/modelgen/main.go
index 4f99049..62c6155 100644
--- a/cmd/modelgen/main.go
+++ b/cmd/modelgen/main.go
@@ -50,7 +50,7 @@ func main() {
 		log.Fatal(err)
 	}
 
-	if err := os.MkdirAll(outDir, 0755); err != nil {
+	if err := os.MkdirAll(filepath.Join(outDir, pkgName), 0700); err != nil {
 		log.Fatal(err)
 	}
 
@@ -86,7 +86,7 @@ func main() {
 		if err != nil {
 			log.Fatal(err)
 		}
-		outFile := filepath.Join(outDir, gen.FileName())
+		outFile := filepath.Join(outDir, pkgName, gen.FileName())
 		if err := writeFile(outFile, code); err != nil {
 			log.Fatal(err)
 		}
diff --git a/cmd/modelgen/table.go b/cmd/modelgen/table.go
index 4a92383..7261b3b 100644
--- a/cmd/modelgen/table.go
+++ b/cmd/modelgen/table.go
@@ -2,7 +2,6 @@ package main
 
 import (
 	"fmt"
-	"sort"
 	"strings"
 	"text/template"
 	"unicode"
@@ -54,16 +53,7 @@ func NewTableGenerator(pkg string, name string, table *ovsdb.TableSchema) Genera
 			Tag:  Tag("_uuid"),
 		})
 
-	// Map iteration order is random, so for predictable generation
-	// lets sort fields by name
-	var order sort.StringSlice
-	for columnName := range table.Columns {
-		order = append(order, columnName)
-	}
-	order.Sort()
-
-	for _, columnName := range order {
-		columnSchema := table.Columns[columnName]
+	for columnName, columnSchema := range table.Columns {
 		templateData.Fields = append(templateData.Fields, Field{
 			Name: FieldName(columnName),
 			Type: FieldType(columnSchema),
diff --git a/cmd/stress/stress.go b/cmd/stress/stress.go
index 2ea2cd6..882e522 100644
--- a/cmd/stress/stress.go
+++ b/cmd/stress/stress.go
@@ -162,7 +162,7 @@ func createBridge(ovs *client.OvsdbClient, iter int) {
 		log.Fatal(err)
 	}
 
-	operations := []ovsdb.Operation{*insertOp, mutateOp[0]}
+	operations := append(insertOp, mutateOp...)
 	ok, uuid := transact(ovs, operations)
 	if ok {
 		if *verbose {
diff --git a/example/play_with_ovs/play_with_ovs.go b/example/play_with_ovs/play_with_ovs.go
index 3502f4f..516aa62 100644
--- a/example/play_with_ovs/play_with_ovs.go
+++ b/example/play_with_ovs/play_with_ovs.go
@@ -6,7 +6,6 @@ import (
 	"log"
 
 	"github.com/ovn-org/libovsdb/client"
-	"github.com/ovn-org/libovsdb/ovsdb"
 )
 
 // Silly game that detects creation of Bridge named "stop" and exits
@@ -82,7 +81,7 @@ func createBridge(ovs *client.OvsdbClient, bridgeName string) {
 		log.Fatal(err)
 	}
 
-	operations := []ovsdb.Operation{*insertOp, mutateOps[0]}
+	operations := append(insertOp, mutateOps...)
 	reply, err := ovs.Transact(operations...)
 	if err != nil {
 		log.Fatal(err)
diff --git a/go.sum b/go.sum
index e8d23b6..cac2fcf 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=


Test patch:
diff --git a/client/api_test.go b/client/api_test.go
index 827451e..b2abdd2 100644
--- a/client/api_test.go
+++ b/client/api_test.go
@@ -529,52 +529,77 @@ func TestAPICreate(t *testing.T) {
 
 	test := []struct {
 		name   string
-		input  Model
-		result *ovsdb.Operation
+		input  []Model
+		result []ovsdb.Operation
 		err    bool
 	}{
 		{
 			name:  "empty",
-			input: &testLogicalSwitch{},
-			result: &ovsdb.Operation{
+			input: []Model{&testLogicalSwitch{}},
+			result: []ovsdb.Operation{{
 				Op:       "insert",
 				Table:    "Logical_Switch",
 				Row:      map[string]interface{}{},
 				UUIDName: "",
-			},
+			}},
 			err: false,
 		},
 		{
 			name: "With some values",
-			input: &testLogicalSwitch{
+			input: []Model{&testLogicalSwitch{
 				Name: "foo",
-			},
-			result: &ovsdb.Operation{
+			}},
+			result: []ovsdb.Operation{{
 				Op:       "insert",
 				Table:    "Logical_Switch",
 				Row:      map[string]interface{}{"name": "foo"},
 				UUIDName: "",
-			},
+			}},
 			err: false,
 		},
 		{
 			name: "With named UUID ",
-			input: &testLogicalSwitch{
+			input: []Model{&testLogicalSwitch{
 				UUID: "foo",
-			},
-			result: &ovsdb.Operation{
+			}},
+			result: []ovsdb.Operation{{
 				Op:       "insert",
 				Table:    "Logical_Switch",
 				Row:      map[string]interface{}{},
 				UUIDName: "foo",
+			}},
+			err: false,
+		},
+		{
+			name: "Multiple",
+			input: []Model{
+				&testLogicalSwitch{
+					UUID: "fooUUID",
+					Name: "foo",
+				},
+				&testLogicalSwitch{
+					UUID: "barUUID",
+					Name: "bar",
+				},
 			},
+			result: []ovsdb.Operation{{
+				Op:       "insert",
+				Table:    "Logical_Switch",
+				Row:      map[string]interface{}{"name": "foo"},
+				UUIDName: "fooUUID",
+			}, {
+				Op:       "insert",
+				Table:    "Logical_Switch",
+				Row:      map[string]interface{}{"name": "bar"},
+				UUIDName: "barUUID",
+			}},
 			err: false,
 		},
 	}
 	for _, tt := range test {
 		t.Run(fmt.Sprintf("ApiCreate: %s", tt.name), func(t *testing.T) {
 			api := newAPI(cache)
-			op, err := api.Create(tt.input)
+			op, err := api.Create(tt.input...)
 			if tt.err {
 				assert.NotNil(t, err)
 			} else {
diff --git a/cmd/modelgen/table_test.go b/cmd/modelgen/table_test.go
deleted file mode 100644
index 1483f31..0000000
--- a/cmd/modelgen/table_test.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package main
-
-import (
-	"encoding/json"
-	"testing"
-
-	"github.com/ovn-org/libovsdb/ovsdb"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestNewTableGenerator(t *testing.T) {
-	rawSchema := []byte(`
-	{
-		"name": "AtomicDB",
-		"version": "0.0.0",
-		"tables": {
-			"atomicTable": {
-				"columns": {
-					"str": {
-						"type": "string"
-					},
-					"int": {
-						"type": "integer"
-					},
-					"float": {
-						"type": "real"
-					}
-				}
-			}
-		}
-	}`)
-
-	expected := `// Code generated by "ovsdb.modelgen"
-// DO NOT EDIT.
-
-package test
-
-// test defines an object in test table
-type test struct {
-	UUID  string  ` + "`" + `ovs:"_uuid"` + "`" + `
-	Float float64 ` + "`" + `ovs:"float"` + "`" + `
-	Int   int     ` + "`" + `ovs:"int"` + "`" + `
-	Str   string  ` + "`" + `ovs:"str"` + "`" + `
-}
-`
-
-	var schema ovsdb.DatabaseSchema
-	err := json.Unmarshal(rawSchema, &schema)
-	if err != nil {
-		t.Fatal(err)
-	}
-	table := schema.Tables["atomicTable"]
-	gen := NewTableGenerator(
-		"test",
-		"test",
-		&table,
-	)
-
-	for i := 0; i < 3; i++ {
-		b, err := gen.Format()
-		if err != nil {
-			t.Fatal(err)
-		}
-		assert.Equal(t, expected, string(b))
-	}
-}
-
-func TestFieldName(t *testing.T) {
-	if s := FieldName("foo"); s != "Foo" {
-		t.Fatalf("got %s, wanted Foo", s)
-	}
-}
-
-func TestStructName(t *testing.T) {
-	if s := StructName("Foo_Bar"); s != "FooBar" {
-		t.Fatalf("got %s, wanted FooBar", s)
-	}
-}
-
-/*
-TODO: Write Test
-func TestFieldType(t *testing.T) {
-	tests := []struct {
-		name string
-		in   *ovsdb.ColumnSchema
-		out  string
-	}
-	if got := FieldType(tt.args.column); got != tt.want {
-		t.Errorf("FieldType() = %v, want %v", got, tt.want)
-	}
-}
-*/
-
-func TestAtomicType(t *testing.T) {
-	tests := []struct {
-		name string
-		in   string
-		out  string
-	}{
-		{"IntegerToInt", ovsdb.TypeInteger, "int"},
-		{"RealToFloat", ovsdb.TypeReal, "float64"},
-		{"BooleanToBool", ovsdb.TypeBoolean, "bool"},
-		{"StringToString", ovsdb.TypeString, "string"},
-		{"UUIDToString", ovsdb.TypeUUID, "string"},
-		{"Invalid", "notAType", ""},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := AtomicType(tt.in); got != tt.out {
-				t.Errorf("got %s, wanted %s", got, tt.out)
-			}
-		})
-	}
-}
-
-func TestTag(t *testing.T) {
-	if s := Tag("Foo_Bar"); s != "`ovs:\"Foo_Bar\"`" {
-		t.Fatalf("got %s, wanted `ovs:\"Foo_Bar\"`", s)
-	}
-}
-
-func TestFileName(t *testing.T) {
-	if s := FileName("foo"); s != "foo.go" {
-		t.Fatalf("got %s, wanted foo.go", s)
-	}
-}
-
-func TestCamelCase(t *testing.T) {
-	if s := camelCase("foo_bar_baz"); s != "FooBarBaz" {
-		t.Fatalf("got %s, wanted FooBarBaz", s)
-	}
-}