Repository: ovn-org/libovsdb
Pull request #110 (2021-05-13 15:57:16)

ovsdb: Add Error Types
This adds all of the errors listed in RFC 7047 and
provides a functions to return all errors for a transaction
in a way that's easy to use

Fixes: #95 

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

* **47** of **157**   **(29.94%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-3.1%**) to **69.944%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [ovsdb/error.go](https://coveralls.io/builds/39664253/source?filename=ovsdb%2Ferror.go#L65) | 47 | 157 | 29.94%
<!-- | **Total:** | **47** | **157** | **29.94%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/39664253/badge)](https://coveralls.io/builds/39664253) |
| :-- | --: |
| Change from base [Build 838247643](https://coveralls.io/builds/39652174): |  -3.1% |
| Covered Lines: | 1508 |
| Relevant Lines: | 2156 |

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


Patch:
diff --git a/client/api.go b/client/api.go
index 19ed60a..ba549c9 100644
--- a/client/api.go
+++ b/client/api.go
@@ -323,17 +323,16 @@ 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:     []ovsdb.Condition{condition},
-			},
-		)
+				Where:     condition,
+			})
 	}
-
 	return operations, nil
 }
 
@@ -357,14 +356,12 @@ 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: []ovsdb.Condition{condition},
-			},
-		)
+		operations = append(operations, ovsdb.Operation{
+			Op:    opUpdate,
+			Table: table,
+			Row:   row,
+			Where: condition,
+		})
 	}
 	return operations, nil
 }
@@ -378,13 +375,11 @@ func (a api) Delete() ([]ovsdb.Operation, error) {
 	}
 
 	for _, condition := range conditions {
-		operations = append(operations,
-			ovsdb.Operation{
-				Op:    opDelete,
-				Table: a.cond.Table(),
-				Where: []ovsdb.Condition{condition},
-			},
-		)
+		operations = append(operations, ovsdb.Operation{
+			Op:    opDelete,
+			Table: a.cond.Table(),
+			Where: condition,
+		})
 	}
 
 	return operations, nil
diff --git a/client/condition.go b/client/condition.go
index 7f11f18..24b120c 100644
--- a/client/condition.go
+++ b/client/condition.go
@@ -3,15 +3,13 @@ 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() ([]ovsdb.Condition, error)
+	Generate() ([][]interface{}, error)
 	// matches returns true if a model matches the condition
 	Matches(m Model) (bool, error)
 	// returns the table that this condition is associated with
@@ -37,12 +35,12 @@ func (c *indexCond) Table() string {
 }
 
 // Generate returns a condition based on the model and the field pointers
