diff --git a/graph/db/sql_migration.go b/graph/db/sql_migration.go new file mode 100644 index 000000000..67607bbb9 --- /dev/null +++ b/graph/db/sql_migration.go @@ -0,0 +1,62 @@ +package graphdb + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/kvdb" +) + +// MigrateGraphToSQL migrates the graph store from a KV backend to a SQL +// backend. +// +// NOTE: this is currently not called from any code path. It is called via tests +// only for now and will be called from the main lnd binary once the +// migration is fully implemented and tested. +func MigrateGraphToSQL(_ context.Context, kvBackend kvdb.Backend, + _ SQLQueries, _ chainhash.Hash) error { + + log.Infof("Starting migration of the graph store from KV to SQL") + t0 := time.Now() + + // Check if there is a graph to migrate. + graphExists, err := checkGraphExists(kvBackend) + if err != nil { + return fmt.Errorf("failed to check graph existence: %w", err) + } + if !graphExists { + log.Infof("No graph found in KV store, skipping the migration") + return nil + } + + log.Infof("Finished migration of the graph store from KV to SQL in %v", + time.Since(t0)) + + return nil +} + +// checkGraphExists checks if the graph exists in the KV backend. +func checkGraphExists(db kvdb.Backend) (bool, error) { + // Check if there is even a graph to migrate. + err := db.View(func(tx kvdb.RTx) error { + // Check for the existence of the node bucket which is a top + // level bucket that would have been created on the initial + // creation of the graph store. + nodes := tx.ReadBucket(nodeBucket) + if nodes == nil { + return ErrGraphNotFound + } + + return nil + }, func() {}) + if errors.Is(err, ErrGraphNotFound) { + return false, nil + } else if err != nil { + return false, err + } + + return true, nil +} diff --git a/graph/db/sql_migration_test.go b/graph/db/sql_migration_test.go new file mode 100644 index 000000000..e21eb2002 --- /dev/null +++ b/graph/db/sql_migration_test.go @@ -0,0 +1,82 @@ +//go:build test_db_postgres || test_db_sqlite + +package graphdb + +import ( + "context" + "testing" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/stretchr/testify/require" +) + +var testChain = *chaincfg.MainNetParams.GenesisHash + +// TestMigrateGraphToSQL tests various deterministic cases that we want to test +// for to ensure that our migration from a graph store backed by a KV DB to a +// SQL database works as expected. At the end of each test, the DBs are compared +// and expected to have the exact same data in them. +func TestMigrateGraphToSQL(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + write func(t *testing.T, db *KVStore, object any) + objects []any + expGraphStats graphStats + }{ + { + name: "empty", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // Set up our source kvdb DB. + kvDB := setUpKVStore(t) + + // Write the test objects to the kvdb store. + for _, object := range test.objects { + test.write(t, kvDB, object) + } + + // Set up our destination SQL DB. + sql, ok := NewTestDB(t).(*SQLStore) + require.True(t, ok) + + // Run the migration. + err := MigrateGraphToSQL( + ctx, kvDB.db, sql.db, testChain, + ) + require.NoError(t, err) + + // Validate that the two databases are now in sync. + assertInSync(t, kvDB, sql, test.expGraphStats) + }) + } +} + +// graphStats holds expected statistics about the graph after migration. +type graphStats struct { +} + +// assertInSync checks that the KVStore and SQLStore both contain the same +// graph data after migration. +func assertInSync(_ *testing.T, _ *KVStore, _ *SQLStore, stats graphStats) { +} + +// setUpKVStore initializes a new KVStore for testing. +func setUpKVStore(t *testing.T) *KVStore { + kvDB, cleanup, err := kvdb.GetTestBackend(t.TempDir(), "graph") + require.NoError(t, err) + t.Cleanup(cleanup) + + kvStore, err := NewKVStore(kvDB) + require.NoError(t, err) + + return kvStore +}