Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using client indices for *string columns #387

Merged
merged 1 commit into from
Aug 20, 2024
Merged
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ The table is inferred from the type that the function accepts as only argument.
The client will track schema indexes and use them when appropriate in `Get`, `Where`, and `WhereAll` as explained above.

Additional indexes can be specified for a client instance to track. Just as schema indexes, client indexes are specified in sets per table.
where each set consists of the columns that compose the index. Unlike schema indexes, a key within a column can be addressed if the column
type is a map.
where each set consists of the columns that compose the index. However, unlike schema indexes, client indexes:
- can be used with columns that are maps, where specific map keys can be indexed (see example below).
- can be used with columns that are optional, where no-value columns are indexed as well.

Client indexes are leveraged through `Where`, and `WhereAll`. Since client indexes value uniqueness is not enforced as it happens with schema indexes,
conditions based on them can match multiple rows.
Expand Down
5 changes: 5 additions & 0 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,11 @@ func valueFromColumnKey(info *mapper.Info, columnKey model.ColumnKey) (interface
return "", fmt.Errorf("can't get key value from map: %v", err)
}
}
// if the value is a non-nil pointer of an optional, dereference
v := reflect.ValueOf(val)
if v.Kind() == reflect.Ptr && !v.IsNil() {
val = v.Elem().Interface()
}
return val, err
}

Expand Down
305 changes: 305 additions & 0 deletions cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,96 @@ func TestRowCacheCreateClientIndex(t *testing.T) {
}
}

func getStringPtr(s string) *string {
return &s
}

var nilString *string

func TestRowCacheCreateOptionalColumnClientIndex(t *testing.T) {
var schema ovsdb.DatabaseSchema
db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}})
db.SetIndexes(map[string][]model.ClientIndex{
"Open_vSwitch": {
{
Columns: []model.ColumnKey{
{
Column: "datapath",
},
},
},
},
})
require.Nil(t, err)
err = json.Unmarshal(getTestSchema(""), &schema)
require.Nil(t, err)
testData := Data{
"Open_vSwitch": map[string]model.Model{"bar": &testModel{Datapath: getStringPtr("bar")}},
}

dbModel, errs := model.NewDatabaseModel(schema, db)
require.Empty(t, errs)

tests := []struct {
name string
uuid string
model *testModel
wantErr bool
expected valueToUUIDs
}{
{
name: "inserts a new row",
uuid: "foo",
model: &testModel{Datapath: getStringPtr("foo")},
wantErr: false,
expected: valueToUUIDs{
"foo": newUUIDSet("foo"),
"bar": newUUIDSet("bar"),
},
},
{
name: "error duplicate uuid",
uuid: "bar",
model: &testModel{Datapath: getStringPtr("foo")},
wantErr: true,
},
{
name: "inserts duplicate index",
uuid: "baz",
model: &testModel{Datapath: getStringPtr("bar")},
wantErr: false,
expected: valueToUUIDs{
"bar": newUUIDSet("bar", "baz"),
},
},
{
name: "inserts nil index",
uuid: "nil",
model: &testModel{Datapath: nil},
wantErr: false,
expected: valueToUUIDs{
"bar": newUUIDSet("bar"),
nilString: newUUIDSet("nil"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tc, err := NewTableCache(dbModel, testData, nil)
require.Nil(t, err)
rc := tc.Table("Open_vSwitch")
require.NotNil(t, rc)
err = rc.Create(tt.uuid, tt.model, true)
if tt.wantErr {
require.Error(t, err)
} else {
require.Nil(t, err)
require.Equal(t, tt.expected, rc.indexes["datapath"])
}
})
}
}

func TestRowCacheCreateMultiIndex(t *testing.T) {
var schema ovsdb.DatabaseSchema
db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}})
Expand Down Expand Up @@ -709,6 +799,129 @@ func TestRowCacheUpdateClientIndex(t *testing.T) {
}
}

func TestRowCacheUpdateOptionalColumnClientIndex(t *testing.T) {
var schema ovsdb.DatabaseSchema
db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}})
require.Nil(t, err)
db.SetIndexes(map[string][]model.ClientIndex{
"Open_vSwitch": {
{
Columns: []model.ColumnKey{
{
Column: "datapath",
},
},
},
},
})
err = json.Unmarshal(getTestSchema(""), &schema)
require.Nil(t, err)
testData := Data{
"Open_vSwitch": map[string]model.Model{
"foo": &testModel{Datapath: getStringPtr("foo"), Bar: "foo"},
"bar": &testModel{Datapath: getStringPtr("bar"), Bar: "bar"},
"foobar": &testModel{Datapath: getStringPtr("bar"), Bar: "foobar"},
"nil": &testModel{Datapath: nilString, Bar: "nil"},
},
}
dbModel, errs := model.NewDatabaseModel(schema, db)
require.Empty(t, errs)

