Repository: ovn-org/libovsdb
Pull request #138 (2021-05-25 13:59:00)

cache: Move cache to its own package
While working on the server it occurred to me that I could
re-use the cache implementation as it already contains both
a DBModel and Mapper. Therefore I've moved cache in to it's
own package.

This also adds a `Set()` API to the Table and RowCache elements.
It makes it easier to write tests that pre-populate cache entries this way.

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>

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

* **33** of **51**   **(64.71%)**  changed or added relevant lines in **5** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-1.4%**) to **69.132%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [cache/cache.go](https://coveralls.io/builds/39992661/source?filename=cache%2Fcache.go#L39) | 13 | 31 | 41.94%
<!-- | **Total:** | **33** | **51** | **64.71%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/39992661/badge)](https://coveralls.io/builds/39992661) |
| :-- | --: |
| Change from base [Build 875164410](https://coveralls.io/builds/39992641): |  -1.4% |
| Covered Lines: | 1823 |
| Relevant Lines: | 2637 |

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


Patch:
diff --git a/README.md b/README.md
index 12ed35a..83662c2 100644
--- a/README.md
+++ b/README.md
@@ -75,21 +75,23 @@ The table is infered from the type that the function accepts as only argument.
 
 This package is divided into several subpackages. Documentation for each subpackage is available at [pkg.go.dev][doc]:
 
-* **client**: ovsdb client, cache and API [![godoc for libovsdb/client][clientbadge]][clientdoc]
+* **client**: ovsdb client and API [![godoc for libovsdb/client][clientbadge]][clientdoc]
 * **mapper**: mapping from tagged structs to ovsdb types [![godoc for libovsdb/mapper][mapperbadge]][mapperdoc]
 * **model**: model and database model used for mapping [![godoc for libovsdb/model][modelbadge]][modeldoc]
 * **ovsdb**: low level OVS types [![godoc for libovsdb/ovsdb][ovsdbbadge]][ovsdbdoc]
+* **cache**: model-based cache [![godoc for libovsdb/cache][cachebadge]][cachedoc]
 
 [doc]: https://pkg.go.dev/
 [clientbadge]: https://pkg.go.dev/badge/github.com/ovn-org/libovsdb/client
 [mapperbadge]: https://pkg.go.dev/badge/github.com/ovn-org/libovsdb/mapper
 [modelbadge]: https://pkg.go.dev/badge/github.com/ovn-org/libovsdb/model
 [ovsdbbadge]: https://pkg.go.dev/badge/github.com/ovn-org/libovsdb/ovsdb
+[cachebadge]: https://pkg.go.dev/badge/github.com/ovn-org/libovsdb/cache
 [clientdoc]: https://pkg.go.dev/github.com/ovn-org/libovsdb/client
 [mapperdoc]: https://pkg.go.dev/github.com/ovn-org/libovsdb/mapper
 [modeldoc]: https://pkg.go.dev/github.com/ovn-org/libovsdb/model
 [ovsdbdoc]: https://pkg.go.dev/github.com/ovn-org/libovsdb/ovsdb
-
+[cachedoc]: https://pkg.go.dev/github.com/ovn-org/libovsdb/cache
 
 ## Quick API Examples
 
@@ -175,7 +177,7 @@ They can also be created based on a list of Conditions:
 
 You can also register a notification handler to get notified every time an element is added, deleted or updated from the database.
 
-    handler := &client.EventHandlerFuncs{
+    handler := &cache.EventHandlerFuncs{
         AddFunc: func(table string, model model.Model) {
             if table == "Logical_Switch" {
                 fmt.Printf("A new switch named %s was added!!\n!", model.(*MyLogicalSwitch).Name)
diff --git a/client/cache.go b/cache/cache.go
similarity index 85%
rename from client/cache.go
rename to cache/cache.go
index ce6689a..1bb52a8 100644
--- a/client/cache.go
+++ b/cache/cache.go
@@ -1,4 +1,4 @@
-package client
+package cache
 
 import (
 	"fmt"
@@ -35,6 +35,17 @@ func (r *RowCache) Row(uuid string) model.Model {
 	return nil
 }
 
+// Set writes the provided content to the cache
+// WARNING: Do not use Set outside of testing
+// as it may case cache corruption if, for example,
+// you write a model.Model that isn't part of the
+// model.DBModel
+func (r *RowCache) Set(uuid string, m model.Model) {
+	r.mutex.Lock()
+	defer r.mutex.Unlock()
+	r.cache[uuid] = m
+}
+
 // Rows returns a list of row UUIDs as strings
 func (r *RowCache) Rows() []string {
 	r.mutex.RLock()
@@ -53,9 +64,14 @@ func (r *RowCache) Len() int {
 	return len(r.cache)
 }
 
-func newRowCache() *RowCache {
+// NewRowCache creates a new row cache with the provided data
+// if the data is nil, and empty RowCache will be created
+func NewRowCache(data map[string]model.Model) *RowCache {
+	if data == nil {
+		data = make(map[string]model.Model)
+	}
 	return &RowCache{
-		cache: make(map[string]model.Model),
+		cache: data,
 		mutex: sync.RWMutex{},
 	}
 }
@@ -106,7 +122,8 @@ type TableCache struct {
 	dbModel        *model.DBModel
 }
 
-func newTableCache(schema *ovsdb.DatabaseSchema, dbModel *model.DBModel) (*TableCache, error) {
+// NewTableCache creates a new TableCache
+func NewTableCache(schema *ovsdb.DatabaseSchema, dbModel *model.DBModel) (*TableCache, error) {
 	if schema == nil || dbModel == nil {
 		return nil, fmt.Errorf("tablecache without databasemodel cannot be populated")
 	}
@@ -119,6 +136,16 @@ func newTableCache(schema *ovsdb.DatabaseSchema, dbModel *model.DBModel) (*Table
 	}, nil
 }
 
+// Mapper returns the mapper
+func (t *TableCache) Mapper() *mapper.Mapper {
+	return t.mapper
+}
+
+// DBModel returns the DBModel
+func (t *TableCache) DBModel() *model.DBModel {
+	return t.dbModel
+}
+
 // Table returns the a Table from the cache with a given name
 func (t *TableCache) Table(name string) *RowCache {
 	t.cacheMutex.RLock()
@@ -129,6 +156,18 @@ func (t *TableCache) Table(name string) *RowCache {
 	return nil
 }
 
+// Set write the provided RowCache to the provided table name in the cache
+// if the provided cache is nil, we'll initialize a new one
+// WARNING: Do not use Set outside of testing
+func (t *TableCache) Set(name string, rc *RowCache) {
+	if rc == nil {
+		rc = NewRowCache(nil)
+	}
+	t.cacheMutex.Lock()
+	defer t.cacheMutex.Unlock()
+	t.cache[name] = rc
+}
+
 // Tables returns a list of table names that are in the cache
 func (t *TableCache) Tables() []string {
 	t.cacheMutex.RLock()
@@ -146,7 +185,7 @@ func (t *TableCache) Update(context interface{}, tableUpdates ovsdb.TableUpdates
 	if len(tableUpdates) == 0 {
 		return
 	}
-	t.populate(tableUpdates)
+	t.Populate(tableUpdates)
 }
 
 // Locked implements the locked method of the NotificationHandler interface
@@ -165,8 +204,8 @@ func (t *TableCache) Echo([]interface{}) {
 func (t *TableCache) Disconnected() {
 }
 
-// populate adds data to the cache and places an event on the channel
-func (t *TableCache) populate(tableUpdates ovsdb.TableUpdates) {
+// Populate adds data to the cache and places an event on the channel
+func (t *TableCache) Populate(tableUpdates ovsdb.TableUpdates) {
 	t.cacheMutex.Lock()
 	defer t.cacheMutex.Unlock()
 	for table := range t.dbModel.Types() {
@@ -176,7 +215,7 @@ func (t *TableCache) populate(tableUpdates ovsdb.TableUpdates) {
 		}
 		var tCache *RowCache
 		if tCache, ok = t.cache[table]; !ok {
-			t.cache[table] = newRowCache()
+			t.cache[table] = NewRowCache(nil)
 			tCache = t.cache[table]
 		}
 		tCache.mutex.Lock()
diff --git a/cache/doc.go b/cache/doc.go
new file mode 100644
index 0000000..3b176f2
--- /dev/null
+++ b/cache/doc.go
@@ -0,0 +1,16 @@
+/*
+Package cache provides a cache of model.Model elements that can be used in an OVSDB client or server.
+
+The cache can be accessed using a simple API:
+
+    cache.Table("Open_vSwitch").Row("<ovs-uuid>")
+
+It implements the ovsdb.NotificationHandler interface
+such that it can be populated automatically by
+update notifications
+
+It also contains an eventProcessor where callers
+may registers functions that will get called on
+every Add/Update/Delete event.
+*/
+package cache
diff --git a/client/api.go b/client/api.go
index 646971b..18fd1d4 100644
--- a/client/api.go
+++ b/client/api.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"reflect"
 
+	"github.com/ovn-org/libovsdb/cache"
 	"github.com/ovn-org/libovsdb/mapper"
 	"github.com/ovn-org/libovsdb/model"
 	"github.com/ovn-org/libovsdb/ovsdb"
@@ -92,7 +93,7 @@ var ErrNotFound = errors.New("object not found")
 // api struct implements both API and ConditionalAPI
 // Where() can be used to create a ConditionalAPI api
 type api struct {
-	cache *TableCache
+	cache *cache.TableCache
 	cond  Conditional
 }
 
@@ -191,13 +192,13 @@ func (a api) conditionFromModel(any bool, model model.Model, cond ...model.Condi
 	}
 
 	if len(cond) == 0 {
-		conditional, err = newEqualityConditional(a.cache.mapper, tableName, any, model)
+		conditional, err = newEqualityConditional(a.cache.Mapper(), tableName, any, model)
 		if err != nil {
 			conditional = newErrorConditional(err)
 		}
 
 	} else {
-		conditional, err = newExplicitConditional(a.cache.mapper, tableName, any, model, cond...)
+		conditional, err = newExplicitConditional(a.cache.Mapper(), tableName, any, model, cond...)
 		if err != nil {
 			conditional = newErrorConditional(err)
 		}
@@ -223,7 +224,7 @@ func (a api) Get(m model.Model) error {
 	}
 
 	// If model contains _uuid value, we can access it via cache index
-	mapperInfo, err := mapper.NewMapperInfo(a.cache.mapper.Schema.Table(table), m)
+	mapperInfo, err := mapper.NewMapperInfo(a.cache.Mapper().Schema.Table(table), m)
 	if err != nil {
 		return err
 	}
@@ -239,7 +240,7 @@ func (a api) Get(m model.Model) error {
 	// Look across the entire cache for table index equality
 	for _, row := range tableCache.Rows() {
 		elem := tableCache.Row(row)
-		equal, err := a.cache.mapper.EqualFields(table, m, elem.(model.Model))
+		equal, err := a.cache.Mapper().EqualFields(table, m, elem.(model.Model))
 		if err != nil {
 			return err
 		}
@@ -265,7 +266,7 @@ func (a api) Create(models ...model.Model) ([]ovsdb.Operation, error) {
 			return nil, err
 		}
 
-		table := a.cache.mapper.Schema.Table(tableName)
+		table := a.cache.Mapper().Schema.Table(tableName)
 
 		// Read _uuid field, and use it as named-uuid
 		info, err := mapper.NewMapperInfo(table, model)
@@ -278,7 +279,7 @@ func (a api) Create(models ...model.Model) ([]ovsdb.Operation, error) {
 			return nil, err
 		}
 
-		row, err := a.cache.mapper.NewRow(tableName, model)
+		row, err := a.cache.Mapper().NewRow(tableName, model)
 		if err != nil {
 			return nil, err
 		}
@@ -302,8 +303,8 @@ func (a api) Mutate(model model.Model, mutationObjs ...model.Mutation) ([]ovsdb.
 		return nil, fmt.Errorf("At least one Mutation must be provided")
 	}
 
-	tableName := a.cache.dbModel.FindTable(reflect.ValueOf(model).Type())
-	table := a.cache.mapper.Schema.Table(tableName)
+	tableName := a.cache.DBModel().FindTable(reflect.ValueOf(model).Type())
+	table := a.cache.Mapper().Schema.Table(tableName)
 	if table == nil {
 		return nil, fmt.Errorf("schema error: table not found in Database Model for type %s", reflect.TypeOf(model))
 	}
@@ -324,7 +325,7 @@ func (a api) Mutate(model model.Model, mutationObjs ...model.Mutation) ([]ovsdb.
 			return nil, err
 		}
 
-		mutation, err := a.cache.mapper.NewMutation(tableName, model, col, mobj.Mutator, mobj.Value)
+		mutation, err := a.cache.Mapper().NewMutation(tableName, model, col, mobj.Mutator, mobj.Value)
 		if err != nil {
 			return nil, err
 		}
@@ -358,7 +359,7 @@ func (a api) Update(model model.Model, fields ...interface{}) ([]ovsdb.Operation
 		return nil, err
 	}
 
-	row, err := a.cache.mapper.NewRow(table, model, fields...)
+	row, err := a.cache.Mapper().NewRow(table, model, fields...)
 	if err != nil {
 		return nil, err
 	}
@@ -403,7 +404,7 @@ func (a api) getTableFromModel(m interface{}) (string, error) {
 	if _, ok := m.(model.Model); !ok {
 		return "", &ErrWrongType{reflect.TypeOf(m), "Type does not implement Model interface"}
 	}
-	table := a.cache.dbModel.FindTable(reflect.TypeOf(m))
+	table := a.cache.DBModel().FindTable(reflect.TypeOf(m))
 	if table == "" {
 		return "", &ErrWrongType{reflect.TypeOf(m), "Model not found in Database Model"}
 	}
@@ -428,7 +429,7 @@ func (a api) getTableFromFunc(predicate interface{}) (string, error) {
 			fmt.Sprintf("Type %s does not implement Model interface", modelType.String())}
 	}
 
-	table := a.cache.dbModel.FindTable(modelType)
+	table := a.cache.DBModel().FindTable(modelType)
 	if table == "" {
 		return "", &ErrWrongType{predType,
 			fmt.Sprintf("Model %s not found in Database Model", modelType.String())}
@@ -437,14 +438,14 @@ func (a api) getTableFromFunc(predicate interface{}) (string, error) {
 }
 
 // newAPI returns a new API to interact with the database
-func newAPI(cache *TableCache) API {
+func newAPI(cache *cache.TableCache) API {
 	return api{
 		cache: cache,
 	}
 }
 
 // newConditionalAPI returns a new ConditionalAPI to interact with the database
-func newConditionalAPI(cache *TableCache, cond Conditional) ConditionalAPI {
+func newConditionalAPI(cache *cache.TableCache, cond Conditional) ConditionalAPI {
 	return api{
 		cache: cache,
 		cond:  cond,
diff --git a/client/client.go b/client/client.go
index c833ce5..2a51f8b 100644
--- a/client/client.go
+++ b/client/client.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/cenkalti/rpc2"
 	"github.com/cenkalti/rpc2/jsonrpc"
+	"github.com/ovn-org/libovsdb/cache"
 	"github.com/ovn-org/libovsdb/model"
 	"github.com/ovn-org/libovsdb/ovsdb"
 )
@@ -22,7 +23,7 @@ type OvsdbClient struct {
 	Schema        ovsdb.DatabaseSchema
 	handlers      []ovsdb.NotificationHandler
 	handlersMutex *sync.Mutex
-	Cache         *TableCache
+	Cache         *cache.TableCache
 	stopCh        chan struct{}
 	api           API
 }
@@ -129,7 +130,7 @@ func newRPC2Client(conn net.Conn, database *model.DBModel) (*OvsdbClient, error)
 
 	if err == nil {
 		ovs.Schema = *schema
-		if cache, err := newTableCache(schema, database); err == nil {
+		if cache, err := cache.NewTableCache(schema, database); err == nil {
 			ovs.Cache = cache
 			ovs.Register(ovs.Cache)
 			ovs.api = newAPI(ovs.Cache)
@@ -298,7 +299,7 @@ func (ovs OvsdbClient) Monitor(jsonContext interface{}, requests map[string]ovsd
 	if err != nil {
 		return err
 	}
-	ovs.Cache.populate(reply)
+	ovs.Cache.Populate(reply)
 	return nil
 }
 
diff --git a/client/condition.go b/client/condition.go
index d030e14..cba7a7f 100644
--- a/client/condition.go
+++ b/client/condition.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"reflect"
 
+	"github.com/ovn-org/libovsdb/cache"
 	"github.com/ovn-org/libovsdb/mapper"
 	"github.com/ovn-org/libovsdb/model"
 	"github.com/ovn-org/libovsdb/ovsdb"
@@ -123,7 +124,7 @@ func newExplicitConditional(mapper *mapper.Mapper, table string, all bool, model
 type predicateConditional struct {
 	tableName string
 	predicate interface{}
-	cache     *TableCache
+	cache     *cache.TableCache
 }
 
 // matches returns the result of the execution of the predicate
@@ -152,7 +153,7 @@ func (c *predicateConditional) Generate() ([][]ovsdb.Condition, error) {
 			return nil, err
 		}
 		if match {
-			elemCond, err := c.cache.mapper.NewEqualityCondition(c.tableName, elem)
+			elemCond, err := c.cache.Mapper().NewEqualityCondition(c.tableName, elem)
 			if err != nil {
 				return nil, err
 			}
@@ -163,7 +164,7 @@ func (c *predicateConditional) Generate() ([][]ovsdb.Condition, error) {
 }
 
 // newPredicateConditional creates a new predicateConditional
-func newPredicateConditional(table string, cache *TableCache, predicate interface{}) (Conditional, error) {
+func newPredicateConditional(table string, cache *cache.TableCache, predicate interface{}) (Conditional, error) {
 	return &predicateConditional{
 		tableName: table,
 		predicate: predicate,
diff --git a/cmd/stress/stress.go b/cmd/stress/stress.go
index 5b3dcd5..34c6b72 100644
--- a/cmd/stress/stress.go
+++ b/cmd/stress/stress.go
@@ -8,6 +8,7 @@ import (
 	"runtime"
 	"runtime/pprof"
 
+	"github.com/ovn-org/libovsdb/cache"
 	"github.com/ovn-org/libovsdb/client"
 	"github.com/ovn-org/libovsdb/model"
 	"github.com/ovn-org/libovsdb/ovsdb"
@@ -50,7 +51,7 @@ func run() {
 	}
 	defer ovs.Disconnect()
 	ovs.Cache.AddEventHandler(
-		&client.EventHandlerFuncs{
+		&cache.EventHandlerFuncs{
 			AddFunc: func(table string, model model.Model) {
 				if ready && table == "Bridge" {
 					insertions++
diff --git a/example/play_with_ovs/play_with_ovs.go b/example/play_with_ovs/play_with_ovs.go
index 607f283..74753dd 100644
--- a/example/play_with_ovs/play_with_ovs.go
+++ b/example/play_with_ovs/play_with_ovs.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"log"
 
+	"github.com/ovn-org/libovsdb/cache"
 	"github.com/ovn-org/libovsdb/client"
 	"github.com/ovn-org/libovsdb/model"
 	"github.com/ovn-org/libovsdb/ovsdb"
@@ -109,7 +110,7 @@ func main() {
 		log.Fatal("Unable to Connect ", err)
 	}
 
-	ovs.Cache.AddEventHandler(&client.EventHandlerFuncs{
+	ovs.Cache.AddEventHandler(&cache.EventHandlerFuncs{
 		AddFunc: func(table string, model model.Model) {
 			if table == bridgeTable {
 				update <- model


Test patch:
diff --git a/client/cache_test.go b/cache/cache_test.go
similarity index 94%
rename from client/cache_test.go
rename to cache/cache_test.go
index 108da14..91205df 100644
--- a/client/cache_test.go
+++ b/cache/cache_test.go
@@ -1,4 +1,4 @@
-package client
+package cache
 
 import (
 	"testing"
@@ -228,7 +228,7 @@ func TestTableCache_Table(t *testing.T) {
 		{
 			"returns nil for an empty table",
 			fields{
-				cache: map[string]*RowCache{"bar": newRowCache()},
+				cache: map[string]*RowCache{"bar": NewRowCache(nil)},
 			},
 			args{
 				"foo",
@@ -238,12 +238,12 @@ func TestTableCache_Table(t *testing.T) {
 		{
 			"returns nil for an empty table",
 			fields{
-				cache: map[string]*RowCache{"bar": newRowCache()},
+				cache: map[string]*RowCache{"bar": NewRowCache(nil)},
 			},
 			args{
 				"bar",
 			},
-			newRowCache(),
+			NewRowCache(nil),
 		},
 	}
 	for _, tt := range tests {
@@ -268,7 +268,7 @@ func TestTableCache_Tables(t *testing.T) {
 	}{
 		{
 			"returns a table that exists",
-			fields{cache: map[string]*RowCache{"test1": newRowCache(), "test2": newRowCache(), "test3": newRowCache()}},
+			fields{cache: map[string]*RowCache{"test1": NewRowCache(nil), "test2": NewRowCache(nil), "test3": NewRowCache(nil)}},
 			[]string{"test1", "test2", "test3"},
 		},
 		{
@@ -307,7 +307,7 @@ func TestTableCache_populate(t *testing.T) {
 	     }
 	`), &schema)
 	assert.Nil(t, err)
-	tc, err := newTableCache(&schema, db)
+	tc, err := NewTableCache(&schema, db)
 	assert.Nil(t, err)
 
 	testRow := ovsdb.Row(map[string]interface{}{"_uuid": "test", "foo": "bar"})
@@ -320,9 +320,9 @@ func TestTableCache_populate(t *testing.T) {
 			},
 		},
 	}
-	tc.populate(updates)
+	tc.Populate(updates)
 
-	got := tc.cache["Open_vSwitch"].cache["test"]
+	got := tc.Table("Open_vSwitch").Row("test")
 	assert.Equal(t, testRowModel, got)
 
 	t.Log("Update")
@@ -332,7 +332,7 @@ func TestTableCache_populate(t *testing.T) {
 		Old: &testRow,
 		New: &updatedRow,
 	}
-	tc.populate(updates)
+	tc.Populate(updates)
 
 	got = tc.cache["Open_vSwitch"].cache["test"]
 	assert.Equal(t, updatedRowModel, got)
@@ -343,7 +343,7 @@ func TestTableCache_populate(t *testing.T) {
 		New: nil,
 	}
 
-	tc.populate(updates)
+	tc.Populate(updates)
 
 	_, ok := tc.cache["Open_vSwitch"].cache["test"]
 	assert.False(t, ok)
diff --git a/client/api_test.go b/client/api_test.go
index 8367cd8..544aa77 100644
--- a/client/api_test.go
+++ b/client/api_test.go
@@ -5,13 +5,14 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/ovn-org/libovsdb/cache"
 	"github.com/ovn-org/libovsdb/model"
 	"github.com/ovn-org/libovsdb/ovsdb"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestAPIListSimple(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lscacheList := []model.Model{
 		&testLogicalSwitch{
 			UUID:        aUUID0,
@@ -39,8 +40,8 @@ func TestAPIListSimple(t *testing.T) {
 	for i := range lscacheList {
 		lscache[lscacheList[i].(*testLogicalSwitch).UUID] = lscacheList[i]
 	}
-	cache.cache["Logical_Switch"] = &RowCache{cache: lscache}
-	cache.cache["Logical_Switch_Port"] = newRowCache() // empty
+	tcache.Set("Logical_Switch", cache.NewRowCache(lscache))
+	tcache.Set("Logical_Switch_Port", nil) // empty
 
 	test := []struct {
 		name       string
@@ -81,7 +82,7 @@ func TestAPIListSimple(t *testing.T) {
 			if tt.initialCap != 0 {
 				result = make([]testLogicalSwitch, tt.initialCap)
 			}
-			api := newAPI(cache)
+			api := newAPI(tcache)
 			err := api.List(&result)
 			if tt.err {
 				assert.NotNil(t, err)
@@ -97,14 +98,14 @@ func TestAPIListSimple(t *testing.T) {
 
 	t.Run("ApiList: Error wrong type", func(t *testing.T) {
 		var result []string
-		api := newAPI(cache)
+		api := newAPI(tcache)
 		err := api.List(&result)
 		assert.NotNil(t, err)
 	})
 
 	t.Run("ApiList: Type Selection", func(t *testing.T) {
 		var result []testLogicalSwitchPort
-		api := newAPI(cache)
+		api := newAPI(tcache)
 		err := api.List(&result)
 		assert.Nil(t, err)
 		assert.Len(t, result, 0, "Should be empty since cache is empty")
@@ -112,7 +113,7 @@ func TestAPIListSimple(t *testing.T) {
 
 	t.Run("ApiList: Empty List", func(t *testing.T) {
 		result := []testLogicalSwitch{}
-		api := newAPI(cache)
+		api := newAPI(tcache)
 		err := api.List(&result)
 		assert.Nil(t, err)
 		assert.Len(t, result, len(lscacheList))
@@ -120,7 +121,7 @@ func TestAPIListSimple(t *testing.T) {
 }
 
 func TestAPIListPredicate(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lscacheList := []model.Model{
 		&testLogicalSwitch{
 			UUID:        aUUID0,
@@ -148,7 +149,7 @@ func TestAPIListPredicate(t *testing.T) {
 	for i := range lscacheList {
 		lscache[lscacheList[i].(*testLogicalSwitch).UUID] = lscacheList[i]
 	}
-	cache.cache["Logical_Switch"] = &RowCache{cache: lscache}
+	tcache.Set("Logical_Switch", cache.NewRowCache(lscache))
 
 	test := []struct {
 		name      string
@@ -196,7 +197,7 @@ func TestAPIListPredicate(t *testing.T) {
 	for _, tt := range test {
 		t.Run(fmt.Sprintf("ApiListPredicate: %s", tt.name), func(t *testing.T) {
 			var result []testLogicalSwitch
-			api := newAPI(cache)
+			api := newAPI(tcache)
 			cond := api.WhereCache(tt.predicate)
 			err := cond.List(&result)
 			if tt.err {
@@ -213,7 +214,7 @@ func TestAPIListPredicate(t *testing.T) {
 }
 
 func TestAPIListFields(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lspcacheList := []model.Model{
 		&testLogicalSwitchPort{
 			UUID:        aUUID0,
@@ -244,7 +245,7 @@ func TestAPIListFields(t *testing.T) {
 	for i := range lspcacheList {
 		lspcache[lspcacheList[i].(*testLogicalSwitchPort).UUID] = lspcacheList[i]
 	}
-	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspcache}
+	tcache.Set("Logical_Switch_Port", cache.NewRowCache(lspcache))
 
 	testObj := testLogicalSwitchPort{}
 
@@ -283,7 +284,7 @@ func TestAPIListFields(t *testing.T) {
 			var result []testLogicalSwitchPort
 			// Clean object
 			testObj = testLogicalSwitchPort{}
-			api := newAPI(cache)
+			api := newAPI(tcache)
 			err := api.Where(&testObj).List(&result)
 			if tt.err {
 				assert.NotNil(t, err)
@@ -297,7 +298,7 @@ func TestAPIListFields(t *testing.T) {
 
 	t.Run("ApiListFields: Wrong table", func(t *testing.T) {
 		var result []testLogicalSwitchPort
-		api := newAPI(cache)
+		api := newAPI(tcache)
 		obj := testLogicalSwitch{
 			UUID: aUUID0,
 		}
@@ -415,7 +416,7 @@ func TestConditionFromModel(t *testing.T) {
 }
 
 func TestAPIGet(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lsCacheList := []model.Model{}
 	lspCacheList := []model.Model{
 		&testLogicalSwitchPort{
@@ -439,8 +440,8 @@ func TestAPIGet(t *testing.T) {
 	for i := range lspCacheList {
 		lspCache[lspCacheList[i].(*testLogicalSwitchPort).UUID] = lspCacheList[i]
 	}
-	cache.cache["Logical_Switch"] = &RowCache{cache: lsCache}
-	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspCache}
+	tcache.Set("Logical_Switch", cache.NewRowCache(lsCache))
+	tcache.Set("Logical_Switch_Port", cache.NewRowCache(lspCache))
 
 	test := []struct {
 		name    string
@@ -482,7 +483,7 @@ func TestAPIGet(t *testing.T) {
 		t.Run(fmt.Sprintf("ApiGet: %s", tt.name), func(t *testing.T) {
 			var result testLogicalSwitchPort
 			tt.prepare(&result)
-			api := newAPI(cache)
+			api := newAPI(tcache)
 			err := api.Get(&result)
 			if tt.err {
 				assert.NotNil(t, err)
@@ -495,7 +496,7 @@ func TestAPIGet(t *testing.T) {
 }
 
 func TestAPICreate(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lsCacheList := []model.Model{}
 	lspCacheList := []model.Model{
 		&testLogicalSwitchPort{
@@ -519,8 +520,8 @@ func TestAPICreate(t *testing.T) {
 	for i := range lspCacheList {
 		lspCache[lspCacheList[i].(*testLogicalSwitchPort).UUID] = lspCacheList[i]
 	}
-	cache.cache["Logical_Switch"] = &RowCache{cache: lsCache}
-	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspCache}
+	tcache.Set("Logical_Switch", cache.NewRowCache(lsCache))
+	tcache.Set("Logical_Switch_Port", cache.NewRowCache(lspCache))
 
 	rowFoo := ovsdb.Row(map[string]interface{}{"name": "foo"})
 	rowBar := ovsdb.Row(map[string]interface{}{"name": "bar"})
@@ -595,7 +596,7 @@ func TestAPICreate(t *testing.T) {
 	}
 	for _, tt := range test {
 		t.Run(fmt.Sprintf("ApiCreate: %s", tt.name), func(t *testing.T) {
-			api := newAPI(cache)
+			api := newAPI(tcache)
 			op, err := api.Create(tt.input...)
 			if tt.err {
 				assert.NotNil(t, err)
@@ -608,7 +609,7 @@ func TestAPICreate(t *testing.T) {
 }
 
 func TestAPIMutate(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lspCache := map[string]model.Model{
 		aUUID0: &testLogicalSwitchPort{
 			UUID:        aUUID0,
@@ -633,7 +634,7 @@ func TestAPIMutate(t *testing.T) {
 			Tag:         []int{1},
 		},
 	}
-	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspCache}
+	tcache.Set("Logical_Switch_Port", cache.NewRowCache(lspCache))
 
 	testObj := testLogicalSwitchPort{}
 
@@ -761,7 +762,7 @@ func TestAPIMutate(t *testing.T) {
 	}
 	for _, tt := range test {
 		t.Run(fmt.Sprintf("ApiMutate: %s", tt.name), func(t *testing.T) {
-			api := newAPI(cache)
+			api := newAPI(tcache)
 			cond := tt.condition(api)
 			ops, err := cond.Mutate(&testObj, tt.mutations...)
 			if tt.err {
@@ -775,7 +776,7 @@ func TestAPIMutate(t *testing.T) {
 }
 
 func TestAPIUpdate(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lspCache := map[string]model.Model{
 		aUUID0: &testLogicalSwitchPort{
 			UUID:        aUUID0,
@@ -801,7 +802,7 @@ func TestAPIUpdate(t *testing.T) {
 			Tag:         []int{1},
 		},
 	}
-	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspCache}
+	tcache.Set("Logical_Switch_Port", cache.NewRowCache(lspCache))
 
 	testObj := testLogicalSwitchPort{}
 	testRow := ovsdb.Row(map[string]interface{}{"type": "somethingElse", "tag": testOvsSet(t, []int{6})})
@@ -1004,7 +1005,7 @@ func TestAPIUpdate(t *testing.T) {
 	}
 	for _, tt := range test {
 		t.Run(fmt.Sprintf("ApiUpdate: %s", tt.name), func(t *testing.T) {
-			api := newAPI(cache)
+			api := newAPI(tcache)
 			cond := tt.condition(api)
 			// clean test Object
 			testObj = testLogicalSwitchPort{}
@@ -1021,7 +1022,7 @@ func TestAPIUpdate(t *testing.T) {
 }
 
 func TestAPIDelete(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lspCache := map[string]model.Model{
 		aUUID0: &testLogicalSwitchPort{
 			UUID:        aUUID0,
@@ -1047,7 +1048,7 @@ func TestAPIDelete(t *testing.T) {
 			Tag:         []int{1},
 		},
 	}
-	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspCache}
+	tcache.Set("Logical_Switch_Port", cache.NewRowCache(lspCache))
 
 	test := []struct {
 		name      string
@@ -1192,7 +1193,7 @@ func TestAPIDelete(t *testing.T) {
 	}
 	for _, tt := range test {
 		t.Run(fmt.Sprintf("ApiDelete: %s", tt.name), func(t *testing.T) {
-			api := newAPI(cache)
+			api := newAPI(tcache)
 			cond := tt.condition(api)
 			ops, err := cond.Delete()
 			if tt.err {
diff --git a/client/api_test_model.go b/client/api_test_model.go
index 018c5f2..a77f936 100644
--- a/client/api_test_model.go
+++ b/client/api_test_model.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"testing"
 
+	"github.com/ovn-org/libovsdb/cache"
 	"github.com/ovn-org/libovsdb/model"
 	"github.com/ovn-org/libovsdb/ovsdb"
 	"github.com/stretchr/testify/assert"
@@ -152,13 +153,13 @@ func (*testLogicalSwitchPort) Table() string {
 	return "Logical_Switch_Port"
 }
 
-func apiTestCache(t *testing.T) *TableCache {
+func apiTestCache(t *testing.T) *cache.TableCache {
 	var schema ovsdb.DatabaseSchema
 	err := json.Unmarshal(apiTestSchema, &schema)
 	assert.Nil(t, err)
 	db, err := model.NewDBModel("OVN_NorthBound", map[string]model.Model{"Logical_Switch": &testLogicalSwitch{}, "Logical_Switch_Port": &testLogicalSwitchPort{}})
 	assert.Nil(t, err)
-	cache, err := newTableCache(&schema, db)
+	cache, err := cache.NewTableCache(&schema, db)
 	assert.Nil(t, err)
 	return cache
 }
diff --git a/client/condition_test.go b/client/condition_test.go
index 40502ff..09ec7b6 100644
--- a/client/condition_test.go
+++ b/client/condition_test.go
@@ -4,13 +4,14 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/ovn-org/libovsdb/cache"
 	"github.com/ovn-org/libovsdb/model"
 	"github.com/ovn-org/libovsdb/ovsdb"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestEqualityConditional(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lspcacheList := []model.Model{
 		&testLogicalSwitchPort{
 			UUID:        aUUID0,
@@ -41,7 +42,7 @@ func TestEqualityConditional(t *testing.T) {
 	for i := range lspcacheList {
 		lspcache[lspcacheList[i].(*testLogicalSwitchPort).UUID] = lspcacheList[i]
 	}
-	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspcache}
+	tcache.Set("Logical_Switch_Port", cache.NewRowCache(lspcache))
 
 	test := []struct {
 		name      string
@@ -125,7 +126,7 @@ func TestEqualityConditional(t *testing.T) {
 	}
 	for _, tt := range test {
 		t.Run(fmt.Sprintf("Equality Conditional: %s", tt.name), func(t *testing.T) {
-			cond, err := newEqualityConditional(cache.mapper, "Logical_Switch_Port", tt.all, tt.model)
+			cond, err := newEqualityConditional(tcache.Mapper(), "Logical_Switch_Port", tt.all, tt.model)
 			assert.Nil(t, err)
 			for model, shouldMatch := range tt.matches {
 				matches, err := cond.Matches(model)
@@ -148,7 +149,7 @@ func TestEqualityConditional(t *testing.T) {
 }
 
 func TestPredicateConditional(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lspcacheList := []model.Model{
 		&testLogicalSwitchPort{
 			UUID:        aUUID0,
@@ -179,7 +180,7 @@ func TestPredicateConditional(t *testing.T) {
 	for i := range lspcacheList {
 		lspcache[lspcacheList[i].(*testLogicalSwitchPort).UUID] = lspcacheList[i]
 	}
-	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspcache}
+	tcache.Set("Logical_Switch_Port", cache.NewRowCache(lspcache))
 
 	test := []struct {
 		name      string
@@ -232,7 +233,7 @@ func TestPredicateConditional(t *testing.T) {
 	}
 	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)
+			cond, err := newPredicateConditional("Logical_Switch_Port", tcache, tt.predicate)
 			assert.Nil(t, err)
 			for model, shouldMatch := range tt.matches {
 				matches, err := cond.Matches(model)
@@ -255,7 +256,7 @@ func TestPredicateConditional(t *testing.T) {
 }
 
 func TestExplicitConditional(t *testing.T) {
-	cache := apiTestCache(t)
+	tcache := apiTestCache(t)
 	lspcacheList := []model.Model{
 		&testLogicalSwitchPort{
 			UUID:        aUUID0,
@@ -286,7 +287,7 @@ func TestExplicitConditional(t *testing.T) {
 	for i := range lspcacheList {
 		lspcache[lspcacheList[i].(*testLogicalSwitchPort).UUID] = lspcacheList[i]
 	}
-	cache.cache["Logical_Switch_Port"] = &RowCache{cache: lspcache}
+	tcache.Set("Logical_Switch_Port", cache.NewRowCache(lspcache))
 
 	testObj := &testLogicalSwitchPort{}
 
@@ -424,7 +425,7 @@ func TestExplicitConditional(t *testing.T) {
 	}
 	for _, tt := range test {
 		t.Run(fmt.Sprintf("Explicit Conditional: %s", tt.name), func(t *testing.T) {
-			cond, err := newExplicitConditional(cache.mapper, "Logical_Switch_Port", tt.all, testObj, tt.args...)
+			cond, err := newExplicitConditional(tcache.Mapper(), "Logical_Switch_Port", tt.all, testObj, tt.args...)
 			assert.Nil(t, err)
 			_, err = cond.Matches(testObj)
 			assert.NotNilf(t, err, "Explicit conditions should fail to match on cache")