1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-23 12:29:09 +00:00

Drop tables instead of removing the database completely (#807)

* Add drop tables

* Fix deadlock

* Use f.Stop in tests

* Rename dropAllStmtFmt to dropBaseStmtFmt

* Fix comment

* More dropAll->dropBase renaming
This commit is contained in:
Tom Lebreux
2025-09-09 15:52:02 -04:00
committed by GitHub
parent 7cff39371f
commit ca3fa10db5
13 changed files with 257 additions and 39 deletions

View File

@@ -148,7 +148,7 @@ func (f *CacheFactory) cacheForLocked(ctx context.Context, fields [][]string, ex
start := time.Now() start := time.Now()
log.Infof("CacheFor STARTS creating informer for %v", gvk) log.Infof("CacheFor STARTS creating informer for %v", gvk)
defer func() { defer func() {
log.Infof("CacheFor IS DONE creating informer for %v (took %v)", gvk, time.Now().Sub(start)) log.Infof("CacheFor IS DONE creating informer for %v (took %v)", gvk, time.Since(start))
}() }()
_, encryptResourceAlways := defaultEncryptedResourceTypes[gvk] _, encryptResourceAlways := defaultEncryptedResourceTypes[gvk]
@@ -201,25 +201,36 @@ func (f *CacheFactory) Stop() error {
return nil return nil
} }
// first of all wait until all CacheFor() calls that create new informers are finished. Also block any new ones // We must stop informers here to unblock those stuck in WaitForCacheSync
// which is blocking DoneWithCache call.
//
// This is fine without a lock as long as multiple Stop() call aren't made
// concurrently (which they currently aren't)
f.cancel()
// Prevent more CacheFor calls
f.mutex.Lock() f.mutex.Lock()
defer f.mutex.Unlock() defer f.mutex.Unlock()
// now that we are alone, stop all informers created until this point // Wait for all informers to have exited
f.cancel()
f.wg.Wait() f.wg.Wait()
f.ctx, f.cancel = context.WithCancel(context.Background()) f.ctx, f.cancel = context.WithCancel(context.Background())
// and get rid of all references to those informers and their mutexes // and get rid of all references to those informers and their mutexes
f.informersMutex.Lock() f.informersMutex.Lock()
defer f.informersMutex.Unlock() defer f.informersMutex.Unlock()
f.informers = make(map[schema.GroupVersionKind]*guardedInformer)
// finally, reset the DB connection for gvk, informer := range f.informers {
_, err := f.dbClient.NewConnection(false) // DropAll needs its own context because the context from the CacheFactory
if err != nil { // is canceled
return err err := informer.informer.DropAll(context.Background())
if err != nil {
return fmt.Errorf("dropall %q: %w", gvk, err)
}
} }
f.informers = make(map[schema.GroupVersionKind]*guardedInformer)
return nil return nil
} }

View File

