Repository: ovn-org/libovsdb
Pull request #114 (2021-05-17 08:09:11)

Simplify Where API
The idea is to simplify the API making less verbose

Remove the api.ConditionByXXX functions and instead have two ways of creating conditionalAPIs:

**Where(Model, ...Condition)** -> For conditions that will be evaluated in the server.

- An empty list of conditions defaults to index-based model equality

**WhereCache(predicate)** -> For conditions that will be evaluated in the cache using a callback function.

Based on #113 


Patch:
diff --git a/client/api.go b/client/api.go
index 19ed60a..8634c09 100644
--- a/client/api.go
+++ b/client/api.go
@@ -23,18 +23,13 @@ type API interface {
 	// If it has a capacity != 0, only 'capacity' elements will be filled in
 	List(result interface{}) error
 
-	// Create a Condition from a Function that is used to filter cached data
+	// Create a Conditional API from a Function that is used to filter cached data
 	// The function must accept a Model implementation and return a boolean. E.g:
 	// ConditionFromFunc(func(l *LogicalSwitch) bool { return l.Enabled })
-	ConditionFromFunc(predicate interface{}) Condition
+	WhereCache(predicate interface{}) ConditionalAPI
 
-	// Create a Condition from a Model's data. It uses the database indexes
-	// to search the most apropriate field to use for matches and conditions
-	// Optionally, a list of fields can indicate an alternative index
-	ConditionFromModel(Model, ...interface{}) Condition
-
-	// Create a ConditionalAPI from a Condition
-	Where(condition Condition) ConditionalAPI
+	// Create a ConditionalAPI from a Model's index data or a list of Conditions
+	Where(Model, ...Condition) ConditionalAPI
 
 	// Get retrieves a model from the cache
 	// The way the object will be fetch depends on the data contained in the
@@ -82,6 +77,16 @@ type Mutation struct {
 	Value interface{}
 }
 
+// Condition is a type that represents a OVSDB Condition
+type Condition struct {
+	// Pointer to the field of the model where the operation applies
+	Field interface{}
+	// Condition function
+	Function ovsdb.ConditionFunction
+	// Value to use in the condition
+	Value interface{}
+}
+
 // InputTypeError is used to report the user provided parameter has the wrong type
 type InputTypeError struct {
 	inputType reflect.Type
@@ -112,7 +117,7 @@ var ErrNotFound = errors.New("object not found")
 // Where() can be used to create a ConditionalAPI api
 type api struct {
 	cache *TableCache
-	cond  Condition
+	cond  Conditional
 }
 
 // List populates a slice of Models given as parameter based on the configured Condition
@@ -169,37 +174,54 @@ func (a api) List(result interface{}) error {
 	return nil
 }
 
-// Where returns a conditionalAPI based a Condition
-func (a api) Where(condition Condition) ConditionalAPI {
-	return newConditionalAPI(a.cache, condition)
+// Where returns a conditionalAPI based on a Condition list
+func (a api) Where(model Model, cond ...Condition) ConditionalAPI {
+	return newConditionalAPI(a.cache, a.conditionFromModel(model, cond...))
+}
+
+// Where returns a conditionalAPI based a Predicate
+func (a api) WhereCache(predicate interface{}) ConditionalAPI {
+	return newConditionalAPI(a.cache, a.conditionFromFunc(predicate))
 }
 
-// ConditionFactory interface implementation
+// Conditional interface implementation
 // FromFunc returns a Condition from a function
-func (a api) ConditionFromFunc(predicate interface{}) Condition {
+func (a api) conditionFromFunc(predicate interface{}) Conditional {
 	table, err := a.getTableFromFunc(predicate)
 	if err != nil {
-		return newErrorCondition(err)
+		return newErrorConditional(err)
 	}
 
-	condition, err := newPredicateCond(table, a.cache, predicate)
+	condition, err := newPredicateConditional(table, a.cache, predicate)
 	if err != nil {
-		return newErrorCondition(err)
+		return newErrorConditional(err)
 	}
 	return condition
 }
 
 // FromModel returns a Condition from a model and a list of fields
-func (a api) ConditionFromModel(model Model, fields ...interface{}) Condition {
+func (a api) conditionFromModel(model Model, cond ...Condition) Conditional {
+	var conditional Conditional
+	var err error
+
 	tableName, err := a.getTableFromModel(model)
 	if tableName == "" {
-		return newErrorCondition(err)
+		return newErrorConditional(err)
 	}
-	condition, err := newIndexCondition(a.cache.orm, tableName, model, fields...)
-	if err != nil {
-		return newErrorCondition(err)
+
+	if len(cond) == 0 {
+		conditional, err = newEqualityConditional(a.cache.orm, tableName, model)
+		if err != nil {
+			conditional = newErrorConditional(err)
+		}
+
+	} else {
+		conditional, err = newExplicitConditional(a.cache.orm, tableName, model, cond...)
+		if err != nil {
+			conditional = newErrorConditional(err)
+		}
 	}
-	return condition
+	return conditional
 }
 
 // Get is a generic Get function capable of returning (through a provided pointer)
@@ -439,7 +461,7 @@ func newAPI(cache *TableCache) API {
 }
 
 // newConditionalAPI returns a new ConditionalAPI to interact with the database
-func newConditionalAPI(cache *TableCache, cond Condition) ConditionalAPI {
+func newConditionalAPI(cache *TableCache, cond Conditional) ConditionalAPI {
 	return api{
 		cache: cache,
 		cond:  cond,
diff --git a/client/client.go b/client/client.go
index dbb5777..43cce94 100644
--- a/client/client.go
+++ b/client/client.go
@@ -371,16 +371,11 @@ func (ovs OvsdbClient) List(result interface{}) error {
 }
 
 //Where implements the API interface's Where function
-func (ovs OvsdbClient) Where(condition Condition) ConditionalAPI {
-	return ovs.api.Where(condition)
+func (ovs OvsdbClient) Where(m Model, conditions ...Condition) ConditionalAPI {
+	return ovs.api.Where(m, conditions...)
 }
 
-//ConditionFromFunc implements the API interface's ConditionFromFunc function
-func (ovs OvsdbClient) ConditionFromFunc(predicate interface{}) Condition {
-	return ovs.api.ConditionFromFunc(predicate)
-}
-
-//ConditionFromModel implements the API interface's ConditionFromModel function
-func (ovs OvsdbClient) ConditionFromModel(m Model, fields ...interface{}) Condition {
-	return ovs.api.ConditionFromModel(m, fields...)
+//WhereCache implements the API interface's WhereCache function
+func (ovs OvsdbClient) WhereCache(predicate interface{}) ConditionalAPI {
+	return ovs.api.WhereCache(predicate)
 }
diff --git a/client/condition.go b/client/condition.go
index 7f11f18..452f501 100644
--- a/client/condition.go
+++ b/client/condition.go
@@ -7,9 +7,9 @@ import (
 	"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 {
+// Conditional is the interface used by the ConditionalAPI to match on cache objects
+// and generate ovsdb conditions
+type Conditional interface {
 	// Generate returns a list of conditions to be used in Operations
 	Generate() ([]ovsdb.Condition, error)
 	// matches returns true if a model matches the condition
@@ -18,46 +18,83 @@ type Condition interface {
 	Table() string
 }
 
-// indexCond uses the information available in a model to generate conditions
+// equalityConditional uses the information available in a model to generate conditions
 // The conditions are based on the equality of the first available index.
-// The priority of indexes is: {user_provided fields}, uuid, {schema index}
-type indexCond struct {
+// The priority of indexes is: uuid, {schema index}
+type equalityConditional struct {
 	orm       *orm
 	tableName string
 	model     Model
-	fields    []interface{}
 }
 
-func (c *indexCond) Matches(m Model) (bool, error) {
-	return c.orm.equalFields(c.tableName, c.model, m, c.fields...)
+func (c *equalityConditional) Matches(m Model) (bool, error) {
+	return c.orm.equalFields(c.tableName, c.model, m)
 }
 
-func (c *indexCond) Table() string {
+func (c *equalityConditional) Table() string {
 	return c.tableName
 }
 
 // Generate returns a condition based on the model and the field pointers
-func (c *indexCond) Generate() ([]ovsdb.Condition, error) {
-	condition, err := c.orm.newCondition(c.tableName, c.model, c.fields...)
+func (c *equalityConditional) Generate() ([]ovsdb.Condition, error) {
+	condition, err := c.orm.newEqualityCondition(c.tableName, c.model)
 	if err != nil {
 		return nil, err
 	}
 	return condition, nil
 }
 
-// newIndexCondition creates a new indexCond
-func newIndexCondition(orm *orm, table string, model Model, fields ...interface{}) (Condition, error) {
-	return &indexCond{
+// newIndexCondition creates a new equalityConditional
+func newEqualityConditional(orm *orm, table string, model Model, fields ...interface{}) (Conditional, error) {
+	return &equalityConditional{
 		orm:       orm,
 		tableName: table,
 		model:     model,
-		fields:    fields,
 	}, nil
 }
 
-// predicateCond is a conditionFactory that calls a provided function pointer
+// explicitConditional generates conditions based on the provided Condition list
+type explicitConditional struct {
+	orm        *orm
+	tableName  string
+	model      Model
+	conditions []Condition
+}
+
+func (c *explicitConditional) Matches(m Model) (bool, error) {
+	return false, fmt.Errorf("Cannot perform Cache comparisons using explicit Conditions")
+}
+
+func (c *explicitConditional) Table() string {
+	return c.tableName
+}
+
+// Generate returns a condition based on the model and the field pointers
+func (c *explicitConditional) Generate() ([]ovsdb.Condition, error) {
+	var result []ovsdb.Condition
+	for _, cond := range c.conditions {
+		ovsdbCond, err := c.orm.newCondition(c.tableName, c.model, cond)
+		if err != nil {
+			return nil, err
+		}
+		result = append(result, *ovsdbCond)
+	}
+	return result, nil
+}
+
+// newIndexCondition creates a new equalityConditional
+func newExplicitConditional(orm *orm, table string, model Model, cond ...Condition) (Conditional, error) {
+	return &explicitConditional{
+		orm:        orm,
+		tableName:  table,
+		model:      model,
+		conditions: cond,
+	}, nil
+}
+
+// predicateConditional is a Conditional that calls a provided function pointer
 // to match on models.
-type predicateCond struct {
+type predicateConditional struct {
 	tableName string
 	predicate interface{}
 	cache     *TableCache
@@ -65,18 +102,18 @@ type predicateCond struct {
 
 // matches returns the result of the execution of the predicate
 // Type verifications are not performed
-func (c *predicateCond) Matches(model Model) (bool, error) {
+func (c *predicateConditional) Matches(model Model) (bool, error) {
 	ret := reflect.ValueOf(c.predicate).Call([]reflect.Value{reflect.ValueOf(model)})
 	return ret[0].Bool(), nil
 }
 
-func (c *predicateCond) Table() string {
+func (c *predicateConditional) Table() string {
 	return c.tableName
 }
 
 // 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) {
+func (c *predicateConditional) Generate() ([]ovsdb.Condition, error) {
 	allConditions := make([]ovsdb.Condition, 0)
 	tableCache := c.cache.Table(c.tableName)
 	if tableCache == nil {
@@ -89,7 +126,7 @@ func (c *predicateCond) Generate() ([]ovsdb.Condition, error) {
 			return nil, err
 		}
 		if match {
-			elemCond, err := c.cache.orm.newCondition(c.tableName, elem)
+			elemCond, err := c.cache.orm.newEqualityCondition(c.tableName, elem)
 			if err != nil {
 				return nil, err
 			}
@@ -99,35 +136,35 @@ func (c *predicateCond) Generate() ([]ovsdb.Condition, error) {
 	return allConditions, nil
 }
 
-// newIndexCondition creates a new predicateCond
-func newPredicateCond(table string, cache *TableCache, predicate interface{}) (Condition, error) {
-	return &predicateCond{
+// newPredicateConditional creates a new predicateConditional
+func newPredicateConditional(table string, cache *TableCache, predicate interface{}) (Conditional, error) {
+	return &predicateConditional{
 		tableName: table,
 		predicate: predicate,
 		cache:     cache,
 	}, nil
 }
 
-// errorCondition is a condition that encapsulates an error
-// It is used to delay the reporting of errors from condition creation to method call
-type errorCondition struct {
+// errorConditional is a conditional that encapsulates an error
+// It is used to delay the reporting of errors from conditional creation to API method call
+type errorConditional struct {
 	err error
 }
 
-func (e *errorCondition) Matches(Model) (bool, error) {
+func (e *errorConditional) Matches(Model) (bool, error) {
 	return false, e.err
 }
 
-func (e *errorCondition) Table() string {
+func (e *errorConditional) Table() string {
 	return ""
 }
 
-func (e *errorCondition) Generate() ([]ovsdb.Condition, error) {
+func (e *errorConditional) Generate() ([]ovsdb.Condition, error) {
 	return nil, e.err
 }
 
-func newErrorCondition(err error) Condition {
-	return &errorCondition{
+func newErrorConditional(err error) Conditional {
+	return &errorConditional{
 		err: fmt.Errorf("conditionerror: %s", err.Error()),
 	}
 }
diff --git a/client/orm.go b/client/orm.go
index 72dc3f5..0614b1e 100644
--- a/client/orm.go
+++ b/client/orm.go
@@ -158,7 +158,7 @@ func (o orm) newRow(tableName string, data interface{}, fields ...interface{}) (
 	return ovsRow, nil
 }
 
-// newCondition returns a list of conditions that match a given object
+// newEquality Condition returns a list of equality conditions that match a given object
 // A list of valid columns that shall be used as a index can be provided.
 // If none are provided, we will try to use object's field that matches the '_uuid' ovs tag
 // If it does not exist or is null (""), then we will traverse all of the table indexes and
@@ -166,7 +166,7 @@ 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) {
+func (o orm) newEqualityCondition(tableName string, data interface{}, fields ...interface{}) ([]ovsdb.Condition, error) {
 	var conditions []ovsdb.Condition
 	var condIndex [][]string
 
@@ -248,6 +248,43 @@ func (o orm) equalFields(tableName string, one, other interface{}, fields ...int
 	return o.equalIndexes(table, one, other, indexes...)
 }
 
+// newCondition returns a ovsdb.Condition based on a client.Condition
+func (o orm) newCondition(tableName string, data interface{}, condition Condition) (*ovsdb.Condition, error) {
+	table := o.schema.Table(tableName)
+	if table == nil {
+		return nil, NewErrNoTable(tableName)
+	}
+
+	ormInfo, err := newORMInfo(table, data)
+	if err != nil {
+		return nil, err
+	}
+
+	column, err := ormInfo.columnByPtr(condition.Field)
+	if err != nil {
+		return nil, err
+	}
+
+	// Check that the condition is valid
+	columnSchema := table.Column(column)
+	if columnSchema == nil {
+		return nil, fmt.Errorf("column %s not found", column)
+	}
+	if err := ovsdb.ValidateCondition(columnSchema, condition.Function, condition.Value); err != nil {
+		return nil, err
+	}
+
+	ovsValue, err := ovsdb.NativeToOvs(columnSchema, condition.Value)
+	if err != nil {
+		return nil, err
+	}
+
+	ovsdbCondition := ovsdb.NewCondition(column, condition.Function, ovsValue)
+
+	return &ovsdbCondition, nil
+
+}
+
 // newMutation creates a RFC7047 mutation object based on an ORM object and the mutation fields (in native format)
 // It takes care of field validation against the column type
 func (o orm) newMutation(tableName string, data interface{}, column string, mutator ovsdb.Mutator, value interface{}) ([]interface{}, error) {
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..dacba83 100644
--- a/cmd/stress/stress.go
+++ b/cmd/stress/stress.go
@@ -105,7 +105,7 @@ func transact(ovs *client.OvsdbClient, operations []ovsdb.Operation) (ok bool, u
 }
 
 func deleteBridge(ovs *client.OvsdbClient, bridge *ormBridge) {
-	deleteOp, err := ovs.Where(ovs.ConditionFromModel(bridge)).Delete()
+	deleteOp, err := ovs.Where(bridge).Delete()
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -113,7 +113,7 @@ func deleteBridge(ovs *client.OvsdbClient, bridge *ormBridge) {
 		UUID: rootUUID,
 	}
 
-	mutateOp, err := ovs.Where(ovs.ConditionFromModel(&ovsRow)).Mutate(&ovsRow, []client.Mutation{
+	mutateOp, err := ovs.Where(&ovsRow).Mutate(&ovsRow, []client.Mutation{
 		{
 			Field:   &ovsRow.Bridges,
 			Mutator: "delete",
@@ -151,7 +151,7 @@ func createBridge(ovs *client.OvsdbClient, iter int) {
 		log.Fatal(err)
 	}
 	ovsRow := ormOvs{}
-	mutateOp, err := ovs.Where(ovs.ConditionFromModel(&ormOvs{UUID: rootUUID})).Mutate(&ovsRow, []client.Mutation{
+	mutateOp, err := ovs.Where(&ormOvs{UUID: rootUUID}).Mutate(&ovsRow, []client.Mutation{
 		{
 			Field:   &ovsRow.Bridges,
 			Mutator: "insert",
diff --git a/example/play_with_ovs/play_with_ovs.go b/example/play_with_ovs/play_with_ovs.go
index 5e62f51..24565a2 100644
--- a/example/play_with_ovs/play_with_ovs.go
+++ b/example/play_with_ovs/play_with_ovs.go
@@ -71,7 +71,7 @@ func createBridge(ovs *client.OvsdbClient, bridgeName string) {
 	ovsRow := ormOvs{
 		UUID: rootUUID,
 	}
-	mutateOps, err := ovs.Where(ovs.ConditionFromModel(&ovsRow)).Mutate(&ovsRow, []client.Mutation{
+	mutateOps, err := ovs.Where(&ovsRow).Mutate(&ovsRow, []client.Mutation{
 		{
 			Field:   &ovsRow.Bridges,
 			Mutator: "insert",
diff --git a/ovsdb/bindings.go b/ovsdb/bindings.go
index 3797761..8a9f73d 100644
--- a/ovsdb/bindings.go
+++ b/ovsdb/bindings.go
@@ -294,6 +294,28 @@ func ValidateMutation(column *ColumnSchema, mutator Mutator, value interface{})
 	}
 }
 
+func ValidateCondition(column *ColumnSchema, function ConditionFunction, nativeValue interface{}) error {
+	if NativeType(column) != reflect.TypeOf(nativeValue) {
+		return NewErrWrongType(fmt.Sprintf("Condition for column %s", column),
+			NativeType(column).String(), nativeValue)
+	}
+
+	switch column.Type {
+	case TypeSet, TypeMap, TypeBoolean, TypeString, TypeUUID:
+		switch function {
+		case ConditionEqual, ConditionNotEqual, ConditionIncludes, ConditionExcludes:
+			return nil
+		default:
+			return fmt.Errorf("wrong condition function %s for type: %s", function, column.Type)
+		}
+	case TypeInteger, TypeReal:
+		// All functions are valid
+		return nil
+	default:
+		panic("Unsupported Type")
+	}
+}
+
 func isDefaultBaseValue(elem interface{}, etype ExtendedType) bool {
 	value := reflect.ValueOf(elem)
 	if !value.IsValid() {


Test patch:
diff --git a/client/api_test.go b/client/api_test.go
index ed45baf..8ac0267 100644
--- a/client/api_test.go
+++ b/client/api_test.go
@@ -196,7 +196,7 @@ func TestAPIListPredicate(t *testing.T) {
 		t.Run(fmt.Sprintf("ApiListPredicate: %s", tt.name), func(t *testing.T) {
 			var result []testLogicalSwitch
 			api := newAPI(cache)
-			cond := api.Where(api.ConditionFromFunc(tt.predicate))
+			cond := api.WhereCache(tt.predicate)
 			err := cond.List(&result)
 			if tt.err {
 				assert.NotNil(t, err)
@@ -275,24 +275,6 @@ func TestAPIListFields(t *testing.T) {
 			content: []Model{lspcache[aUUID2]},
 			err:     false,
 		},
-		{
-			name: "List unique by extra field",
-			prepare: func(t *testLogicalSwitchPort) {
-				t.ExternalIds = map[string]string{"unique": "id"}
-			},
-			content: []Model{lspcache[aUUID2]},
-			fields:  []interface{}{&testObj.ExternalIds},
-			err:     false,
-		},
-		{
-			name: "List by extra field",
-			prepare: func(t *testLogicalSwitchPort) {
-				t.Enabled = []bool{true}
-			},
-			content: []Model{lspcache[aUUID0], lspcache[aUUID3]},
-			fields:  []interface{}{&testObj.Enabled},
-			err:     false,
-		},
 	}
 
 	for _, tt := range test {
@@ -301,7 +283,7 @@ func TestAPIListFields(t *testing.T) {
 			// Clean object
 			testObj = testLogicalSwitchPort{}
 			api := newAPI(cache)
-			err := api.Where(api.ConditionFromModel(&testObj, tt.fields...)).List(&result)
+			err := api.Where(&testObj).List(&result)
 			if tt.err {
 				assert.NotNil(t, err)
 			} else {
@@ -319,19 +301,7 @@ func TestAPIListFields(t *testing.T) {
 			UUID: aUUID0,
 		}
 
-		err := api.Where(api.ConditionFromModel(&obj)).List(&result)
-		assert.NotNil(t, err)
-	})
-
-	t.Run("ApiListFields: Wrong object field", func(t *testing.T) {
-		var result []testLogicalSwitchPort
-		api := newAPI(cache)
-		obj := testLogicalSwitch{}
-		obj2 := testLogicalSwitch{
-			UUID: aUUID0,
-		}
-
-		err := api.Where(api.ConditionFromModel(&obj, &obj2.UUID)).List(&result)
+		err := api.Where(&obj).List(&result)
 		assert.NotNil(t, err)
 	})
 }
@@ -366,14 +336,14 @@ func TestConditionFromFunc(t *testing.T) {
 	}
 
 	for _, tt := range test {
-		t.Run(fmt.Sprintf("ConditionFromFunc: %s", tt.name), func(t *testing.T) {
+		t.Run(fmt.Sprintf("conditionFromFunc: %s", tt.name), func(t *testing.T) {
 			cache := apiTestCache(t)
-			api := newAPI(cache)
-			condition := api.ConditionFromFunc(tt.arg)
+			apiIface := newAPI(cache)
+			condition := apiIface.(api).conditionFromFunc(tt.arg)
 			if tt.err {
-				assert.IsType(t, &errorCondition{}, condition)
+				assert.IsType(t, &errorConditional{}, condition)
 			} else {
-				assert.IsType(t, &predicateCond{}, condition)
+				assert.IsType(t, &predicateConditional{}, condition)
 			}
 		})
 	}
@@ -382,10 +352,10 @@ func TestConditionFromFunc(t *testing.T) {
 func TestConditionFromModel(t *testing.T) {
 	var testObj testLogicalSwitch
 	test := []struct {
-		name   string
-		model  Model
-		fields []interface{}
-		err    bool
+		name  string
+		model Model
+		conds []Condition
+		err   bool
 	}{
 		{
 			name:  "wrong model must fail",
@@ -393,12 +363,12 @@ func TestConditionFromModel(t *testing.T) {
 			err:   true,
 		},
 		{
-			name: "wrong fields must fail",
+			name: "wrong condition must fail",
 			model: &struct {
 				a string `ovs:"_uuid"`
 			}{},
-			fields: []interface{}{"foo"},
-			err:    true,
+			conds: []Condition{{Field: "foo"}},
+			err:   true,
 		},
 		{
 			name:  "correct model must succeed",
@@ -406,22 +376,38 @@ func TestConditionFromModel(t *testing.T) {
 			err:   false,
 		},
 		{
-			name:   "correct model with fields must succeed",
-			model:  &testObj,
-			fields: []interface{}{&testObj.Name},
-			err:    false,
+			name:  "correct model with valid condition must succeed",
+			model: &testObj,
+			conds: []Condition{
+				{
+					Field:    &testObj.Name,
+					Function: ovsdb.ConditionEqual,
+					Value:    "foo",
+				},
+				{
+					Field:    &testObj.Ports,
+					Function: ovsdb.ConditionIncludes,
+					Value:    []string{"foo"},
+				},
+			},
+			err: false,
 		},
 	}
 
 	for _, tt := range test {
-		t.Run(fmt.Sprintf("ConditionFromModel: %s", tt.name), func(t *testing.T) {
+		t.Run(fmt.Sprintf("conditionFromModel: %s", tt.name), func(t *testing.T) {
 			cache := apiTestCache(t)
-			api := newAPI(cache)
-			condition := api.ConditionFromModel(tt.model, tt.fields...)
+			apiIface := newAPI(cache)
+			condition := apiIface.(api).conditionFromModel(tt.model, tt.conds...)
 			if tt.err {
-				assert.IsType(t, &errorCondition{}, condition)
+				assert.IsType(t, &errorConditional{}, condition)
 			} else {
-				assert.IsType(t, &indexCond{}, condition)
+				if len(tt.conds) > 0 {
+					assert.IsType(t, &explicitConditional{}, condition)
+				} else {
+					assert.IsType(t, &equalityConditional{}, condition)
+				}
+
 			}
 		})
 	}
@@ -660,9 +646,9 @@ func TestAPIMutate(t *testing.T) {
 		{
 			name: "select by UUID addElement to set",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromModel(&testLogicalSwitch{
+				return a.Where(&testLogicalSwitch{
 					UUID: aUUID0,
-				}))
+				})
 			},
 			mutations: []Mutation{
 				{
@@ -684,9 +670,9 @@ func TestAPIMutate(t *testing.T) {
 		{
 			name: "select by name delete element from map",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromModel(&testLogicalSwitchPort{
+				return a.Where(&testLogicalSwitchPort{
 					Name: "lsp2",
-				}))
+				})
 			},
 			mutations: []Mutation{
 				{
@@ -708,9 +694,9 @@ func TestAPIMutate(t *testing.T) {
 		{
 			name: "select single by predicate name insert element in map",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromFunc(func(lsp *testLogicalSwitchPort) bool {
+				return a.WhereCache(func(lsp *testLogicalSwitchPort) bool {
 					return lsp.Name == "lsp2"
-				}))
+				})
 			},
 			mutations: []Mutation{
 				{
@@ -732,9 +718,9 @@ func TestAPIMutate(t *testing.T) {
 		{
 			name: "select many by predicate name insert element in map",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromFunc(func(lsp *testLogicalSwitchPort) bool {
+				return a.WhereCache(func(lsp *testLogicalSwitchPort) bool {
 					return lsp.Type == "someType"
-				}))
+				})
 			},
 			mutations: []Mutation{
 				{
@@ -810,22 +796,20 @@ func TestAPIUpdate(t *testing.T) {
 		name      string
 		condition func(API) ConditionalAPI
 		prepare   func(t *testLogicalSwitchPort)
-		fields    []interface{}
 		result    []ovsdb.Operation
 		err       bool
 	}{
 		{
 			name: "select by UUID change multiple field",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromModel(&testLogicalSwitch{
+				return a.Where(&testLogicalSwitch{
 					UUID: aUUID0,
-				}))
+				})
 			},
 			prepare: func(t *testLogicalSwitchPort) {
 				t.Type = "somethingElse"
 				t.Tag = []int{6}
 			},
-			fields: []interface{}{},
 			result: []ovsdb.Operation{
 				{
 					Op:    opUpdate,
@@ -839,15 +823,14 @@ func TestAPIUpdate(t *testing.T) {
 		{
 			name: "select by index change multiple field",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromModel(&testLogicalSwitchPort{
+				return a.Where(&testLogicalSwitchPort{
 					Name: "lsp1",
-				}))
+				})
 			},
 			prepare: func(t *testLogicalSwitchPort) {
 				t.Type = "somethingElse"
 				t.Tag = []int{6}
 			},
-			fields: []interface{}{},
 			result: []ovsdb.Operation{
 				{
 					Op:    opUpdate,
@@ -865,12 +848,15 @@ func TestAPIUpdate(t *testing.T) {
 					Type:    "sometype",
 					Enabled: []bool{true},
 				}
-				return a.Where(a.ConditionFromModel(&t, &t.Type))
+				return a.Where(&t, Condition{
+					Field:    &t.Type,
+					Function: ovsdb.ConditionEqual,
+					Value:    "sometype",
+				})
 			},
 			prepare: func(t *testLogicalSwitchPort) {
 				t.Tag = []int{6}
 			},
-			fields: []interface{}{},
 			result: []ovsdb.Operation{
 				{
 					Op:    opUpdate,
@@ -881,18 +867,43 @@ func TestAPIUpdate(t *testing.T) {
 			},
 			err: false,
 		},
+		{
+			name: "select by field inequality change multiple field",
+			condition: func(a API) ConditionalAPI {
+				t := testLogicalSwitchPort{
+					Type:    "sometype",
+					Enabled: []bool{true},
+				}
+				return a.Where(&t, Condition{
+					Field:    &t.Type,
+					Function: ovsdb.ConditionNotEqual,
+					Value:    "sometype",
+				})
+			},
+			prepare: func(t *testLogicalSwitchPort) {
+				t.Tag = []int{6}
+			},
+			result: []ovsdb.Operation{
+				{
+					Op:    opUpdate,
+					Table: "Logical_Switch_Port",
+					Row:   map[string]interface{}{"tag": testOvsSet(t, []int{6})},
+					Where: []ovsdb.Condition{{Column: "type", Function: ovsdb.ConditionNotEqual, Value: "sometype"}},
+				},
+			},
+			err: false,
+		},
 		{
 			name: "select multiple by predicate change multiple field",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromFunc(func(t *testLogicalSwitchPort) bool {
+				return a.WhereCache(func(t *testLogicalSwitchPort) bool {
 					return t.Enabled != nil && t.Enabled[0] == true
-				}))
+				})
 			},
 			prepare: func(t *testLogicalSwitchPort) {
 				t.Type = "somethingElse"
 				t.Tag = []int{6}
 			},
-			fields: []interface{}{},
 			result: []ovsdb.Operation{
 				{
 					Op:    opUpdate,
@@ -966,9 +977,9 @@ func TestAPIDelete(t *testing.T) {
 		{
 			name: "select by UUID",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromModel(&testLogicalSwitch{
+				return a.Where(&testLogicalSwitch{
 					UUID: aUUID0,
-				}))
+				})
 			},
 			result: []ovsdb.Operation{
 				{
@@ -982,9 +993,9 @@ func TestAPIDelete(t *testing.T) {
 		{
 			name: "select by index",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromModel(&testLogicalSwitchPort{
+				return a.Where(&testLogicalSwitchPort{
 					Name: "lsp1",
-				}))
+				})
 			},
 			result: []ovsdb.Operation{
 				{
@@ -996,13 +1007,16 @@ func TestAPIDelete(t *testing.T) {
 			err: false,
 		},
 		{
-			name: "select by field",
+			name: "select by field equality",
 			condition: func(a API) ConditionalAPI {
 				t := testLogicalSwitchPort{
-					Type:    "sometype",
 					Enabled: []bool{true},
 				}
-				return a.Where(a.ConditionFromModel(&t, &t.Type))
+				return a.Where(&t, Condition{
+					Field:    &t.Type,
+					Function: ovsdb.ConditionEqual,
+					Value:    "sometype",
+				})
 			},
 			result: []ovsdb.Operation{
 				{
@@ -1016,9 +1030,9 @@ func TestAPIDelete(t *testing.T) {
 		{
 			name: "select multiple by predicate",
 			condition: func(a API) ConditionalAPI {
-				return a.Where(a.ConditionFromFunc(func(t *testLogicalSwitchPort) bool {
+				return a.WhereCache(func(t *testLogicalSwitchPort) bool {
 					return t.Enabled != nil && t.Enabled[0] == true
-				}))
+				})
 			},
 			result: []ovsdb.Operation{
 				{
diff --git a/client/condition_test.go b/client/condition_test.go
new file mode 100644
index 0000000..09e54ee
--- /dev/null
+++ b/client/condition_test.go
@@ -0,0 +1,348 @@
+package client
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/ovn-org/libovsdb/ovsdb"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestEqualityConditional(t *testing.T) {
+	cache := apiTestCache(t)
+	lspcacheList := []Model{
+		&testLogicalSwitchPort{
+			UUID:        aUUID0,
+			Name:        "lsp0",
+			ExternalIds: map[string]string{"foo": "bar"},
+			Enabled:     []bool{true},
+		},
+		&testLogicalSwitchPort{
+			UUID:        aUUID1,
+			Name:        "lsp1",
+			ExternalIds: map[string]string{"foo": "baz"},
+			Enabled:     []bool{false},
+		},
+		&testLogicalSwitchPort{
+			UUID:        aUUID2,
+			Name:        "lsp2",
+			ExternalIds: map[string]string{"unique": "id"},
+			Enabled:     []bool{false},
+		},
+		&testLogicalSwitchPort{
+			UUID:        aUUID3,
+			Name:        "lsp3",
+			ExternalIds: map[string]string{"foo": "baz"},
+			Enabled:     []bool{true},
+		},
+	}
+	lspcache := map[string]Model{}
+	for i := range lspcacheList {
+		lspcache[lspcacheList[i].(*testLogicalSwitchPort).UUID] = lspcacheList[i]
+	}
+	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspcache}
+
+	test := []struct {
+		name      string
+		model     Model
+		condition []ovsdb.Condition
+		matches   map[Model]bool
+		err       bool
+	}{
+		{
+			name:  "by uuid",
+			model: &testLogicalSwitchPort{UUID: aUUID0, Name: "different"},
+			condition: []ovsdb.Condition{
+				{
+					Column:   "_uuid",
+					Function: ovsdb.ConditionEqual,
+					Value:    ovsdb.UUID{GoUUID: aUUID0},
+				}},
+			matches: map[Model]bool{
+				&testLogicalSwitchPort{UUID: aUUID0}:              true,
+				&testLogicalSwitchPort{UUID: aUUID1}:              false,
+				&testLogicalSwitchPort{UUID: aUUID0, Name: "foo"}: true,
+			},
+		},
+		{
+			name:  "by index",
+			model: &testLogicalSwitchPort{Name: "lsp1"},
+			condition: []ovsdb.Condition{
+				{
+					Column:   "name",
+					Function: ovsdb.ConditionEqual,
+					Value:    "lsp1",
+				}},
+			matches: map[Model]bool{
+				&testLogicalSwitchPort{UUID: aUUID1}:               false,
+				&testLogicalSwitchPort{UUID: aUUID1, Name: "lsp1"}: true,
+				&testLogicalSwitchPort{UUID: aUUID0, Name: "lsp1"}: true,
+			},
+		},
+		{
+			name:  "by non index",
+			model: &testLogicalSwitchPort{ExternalIds: map[string]string{"foo": "baz"}},
+			err:   true,
+		},
+	}
+	for _, tt := range test {
+		t.Run(fmt.Sprintf("Equality Conditional: %s", tt.name), func(t *testing.T) {
+			cond, err := newEqualityConditional(cache.orm, "Logical_Switch_Port", tt.model)
+			assert.Nil(t, err)
+			for model, shouldMatch := range tt.matches {
+				matches, err := cond.Matches(model)
+				if tt.err {
+					assert.NotNil(t, err)
+				} else {
+					assert.Nil(t, err)
+					assert.Equalf(t, shouldMatch, matches, fmt.Sprintf("Match on model %#+v should be %v", model, shouldMatch))
+				}
+			}
+			generated, err := cond.Generate()
+			if tt.err {
+				assert.NotNil(t, err)
+			} else {
+				assert.Nil(t, err)
+				assert.ElementsMatch(t, tt.condition, generated)
+			}
+		})
+	}
+}
+
+func TestPredicateConditional(t *testing.T) {
+	cache := apiTestCache(t)
+	lspcacheList := []Model{
+		&testLogicalSwitchPort{
+			UUID:        aUUID0,
+			Name:        "lsp0",
+			ExternalIds: map[string]string{"foo": "bar"},
+			Enabled:     []bool{true},
+		},
+		&testLogicalSwitchPort{
+			UUID:        aUUID1,
+			Name:        "lsp1",
+			ExternalIds: map[string]string{"foo": "baz"},
+			Enabled:     []bool{false},
+		},
+		&testLogicalSwitchPort{
+			UUID:        aUUID2,
+			Name:        "lsp2",
+			ExternalIds: map[string]string{"unique": "id"},
+			Enabled:     []bool{false},
+		},
+		&testLogicalSwitchPort{
+			UUID:        aUUID3,
+			Name:        "lsp3",
+			ExternalIds: map[string]string{"foo": "baz"},
+			Enabled:     []bool{true},
+		},
+	}
+	lspcache := map[string]Model{}
+	for i := range lspcacheList {
+		lspcache[lspcacheList[i].(*testLogicalSwitchPort).UUID] = lspcacheList[i]
+	}
+	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspcache}
+
+	test := []struct {
+		name      string
+		predicate interface{}
+		condition []ovsdb.Condition
+		matches   map[Model]bool
+		err       bool
+	}{
+		{
+			name: "simple value comparison",
+			predicate: func(lsp *testLogicalSwitchPort) bool {
+				return lsp.UUID == aUUID0
+			},
+			condition: []ovsdb.Condition{
+				{
+					Column:   "_uuid",
+					Function: ovsdb.ConditionEqual,
+					Value:    ovsdb.UUID{GoUUID: aUUID0},
+				}},
+			matches: map[Model]bool{
+				&testLogicalSwitchPort{UUID: aUUID0}:              true,
+				&testLogicalSwitchPort{UUID: aUUID1}:              false,
+				&testLogicalSwitchPort{UUID: aUUID0, Name: "foo"}: true,
+			},
+		},
+		{
+			name: "by random field",
+			predicate: func(lsp *testLogicalSwitchPort) bool {
+				return lsp.Enabled[0] == false
+			},
+			condition: []ovsdb.Condition{
+				{
+					Column:   "_uuid",
+					Function: ovsdb.ConditionEqual,
+					Value:    ovsdb.UUID{GoUUID: aUUID1},
+				},
+				{
+					Column:   "_uuid",
+					Function: ovsdb.ConditionEqual,
+					Value:    ovsdb.UUID{GoUUID: aUUID2},
+				}},
+			matches: map[Model]bool{
+				&testLogicalSwitchPort{UUID: aUUID1, Enabled: []bool{true}}:  false,
+				&testLogicalSwitchPort{UUID: aUUID1, Enabled: []bool{false}}: true,
+			},
+		},
+	}
+	for _, tt := range test {
+		t.Run(fmt.Sprintf("Predicate Conditional: %s", tt.name), func(t *testing.T) {
+			cond, err := newPredicateConditional("Logical_Switch_Port", cache, tt.predicate)
+			assert.Nil(t, err)
+			for model, shouldMatch := range tt.matches {
+				matches, err := cond.Matches(model)
+				if tt.err {
+					assert.NotNil(t, err)
+				} else {
+					assert.Nil(t, err)
+					assert.Equalf(t, shouldMatch, matches, fmt.Sprintf("Match on model %#+v should be %v", model, shouldMatch))
+				}
+			}
+			generated, err := cond.Generate()
+			if tt.err {
+				assert.NotNil(t, err)
+			} else {
+				assert.Nil(t, err)
+				assert.ElementsMatch(t, tt.condition, generated)
+			}
+		})
+	}
+}
+
+func TestExplicitConditional(t *testing.T) {
+	cache := apiTestCache(t)
+	lspcacheList := []Model{
+		&testLogicalSwitchPort{
+			UUID:        aUUID0,
+			Name:        "lsp0",
+			ExternalIds: map[string]string{"foo": "bar"},
+			Enabled:     []bool{true},
+		},
+		&testLogicalSwitchPort{
+			UUID:        aUUID1,
+			Name:        "lsp1",
+			ExternalIds: map[string]string{"foo": "baz"},
+			Enabled:     []bool{false},
+		},
+		&testLogicalSwitchPort{
+			UUID:        aUUID2,
+			Name:        "lsp2",
+			ExternalIds: map[string]string{"unique": "id"},
+			Enabled:     []bool{false},
+		},
+		&testLogicalSwitchPort{
+			UUID:        aUUID3,
+			Name:        "lsp3",
+			ExternalIds: map[string]string{"foo": "baz"},
+			Enabled:     []bool{true},
+		},
+	}
+	lspcache := map[string]Model{}
+	for i := range lspcacheList {
+		lspcache[lspcacheList[i].(*testLogicalSwitchPort).UUID] = lspcacheList[i]
+	}
+	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspcache}
+
+	testObj := &testLogicalSwitchPort{}
+
+	test := []struct {
+		name   string
+		args   []Condition
+		result []ovsdb.Condition
+		err    bool
+	}{
+		{
+			name: "inequality comparison",
+			args: []Condition{
+				{
+					Field:    &testObj.Name,
+					Function: ovsdb.ConditionNotEqual,
+					Value:    "lsp0",
+				},
+			},
+			result: []ovsdb.Condition{
+				{
+					Column:   "name",
+					Function: ovsdb.ConditionNotEqual,
+					Value:    "lsp0",
+				}},
+		},
+		{
+			name: "map comparison",
+			args: []Condition{
+				{
+					Field:    &testObj.ExternalIds,
+					Function: ovsdb.ConditionIncludes,
+					Value:    map[string]string{"foo": "baz"},
+				},
+			},
+			result: []ovsdb.Condition{
+				{
+					Column:   "external_ids",
+					Function: ovsdb.ConditionIncludes,
+					Value:    testOvsMap(t, map[string]string{"foo": "baz"}),
+				}},
+		},
+		{
+			name: "set comparison",
+			args: []Condition{
+				{
+					Field:    &testObj.Enabled,
+					Function: ovsdb.ConditionEqual,
+					Value:    []bool{true},
+				},
+			},
+			result: []ovsdb.Condition{
+				{
+					Column:   "enabled",
+					Function: ovsdb.ConditionEqual,
+					Value:    testOvsSet(t, []bool{true}),
+				}},
+		},
+		{
+			name: "multiple conditions",
+			args: []Condition{
+				{
+					Field:    &testObj.Enabled,
+					Function: ovsdb.ConditionEqual,
+					Value:    []bool{true},
+				},
+				{
+					Field:    &testObj.Name,
+					Function: ovsdb.ConditionNotEqual,
+					Value:    "foo",
+				},
+			},
+			result: []ovsdb.Condition{
+				{
+					Column:   "enabled",
+					Function: ovsdb.ConditionEqual,
+					Value:    testOvsSet(t, []bool{true}),
+				},
+				{
+					Column:   "name",
+					Function: ovsdb.ConditionNotEqual,
+					Value:    "foo",
+				}},
+		},
+	}
+	for _, tt := range test {
+		t.Run(fmt.Sprintf("Explicit Conditional: %s", tt.name), func(t *testing.T) {
+			cond, err := newExplicitConditional(cache.orm, "Logical_Switch_Port", testObj, tt.args...)
+			assert.Nil(t, err)
+			_, err = cond.Matches(testObj)
+			assert.NotNilf(t, err, "Explicit conditions should fail to match on cache")
+			generated, err := cond.Generate()
+			if tt.err {
+				assert.NotNil(t, err)
+			} else {
+				assert.Nil(t, err)
+				assert.ElementsMatch(t, tt.result, generated)
+			}
+		})
+	}
+}
diff --git a/client/orm_test.go b/client/orm_test.go
index 7abf5f7..ee2c18e 100644
--- a/client/orm_test.go
+++ b/client/orm_test.go
@@ -580,9 +580,9 @@ func TestORMCondition(t *testing.T) {
 		},
 	}
 	for _, tt := range tests {
-		t.Run(fmt.Sprintf("newCondition_%s", tt.name), func(t *testing.T) {
+		t.Run(fmt.Sprintf("newEqualityCondition_%s", tt.name), func(t *testing.T) {
 			tt.prepare(&testObj)
-			conds, err := orm.newCondition("TestTable", &testObj, tt.index...)
+			conds, err := orm.newEqualityCondition("TestTable", &testObj, tt.index...)
 			if tt.err {
 				if err == nil {
 					t.Errorf("expected an error but got none")
diff --git a/client/ovs_integration_test.go b/client/ovs_integration_test.go
index 49695eb..671121f 100644
--- a/client/ovs_integration_test.go
+++ b/client/ovs_integration_test.go
@@ -192,7 +192,7 @@ func TestInsertTransactIntegration(t *testing.T) {
 
 	// 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 })).
+	mutateOp, err := ovs.WhereCache(func(*ovsType) bool { return true }).
 		Mutate(&ovsRow, []Mutation{
 			{
 				Field:   &ovsRow.Bridges,
@@ -245,11 +245,11 @@ func TestDeleteTransactIntegration(t *testing.T) {
 	err = ovs.MonitorAll(nil)
 	assert.Nil(t, err)
 
-	deleteOp, err := ovs.Where(ovs.ConditionFromModel(&bridgeType{Name: bridgeName})).Delete()
+	deleteOp, err := ovs.Where(&bridgeType{Name: bridgeName}).Delete()
 	assert.Nil(t, err)
 
 	ovsRow := ovsType{}
-	mutateOp, err := ovs.Where(ovs.ConditionFromFunc(func(*ovsType) bool { return true })).
+	mutateOp, err := ovs.WhereCache(func(*ovsType) bool { return true }).
 		Mutate(&ovsRow, []Mutation{
 			{
 				Field:   &ovsRow.Bridges,
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/bindings_test.go b/ovsdb/bindings_test.go
index 71cd2eb..a0d65ad 100644
--- a/ovsdb/bindings_test.go
+++ b/ovsdb/bindings_test.go
@@ -939,3 +939,129 @@ func TestMutationValidation(t *testing.T) {
 		})
 	}
 }
+
+func TestConditionValidation(t *testing.T) {
+	type Test struct {
+		name      string
+		column    []byte
+		functions []ConditionFunction
+		value     interface{}
+		valid     bool
+	}
+	tests := []Test{
+		{
+			name:      "string",
+			column:    []byte(`{"type":"string"}`),
+			functions: []ConditionFunction{ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     "foo",
+			valid:     true,
+		},
+		{
+			name:      "uuid",
+			column:    []byte(`{"type":"uuid"}`),
+			functions: []ConditionFunction{ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     "foo",
+			valid:     true,
+		},
+		{
+			name:      "string wrong type",
+			column:    []byte(`{"type":"string"}`),
+			functions: []ConditionFunction{ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     42,
+			valid:     false,
+		},
+		{
+			name:      "numeric",
+			column:    []byte(`{"type":"integer"}`),
+			functions: []ConditionFunction{ConditionGreaterThanOrEqual, ConditionGreaterThan, ConditionLessThan, ConditionLessThanOrEqual, ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     1000,
+			valid:     true,
+		},
+		{
+			name:      "numeric wrong type",
+			column:    []byte(`{"type":"integer"}`),
+			functions: []ConditionFunction{ConditionGreaterThanOrEqual, ConditionGreaterThan, ConditionLessThan, ConditionLessThanOrEqual, ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     "foo",
+			valid:     false,
+		},
+		{
+			name: "set",
+			column: []byte(`{
+				   "type": {
+				     "key": "string",
+				     "max": "unlimited",
+				     "min": 0
+				   }
+				 }`),
+			functions: []ConditionFunction{ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     []string{"foo", "bar"},
+			valid:     true,
+		},
+		{
+			name: "set wrong type",
+			column: []byte(`{
+				   "type": {
+				     "key": "string",
+				     "max": "unlimited",
+				     "min": 0
+				   }
+				 }`),
+			functions: []ConditionFunction{ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     32,
+			valid:     false,
+		},
+		{
+			name: "set wrong type2",
+			column: []byte(`{
+				   "type": {
+				     "key": "string",
+				     "max": "unlimited",
+				     "min": 0
+				   }
+				 }`),
+			functions: []ConditionFunction{ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     "foo",
+			valid:     false,
+		},
+		{
+			name: "map",
+			column: []byte(`{
+				   "type": {
+				     "key": "string",
+				     "value": "string"
+				   }
+				 }`),
+			functions: []ConditionFunction{ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     map[string]string{"foo": "bar"},
+			valid:     true,
+		},
+		{
+			name: "map wrong type",
+			column: []byte(`{
+				   "type": {
+				     "key": "string",
+				     "value": "string"
+				   }
+				 }`),
+			functions: []ConditionFunction{ConditionEqual, ConditionIncludes, ConditionNotEqual, ConditionExcludes},
+			value:     map[string]int{"foo": 42},
+			valid:     false,
+		},
+	}
+	for _, test := range tests {
+		t.Run(fmt.Sprintf("ConditionValidation: %s", test.name), func(t *testing.T) {
+			var column ColumnSchema
+			err := json.Unmarshal(test.column, &column)
+			assert.Nil(t, err)
+
+			for _, f := range test.functions {
+				result := ValidateCondition(&column, f, test.value)
+				if test.valid {
+					assert.Nil(t, result)
+				} else {
+					assert.NotNil(t, result)
+				}
+			}
+		})
+	}
+}