Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ create an equality condition (using `ovsdb.ConditionEqual`) on the `_uuid` field

The table is inferred from the type that the function accepts as only argument.

**WhereCacheTyped()**: `WhereCacheTyped()` is a high-performance, type-safe alternative to `WhereCache()` that uses Go generics. It provides the same functionality as `WhereCache()` but with significantly better performance and zero memory allocations for predicate evaluation. This is the recommended approach for performance-critical applications. Example:

lsList := []LogicalSwitch{}
client.WhereCacheTyped(ovs, func(ls *MyLogicalSwitch) bool {
return strings.HasPrefix(ls.Name, "ext_")
}).List(&lsList)

The generic type parameter is automatically inferred from the predicate function signature, eliminating the need for runtime type assertions and reflection. This API is available for all operations that `WhereCache()` supports, including `List()`, `Update()`, `Delete()`, and `Mutate()`.

### Client indexes

The client will track schema indexes and use them when appropriate in `Get`, `Where`, and `WhereAll` as explained above.
Expand Down Expand Up @@ -180,6 +189,17 @@ Search the cache for elements that match a certain predicate:
fmt.Printf("%+v\n", ls)
}

Search the cache using the high-performance typed API:

var lsList *[]MyLogicalSwitch
client.WhereCacheTyped(ovs, func(ls *MyLogicalSwitch) bool {
return strings.HasPrefix(ls.Name, "ext_")
}).List(&lsList)

for _, ls := range lsList {
fmt.Printf("%+v\n", ls)
}

Create a new element

ops, _ := ovs.Create(&MyLogicalSwitch{
Expand Down
104 changes: 104 additions & 0 deletions client/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,110 @@ func BenchmarkAPIList(b *testing.B) {
}
}

func BenchmarkWhereCacheTypedList(b *testing.B) {
const numRows = 10000

lscacheList := make([]*testBridge, 0, numRows)

for i := 0; i < numRows; i++ {
lscacheList = append(lscacheList,
&testBridge{
UUID: uuid.New().String(),
Name: fmt.Sprintf("ls%d", i),
ExternalIDs: map[string]string{"foo": "bar"},
})
}
lscache := map[string]model.Model{}
for i := range lscacheList {
lscache[lscacheList[i].UUID] = lscacheList[i]
}
testData := cache.Data{
"Bridge": lscache,
}
tcache := apiTestCache(b, testData)
ovs, err := newOVSDBClient(defDB)
require.NoError(b, err)
ovs.primaryDB().cache = tcache
ovs.primaryDB().api = newAPI(tcache, &discardLogger, false)

r := rand.New(rand.NewSource(int64(b.N)))
var index int

test := []struct {
name string
predicate any
predicateTyped func(*testBridge) bool
}{
{
name: "predicate returns none",
predicate: func(_ *testBridge) bool {
return false
},
},
{
name: "predicate returns all",
predicate: func(_ *testBridge) bool {
return true
},
},
{
name: "predicate on an arbitrary condition",
predicate: func(t *testBridge) bool {
return strings.HasPrefix(t.Name, "ls1")
},
},
{
name: "predicate matches name",
predicate: func(t *testBridge) bool {
return t.Name == lscacheList[index].Name
},
},
// Typed versions for comparison
{
name: "typed: predicate returns none",
predicateTyped: func(_ *testBridge) bool {
return false
},
},
{
name: "typed: predicate returns all",
predicateTyped: func(_ *testBridge) bool {
return true
},
},
{
name: "typed: predicate on an arbitrary condition",
predicateTyped: func(t *testBridge) bool {
return strings.HasPrefix(t.Name, "ls1")
},
},
{
name: "typed: predicate matches name",
predicateTyped: func(t *testBridge) bool {
return t.Name == lscacheList[index].Name
},
},
}

for _, tt := range test {
b.Run(tt.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
index = r.Intn(numRows)
var result []*testBridge
var cond ConditionalAPI

if tt.predicateTyped != nil {
cond = WhereCacheTyped(ovs, tt.predicateTyped)
} else if tt.predicate != nil {
cond = ovs.WhereCache(tt.predicate)
}
err := cond.List(context.Background(), &result)
assert.NoError(b, err)
}
})
}
}