-func (c *indexCond) Generate() ([]ovsdb.Condition, error) {
+func (c *indexCond) Generate() ([][]interface{}, error) {
 	condition, err := c.orm.newCondition(c.tableName, c.model, c.fields...)
 	if err != nil {
 		return nil, err
 	}
-	return condition, nil
+	return [][]interface{}{condition}, nil
 }
 
 // newIndexCondition creates a new indexCond
@@ -76,8 +74,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() ([]ovsdb.Condition, error) {
-	allConditions := make([]ovsdb.Condition, 0)
+func (c *predicateCond) Generate() ([][]interface{}, error) {
+	allConditions := make([][]interface{}, 0)
 	tableCache := c.cache.Table(c.tableName)
 	if tableCache == nil {
 		return nil, ErrNotFound
@@ -93,7 +91,7 @@ func (c *predicateCond) Generate() ([]ovsdb.Condition, error) {
 			if err != nil {
 				return nil, err
 			}
-			allConditions = append(allConditions, elemCond...)
+			allConditions = append(allConditions, elemCond)
 		}
 	}
 	return allConditions, nil
@@ -122,7 +120,7 @@ func (e *errorCondition) Table() string {
 	return ""
 }
 
-func (e *errorCondition) Generate() ([]ovsdb.Condition, error) {
+func (e *errorCondition) Generate() ([][]interface{}, error) {
 	return nil, e.err
 }
 
diff --git a/client/orm.go b/client/orm.go
index 72dc3f5..a33b707 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{}) ([]ovsdb.Condition, error) {
-	var conditions []ovsdb.Condition
+func (o orm) newCondition(tableName string, data interface{}, fields ...interface{}) ([]interface{}, error) {
+	var conditions []interface{}
 	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, ovsdb.NewCondition(col, ovsdb.ConditionEqual, ovsVal))
+		conditions = append(conditions, []interface{}{col, "==", ovsVal})
 	}
 	return conditions, nil
 }
diff --git a/cmd/modelgen/dbmodel.go b/cmd/modelgen/dbmodel.go
index 9c58fab..2bd8603 100644
--- a/cmd/modelgen/dbmodel.go
+++ b/cmd/modelgen/dbmodel.go
@@ -16,7 +16,7 @@ import (
 	"github.com/ovn-org/libovsdb/client"
 )
 
-// FullDatabaseModel returns the DatabaseModel object to be used in libovsdb
+// FullDatabaseModel() returns the DatabaseModel object to be used in libovsdb
 func FullDatabaseModel() (*client.DBModel, error) {
 	return client.NewDBModel("{{ .DatabaseName }}", map[string]client.Model{
     {{ range $tableName, $structName := .Tables }} "{{ $tableName }}" : &{{ $structName }}{}, 
diff --git a/cmd/modelgen/table.go b/cmd/modelgen/table.go
index 97b796c..4a92383 100644
--- a/cmd/modelgen/table.go
+++ b/cmd/modelgen/table.go
@@ -5,6 +5,7 @@ import (
 	"sort"
 	"strings"
 	"text/template"
+	"unicode"
 
 	"github.com/ovn-org/libovsdb/ovsdb"
 )
@@ -126,60 +127,14 @@ func FileName(table string) string {
 	return fmt.Sprintf("%s.go", strings.ToLower(table))
 }
 
-// common initialisms used in ovsdb schemas
-var initialisms = map[string]bool{
-	"ACL":   true,
-	"BFD":   true,
-	"CFM":   true,
-	"CT":    true,
-	"CVLAN": true,
-	"DNS":   true,
-	"DSCP":  true,
-	"ID":    true,
-	"IP":    true,
-	"IPFIX": true,
-	"LACP":  true,
-	"LLDP":  true,
-	"MAC":   true,
-	"MTU":   true,
-	"OVS":   true,
-	"QOS":   true,
-	"RSTP":  true,
-	"SSL":   true,
-	"STP":   true,
-	"TCP":   true,
-	"UDP":   true,
-	"UUID":  true,
-	"VLAN":  true,
-}
-
 func camelCase(field string) string {
-	s := strings.ToLower(field)
-	parts := strings.FieldsFunc(s, func(r rune) bool {
-		return r == '_' || r == '-'
-	})
-	if len(parts) > 1 {
-		s = ""
-		for _, p := range parts {
-			s += strings.Title(expandInitilaisms(p))
-		}
-	} else {
-		s = strings.Title(expandInitilaisms(s))
-	}
-	return s
-}
-
-func expandInitilaisms(s string) string {
-	// check initialisms
-	if u := strings.ToUpper(s); initialisms[u] {
-		return strings.ToUpper(s)
-	}
-	// check for plurals too
-	if strings.HasSuffix(s, "s") {
-		sub := s[:len(s)-1]
-		if u := strings.ToUpper(sub); initialisms[u] {
-			return strings.ToUpper(sub) + "s"
+	capNext := true
+	orig := []rune(field)
+	for i, c := range orig {
+		if capNext {
+			orig[i] = unicode.ToUpper(c)
 		}
+		capNext = c == '_' || c == '-'
 	}
-	return s
+	return strings.ReplaceAll(string(orig), "_", "")
 }
diff --git a/cmd/stress/stress.go b/cmd/stress/stress.go
index 01bcd0e..dd053a2 100644
--- a/cmd/stress/stress.go
+++ b/cmd/stress/stress.go
@@ -85,20 +85,14 @@ func run() {
 }
 
 func transact(ovs *client.OvsdbClient, operations []ovsdb.Operation) (ok bool, uuid string) {
-	reply, _ := ovs.Transact(operations...)
-
-	if len(reply) < len(operations) {
-		fmt.Println("Number of Replies should be atleast equal to number of Operations")
-	}
-	ok = true
-	for i, o := range reply {
-		if o.Error != "" && i < len(operations) {
-			fmt.Println("Transaction Failed due to an error :", o.Error, " details:", o.Details, " in ", operations[i])
-			ok = false
-		} else if o.Error != "" {
-			fmt.Println("Transaction Failed due to an error :", o.Error)
-			ok = false
-		}
+	reply, err := ovs.Transact(operations...)
+	if err != nil {
+		ok = false
+		return
+	}
+	if _, err := ovsdb.CheckOperationResults(reply, operations); err != nil {
+		ok = false
+		return
 	}
 	uuid = reply[0].UUID.GoUUID
 	return
diff --git a/example/play_with_ovs/play_with_ovs.go b/example/play_with_ovs/play_with_ovs.go
index 5e62f51..f473099 100644
--- a/example/play_with_ovs/play_with_ovs.go
+++ b/example/play_with_ovs/play_with_ovs.go
@@ -6,6 +6,7 @@ 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
@@ -86,23 +87,10 @@ func createBridge(ovs *client.OvsdbClient, bridgeName string) {
 	if err != nil {
 		log.Fatal(err)
 	}
-
-	if len(reply) < len(operations) {
-		fmt.Println("Number of Replies should be atleast equal to number of Operations")
-	}
-	ok := true
-	for i, o := range reply {
-		if o.Error != "" && i < len(operations) {
-			fmt.Println("Transaction Failed due to an error :", o.Error, " details:", o.Details, " in ", operations[i])
-			ok = false
-		} else if o.Error != "" {
-			fmt.Println("Transaction Failed due to an error :", o.Error)
-			ok = false
-		}
-	}
-	if ok {
-		fmt.Println("Bridge Addition Successful : ", reply[0].UUID.GoUUID)
+	if _, err := ovsdb.CheckOperationResults(reply, operations); err != nil {
+		log.Fatal(err)
 	}
+	fmt.Println("Bridge Addition Successful : ", reply[0].UUID.GoUUID)
 }
 
 func processInput(ovs *client.OvsdbClient) {
diff --git a/ovsdb/condition.go b/ovsdb/condition.go
deleted file mode 100644
index 6c7ba3f..0000000
--- a/ovsdb/condition.go
+++ /dev/null
@@ -1,70 +0,0 @@
-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/error.go b/ovsdb/error.go
new file mode 100644
index 0000000..577fe0e
--- /dev/null
+++ b/ovsdb/error.go
@@ -0,0 +1,333 @@
+package ovsdb
+
+import "fmt"
+
+const (
+	referentialIntegrityViolation = "referential integrity violation"
+	constraintViolation           = "constraint violation"
+	resourcesExhausted            = "resources exhausted"
+	ioError                       = "I/O error"
+	duplicateUUIDName             = "duplicate uuid name"
+	domainError                   = "domain error"
+	rangeError                    = "range error"
+	timedOut                      = "timed out"
+	notSupported                  = "not supported"
+	aborted                       = "aborted"
+	notOwner                      = "not owner"
+)
+
+// errorFromResult returns an specific OVSDB error type from
+// an OperationResult
+func errorFromResult(op *Operation, r OperationResult) OperationError {
+	if r.Error == "" {
+		return nil
+	}
+	switch r.Error {
+	case referentialIntegrityViolation:
+		return &ReferentialIntegrityViolation{r.Details, op}
+	case constraintViolation:
+		return &ConstraintViolation{r.Details, op}
+	case resourcesExhausted:
+		return &ResourcesExhausted{r.Details, op}
+	case ioError:
+		return &IOError{r.Details, op}
+	case duplicateUUIDName:
+		return &DuplicateUUIDName{r.Details, op}
+	case domainError:
+		return &DomainError{r.Details, op}
+	case rangeError:
+		return &RangeError{r.Details, op}
+	case timedOut:
+		return &TimedOut{r.Details, op}
+	case notSupported:
+		return &NotSupported{r.Details, op}
+	case aborted:
+		return &Aborted{r.Details, op}
+	case notOwner:
+		return &NotOwner{r.Details, op}
+	default:
+		return &Error{r.Error, r.Details, op}
+	}
+}
+
+// CheckOperationResults checks whether the provided operation was a success
+// If the operation was a success, it will return nil, nil
+// If the operation failed, due to a error committing the transaction it will
+// return nil, error.
+// Finally, in the case where one or more of the operations in the transaction
+// failed, we return []OperationErrors, error
+// Within []OperationErrors, the OperationErrors.Index() corresponds to the same index in
+// the original Operations struct. You may also perform type assertions against
+// the error so the caller can decide how best to handle it
+func CheckOperationResults(result []OperationResult, ops []Operation) ([]OperationError, error) {
+	// this shouldn't happen, but we'll cover the case to be certain
+	if len(result) < len(ops) {
+		return nil, fmt.Errorf("ovsdb transaction error. %d operations submitted but only %d results recieved", len(ops), len(result))
+	}
+	var errs []OperationError
+	for i, op := range result {
+		// RFC 7047: if all of the operations succeed, but the results cannot
+		// be committed, then "result" will have one more element than "params",
+		// with the additional element being an <error>.
+		if i >= len(ops) {
+			return errs, errorFromResult(nil, op)
+		}
+		if err := errorFromResult(&ops[i], op); err != nil {
+			errs = append(errs, err)
+		}
+	}
+	if len(errs) > 0 {
+		return errs, fmt.Errorf("%d ovsdb operations failed", len(errs))
+	}
+	return nil, nil
+}
+
+// OperationError represents an error that occurred as part of an
+// OVSDB Operation
+type OperationError interface {
+	error
+	// Operation is a pointer to the operation which casued the error
+	Operation() *Operation
+}
+
+// ReferentialIntegrityViolation is explained in RFC 7047 4.1.3
+type ReferentialIntegrityViolation struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *ReferentialIntegrityViolation) Error() string {
+	msg := referentialIntegrityViolation
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *ReferentialIntegrityViolation) Operation() *Operation {
+	return e.operation
+}
+
+// ConstraintViolation is described in RFC 7047: 4.1.3
+type ConstraintViolation struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *ConstraintViolation) Error() string {
+	msg := constraintViolation
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *ConstraintViolation) Operation() *Operation {
+	return e.operation
+}
+
+// ResourcesExhasued is described in RFC 7047: 4.1.3
+type ResourcesExhausted struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *ResourcesExhausted) Error() string {
+	msg := resourcesExhausted
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *ResourcesExhausted) Operation() *Operation {
+	return e.operation
+}
+
+// IOError is described in RFC7047: 4.1.3
+type IOError struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *IOError) Error() string {
+	msg := ioError
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *IOError) Operation() *Operation {
+	return e.operation
+}
+
+// DuplicateUUIDName is described in RFC7047 5.2.1
+type DuplicateUUIDName struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *DuplicateUUIDName) Error() string {
+	msg := duplicateUUIDName
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *DuplicateUUIDName) Operation() *Operation {
+	return e.operation
+}
+
+// DomainError is described in RFC 7047: 5.2.4
+type DomainError struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *DomainError) Error() string {
+	msg := domainError
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *DomainError) Operation() *Operation {
+	return e.operation
+}
+
+// RangeError is described in RFC 7047: 5.2.4
+type RangeError struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *RangeError) Error() string {
+	msg := rangeError
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *RangeError) Operation() *Operation {
+	return e.operation
+}
+
+// TimedOut is described in RFC 7047: 5.2.6
+type TimedOut struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *TimedOut) Error() string {
+	msg := timedOut
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *TimedOut) Operation() *Operation {
+	return e.operation
+}
+
+// NotSupported is described in RFC 7047: 5.2.7
+type NotSupported struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *NotSupported) Error() string {
+	msg := notSupported
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *NotSupported) Operation() *Operation {
+	return e.operation
+}
+
+// ABorted is described in RFC 7047: 5.2.8
+type Aborted struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *Aborted) Error() string {
+	msg := aborted
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *Aborted) Operation() *Operation {
+	return e.operation
+}
+
+// NotOwner is described in RFC 7047: 5.2.9
+type NotOwner struct {
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *NotOwner) Error() string {
+	msg := notOwner
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *NotOwner) Operation() *Operation {
+	return e.operation
+}
+
+// Error is a generic OVSDB Error type that implements the
+// OperationError and error interfaces
+type Error struct {
+	name      string
+	details   string
+	operation *Operation
+}
+
+// Error implements the error interface
+func (e *Error) Error() string {
+	msg := e.name
+	if e.details != "" {
+		msg += ": " + e.details
+	}
+	return msg
+}
+
+// Operation implements the OperationError interface
+func (e *Error) Operation() *Operation {
+	return e.operation
+}
diff --git a/ovsdb/notation.go b/ovsdb/notation.go
index fc9a051..ebaf4ee 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     []Condition              `json:"where,omitempty"`
+	Where     []interface{}            `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([]Condition, 0)
+			where = make([]interface{}, 0)
 		}
 		return json.Marshal(&struct {
-			Where []Condition `json:"where"`
+			Where []interface{} `json:"where"`
 			OpAlias
 		}{
 			Where:   where,
@@ -91,6 +91,11 @@ 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 ed45baf..f1cd984 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:     []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
+					Where:     []interface{}{[]interface{}{"_uuid", "==", 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:     []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "lsp2"}},
+					Where:     []interface{}{[]interface{}{"name", "==", "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:     []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID2}}},
+					Where:     []interface{}{[]interface{}{"_uuid", "==", 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:     []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
+					Where:     []interface{}{[]interface{}{"_uuid", "==", 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:     []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID1}}},
+					Where:     []interface{}{[]interface{}{"_uuid", "==", 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: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
+					Where: []interface{}{[]interface{}{"_uuid", "==", 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: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "lsp1"}},
+					Where: []interface{}{[]interface{}{"name", "==", "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: []ovsdb.Condition{{Column: "type", Function: ovsdb.ConditionEqual, Value: "sometype"}},
+					Where: []interface{}{[]interface{}{"type", "==", "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: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
+					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 				{
 					Op:    opUpdate,
 					Table: "Logical_Switch_Port",
 					Row:   map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})},
-					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID1}}},
+					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID1}}},
 				},
 			},
 			err: false,
@@ -974,7 +974,7 @@ func TestAPIDelete(t *testing.T) {
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch",
-					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
+					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 			},
 			err: false,
@@ -990,7 +990,7 @@ func TestAPIDelete(t *testing.T) {
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch_Port",
-					Where: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "lsp1"}},
+					Where: []interface{}{[]interface{}{"name", "==", "lsp1"}},
 				},
 			},
 			err: false,
@@ -1008,7 +1008,7 @@ func TestAPIDelete(t *testing.T) {
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch_Port",
-					Where: []ovsdb.Condition{{Column: "type", Function: ovsdb.ConditionEqual, Value: "sometype"}},
+					Where: []interface{}{[]interface{}{"type", "==", "sometype"}},
 				},
 			},
 			err: false,
@@ -1024,12 +1024,12 @@ func TestAPIDelete(t *testing.T) {
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch_Port",
-					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
+					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
 				},
 				{
 					Op:    opDelete,
 					Table: "Logical_Switch_Port",
-					Where: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID1}}},
+					Where: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID1}}},
 				},
 			},
 			err: false,
diff --git a/client/orm_test.go b/client/orm_test.go
index 7abf5f7..d40e1bd 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 []ovsdb.Condition
+		expected []interface{}
 		index    []interface{}
 		err      bool
 	}
@@ -509,7 +509,7 @@ func TestORMCondition(t *testing.T) {
 				t.Comp2 = ""
 			},
 			index:    []interface{}{},
-			expected: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "foo"}},
+			expected: []interface{}{[]interface{}{"name", "==", "foo"}},
 			err:      false,
 		},
 		{
@@ -522,7 +522,7 @@ func TestORMCondition(t *testing.T) {
 				t.Comp2 = ""
 			},
 			index:    []interface{}{},
-			expected: []ovsdb.Condition{{Column: "_uuid", Function: ovsdb.ConditionEqual, Value: ovsdb.UUID{GoUUID: aUUID0}}},
+			expected: []interface{}{[]interface{}{"_uuid", "==", ovsdb.UUID{GoUUID: aUUID0}}},
 			err:      false,
 		},
 		{
@@ -535,7 +535,7 @@ func TestORMCondition(t *testing.T) {
 				t.Comp2 = ""
 			},
 			index:    []interface{}{&testObj.MyName},
-			expected: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "foo"}},
+			expected: []interface{}{[]interface{}{"name", "==", "foo"}},
 			err:      false,
 		},
 		{
@@ -547,9 +547,8 @@ func TestORMCondition(t *testing.T) {
 				t.Comp1 = "foo"
 				t.Comp2 = "bar"
 			},
-			expected: []ovsdb.Condition{
-				{Column: "composed_1", Function: ovsdb.ConditionEqual, Value: "foo"},
-				{Column: "composed_2", Function: ovsdb.ConditionEqual, Value: "bar"}},
+			expected: []interface{}{[]interface{}{"composed_1", "==", "foo"},
+				[]interface{}{"composed_2", "==", "bar"}},
 			index: []interface{}{},
 			err:   false,
 		},
@@ -562,7 +561,7 @@ func TestORMCondition(t *testing.T) {
 				t.Comp1 = "foo"
 				t.Comp2 = "bar"
 			},
-			expected: []ovsdb.Condition{{Column: "name", Function: ovsdb.ConditionEqual, Value: "something"}},
+			expected: []interface{}{[]interface{}{"name", "==", "something"}},
 			index:    []interface{}{},
 			err:      false,
 		},
diff --git a/client/ovs_integration_test.go b/client/ovs_integration_test.go
index 49695eb..b5a057c 100644
--- a/client/ovs_integration_test.go
+++ b/client/ovs_integration_test.go
@@ -2,7 +2,6 @@ package client
 
 import (
 	"bytes"
-	"fmt"
 	"log"
 	"os"
 	"testing"
@@ -208,23 +207,14 @@ func TestInsertTransactIntegration(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(reply) < len(operations) {
-		t.Error("Number of Replies should be atleast equal to number of Operations")
-	}
-	ok := true
-	for i, o := range reply {
-		if o.Error != "" && i < len(operations) {
-			t.Error("Transaction Failed due to an error :", o.Error, " details:", o.Details, " in ", operations[i])
-			ok = false
-		} else if o.Error != "" {
-			t.Error("Transaction Failed due to an error :", o.Error)
-			ok = false
+	operationErrs, err := ovsdb.CheckOperationResults(reply, operations)
+	if err != nil {
+		for _, oe := range operationErrs {
+			t.Error(oe)
 		}
+		t.Fatal(err)
 	}
-	if ok {
-		fmt.Println("Bridge Addition Successful : ", reply[0].UUID.GoUUID)
-		bridgeUUID = reply[0].UUID.GoUUID
-	}
+	bridgeUUID = reply[0].UUID.GoUUID
 	ovs.Disconnect()
 }
 
@@ -266,21 +256,12 @@ func TestDeleteTransactIntegration(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(reply) < len(operations) {
-		t.Error("Number of Replies should be atleast equal to number of Operations")
-	}
-	ok := true
-	for i, o := range reply {
-		if o.Error != "" && i < len(operations) {
-			t.Error("Transaction Failed due to an error :", o.Error, " in ", operations[i])
-			ok = false
-		} else if o.Error != "" {
-			t.Error("Transaction Failed due to an error :", o.Error)
-			ok = false
+	operationErrs, err := ovsdb.CheckOperationResults(reply, operations)
+	if err != nil {
+		for _, oe := range operationErrs {
+			t.Error(oe)
 		}
-	}
-	if ok {
-		fmt.Println("Bridge Delete Successful", reply[0].Count)
+		t.Fatal(err)
 	}
 	ovs.Disconnect()
 }
@@ -330,7 +311,7 @@ func TestNotifyIntegration(t *testing.T) {
 
 	select {
 	case <-timeoutChan:
-		fmt.Println("Nothing changed to notify")
+		t.Fatal("timed out")
 	case <-notifyEchoChan:
 		break
 	}
@@ -536,3 +517,66 @@ func TestMonitorCancelIntegration(t *testing.T) {
 	}
 	ovs.Disconnect()
 }
+
+func TestInsertDuplicateTransactIntegration(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping integration test")
+	}
+	SetConfig()
+
+	ovs, err := Connect(cfg.Addr, defDB, nil)
+	if err != nil {
+		t.Fatalf("Failed to Connect. error: %s", err)
+	}
+	err = ovs.MonitorAll(nil)
+	assert.Nil(t, err)
+
+	// NamedUUID is used to add multiple related Operations in a single Transact operation
+	namedUUID := "gopher"
+	br := bridgeType{
+		UUID: namedUUID,
+		Name: "br-dup",
+		ExternalIds: map[string]string{
+			"go":     "awesome",
+			"docker": "made-for-each-other",
+		},
+	}
+
+	insertOp, err := ovs.Create(&br)
+	assert.Nil(t, err)
+
+	// Inserting a Bridge row in Bridge table requires mutating the open_vswitch table.
+	ovsRow := ovsType{}
+	mutateOp, err := ovs.Where(ovs.ConditionFromFunc(func(*ovsType) bool { return true })).
+		Mutate(&ovsRow, []Mutation{
+			{
+				Field:   &ovsRow.Bridges,
+				Mutator: ovsdb.MutateOperationInsert,
+				Value:   []string{namedUUID},
+			},
+		})
+	assert.Nil(t, err)
+
+	operations := append(insertOp, mutateOp...)
+	reply, err := ovs.Transact(operations...)
+	if err != nil {
+		t.Fatal(err)
+	}
+	operationErrs, err := ovsdb.CheckOperationResults(reply, operations)
+	if err != nil {
+		for _, oe := range operationErrs {
+			t.Error(oe)
+		}
+		t.Fatal(err)
+	}
+	bridgeUUID = reply[0].UUID.GoUUID
+
+	reply, err = ovs.Transact(operations...)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = ovsdb.CheckOperationResults(reply, operations)
+	assert.Error(t, err)
+	assert.IsType(t, &ovsdb.ConstraintViolation{}, err)
+	ovs.Disconnect()
+}
diff --git a/cmd/modelgen/table_test.go b/cmd/modelgen/table_test.go
index cdcd7cb..1483f31 100644
--- a/cmd/modelgen/table_test.go
+++ b/cmd/modelgen/table_test.go
@@ -66,18 +66,9 @@ type test struct {
 }
 
 func TestFieldName(t *testing.T) {
-	cases := []struct {
-		in       string
-		expected string
-	}{
-		{"foo", "Foo"},
-	}
-	for _, tt := range cases {
-		if s := FieldName(tt.in); s != tt.expected {
-			t.Fatalf("got %s, wanted %s", s, tt.expected)
-		}
+	if s := FieldName("foo"); s != "Foo" {
+		t.Fatalf("got %s, wanted Foo", s)
 	}
-
 }
 
 func TestStructName(t *testing.T) {
@@ -135,23 +126,7 @@ func TestFileName(t *testing.T) {
 }
 
 func TestCamelCase(t *testing.T) {
-	cases := []struct {
-		in       string
-		expected string
-	}{
-		{"foo_bar_baz", "FooBarBaz"},
-		{"foo-bar-baz", "FooBarBaz"},
-		{"foos-bars-bazs", "FoosBarsBazs"},
-		{"ip_port_mappings", "IPPortMappings"},
-		{"external_ids", "ExternalIDs"},
-		{"ip_prefix", "IPPrefix"},
-		{"dns_records", "DNSRecords"},
-		{"logical_ip", "LogicalIP"},
-		{"ip", "IP"},
-	}
-	for _, tt := range cases {
-		if s := camelCase(tt.in); s != tt.expected {
-			t.Fatalf("got %s, wanted %s", s, tt.expected)
-		}
+	if s := camelCase("foo_bar_baz"); s != "FooBarBaz" {
+		t.Fatalf("got %s, wanted FooBarBaz", s)
 	}
 }
diff --git a/ovsdb/condition_test.go b/ovsdb/condition_test.go
deleted file mode 100644
index e1940ec..0000000
--- a/ovsdb/condition_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-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/error_test.go b/ovsdb/error_test.go
new file mode 100644
index 0000000..712d921
--- /dev/null
+++ b/ovsdb/error_test.go
@@ -0,0 +1,136 @@
+package ovsdb
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestErrorFromResult(t *testing.T) {
+	type args struct {
+		op *Operation
+		r  OperationResult
+	}
+	tests := []struct {
+		name     string
+		args     args
+		expected interface{}
+	}{
+		{
+			referentialIntegrityViolation,
+			args{nil, OperationResult{Error: referentialIntegrityViolation}},
+			&ReferentialIntegrityViolation{},
+		},
+		{
+			constraintViolation,
+			args{nil, OperationResult{Error: constraintViolation}},
+			&ConstraintViolation{},
+		},
+		{
+			resourcesExhausted,
+			args{nil, OperationResult{Error: resourcesExhausted}},
+			&ResourcesExhausted{},
+		},
+		{
+			ioError,
+			args{nil, OperationResult{Error: ioError}},
+			&IOError{},
+		},
+		{
+			duplicateUUIDName,
+			args{nil, OperationResult{Error: duplicateUUIDName}},
+			&DuplicateUUIDName{},
+		},
+		{
+			domainError,
+			args{nil, OperationResult{Error: domainError}},
+			&DomainError{},
+		},
+		{
+			rangeError,
+			args{nil, OperationResult{Error: rangeError}},
+			&RangeError{},
+		},
+		{
+			timedOut,
+			args{nil, OperationResult{Error: timedOut}},
+			&TimedOut{},
+		},
+		{
+			notSupported,
+			args{nil, OperationResult{Error: notSupported}},
+			&NotSupported{},
+		},
+		{
+			aborted,
+			args{nil, OperationResult{Error: aborted}},
+			&Aborted{},
+		},
+		{
+			notOwner,
+			args{nil, OperationResult{Error: notOwner}},
+			&NotOwner{},
+		},
+		{
+			"generic error",
+			args{nil, OperationResult{Error: "foo"}},
+			&Error{},
+		},
+		{
+			"nil",
+			args{nil, OperationResult{Error: ""}},
+			nil,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := errorFromResult(tt.args.op, tt.args.r)
+			assert.IsType(t, tt.expected, err)
+		})
+	}
+}
+
+func TestCheckOperationResults(t *testing.T) {
+	type args struct {
+		result []OperationResult
+		ops    []Operation
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    []OperationError
+		wantErr bool
+	}{
+		{
+			"success",
+			args{[]OperationResult{{}}, []Operation{{Op: "insert"}}},
+			nil,
+			false,
+		},
+		{
+			"commit error",
+			args{[]OperationResult{{}, {Error: constraintViolation}}, []Operation{{Op: "insert"}}},
+			nil,
+			true,
+		},
+		{
+			"transaction error",
+			args{[]OperationResult{{Error: constraintViolation, Details: "foo"}, {Error: constraintViolation, Details: "bar"}}, []Operation{{Op: "insert"}, {Op: "mutate"}}},
+			[]OperationError{&ConstraintViolation{details: "foo", operation: &Operation{Op: "insert"}}, &ConstraintViolation{details: "bar", operation: &Operation{Op: "mutate"}}},
+			true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := CheckOperationResults(tt.args.result, tt.args.ops)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("CheckOperationResults() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("CheckOperationResults() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/ovsdb/notation_test.go b/ovsdb/notation_test.go
index 7c3e1a3..a8ad879 100644
--- a/ovsdb/notation_test.go
+++ b/ovsdb/notation_test.go
@@ -156,6 +156,15 @@ 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)