diff --git a/loopdb/interface.go b/loopdb/interface.go index ce8a1b97..c3c049ca 100644 --- a/loopdb/interface.go +++ b/loopdb/interface.go @@ -70,6 +70,13 @@ type SwapStore interface { BatchUpdateLoopOutSwapCosts(ctx context.Context, swaps map[lntypes.Hash]SwapCost) error + // HasMigration returns true if the migration with the given ID has + // been done. + HasMigration(ctx context.Context, migrationID string) (bool, error) + + // SetMigration marks the migration with the given ID as done. + SetMigration(ctx context.Context, migrationID string) error + // Close closes the underlying database. Close() error } diff --git a/loopdb/sql_store.go b/loopdb/sql_store.go index 60e9d7a0..6d9a49bc 100644 --- a/loopdb/sql_store.go +++ b/loopdb/sql_store.go @@ -439,6 +439,29 @@ func (b *BaseDB) BatchUpdateLoopOutSwapCosts(ctx context.Context, }) } +// HasMigration returns true if the migration with the given ID has been done. +func (b *BaseDB) HasMigration(ctx context.Context, migrationID string) ( + bool, error) { + + migration, err := b.GetMigration(ctx, migrationID) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return false, err + } + + return migration.MigrationTs.Valid, nil +} + +// SetMigration marks the migration with the given ID as done. +func (b *BaseDB) SetMigration(ctx context.Context, migrationID string) error { + return b.InsertMigration(ctx, sqlc.InsertMigrationParams{ + MigrationID: migrationID, + MigrationTs: sql.NullTime{ + Time: time.Now().UTC(), + Valid: true, + }, + }) +} + // loopToInsertArgs converts a SwapContract struct to the arguments needed to // insert it into the database. func loopToInsertArgs(hash lntypes.Hash, diff --git a/loopdb/sql_test.go b/loopdb/sql_test.go index 76ecf91f..5919236e 100644 --- a/loopdb/sql_test.go +++ b/loopdb/sql_test.go @@ -515,6 +515,22 @@ func TestBatchUpdateCost(t *testing.T) { require.Equal(t, updateMap[hash2], swapsMap[hash2].State().Cost) } +// TestMigrationTracker tests the migration tracker functionality. +func TestMigrationTracker(t *testing.T) { + ctxb := context.Background() + + // Create a new sqlite store for testing. + sqlDB := NewTestDB(t) + hasMigration, err := sqlDB.HasMigration(ctxb, "test") + require.NoError(t, err) + require.False(t, hasMigration) + + require.NoError(t, sqlDB.SetMigration(ctxb, "test")) + hasMigration, err = sqlDB.HasMigration(ctxb, "test") + require.NoError(t, err) + require.True(t, hasMigration) +} + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" func randomString(length int) string { diff --git a/loopdb/sqlc/migration_tracker.sql.go b/loopdb/sqlc/migration_tracker.sql.go new file mode 100644 index 00000000..e68c56d5 --- /dev/null +++ b/loopdb/sqlc/migration_tracker.sql.go @@ -0,0 +1,45 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.25.0 +// source: migration_tracker.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const getMigration = `-- name: GetMigration :one +SELECT + migration_id, + migration_ts +FROM + migration_tracker +WHERE + migration_id = $1 +` + +func (q *Queries) GetMigration(ctx context.Context, migrationID string) (MigrationTracker, error) { + row := q.db.QueryRowContext(ctx, getMigration, migrationID) + var i MigrationTracker + err := row.Scan(&i.MigrationID, &i.MigrationTs) + return i, err +} + +const insertMigration = `-- name: InsertMigration :exec +INSERT INTO migration_tracker ( + migration_id, + migration_ts +) VALUES ($1, $2) +` + +type InsertMigrationParams struct { + MigrationID string + MigrationTs sql.NullTime +} + +func (q *Queries) InsertMigration(ctx context.Context, arg InsertMigrationParams) error { + _, err := q.db.ExecContext(ctx, insertMigration, arg.MigrationID, arg.MigrationTs) + return err +} diff --git a/loopdb/sqlc/migrations/000008_migration_tracker.down.sql b/loopdb/sqlc/migrations/000008_migration_tracker.down.sql new file mode 100644 index 00000000..6423aa6e --- /dev/null +++ b/loopdb/sqlc/migrations/000008_migration_tracker.down.sql @@ -0,0 +1,2 @@ +DROP TABLE migration_tracker; + diff --git a/loopdb/sqlc/migrations/000008_migration_tracker.up.sql b/loopdb/sqlc/migrations/000008_migration_tracker.up.sql new file mode 100644 index 00000000..01b8a72f --- /dev/null +++ b/loopdb/sqlc/migrations/000008_migration_tracker.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE migration_tracker ( + -- migration_id is the id of the migration. + migration_id TEXT NOT NULL, + + -- migration_ts is the timestamp at which the migration was run. + migration_ts TIMESTAMP, + + PRIMARY KEY (migration_id) +); diff --git a/loopdb/sqlc/models.go b/loopdb/sqlc/models.go index 45185d30..03c5c676 100644 --- a/loopdb/sqlc/models.go +++ b/loopdb/sqlc/models.go @@ -67,6 +67,11 @@ type LoopoutSwap struct { PaymentTimeout int32 } +type MigrationTracker struct { + MigrationID string + MigrationTs sql.NullTime +} + type Reservation struct { ID int32 ReservationID []byte diff --git a/loopdb/sqlc/querier.go b/loopdb/sqlc/querier.go index c930b8b7..4c94d146 100644 --- a/loopdb/sqlc/querier.go +++ b/loopdb/sqlc/querier.go @@ -23,6 +23,7 @@ type Querier interface { GetLoopInSwaps(ctx context.Context) ([]GetLoopInSwapsRow, error) GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopOutSwapRow, error) GetLoopOutSwaps(ctx context.Context) ([]GetLoopOutSwapsRow, error) + GetMigration(ctx context.Context, migrationID string) (MigrationTracker, error) GetParentBatch(ctx context.Context, swapHash []byte) (SweepBatch, error) GetReservation(ctx context.Context, reservationID []byte) (Reservation, error) GetReservationUpdates(ctx context.Context, reservationID []byte) ([]ReservationUpdate, error) @@ -36,6 +37,7 @@ type Querier interface { InsertInstantOutUpdate(ctx context.Context, arg InsertInstantOutUpdateParams) error InsertLoopIn(ctx context.Context, arg InsertLoopInParams) error InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) error + InsertMigration(ctx context.Context, arg InsertMigrationParams) error InsertReservationUpdate(ctx context.Context, arg InsertReservationUpdateParams) error InsertSwap(ctx context.Context, arg InsertSwapParams) error InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error diff --git a/loopdb/sqlc/queries/migration_tracker.sql b/loopdb/sqlc/queries/migration_tracker.sql new file mode 100644 index 00000000..da1fd178 --- /dev/null +++ b/loopdb/sqlc/queries/migration_tracker.sql @@ -0,0 +1,14 @@ +-- name: InsertMigration :exec +INSERT INTO migration_tracker ( + migration_id, + migration_ts +) VALUES ($1, $2); + +-- name: GetMigration :one +SELECT + migration_id, + migration_ts +FROM + migration_tracker +WHERE + migration_id = $1; diff --git a/loopdb/store.go b/loopdb/store.go index dd6b6510..a43db544 100644 --- a/loopdb/store.go +++ b/loopdb/store.go @@ -1017,3 +1017,17 @@ func (b *boltSwapStore) BatchUpdateLoopOutSwapCosts(ctx context.Context, return errUnimplemented } + +// HasMigration returns true if the migration with the given ID has been done. +func (b *boltSwapStore) HasMigration(ctx context.Context, migrationID string) ( + bool, error) { + + return false, errUnimplemented +} + +// SetMigration marks the migration with the given ID as done. +func (b *boltSwapStore) SetMigration(ctx context.Context, + migrationID string) error { + + return errUnimplemented +} diff --git a/loopdb/store_mock.go b/loopdb/store_mock.go index c64b0c06..268bba6b 100644 --- a/loopdb/store_mock.go +++ b/loopdb/store_mock.go @@ -359,3 +359,17 @@ func (s *StoreMock) BatchUpdateLoopOutSwapCosts(ctx context.Context, return nil } + +// HasMigration returns true if the migration with the given ID has been done. +func (s *StoreMock) HasMigration(ctx context.Context, migrationID string) ( + bool, error) { + + return false, errUnimplemented +} + +// SetMigration marks the migration with the given ID as done. +func (s *StoreMock) SetMigration(ctx context.Context, + migrationID string) error { + + return errUnimplemented +}