tests := []struct {
name string
uuid string
model *testModel
wantErr bool
expected valueToUUIDs
}{
{
name: "error if row does not exist",
uuid: "baz",
model: &testModel{Datapath: getStringPtr("baz")},
wantErr: true,
},
{
name: "update non-index",
uuid: "foo",
model: &testModel{Datapath: getStringPtr("foo"), Bar: "bar"},
wantErr: false,
expected: valueToUUIDs{
"foo": newUUIDSet("foo"),
"bar": newUUIDSet("bar", "foobar"),
nilString: newUUIDSet("nil"),
},
},
{
name: "update unique index to new index",
uuid: "foo",
model: &testModel{Datapath: getStringPtr("baz")},
wantErr: false,
expected: valueToUUIDs{
"baz": newUUIDSet("foo"),
"bar": newUUIDSet("bar", "foobar"),
nilString: newUUIDSet("nil"),
},
},
{
name: "update unique index to existing index",
uuid: "foo",
model: &testModel{Datapath: getStringPtr("bar")},
wantErr: false,
expected: valueToUUIDs{
"bar": newUUIDSet("foo", "bar", "foobar"),
nilString: newUUIDSet("nil"),
},
},
{
name: "update multi index to different index",
uuid: "foobar",
model: &testModel{Datapath: getStringPtr("foo")},
wantErr: false,
expected: valueToUUIDs{
"foo": newUUIDSet("foo", "foobar"),
"bar": newUUIDSet("bar"),
nilString: newUUIDSet("nil"),
},
},
{
name: "update nil index to new index",
uuid: "nil",
model: &testModel{Datapath: getStringPtr("foo")},
wantErr: false,
expected: valueToUUIDs{
"foo": newUUIDSet("nil", "foo"),
"bar": newUUIDSet("bar", "foobar"),
},
},
{
name: "update multi index to nil index",
uuid: "foobar",
model: &testModel{Datapath: nilString},
wantErr: false,
expected: valueToUUIDs{
"foo": newUUIDSet("foo"),
"bar": newUUIDSet("bar"),
nilString: newUUIDSet("nil", "foobar"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tc, err := NewTableCache(dbModel, testData, nil)
require.Nil(t, err)
rc := tc.Table("Open_vSwitch")
require.NotNil(t, rc)
_, err = rc.Update(tt.uuid, tt.model, true)
if tt.wantErr {
require.Error(t, err)
} else {
require.Nil(t, err)
require.Equal(t, tt.expected, rc.indexes["datapath"])
}
})
}
}

func TestRowCacheUpdateMultiIndex(t *testing.T) {
var schema ovsdb.DatabaseSchema
db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}})
Expand Down Expand Up @@ -1168,6 +1381,98 @@ func TestRowCacheDeleteClientIndex(t *testing.T) {
}
}

func TestRowCacheDeleteOptionalColumnClientIndex(t *testing.T) {
var schema ovsdb.DatabaseSchema
db, err := model.NewClientDBModel("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &testModel{}})
require.Nil(t, err)

db.SetIndexes(map[string][]model.ClientIndex{
"Open_vSwitch": {
{
Columns: []model.ColumnKey{
{
Column: "datapath",
},
},
},
},
})
err = json.Unmarshal(getTestSchema(""), &schema)
require.Nil(t, err)

testData := Data{
"Open_vSwitch": map[string]model.Model{
"foo": &testModel{Datapath: getStringPtr("foo"), Bar: "foo"},
"bar": &testModel{Datapath: getStringPtr("bar"), Bar: "bar"},
"foobar": &testModel{Datapath: getStringPtr("bar"), Bar: "foobar"},
"nil": &testModel{Datapath: nilString, Bar: "nil"},
},
}
dbModel, errs := model.NewDatabaseModel(schema, db)
require.Empty(t, errs)

tests := []struct {
name string
uuid string
model *testModel
wantErr bool
expected valueToUUIDs
}{
{
name: "error if row does not exist",
uuid: "baz",
model: &testModel{Datapath: getStringPtr("baz")},
wantErr: true,
},
{
name: "delete a row with unique index",
uuid: "foo",
model: &testModel{Datapath: getStringPtr("foo"), Bar: "foo"},
wantErr: false,
expected: valueToUUIDs{
"bar": newUUIDSet("bar", "foobar"),
nilString: newUUIDSet("nil"),
},
},
{
name: "delete a row with duplicated index",
uuid: "bar",
model: &testModel{Datapath: getStringPtr("bar"), Bar: "bar"},
wantErr: false,
expected: valueToUUIDs{
"foo": newUUIDSet("foo"),
"bar": newUUIDSet("foobar"),
nilString: newUUIDSet("nil"),
},
},
{
name: "delete a row with nil index",
uuid: "nil",
model: &testModel{Datapath: nilString, Bar: "nil"},
wantErr: false,
expected: valueToUUIDs{
"foo": newUUIDSet("foo"),
"bar": newUUIDSet("bar", "foobar"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tc, err := NewTableCache(dbModel, testData, nil)
require.Nil(t, err)
rc := tc.Table("Open_vSwitch")
require.NotNil(t, rc)
err = rc.Delete(tt.uuid)
if tt.wantErr {
require.Error(t, err)
} else {
require.Nil(t, err)
require.Equal(t, tt.expected, rc.indexes["datapath"])
}
})
}
}

func TestEventHandlerFuncs_OnAdd(t *testing.T) {
calls := 0
type fields struct {
Expand Down
Loading