func BenchmarkAPIListMultiple(b *testing.B) {
const numRows = 500

Expand Down
10 changes: 10 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ type Client interface {
MonitorCancel(ctx context.Context, cookie MonitorCookie) error
NewMonitor(...MonitorOption) *Monitor
CurrentEndpoint() string
Logger() *logr.Logger
Option() *options
API
// GetSelectResultsByIndex parses the result of the select operation indicated by
// the 0-based index from the transaction results of the provided operations.
Expand Down Expand Up @@ -1576,3 +1578,11 @@ func (o *ovsdbClient) GetSelectResultsByIndex(ops []ovsdb.Operation, results []o
func (o *ovsdbClient) GetSelectResults(ops []ovsdb.Operation, results []ovsdb.OperationResult, target interface{}) error {
return o.GetSelectResultsByIndex(ops, results, target, 0)
}

func (o *ovsdbClient) Logger() *logr.Logger {
return o.logger
}

func (o *ovsdbClient) Option() *options {
return o.options
}
76 changes: 76 additions & 0 deletions client/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,82 @@ func newPredicateConditional(table string, cache *cache.TableCache, predicate an
}, nil
}

// predicateConditionalTyped is a type-safe version using generics
type predicateConditionalTyped[T model.Model] struct {
tableName string
predicate func(T) bool
cache *cache.TableCache
}

// Matches returns the models that match the predicate without reflection
func (c *predicateConditionalTyped[T]) Matches() (map[string]model.Model, error) {
tableCache := c.cache.Table(c.tableName)
if tableCache == nil {
return nil, ErrNotFound
}
found := map[string]model.Model{}
// run the predicate on a shallow copy of the models for speed and only
// clone the matches
for u, m := range tableCache.RowsShallow() {
// Type assertion instead of reflection - much faster
if typedModel, ok := m.(T); ok {
if c.predicate(typedModel) {
found[u] = model.Clone(m)
}
}
}
return found, nil
}

func (c *predicateConditionalTyped[T]) 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 *predicateConditionalTyped[T]) Generate() ([][]ovsdb.Condition, error) {
models, err := c.Matches()
if err != nil {
return nil, err
}
return generateConditionsFromModels(c.cache.DatabaseModel(), models)
}

// newPredicateConditionalTyped creates a type-safe predicate conditional
// This is the recommended high-performance alternative to newPredicateConditional
func newPredicateConditionalTyped[T model.Model](table string, cache *cache.TableCache, predicate func(T) bool) (Conditional, error) {
return &predicateConditionalTyped[T]{
tableName: table,
predicate: predicate,
cache: cache,
}, nil
}

// WhereCacheTyped creates a high-performance ConditionalAPI using generics
// Usage: WhereCacheTyped(client, func(m *MyModel) bool { return m.Field > value })
func WhereCacheTyped[T model.Model](client Client, predicate func(T) bool) ConditionalAPI {
clientCache := client.Cache()
logger := client.Logger()
options := client.Option()

// Get table name from the generic type T
var zero T
table := clientCache.DatabaseModel().FindTable(reflect.TypeOf(zero))
if table == "" {
// Return an error conditional if table not found
errorCond := newErrorConditional(fmt.Errorf("model %T not found in Database Model", zero))
return newConditionalAPI(clientCache, errorCond, logger, options.validateModel)
}

condition, err := newPredicateConditionalTyped(table, clientCache, predicate)
if err != nil {
errorCond := newErrorConditional(err)
return newConditionalAPI(clientCache, errorCond, logger, options.validateModel)
}

return newConditionalAPI(clientCache, condition, logger, options.validateModel)
}

// 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 {
Expand Down
16 changes: 16 additions & 0 deletions test/ovs/ovs_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2030,3 +2030,19 @@ func (suite *OVSIntegrationSuite) TestSelectIntegrity() {

// Cleanup is handled by TearDownTest
}
func (suite *OVSIntegrationSuite) TestWhereCache() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

var ovs []*ovsType
err := suite.clientWithoutInactvityCheck.WhereCache(func(*ovsType) bool { return true }).List(ctx, &ovs)
suite.Require().NoError(err)
suite.Len(ovs, 1)

var ovs1 []*ovsType
err = client.WhereCacheTyped(suite.clientWithoutInactvityCheck, func(*ovsType) bool { return true }).List(ctx, &ovs1)
suite.Require().NoError(err)
suite.Len(ovs1, 1)

suite.Equal(ovs[0].UUID, ovs1[0].UUID)
}
Loading