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

Support database queries on arbitrary labels #117

Merged
merged 24 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a8a924b
Support searching by metadata.label fields:
ericpromislow Nov 4, 2024
82b7994
Add a unit test for label tests.
ericpromislow Nov 5, 2024
991d637
Stick with terse in-place table alias names rather than interpolate d…
ericpromislow Nov 6, 2024
305b92b
Run go generate after refactoring label handling.
ericpromislow Nov 7, 2024
cbde1d8
Move label handling out of store.go and into the listoption indexer.
ericpromislow Nov 7, 2024
2e3a890
Fix the number of args in the mocked calls to creating the indices ta…
ericpromislow Nov 7, 2024
e555a6f
Split 'ListOptionIndexer.afterUpsert' into two funcs with more descri…
ericpromislow Nov 8, 2024
ef1acd2
Split the 'afterX' methods into two parts, with better names.
ericpromislow Nov 8, 2024
29f233f
Refactor the query-generator into its own method.
ericpromislow Nov 14, 2024
31a4b97
Split 'ListByOptions' into two functions to simplify testing.
ericpromislow Nov 15, 2024
95274c3
Add tests for converting other ops to sql stmts, fixing breakage.
ericpromislow Nov 15, 2024
b8a8223
Add more tests, fix NOT-EXISTS for labels.
ericpromislow Nov 15, 2024
3257e61
Code quality changes:
ericpromislow Nov 16, 2024
e908e62
Simplify the way the final queryInfo struct is built.
ericpromislow Dec 3, 2024
8501428
No need to map nil to an empty array when there's no count query.
ericpromislow Dec 3, 2024
8cb2364
Process comparisons in the query strings.
ericpromislow Dec 3, 2024
15adcaa
Allow exist-tests on filters only on labels.
ericpromislow Dec 4, 2024
141e062
Fix the labels-not-exists (and not-equals) sql expressions.
ericpromislow Dec 5, 2024
6ba8e35
Use 'LEFT OUTER JOIN' on label tables only where necessary.
ericpromislow Dec 5, 2024
914f797
LABEL <label> NOT IN (...) tests also succeed where the <label> isn't…
ericpromislow Dec 6, 2024
865f8f1
Add unit tests on multiple filters with only positive-tests on labels.
ericpromislow Dec 6, 2024
be36007
Always LEFT-OUTER-JOIN when testing labels.
ericpromislow Dec 9, 2024
cc814ce
Two simplifications when querying on labels:
ericpromislow Dec 9, 2024
d676381
Fix multiple filters involving label tests.
ericpromislow Jan 7, 2025
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
2 changes: 1 addition & 1 deletion pkg/cache/sql/db/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func (c *Client) ReadInt(rows Rows) (int, error) {
// If forWriting is true, this method blocks until all other concurrent forWriting
// transactions have either committed or rolled back.
// If forWriting is false, it is assumed the returned transaction will exclusively
// be used for DQL (eg. SELECT) queries.
// be used for DQL (e.g. SELECT) queries.
// Not respecting the above rule might result in transactions failing with unexpected
// SQLITE_BUSY (5) errors (aka "Runtime error: database is locked").
// See discussion in https://github.com/rancher/lasso/pull/98 for details
Expand Down
2 changes: 2 additions & 0 deletions pkg/cache/sql/db/transaction/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package transaction
import (
"context"
"database/sql"
"github.com/sirupsen/logrus"

"github.com/pkg/errors"
)
Expand Down Expand Up @@ -64,6 +65,7 @@ func (c *Client) Stmt(stmt *sql.Stmt) Stmt {
func (c *Client) StmtExec(stmt Stmt, args ...any) error {
_, err := stmt.Exec(args...)
if err != nil {
logrus.Debugf("StmtExec failed: query %s, args: %s, err: %s", stmt, args, err)
return c.rollback(c.sqlTx, err)
}
return nil
Expand Down
1 change: 1 addition & 0 deletions pkg/cache/sql/informer/factory/informer_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ var defaultEncryptedResourceTypes = map[schema.GroupVersionKind]struct{}{
}

// NewCacheFactory returns an informer factory instance
// This is currently called from steve via initial calls to `s.cacheFactory.CacheFor(...)`
func NewCacheFactory() (*CacheFactory, error) {
m, err := encryption.NewManager()
if err != nil {
Expand Down
9 changes: 5 additions & 4 deletions pkg/cache/sql/informer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,13 @@ func NewIndexer(indexers cache.Indexers, s Store) (*Indexer, error) {
if err != nil {
return nil, err
}
createTableQuery := fmt.Sprintf(createTableFmt, db.Sanitize(s.GetName()))
dbName := db.Sanitize(s.GetName())
createTableQuery := fmt.Sprintf(createTableFmt, dbName)
err = tx.Exec(createTableQuery)
if err != nil {
return nil, &db.QueryError{QueryString: createTableQuery, Err: err}
}
createIndexQuery := fmt.Sprintf(createIndexFmt, db.Sanitize(s.GetName()))
createIndexQuery := fmt.Sprintf(createIndexFmt, dbName)
err = tx.Exec(createIndexQuery)
if err != nil {
return nil, &db.QueryError{QueryString: createIndexQuery, Err: err}
Expand Down Expand Up @@ -135,7 +136,7 @@ func (i *Indexer) AfterUpsert(key string, obj any, tx db.TXClient) error {
return &db.QueryError{QueryString: i.deleteIndicesQuery, Err: err}
}

// re-insert all
// re-insert all values
i.indexersLock.RLock()
defer i.indexersLock.RUnlock()
for indexName, indexFunc := range i.indexers {
Expand Down Expand Up @@ -181,7 +182,7 @@ func (i *Indexer) Index(indexName string, obj any) ([]any, error) {

// atypical case - more than one value to lookup
// HACK: sql.Statement.Query does not allow to pass slices in as of go 1.19 - create an ad-hoc statement
query := fmt.Sprintf(fmt.Sprintf(selectQueryFmt, db.Sanitize(i.GetName()), strings.Repeat(", ?", len(values)-1)))
query := fmt.Sprintf(selectQueryFmt, db.Sanitize(i.GetName()), strings.Repeat(", ?", len(values)-1))
stmt := i.Prepare(query)
defer i.CloseStmt(stmt)
// HACK: Query will accept []any but not []string
Expand Down
2 changes: 1 addition & 1 deletion pkg/cache/sql/informer/informer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type ByOptionsLister interface {
ListByOptions(ctx context.Context, lo ListOptions, partitions []partition.Partition, namespace string) (*unstructured.UnstructuredList, int, string, error)
}

// this is set to a var so that it can be overriden by test code for mocking purposes
// this is set to a var so that it can be overridden by test code for mocking purposes
var newInformer = cache.NewSharedIndexInformer

// NewInformer returns a new SQLite-backed Informer for the type specified by schema in unstructured.Unstructured form
Expand Down
62 changes: 34 additions & 28 deletions pkg/cache/sql/informer/informer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,27 @@ func TestNewInformer(t *testing.T) {
// NewStore() from store package logic. This package is only concerned with whether it returns err or not as NewStore
// is tested in depth in its own package.
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(nil)
dbClient.EXPECT().Prepare(gomock.Any()).Return(&sql.Stmt{}).AnyTimes()

// NewIndexer() logic (within NewListOptionIndexer(). This test is only concerned with whether it returns err or not as NewIndexer
// is tested in depth in its own indexer_test.go
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(nil)

// NewListOptionIndexer() logic. This test is only concerned with whether it returns err or not as NewIndexer
// is tested in depth in its own indexer_test.go
dbClient.EXPECT().BeginTx(context.Background(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(nil)

informer, err := NewInformer(dynamicClient, fields, nil, gvk, dbClient, false, true)
Expand All @@ -78,7 +80,7 @@ func TestNewInformer(t *testing.T) {
// NewStore() from store package logic. This package is only concerned with whether it returns err or not as NewStore
// is tested in depth in its own package.
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(fmt.Errorf("error"))

_, err := NewInformer(dynamicClient, fields, nil, gvk, dbClient, false, true)
Expand All @@ -95,15 +97,15 @@ func TestNewInformer(t *testing.T) {
// NewStore() from store package logic. This package is only concerned with whether it returns err or not as NewStore
// is tested in depth in its own package.
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(nil)
dbClient.EXPECT().Prepare(gomock.Any()).Return(&sql.Stmt{}).AnyTimes()

// NewIndexer() logic (within NewListOptionIndexer(). This test is only concerned with whether it returns err or not as NewIndexer
// is tested in depth in its own indexer_test.go
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(fmt.Errorf("error"))

_, err := NewInformer(dynamicClient, fields, nil, gvk, dbClient, false, true)
Expand All @@ -120,25 +122,27 @@ func TestNewInformer(t *testing.T) {
// NewStore() from store package logic. This package is only concerned with whether it returns err or not as NewStore
// is tested in depth in its own package.
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(nil)
dbClient.EXPECT().Prepare(gomock.Any()).Return(&sql.Stmt{}).AnyTimes()

// NewIndexer() logic (within NewListOptionIndexer(). This test is only concerned with whether it returns err or not as NewIndexer
// is tested in depth in its own indexer_test.go
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(nil)

// NewListOptionIndexer() logic. This test is only concerned with whether it returns err or not as NewIndexer
// is tested in depth in its own indexer_test.go
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(fmt.Errorf("error"))

_, err := NewInformer(dynamicClient, fields, nil, gvk, dbClient, false, true)
Expand All @@ -163,25 +167,27 @@ func TestNewInformer(t *testing.T) {
// NewStore() from store package logic. This package is only concerned with whether it returns err or not as NewStore
// is tested in depth in its own package.
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(nil)
dbClient.EXPECT().Prepare(gomock.Any()).Return(&sql.Stmt{}).AnyTimes()

// NewIndexer() logic (within NewListOptionIndexer(). This test is only concerned with whether it returns err or not as NewIndexer
// is tested in depth in its own indexer_test.go
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(nil)

// NewListOptionIndexer() logic. This test is only concerned with whether it returns err or not as NewIndexer
// is tested in depth in its own indexer_test.go
dbClient.EXPECT().BeginTx(gomock.Any(), true).Return(txClient, nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Exec(gomock.Any()).Return(nil)
txClient.EXPECT().Commit().Return(nil)

transformFunc := func(input interface{}) (interface{}, error) {
Expand Down
Loading
Loading