@@ -67,6 +67,7 @@ func TestCacheFor(t *testing.T) {
expectedGVK := schema.GroupVersionKind{} expectedGVK := schema.GroupVersionKind{}
bloi := NewMockByOptionsLister(gomock.NewController(t)) bloi := NewMockByOptionsLister(gomock.NewController(t))
bloi.EXPECT().RunGC(gomock.Any()).AnyTimes() bloi.EXPECT().RunGC(gomock.Any()).AnyTimes()
bloi.EXPECT().DropAll(gomock.Any()).AnyTimes()
sii := NewMockSharedIndexInformer(gomock.NewController(t)) sii := NewMockSharedIndexInformer(gomock.NewController(t))
sii.EXPECT().HasSynced().Return(true).AnyTimes() sii.EXPECT().HasSynced().Return(true).AnyTimes()
sii.EXPECT().Run(gomock.Any()).MinTimes(1) sii.EXPECT().Run(gomock.Any()).MinTimes(1)
@@ -99,11 +100,9 @@ func TestCacheFor(t *testing.T) {
go func() { go func() {
// this function ensures that ctx is open for the duration of this test but if part of a longer process it will be closed eventually // this function ensures that ctx is open for the duration of this test but if part of a longer process it will be closed eventually
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
f.cancel() f.Stop()
}() }()
var c *Cache c, err := f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
var err error
c, err = f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, expectedC, c) assert.Equal(t, expectedC, c)
// this sleep is critical to the test. It ensure there has been enough time for expected function like Run to be invoked in their go routines. // this sleep is critical to the test. It ensure there has been enough time for expected function like Run to be invoked in their go routines.
@@ -120,6 +119,7 @@ func TestCacheFor(t *testing.T) {
bloi := NewMockByOptionsLister(gomock.NewController(t)) bloi := NewMockByOptionsLister(gomock.NewController(t))
bloi.EXPECT().RunGC(gomock.Any()).AnyTimes() bloi.EXPECT().RunGC(gomock.Any()).AnyTimes()
bloi.EXPECT().DropAll(gomock.Any()).AnyTimes()
sii := NewMockSharedIndexInformer(gomock.NewController(t)) sii := NewMockSharedIndexInformer(gomock.NewController(t))
sii.EXPECT().HasSynced().Return(false).AnyTimes() sii.EXPECT().HasSynced().Return(false).AnyTimes()
sii.EXPECT().Run(gomock.Any()) sii.EXPECT().Run(gomock.Any())
@@ -148,7 +148,7 @@ func TestCacheFor(t *testing.T) {
go func() { go func() {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
f.cancel() f.Stop()
}() }()
var err error var err error
_, err = f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true) _, err = f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
@@ -163,6 +163,7 @@ func TestCacheFor(t *testing.T) {
bloi := NewMockByOptionsLister(gomock.NewController(t)) bloi := NewMockByOptionsLister(gomock.NewController(t))
bloi.EXPECT().RunGC(gomock.Any()).AnyTimes() bloi.EXPECT().RunGC(gomock.Any()).AnyTimes()
bloi.EXPECT().DropAll(gomock.Any()).AnyTimes()
sii := NewMockSharedIndexInformer(gomock.NewController(t)) sii := NewMockSharedIndexInformer(gomock.NewController(t))
sii.EXPECT().HasSynced().Return(true).AnyTimes() sii.EXPECT().HasSynced().Return(true).AnyTimes()
// may or may not call run initially // may or may not call run initially
@@ -192,11 +193,9 @@ func TestCacheFor(t *testing.T) {
informers: map[schema.GroupVersionKind]*guardedInformer{}, informers: map[schema.GroupVersionKind]*guardedInformer{},
} }
f.ctx, f.cancel = context.WithCancel(context.Background()) f.ctx, f.cancel = context.WithCancel(context.Background())
f.cancel() f.Stop()
var c *Cache c, err := f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
var err error
c, err = f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, expectedC, c) assert.Equal(t, expectedC, c)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
@@ -208,6 +207,7 @@ func TestCacheFor(t *testing.T) {
expectedGVK := schema.GroupVersionKind{} expectedGVK := schema.GroupVersionKind{}
bloi := NewMockByOptionsLister(gomock.NewController(t)) bloi := NewMockByOptionsLister(gomock.NewController(t))
bloi.EXPECT().RunGC(gomock.Any()).AnyTimes() bloi.EXPECT().RunGC(gomock.Any()).AnyTimes()
bloi.EXPECT().DropAll(gomock.Any()).AnyTimes()
sii := NewMockSharedIndexInformer(gomock.NewController(t)) sii := NewMockSharedIndexInformer(gomock.NewController(t))
sii.EXPECT().HasSynced().Return(true) sii.EXPECT().HasSynced().Return(true)
sii.EXPECT().Run(gomock.Any()).MinTimes(1).AnyTimes() sii.EXPECT().Run(gomock.Any()).MinTimes(1).AnyTimes()
@@ -240,11 +240,9 @@ func TestCacheFor(t *testing.T) {
go func() { go func() {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
f.cancel() f.Stop()
}() }()
var c *Cache c, err := f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
var err error
c, err = f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, expectedC, c) assert.Equal(t, expectedC, c)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
@@ -261,6 +259,7 @@ func TestCacheFor(t *testing.T) {
} }
bloi := NewMockByOptionsLister(gomock.NewController(t)) bloi := NewMockByOptionsLister(gomock.NewController(t))
bloi.EXPECT().RunGC(gomock.Any()).AnyTimes() bloi.EXPECT().RunGC(gomock.Any()).AnyTimes()
bloi.EXPECT().DropAll(gomock.Any()).AnyTimes()
sii := NewMockSharedIndexInformer(gomock.NewController(t)) sii := NewMockSharedIndexInformer(gomock.NewController(t))
sii.EXPECT().HasSynced().Return(true) sii.EXPECT().HasSynced().Return(true)
sii.EXPECT().Run(gomock.Any()).MinTimes(1).AnyTimes() sii.EXPECT().Run(gomock.Any()).MinTimes(1).AnyTimes()
@@ -293,11 +292,9 @@ func TestCacheFor(t *testing.T) {
go func() { go func() {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
f.cancel() f.Stop()
}() }()
var c *Cache c, err := f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
var err error
c, err = f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, expectedC, c) assert.Equal(t, expectedC, c)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
@@ -313,6 +310,7 @@ func TestCacheFor(t *testing.T) {
} }
bloi := NewMockByOptionsLister(gomock.NewController(t)) bloi := NewMockByOptionsLister(gomock.NewController(t))
bloi.EXPECT().RunGC(gomock.Any()).AnyTimes() bloi.EXPECT().RunGC(gomock.Any()).AnyTimes()
bloi.EXPECT().DropAll(gomock.Any()).AnyTimes()
sii := NewMockSharedIndexInformer(gomock.NewController(t)) sii := NewMockSharedIndexInformer(gomock.NewController(t))
sii.EXPECT().HasSynced().Return(true) sii.EXPECT().HasSynced().Return(true)
sii.EXPECT().Run(gomock.Any()).MinTimes(1).AnyTimes() sii.EXPECT().Run(gomock.Any()).MinTimes(1).AnyTimes()
@@ -345,11 +343,9 @@ func TestCacheFor(t *testing.T) {
go func() { go func() {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
f.cancel() f.Stop()
}() }()
var c *Cache c, err := f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
var err error
c, err = f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, expectedC, c) assert.Equal(t, expectedC, c)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
@@ -362,6 +358,7 @@ func TestCacheFor(t *testing.T) {
expectedGVK := schema.GroupVersionKind{} expectedGVK := schema.GroupVersionKind{}
bloi := NewMockByOptionsLister(gomock.NewController(t)) bloi := NewMockByOptionsLister(gomock.NewController(t))
bloi.EXPECT().RunGC(gomock.Any()).AnyTimes() bloi.EXPECT().RunGC(gomock.Any()).AnyTimes()
bloi.EXPECT().DropAll(gomock.Any()).AnyTimes()
sii := NewMockSharedIndexInformer(gomock.NewController(t)) sii := NewMockSharedIndexInformer(gomock.NewController(t))
sii.EXPECT().HasSynced().Return(true) sii.EXPECT().HasSynced().Return(true)
sii.EXPECT().Run(gomock.Any()).MinTimes(1) sii.EXPECT().Run(gomock.Any()).MinTimes(1)
@@ -405,7 +402,7 @@ func TestCacheFor(t *testing.T) {
go func() { go func() {
// this function ensures that ctx is not canceled for the duration of this test but if part of a longer process it will be closed eventually // this function ensures that ctx is not canceled for the duration of this test but if part of a longer process it will be closed eventually
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
f.cancel() f.Stop()
}() }()
var c *Cache var c *Cache
var err error var err error
@@ -421,6 +418,7 @@ func TestCacheFor(t *testing.T) {
expectedGVK := schema.GroupVersionKind{} expectedGVK := schema.GroupVersionKind{}
bloi := NewMockByOptionsLister(gomock.NewController(t)) bloi := NewMockByOptionsLister(gomock.NewController(t))
bloi.EXPECT().RunGC(gomock.Any()).AnyTimes() bloi.EXPECT().RunGC(gomock.Any()).AnyTimes()
bloi.EXPECT().DropAll(gomock.Any()).AnyTimes()
sii := NewMockSharedIndexInformer(gomock.NewController(t)) sii := NewMockSharedIndexInformer(gomock.NewController(t))
sii.EXPECT().HasSynced().Return(true) sii.EXPECT().HasSynced().Return(true)
sii.EXPECT().Run(gomock.Any()).MinTimes(1).AnyTimes() sii.EXPECT().Run(gomock.Any()).MinTimes(1).AnyTimes()
@@ -456,12 +454,10 @@ func TestCacheFor(t *testing.T) {
go func() { go func() {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
f.cancel() f.Stop()
}() }()
var c *Cache
var err error
// CacheFor(ctx context.Context, fields [][]string, externalUpdateInfo *sqltypes.ExternalGVKUpdates, transform cache.TransformFunc, client dynamic.ResourceInterface, gvk schema.GroupVersionKind, namespaced bool, watchable bool) // CacheFor(ctx context.Context, fields [][]string, externalUpdateInfo *sqltypes.ExternalGVKUpdates, transform cache.TransformFunc, client dynamic.ResourceInterface, gvk schema.GroupVersionKind, namespaced bool, watchable bool)
c, err = f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true) c, err := f.CacheFor(context.Background(), fields, nil, nil, nil, dynamicClient, expectedGVK, false, true)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, expectedC, c) assert.Equal(t, expectedC, c)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)

View File

@@ -45,6 +45,20 @@ func (m *MockByOptionsLister) EXPECT() *MockByOptionsListerMockRecorder {
return m.recorder return m.recorder
} }
// DropAll mocks base method.
func (m *MockByOptionsLister) DropAll(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DropAll", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DropAll indicates an expected call of DropAll.
func (mr *MockByOptionsListerMockRecorder) DropAll(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropAll", reflect.TypeOf((*MockByOptionsLister)(nil).DropAll), arg0)
}
// GetLatestResourceVersion mocks base method. // GetLatestResourceVersion mocks base method.
func (m *MockByOptionsLister) GetLatestResourceVersion() []string { func (m *MockByOptionsLister) GetLatestResourceVersion() []string {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@@ -31,6 +31,7 @@ const (
createIndexFmt = `CREATE INDEX IF NOT EXISTS "%[1]s_indices_index" ON "%[1]s_indices"(name, value)` createIndexFmt = `CREATE INDEX IF NOT EXISTS "%[1]s_indices_index" ON "%[1]s_indices"(name, value)`
deleteIndicesFmt = `DELETE FROM "%s_indices" WHERE key = ?` deleteIndicesFmt = `DELETE FROM "%s_indices" WHERE key = ?`
dropIndicesFmt = `DROP TABLE IF EXISTS "%s_indices"`
addIndexFmt = `INSERT INTO "%s_indices" (name, value, key) VALUES (?, ?, ?) ON CONFLICT DO NOTHING` addIndexFmt = `INSERT INTO "%s_indices" (name, value, key) VALUES (?, ?, ?) ON CONFLICT DO NOTHING`
listByIndexFmt = `SELECT object, objectnonce, dekid FROM "%[1]s" listByIndexFmt = `SELECT object, objectnonce, dekid FROM "%[1]s"
WHERE key IN ( WHERE key IN (
@@ -50,12 +51,14 @@ type Indexer struct {
indexersLock sync.RWMutex indexersLock sync.RWMutex
deleteIndicesQuery string deleteIndicesQuery string
dropIndicesQuery string
addIndexQuery string addIndexQuery string
listByIndexQuery string listByIndexQuery string
listKeysByIndexQuery string listKeysByIndexQuery string
listIndexValuesQuery string listIndexValuesQuery string
deleteIndicesStmt *sql.Stmt deleteIndicesStmt *sql.Stmt
dropIndicesStmt *sql.Stmt
addIndexStmt *sql.Stmt addIndexStmt *sql.Stmt
listByIndexStmt *sql.Stmt listByIndexStmt *sql.Stmt
listKeysByIndexStmt *sql.Stmt listKeysByIndexStmt *sql.Stmt
@@ -74,8 +77,10 @@ type Store interface {
RegisterAfterUpdate(f func(key string, obj any, tx transaction.Client) error) RegisterAfterUpdate(f func(key string, obj any, tx transaction.Client) error)
RegisterAfterDelete(f func(key string, obj any, tx transaction.Client) error) RegisterAfterDelete(f func(key string, obj any, tx transaction.Client) error)
RegisterAfterDeleteAll(f func(tx transaction.Client) error) RegisterAfterDeleteAll(f func(tx transaction.Client) error)
RegisterBeforeDropAll(f func(tx transaction.Client) error)
GetShouldEncrypt() bool GetShouldEncrypt() bool
GetType() reflect.Type GetType() reflect.Type
DropAll(ctx context.Context) error
} }
// NewIndexer returns a cache.Indexer backed by SQLite for objects of the given example type // NewIndexer returns a cache.Indexer backed by SQLite for objects of the given example type
@@ -106,14 +111,17 @@ func NewIndexer(ctx context.Context, indexers cache.Indexers, s Store) (*Indexer
} }
i.RegisterAfterAdd(i.AfterUpsert) i.RegisterAfterAdd(i.AfterUpsert)
i.RegisterAfterUpdate(i.AfterUpsert) i.RegisterAfterUpdate(i.AfterUpsert)
i.RegisterBeforeDropAll(i.dropIndices)
i.deleteIndicesQuery = fmt.Sprintf(deleteIndicesFmt, db.Sanitize(s.GetName())) i.deleteIndicesQuery = fmt.Sprintf(deleteIndicesFmt, db.Sanitize(s.GetName()))
i.dropIndicesQuery = fmt.Sprintf(dropIndicesFmt, db.Sanitize(s.GetName()))
i.addIndexQuery = fmt.Sprintf(addIndexFmt, db.Sanitize(s.GetName())) i.addIndexQuery = fmt.Sprintf(addIndexFmt, db.Sanitize(s.GetName()))
i.listByIndexQuery = fmt.Sprintf(listByIndexFmt, db.Sanitize(s.GetName())) i.listByIndexQuery = fmt.Sprintf(listByIndexFmt, db.Sanitize(s.GetName()))
i.listKeysByIndexQuery = fmt.Sprintf(listKeyByIndexFmt, db.Sanitize(s.GetName())) i.listKeysByIndexQuery = fmt.Sprintf(listKeyByIndexFmt, db.Sanitize(s.GetName()))
i.listIndexValuesQuery = fmt.Sprintf(listIndexValuesFmt, db.Sanitize(s.GetName())) i.listIndexValuesQuery = fmt.Sprintf(listIndexValuesFmt, db.Sanitize(s.GetName()))
i.deleteIndicesStmt = s.Prepare(i.deleteIndicesQuery) i.deleteIndicesStmt = s.Prepare(i.deleteIndicesQuery)
i.dropIndicesStmt = s.Prepare(i.dropIndicesQuery)
i.addIndexStmt = s.Prepare(i.addIndexQuery) i.addIndexStmt = s.Prepare(i.addIndexQuery)
i.listByIndexStmt = s.Prepare(i.listByIndexQuery) i.listByIndexStmt = s.Prepare(i.listByIndexQuery)
i.listKeysByIndexStmt = s.Prepare(i.listKeysByIndexQuery) i.listKeysByIndexStmt = s.Prepare(i.listKeysByIndexQuery)
@@ -200,6 +208,14 @@ func (i *Indexer) Index(indexName string, obj any) (result []any, err error) {
return i.ReadObjects(rows, i.GetType(), i.GetShouldEncrypt()) return i.ReadObjects(rows, i.GetType(), i.GetShouldEncrypt())
} }
func (i *Indexer) dropIndices(tx transaction.Client) error {
_, err := tx.Stmt(i.dropIndicesStmt).Exec()
if err != nil {
return &db.QueryError{QueryString: i.dropIndicesQuery, Err: err}
}
return nil
}
// ByIndex returns the stored objects whose set of indexed values // ByIndex returns the stored objects whose set of indexed values
// for the named index includes the given indexed value // for the named index includes the given indexed value
func (i *Indexer) ByIndex(indexName, indexedValue string) ([]any, error) { func (i *Indexer) ByIndex(indexName, indexedValue string) ([]any, error) {

View File

@@ -60,7 +60,9 @@ func TestNewIndexer(t *testing.T) {
}) })
store.EXPECT().RegisterAfterAdd(gomock.Any()) store.EXPECT().RegisterAfterAdd(gomock.Any())
store.EXPECT().RegisterAfterUpdate(gomock.Any()) store.EXPECT().RegisterAfterUpdate(gomock.Any())
store.EXPECT().RegisterBeforeDropAll(gomock.Any())
store.EXPECT().Prepare(fmt.Sprintf(deleteIndicesFmt, storeName)) store.EXPECT().Prepare(fmt.Sprintf(deleteIndicesFmt, storeName))
store.EXPECT().Prepare(fmt.Sprintf(dropIndicesFmt, storeName))
store.EXPECT().Prepare(fmt.Sprintf(addIndexFmt, storeName)) store.EXPECT().Prepare(fmt.Sprintf(addIndexFmt, storeName))
store.EXPECT().Prepare(fmt.Sprintf(listByIndexFmt, storeName, storeName)) store.EXPECT().Prepare(fmt.Sprintf(listByIndexFmt, storeName, storeName))
store.EXPECT().Prepare(fmt.Sprintf(listKeyByIndexFmt, storeName)) store.EXPECT().Prepare(fmt.Sprintf(listKeyByIndexFmt, storeName))

View File

@@ -51,6 +51,7 @@ type ByOptionsLister interface {
Watch(ctx context.Context, options WatchOptions, eventsCh chan<- watch.Event) error Watch(ctx context.Context, options WatchOptions, eventsCh chan<- watch.Event) error
GetLatestResourceVersion() []string GetLatestResourceVersion() []string
RunGC(context.Context) RunGC(context.Context)
DropAll(context.Context) error
} }
// this is set to a var so that it can be overridden by test code for mocking purposes // this is set to a var so that it can be overridden by test code for mocking purposes

View File

@@ -44,6 +44,20 @@ func (m *MockByOptionsLister) EXPECT() *MockByOptionsListerMockRecorder {
return m.recorder return m.recorder
} }
// DropAll mocks base method.
func (m *MockByOptionsLister) DropAll(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DropAll", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DropAll indicates an expected call of DropAll.
func (mr *MockByOptionsListerMockRecorder) DropAll(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropAll", reflect.TypeOf((*MockByOptionsLister)(nil).DropAll), arg0)
}
// GetLatestResourceVersion mocks base method. // GetLatestResourceVersion mocks base method.
func (m *MockByOptionsLister) GetLatestResourceVersion() []string { func (m *MockByOptionsLister) GetLatestResourceVersion() []string {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@@ -53,23 +53,29 @@ type ListOptionIndexer struct {
findEventsRowByRVQuery string findEventsRowByRVQuery string
listEventsAfterQuery string listEventsAfterQuery string
deleteEventsByCountQuery string deleteEventsByCountQuery string
dropEventsQuery string
addFieldsQuery string addFieldsQuery string
deleteFieldsByKeyQuery string deleteFieldsByKeyQuery string
deleteFieldsQuery string deleteFieldsQuery string
dropFieldsQuery string
upsertLabelsQuery string upsertLabelsQuery string
deleteLabelsByKeyQuery string deleteLabelsByKeyQuery string
deleteLabelsQuery string deleteLabelsQuery string
dropLabelsQuery string
upsertEventsStmt *sql.Stmt upsertEventsStmt *sql.Stmt
findEventsRowByRVStmt *sql.Stmt findEventsRowByRVStmt *sql.Stmt
listEventsAfterStmt *sql.Stmt listEventsAfterStmt *sql.Stmt
deleteEventsByCountStmt *sql.Stmt deleteEventsByCountStmt *sql.Stmt
dropEventsStmt *sql.Stmt
addFieldsStmt *sql.Stmt addFieldsStmt *sql.Stmt
deleteFieldsByKeyStmt *sql.Stmt deleteFieldsByKeyStmt *sql.Stmt
deleteFieldsStmt *sql.Stmt deleteFieldsStmt *sql.Stmt
dropFieldsStmt *sql.Stmt
upsertLabelsStmt *sql.Stmt upsertLabelsStmt *sql.Stmt
deleteLabelsByKeyStmt *sql.Stmt deleteLabelsByKeyStmt *sql.Stmt
deleteLabelsStmt *sql.Stmt deleteLabelsStmt *sql.Stmt
dropLabelsStmt *sql.Stmt
} }
var ( var (
@@ -112,6 +118,7 @@ const (
SELECT rowid FROM "%s_events" ORDER BY rowid DESC LIMIT ? SELECT rowid FROM "%s_events" ORDER BY rowid DESC LIMIT ?
) q ) q
)` )`
dropEventsFmt = `DROP TABLE IF EXISTS "%s_events"`
createFieldsTableFmt = `CREATE TABLE "%s_fields" ( createFieldsTableFmt = `CREATE TABLE "%s_fields" (
key TEXT NOT NULL PRIMARY KEY, key TEXT NOT NULL PRIMARY KEY,
@@ -119,6 +126,7 @@ const (
)` )`
createFieldsIndexFmt = `CREATE INDEX "%s_%s_index" ON "%s_fields"("%s")` createFieldsIndexFmt = `CREATE INDEX "%s_%s_index" ON "%s_fields"("%s")`
deleteFieldsFmt = `DELETE FROM "%s_fields"` deleteFieldsFmt = `DELETE FROM "%s_fields"`
dropFieldsFmt = `DROP TABLE IF EXISTS "%s_fields"`
failedToGetFromSliceFmt = "[listoption indexer] failed to get subfield [%s] from slice items" failedToGetFromSliceFmt = "[listoption indexer] failed to get subfield [%s] from slice items"
@@ -133,6 +141,7 @@ const (
upsertLabelsStmtFmt = `REPLACE INTO "%s_labels"(key, label, value) VALUES (?, ?, ?)` upsertLabelsStmtFmt = `REPLACE INTO "%s_labels"(key, label, value) VALUES (?, ?, ?)`
deleteLabelsByKeyStmtFmt = `DELETE FROM "%s_labels" WHERE KEY = ?` deleteLabelsByKeyStmtFmt = `DELETE FROM "%s_labels" WHERE KEY = ?`
deleteLabelsStmtFmt = `DELETE FROM "%s_labels"` deleteLabelsStmtFmt = `DELETE FROM "%s_labels"`
dropLabelsStmtFmt = `DROP TABLE IF EXISTS "%s_labels"`
) )
type ListOptionIndexerOptions struct { type ListOptionIndexerOptions struct {
@@ -190,6 +199,9 @@ func NewListOptionIndexer(ctx context.Context, s Store, opts ListOptionIndexerOp
l.RegisterAfterDelete(l.notifyEventDeleted) l.RegisterAfterDelete(l.notifyEventDeleted)
l.RegisterAfterDeleteAll(l.deleteFields) l.RegisterAfterDeleteAll(l.deleteFields)
l.RegisterAfterDeleteAll(l.deleteLabels) l.RegisterAfterDeleteAll(l.deleteLabels)
l.RegisterBeforeDropAll(l.dropEvents)
l.RegisterBeforeDropAll(l.dropLabels)
l.RegisterBeforeDropAll(l.dropFields)
columnDefs := make([]string, len(indexedFields)) columnDefs := make([]string, len(indexedFields))
for index, field := range indexedFields { for index, field := range indexedFields {
column := fmt.Sprintf(`"%s" TEXT`, field) column := fmt.Sprintf(`"%s" TEXT`, field)
@@ -266,6 +278,9 @@ func NewListOptionIndexer(ctx context.Context, s Store, opts ListOptionIndexerOp
l.deleteEventsByCountQuery = fmt.Sprintf(deleteEventsByCountFmt, dbName, dbName) l.deleteEventsByCountQuery = fmt.Sprintf(deleteEventsByCountFmt, dbName, dbName)
l.deleteEventsByCountStmt = l.Prepare(l.deleteEventsByCountQuery) l.deleteEventsByCountStmt = l.Prepare(l.deleteEventsByCountQuery)
l.dropEventsQuery = fmt.Sprintf(dropEventsFmt, dbName)
l.dropEventsStmt = l.Prepare(l.dropEventsQuery)
l.addFieldsQuery = fmt.Sprintf( l.addFieldsQuery = fmt.Sprintf(
`INSERT INTO "%s_fields"(key, %s) VALUES (?, %s) ON CONFLICT DO UPDATE SET %s`, `INSERT INTO "%s_fields"(key, %s) VALUES (?, %s) ON CONFLICT DO UPDATE SET %s`,
dbName, dbName,
@@ -275,17 +290,21 @@ func NewListOptionIndexer(ctx context.Context, s Store, opts ListOptionIndexerOp
) )
l.deleteFieldsByKeyQuery = fmt.Sprintf(`DELETE FROM "%s_fields" WHERE key = ?`, dbName) l.deleteFieldsByKeyQuery = fmt.Sprintf(`DELETE FROM "%s_fields" WHERE key = ?`, dbName)
l.deleteFieldsQuery = fmt.Sprintf(deleteFieldsFmt, dbName) l.deleteFieldsQuery = fmt.Sprintf(deleteFieldsFmt, dbName)
l.dropFieldsQuery = fmt.Sprintf(dropFieldsFmt, dbName)
l.addFieldsStmt = l.Prepare(l.addFieldsQuery) l.addFieldsStmt = l.Prepare(l.addFieldsQuery)
l.deleteFieldsByKeyStmt = l.Prepare(l.deleteFieldsByKeyQuery) l.deleteFieldsByKeyStmt = l.Prepare(l.deleteFieldsByKeyQuery)
l.deleteFieldsStmt = l.Prepare(l.deleteFieldsQuery) l.deleteFieldsStmt = l.Prepare(l.deleteFieldsQuery)
l.dropFieldsStmt = l.Prepare(l.dropFieldsQuery)
l.upsertLabelsQuery = fmt.Sprintf(upsertLabelsStmtFmt, dbName) l.upsertLabelsQuery = fmt.Sprintf(upsertLabelsStmtFmt, dbName)
l.deleteLabelsByKeyQuery = fmt.Sprintf(deleteLabelsByKeyStmtFmt, dbName) l.deleteLabelsByKeyQuery = fmt.Sprintf(deleteLabelsByKeyStmtFmt, dbName)
l.deleteLabelsQuery = fmt.Sprintf(deleteLabelsStmtFmt, dbName) l.deleteLabelsQuery = fmt.Sprintf(deleteLabelsStmtFmt, dbName)
l.dropLabelsQuery = fmt.Sprintf(dropLabelsStmtFmt, dbName)
l.upsertLabelsStmt = l.Prepare(l.upsertLabelsQuery) l.upsertLabelsStmt = l.Prepare(l.upsertLabelsQuery)
l.deleteLabelsByKeyStmt = l.Prepare(l.deleteLabelsByKeyQuery) l.deleteLabelsByKeyStmt = l.Prepare(l.deleteLabelsByKeyQuery)
l.deleteLabelsStmt = l.Prepare(l.deleteLabelsQuery) l.deleteLabelsStmt = l.Prepare(l.deleteLabelsQuery)
l.dropLabelsStmt = l.Prepare(l.dropLabelsQuery)
l.gcInterval = opts.GCInterval l.gcInterval = opts.GCInterval
l.gcKeepCount = opts.GCKeepCount l.gcKeepCount = opts.GCKeepCount
@@ -533,6 +552,14 @@ func (l *ListOptionIndexer) upsertEvent(tx transaction.Client, eventType watch.E
return err return err
} }
func (l *ListOptionIndexer) dropEvents(tx transaction.Client) error {
_, err := tx.Stmt(l.dropEventsStmt).Exec()
if err != nil {
return &db.QueryError{QueryString: l.dropEventsQuery, Err: err}
}
return nil
}
// addIndexFields saves sortable/filterable fields into tables // addIndexFields saves sortable/filterable fields into tables
func (l *ListOptionIndexer) addIndexFields(key string, obj any, tx transaction.Client) error { func (l *ListOptionIndexer) addIndexFields(key string, obj any, tx transaction.Client) error {
args := []any{key} args := []any{key}
@@ -596,6 +623,14 @@ func (l *ListOptionIndexer) deleteFields(tx transaction.Client) error {
return nil return nil
} }
func (l *ListOptionIndexer) dropFields(tx transaction.Client) error {
_, err := tx.Stmt(l.dropFieldsStmt).Exec()
if err != nil {
return &db.QueryError{QueryString: l.dropFieldsQuery, Err: err}
}
return nil
}
func (l *ListOptionIndexer) deleteLabelsByKey(key string, _ any, tx transaction.Client) error { func (l *ListOptionIndexer) deleteLabelsByKey(key string, _ any, tx transaction.Client) error {
_, err := tx.Stmt(l.deleteLabelsByKeyStmt).Exec(key) _, err := tx.Stmt(l.deleteLabelsByKeyStmt).Exec(key)
if err != nil { if err != nil {
@@ -612,6 +647,14 @@ func (l *ListOptionIndexer) deleteLabels(tx transaction.Client) error {
return nil return nil
} }
func (l *ListOptionIndexer) dropLabels(tx transaction.Client) error {
_, err := tx.Stmt(l.dropLabelsStmt).Exec()
if err != nil {
return &db.QueryError{QueryString: l.dropLabelsQuery, Err: err}
}
return nil
}
// ListByOptions returns objects according to the specified list options and partitions. // ListByOptions returns objects according to the specified list options and partitions.
// Specifically: // Specifically:
// - an unstructured list of resources belonging to any of the specified partitions // - an unstructured list of resources belonging to any of the specified partitions

View File

@@ -65,10 +65,12 @@ func makeListOptionIndexer(ctx context.Context, opts ListOptionIndexerOptions, s
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
for _, item := range nsList.Items { if nsList != nil {
err = listOptionIndexer.Add(&item) for _, item := range nsList.Items {
if err != nil { err = listOptionIndexer.Add(&item)
return nil, "", err if err != nil {
return nil, "", err
}
} }
} }
@@ -144,6 +146,7 @@ func TestNewListOptionIndexer(t *testing.T) {
store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3) store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3) store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2) store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2)
store.EXPECT().RegisterBeforeDropAll(gomock.Any()).AnyTimes()
// create events table // create events table
txClient.EXPECT().Exec(fmt.Sprintf(createEventsTableFmt, id)).Return(nil, nil) txClient.EXPECT().Exec(fmt.Sprintf(createEventsTableFmt, id)).Return(nil, nil)
@@ -221,6 +224,7 @@ func TestNewListOptionIndexer(t *testing.T) {
store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3) store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3) store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2) store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2)
store.EXPECT().RegisterBeforeDropAll(gomock.Any()).AnyTimes()
store.EXPECT().WithTransaction(gomock.Any(), true, gomock.Any()).Return(fmt.Errorf("error")) store.EXPECT().WithTransaction(gomock.Any(), true, gomock.Any()).Return(fmt.Errorf("error"))
@@ -256,6 +260,7 @@ func TestNewListOptionIndexer(t *testing.T) {
store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3) store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3) store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2) store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2)
store.EXPECT().RegisterBeforeDropAll(gomock.Any()).AnyTimes()
txClient.EXPECT().Exec(fmt.Sprintf(createEventsTableFmt, id)).Return(nil, nil) txClient.EXPECT().Exec(fmt.Sprintf(createEventsTableFmt, id)).Return(nil, nil)
txClient.EXPECT().Exec(fmt.Sprintf(createFieldsTableFmt, id, `"metadata.name" TEXT, "metadata.creationTimestamp" TEXT, "metadata.namespace" TEXT, "something" TEXT`)).Return(nil, nil) txClient.EXPECT().Exec(fmt.Sprintf(createFieldsTableFmt, id, `"metadata.name" TEXT, "metadata.creationTimestamp" TEXT, "metadata.namespace" TEXT, "something" TEXT`)).Return(nil, nil)
@@ -301,6 +306,7 @@ func TestNewListOptionIndexer(t *testing.T) {
store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3) store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3) store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2) store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2)
store.EXPECT().RegisterBeforeDropAll(gomock.Any()).AnyTimes()
txClient.EXPECT().Exec(fmt.Sprintf(createEventsTableFmt, id)).Return(nil, nil) txClient.EXPECT().Exec(fmt.Sprintf(createEventsTableFmt, id)).Return(nil, nil)
txClient.EXPECT().Exec(fmt.Sprintf(createFieldsTableFmt, id, `"metadata.name" TEXT, "metadata.creationTimestamp" TEXT, "metadata.namespace" TEXT, "something" TEXT`)).Return(nil, nil) txClient.EXPECT().Exec(fmt.Sprintf(createFieldsTableFmt, id, `"metadata.name" TEXT, "metadata.creationTimestamp" TEXT, "metadata.namespace" TEXT, "something" TEXT`)).Return(nil, nil)
@@ -350,6 +356,7 @@ func TestNewListOptionIndexer(t *testing.T) {
store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3) store.EXPECT().RegisterAfterUpdate(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3) store.EXPECT().RegisterAfterDelete(gomock.Any()).Times(3)
store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2) store.EXPECT().RegisterAfterDeleteAll(gomock.Any()).Times(2)
store.EXPECT().RegisterBeforeDropAll(gomock.Any()).AnyTimes()
txClient.EXPECT().Exec(fmt.Sprintf(createEventsTableFmt, id)).Return(nil, nil) txClient.EXPECT().Exec(fmt.Sprintf(createEventsTableFmt, id)).Return(nil, nil)
txClient.EXPECT().Exec(fmt.Sprintf(createFieldsTableFmt, id, `"metadata.name" TEXT, "metadata.creationTimestamp" TEXT, "metadata.namespace" TEXT, "something" TEXT`)).Return(nil, nil) txClient.EXPECT().Exec(fmt.Sprintf(createFieldsTableFmt, id, `"metadata.name" TEXT, "metadata.creationTimestamp" TEXT, "metadata.namespace" TEXT, "something" TEXT`)).Return(nil, nil)
@@ -1223,6 +1230,35 @@ func TestNewListOptionIndexerEasy(t *testing.T) {
} }
} }
func TestDropAll(t *testing.T) {
ctx := t.Context()
opts := ListOptionIndexerOptions{
IsNamespaced: true,
}
loi, dbPath, err := makeListOptionIndexer(ctx, opts, false, nil)
defer cleanTempFiles(dbPath)
assert.NoError(t, err)
obj1 := &unstructured.Unstructured{
Object: map[string]any{
"metadata": map[string]any{
"name": "obj1",
},
},
}
err = loi.Add(obj1)
assert.NoError(t, err)
_, _, _, err = loi.ListByOptions(ctx, &sqltypes.ListOptions{}, []partition.Partition{{All: true}}, "")
assert.NoError(t, err)
loi.DropAll(ctx)
_, _, _, err = loi.ListByOptions(ctx, &sqltypes.ListOptions{}, []partition.Partition{{All: true}}, "")
assert.Error(t, err)
}
func makePseudoRandomList(size int) *unstructured.UnstructuredList { func makePseudoRandomList(size int) *unstructured.UnstructuredList {
numLength := 1 + int(math.Floor(math.Log10(float64(size)))) numLength := 1 + int(math.Floor(math.Log10(float64(size))))
name_template := fmt.Sprintf("n%%0%dd", numLength) name_template := fmt.Sprintf("n%%0%dd", numLength)

View File

@@ -99,6 +99,20 @@ func (mr *MockStoreMockRecorder) Delete(obj any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockStore)(nil).Delete), obj) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockStore)(nil).Delete), obj)
} }
// DropAll mocks base method.
func (m *MockStore) DropAll(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DropAll", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// DropAll indicates an expected call of DropAll.
func (mr *MockStoreMockRecorder) DropAll(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropAll", reflect.TypeOf((*MockStore)(nil).DropAll), ctx)
}
// Encryptor mocks base method. // Encryptor mocks base method.
func (m *MockStore) Encryptor() db.Encryptor { func (m *MockStore) Encryptor() db.Encryptor {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@@ -372,6 +386,18 @@ func (mr *MockStoreMockRecorder) RegisterAfterUpdate(f any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterAfterUpdate", reflect.TypeOf((*MockStore)(nil).RegisterAfterUpdate), f) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterAfterUpdate", reflect.TypeOf((*MockStore)(nil).RegisterAfterUpdate), f)
} }
// RegisterBeforeDropAll mocks base method.
func (m *MockStore) RegisterBeforeDropAll(f func(transaction.Client) error) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "RegisterBeforeDropAll", f)
}
// RegisterBeforeDropAll indicates an expected call of RegisterBeforeDropAll.
func (mr *MockStoreMockRecorder) RegisterBeforeDropAll(f any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterBeforeDropAll", reflect.TypeOf((*MockStore)(nil).RegisterBeforeDropAll), f)
}
// Replace mocks base method. // Replace mocks base method.
func (m *MockStore) Replace(arg0 []any, arg1 string) error { func (m *MockStore) Replace(arg0 []any, arg1 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@@ -26,6 +26,7 @@ const (
upsertStmtFmt = `REPLACE INTO "%s"(key, object, objectnonce, dekid) VALUES (?, ?, ?, ?)` upsertStmtFmt = `REPLACE INTO "%s"(key, object, objectnonce, dekid) VALUES (?, ?, ?, ?)`
deleteStmtFmt = `DELETE FROM "%s" WHERE key = ?` deleteStmtFmt = `DELETE FROM "%s" WHERE key = ?`
deleteAllStmtFmt = `DELETE FROM "%s"` deleteAllStmtFmt = `DELETE FROM "%s"`
dropBaseStmtFmt = `DROP TABLE IF EXISTS "%s"`
getStmtFmt = `SELECT object, objectnonce, dekid FROM "%s" WHERE key = ?` getStmtFmt = `SELECT object, objectnonce, dekid FROM "%s" WHERE key = ?`
listStmtFmt = `SELECT object, objectnonce, dekid FROM "%s"` listStmtFmt = `SELECT object, objectnonce, dekid FROM "%s"`
listKeysStmtFmt = `SELECT key FROM "%s"` listKeysStmtFmt = `SELECT key FROM "%s"`
@@ -56,10 +57,12 @@ type Store struct {
getQuery string getQuery string
listQuery string listQuery string
listKeysQuery string listKeysQuery string
dropBaseQuery string
upsertStmt *sql.Stmt upsertStmt *sql.Stmt
deleteStmt *sql.Stmt deleteStmt *sql.Stmt
deleteAllStmt *sql.Stmt deleteAllStmt *sql.Stmt
dropBaseStmt *sql.Stmt
getStmt *sql.Stmt getStmt *sql.Stmt
listStmt *sql.Stmt listStmt *sql.Stmt
listKeysStmt *sql.Stmt listKeysStmt *sql.Stmt
@@ -68,6 +71,7 @@ type Store struct {
afterUpdate []func(key string, obj any, tx transaction.Client) error afterUpdate []func(key string, obj any, tx transaction.Client) error
afterDelete []func(key string, obj any, tx transaction.Client) error afterDelete []func(key string, obj any, tx transaction.Client) error
afterDeleteAll []func(tx transaction.Client) error afterDeleteAll []func(tx transaction.Client) error
beforeDropAll []func(tx transaction.Client) error
} }
// Test that Store implements cache.Indexer // Test that Store implements cache.Indexer
@@ -110,6 +114,7 @@ func NewStore(ctx context.Context, example any, keyFunc cache.KeyFunc, c db.Clie
s.upsertQuery = fmt.Sprintf(upsertStmtFmt, dbName) s.upsertQuery = fmt.Sprintf(upsertStmtFmt, dbName)
s.deleteQuery = fmt.Sprintf(deleteStmtFmt, dbName) s.deleteQuery = fmt.Sprintf(deleteStmtFmt, dbName)
s.deleteAllQuery = fmt.Sprintf(deleteAllStmtFmt, dbName) s.deleteAllQuery = fmt.Sprintf(deleteAllStmtFmt, dbName)
s.dropBaseQuery = fmt.Sprintf(dropBaseStmtFmt, dbName)
s.getQuery = fmt.Sprintf(getStmtFmt, dbName) s.getQuery = fmt.Sprintf(getStmtFmt, dbName)
s.listQuery = fmt.Sprintf(listStmtFmt, dbName) s.listQuery = fmt.Sprintf(listStmtFmt, dbName)
s.listKeysQuery = fmt.Sprintf(listKeysStmtFmt, dbName) s.listKeysQuery = fmt.Sprintf(listKeysStmtFmt, dbName)
@@ -117,6 +122,7 @@ func NewStore(ctx context.Context, example any, keyFunc cache.KeyFunc, c db.Clie
s.upsertStmt = s.Prepare(s.upsertQuery) s.upsertStmt = s.Prepare(s.upsertQuery)
s.deleteStmt = s.Prepare(s.deleteQuery) s.deleteStmt = s.Prepare(s.deleteQuery)
s.deleteAllStmt = s.Prepare(s.deleteAllQuery) s.deleteAllStmt = s.Prepare(s.deleteAllQuery)
s.dropBaseStmt = s.Prepare(s.dropBaseQuery)
s.getStmt = s.Prepare(s.getQuery) s.getStmt = s.Prepare(s.getQuery)
s.listStmt = s.Prepare(s.listQuery) s.listStmt = s.Prepare(s.listQuery)
s.listKeysStmt = s.Prepare(s.listKeysQuery) s.listKeysStmt = s.Prepare(s.listKeysQuery)
@@ -524,6 +530,34 @@ func (s *Store) RegisterAfterDeleteAll(f func(txC transaction.Client) error) {
s.afterDeleteAll = append(s.afterDeleteAll, f) s.afterDeleteAll = append(s.afterDeleteAll, f)
} }
func (s *Store) RegisterBeforeDropAll(f func(txC transaction.Client) error) {
s.beforeDropAll = append(s.beforeDropAll, f)
}
// DropAll effectively removes the store from the database. The store must be
// recreated with NewStore.
//
// The store shouldn't be used once DropAll is called.
func (s *Store) DropAll(ctx context.Context) error {
err := s.WithTransaction(ctx, true, func(tx transaction.Client) error {
err := s.runBeforeDropAll(tx)
if err != nil {
return err
}
_, err = tx.Stmt(s.dropBaseStmt).Exec(s.GetName())
if err != nil {
return &db.QueryError{QueryString: s.dropBaseQuery, Err: err}
}
return nil
})
if err != nil {
return fmt.Errorf("dropall for %q: %w", s.GetName(), err)
}
return nil
}
// runAfterAdd executes functions registered to run after add event // runAfterAdd executes functions registered to run after add event
func (s *Store) runAfterAdd(key string, obj any, txC transaction.Client) error { func (s *Store) runAfterAdd(key string, obj any, txC transaction.Client) error {
for _, f := range s.afterAdd { for _, f := range s.afterAdd {
@@ -568,3 +602,13 @@ func (s *Store) runAfterDeleteAll(txC transaction.Client) error {
} }
return nil return nil
} }
func (s *Store) runBeforeDropAll(txC transaction.Client) error {
for _, f := range s.beforeDropAll {
err := f(txC)
if err != nil {
return err
}
}
return nil
}

View File

@@ -1028,6 +1028,7 @@ func SetupMockDB(t *testing.T) (*MockClient, *MockTXClient) {
dbC.EXPECT().Prepare(fmt.Sprintf(upsertStmtFmt, "testStoreObject")).Return(&sql.Stmt{}) dbC.EXPECT().Prepare(fmt.Sprintf(upsertStmtFmt, "testStoreObject")).Return(&sql.Stmt{})
dbC.EXPECT().Prepare(fmt.Sprintf(deleteStmtFmt, "testStoreObject")).Return(&sql.Stmt{}) dbC.EXPECT().Prepare(fmt.Sprintf(deleteStmtFmt, "testStoreObject")).Return(&sql.Stmt{})
dbC.EXPECT().Prepare(fmt.Sprintf(deleteAllStmtFmt, "testStoreObject")).Return(&sql.Stmt{}) dbC.EXPECT().Prepare(fmt.Sprintf(deleteAllStmtFmt, "testStoreObject")).Return(&sql.Stmt{})
dbC.EXPECT().Prepare(fmt.Sprintf(dropBaseStmtFmt, "testStoreObject")).Return(&sql.Stmt{})
dbC.EXPECT().Prepare(fmt.Sprintf(getStmtFmt, "testStoreObject")).Return(&sql.Stmt{}) dbC.EXPECT().Prepare(fmt.Sprintf(getStmtFmt, "testStoreObject")).Return(&sql.Stmt{})
dbC.EXPECT().Prepare(fmt.Sprintf(listStmtFmt, "testStoreObject")).Return(&sql.Stmt{}) dbC.EXPECT().Prepare(fmt.Sprintf(listStmtFmt, "testStoreObject")).Return(&sql.Stmt{})
dbC.EXPECT().Prepare(fmt.Sprintf(listKeysStmtFmt, "testStoreObject")).Return(&sql.Stmt{}) dbC.EXPECT().Prepare(fmt.Sprintf(listKeysStmtFmt, "testStoreObject")).Return(&sql.Stmt{})

View File

@@ -45,6 +45,20 @@ func (m *MockByOptionsLister) EXPECT() *MockByOptionsListerMockRecorder {
return m.recorder return m.recorder
} }
// DropAll mocks base method.
func (m *MockByOptionsLister) DropAll(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DropAll", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DropAll indicates an expected call of DropAll.
func (mr *MockByOptionsListerMockRecorder) DropAll(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropAll", reflect.TypeOf((*MockByOptionsLister)(nil).DropAll), arg0)
}
// GetLatestResourceVersion mocks base method. // GetLatestResourceVersion mocks base method.
func (m *MockByOptionsLister) GetLatestResourceVersion() []string { func (m *MockByOptionsLister) GetLatestResourceVersion() []string {
m.ctrl.T.Helper() m.ctrl.T.Helper()