diff --git a/pkg/dbsql/crud.go b/pkg/dbsql/crud.go index 7b52bf4..d212e7a 100644 --- a/pkg/dbsql/crud.go +++ b/pkg/dbsql/crud.go @@ -109,6 +109,7 @@ type CRUD[T Resource] interface { UpdateMany(ctx context.Context, filter ffapi.Filter, update ffapi.Update, hooks ...PostCompletionHook) (err error) Delete(ctx context.Context, id string, hooks ...PostCompletionHook) (err error) DeleteMany(ctx context.Context, filter ffapi.Filter, hooks ...PostCompletionHook) (err error) // no events + Scoped(scope sq.Eq) *CrudBase[T] // allows dynamic scoping to a collection } type CrudBase[T Resource] struct { @@ -135,6 +136,12 @@ type CrudBase[T Resource] struct { ReadQueryModifier func(sq.SelectBuilder) sq.SelectBuilder } +func (c *CrudBase[T]) Scoped(scope sq.Eq) *CrudBase[T] { + cScoped := *c + cScoped.ScopedFilter = func() sq.Eq { return scope } + return &cScoped +} + func UUIDValidator(ctx context.Context, idStr string) error { _, err := fftypes.ParseUUID(ctx, idStr) return err diff --git a/pkg/dbsql/crud_test.go b/pkg/dbsql/crud_test.go index 56b361e..436a740 100644 --- a/pkg/dbsql/crud_test.go +++ b/pkg/dbsql/crud_test.go @@ -329,6 +329,11 @@ func TestCRUDWithDBEnd2End(t *testing.T) { assert.NoError(t, err) checkEqualExceptTimes(t, *c1, *c1copy) + // Check we don't get it back by name if we scope to a different ns + c1NotFound, err := iCrud.Scoped(sq.Eq{"ns": "ns2"}).GetByName(ctx, *c1.Name) + assert.NoError(t, err) + assert.Nil(t, c1NotFound) + // Check we get it back by name, in name or UUID c1copy, err = iCrud.GetByUUIDOrName(ctx, *c1.Name) assert.NoError(t, err) diff --git a/pkg/ffapi/filter.go b/pkg/ffapi/filter.go index 5bd0f44..dc38c16 100644 --- a/pkg/ffapi/filter.go +++ b/pkg/ffapi/filter.go @@ -414,7 +414,16 @@ func (f *baseFilter) Finalize() (fi *FilterInfo, err error) { case string: switch { case field.FilterAsString(): - value = &stringField{} + // We need to have a stringField for the filter serialization, but in the case of StringFieldLower + // (or other modified stringField types) we might still need to run transformations. So... + normalFilter := field.GetSerialization() + switch normalFilter.(type) { + case *stringField: + value = normalFilter + default: + // ... only switch to a default &stringField{} if the Serialization is not already a string + value = &stringField{} + } case filterOpIsStringMatch(f.op): return nil, i18n.NewError(f.fb.ctx, i18n.MsgFieldTypeNoStringMatching, name, field.Description()) default: diff --git a/pkg/ffapi/filter_test.go b/pkg/ffapi/filter_test.go index 618a594..42f48e2 100644 --- a/pkg/ffapi/filter_test.go +++ b/pkg/ffapi/filter_test.go @@ -189,7 +189,7 @@ func TestLowerField(t *testing.T) { fb.In("address", []driver.Value{addr1, addr2}), ).Finalize() assert.NoError(t, err) - assert.Equal(t, "( lower(address) == '0xf698D78272a0bCD63A3feb097B24a866f6b8a5a0' ) && ( lower(address) IN ['0xf698D78272a0bCD63A3feb097B24a866f6b8a5a0','0xb9B919763dBC54D4D634150446Bf3991A9ef5eD7'] )", f.String()) + assert.Equal(t, "( lower(address) == '0xf698d78272a0bcd63a3feb097b24a866f6b8a5a0' ) && ( lower(address) IN ['0xf698d78272a0bcd63a3feb097b24a866f6b8a5a0','0xb9b919763dbc54d4d634150446bf3991a9ef5ed7'] )", f.String()) } func TestBuildMessageStringConvert(t *testing.T) { diff --git a/pkg/ffapi/query_fields.go b/pkg/ffapi/query_fields.go index 7565c62..d7122c8 100644 --- a/pkg/ffapi/query_fields.go +++ b/pkg/ffapi/query_fields.go @@ -94,7 +94,10 @@ func (f *nullField) Value() (driver.Value, error) { return nil, nil } func (f *nullField) String() string { return fftypes.NullString } type StringField struct{} -type stringField struct{ s string } +type stringField struct { + s string + lower bool +} func (f *stringField) Scan(src interface{}) error { switch tv := src.(type) { @@ -136,7 +139,12 @@ func (f *stringField) Scan(src interface{}) error { } return nil } -func (f *stringField) Value() (driver.Value, error) { return f.s, nil } +func (f *stringField) Value() (driver.Value, error) { + if f.lower { + return strings.ToLower(f.s), nil + } + return f.s, nil +} func (f *stringField) String() string { return f.s } func (f *StringField) GetSerialization() FieldSerialization { return &stringField{} } func (f *StringField) FilterAsString() bool { return true } @@ -146,7 +154,8 @@ type StringFieldLower struct { StringField } -func (f *StringFieldLower) GetSerialization() FieldSerialization { return &stringField{} } +func (f *StringFieldLower) GetSerialization() FieldSerialization { return &stringField{lower: true} } +func (f *StringFieldLower) FilterAsString() bool { return true } func (f *StringFieldLower) FieldMods() []FieldMod { return []FieldMod{FieldModLower} } type UUIDField struct{}