Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5e08d52

Browse files
committedMay 27, 2025
session: add migration code from kvdb to SQL
This commit introduces the migration logic for transitioning the sessions store from kvdb to SQL. Note that as of this commit, the migration is not yet triggered by any production code, i.e. only tests execute the migration logic.
1 parent 6b7d00d commit 5e08d52

File tree

2 files changed

+716
-0
lines changed

2 files changed

+716
-0
lines changed
 

‎session/sql_migration.go

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
package session
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"fmt"
8+
"reflect"
9+
"time"
10+
11+
"github.com/davecgh/go-spew/spew"
12+
"github.com/lightninglabs/lightning-terminal/accounts"
13+
"github.com/lightninglabs/lightning-terminal/db/sqlc"
14+
"github.com/pmezard/go-difflib/difflib"
15+
)
16+
17+
var (
18+
// ErrMigrationMismatch is returned when the migrated session does not
19+
// match the original session.
20+
ErrMigrationMismatch = fmt.Errorf("migrated session does not match " +
21+
"original session")
22+
)
23+
24+
// MigrateSessionStoreToSQL runs the migration of all sessions from the KV
25+
// database to the SQL database. The migration is done in a single transaction
26+
// to ensure that all sessions are migrated or none at all.
27+
//
28+
// NOTE: As sessions may contain linked accounts, the accounts sql migration
29+
// MUST be run prior to this migration.
30+
func MigrateSessionStoreToSQL(ctx context.Context, kvStore *BoltStore,
31+
tx SQLQueries) error {
32+
33+
log.Infof("Starting migration of the KV sessions store to SQL")
34+
35+
kvSessions, err := kvStore.ListAllSessions(ctx)
36+
if err != nil {
37+
return err
38+
}
39+
40+
// If sessions are linked to a group, we must insert the initial session
41+
// of each group before the other sessions in that group. This ensures
42+
// we can retrieve the SQL group ID when inserting the remaining
43+
// sessions. Therefore, we first insert all initial group sessions,
44+
// allowing us to fetch the group IDs and insert the rest of the
45+
// sessions afterward.
46+
// We therefore filter out the initial sessions first, and then migrate
47+
// them prior to the rest of the sessions.
48+
var (
49+
initialGroupSessions []*Session
50+
linkedSessions []*Session
51+
)
52+
53+
for _, kvSession := range kvSessions {
54+
if kvSession.GroupID == kvSession.ID {
55+
initialGroupSessions = append(
56+
initialGroupSessions, kvSession,
57+
)
58+
} else {
59+
linkedSessions = append(linkedSessions, kvSession)
60+
}
61+
}
62+
63+
err = migrateSessionsToSQLAndValidate(ctx, tx, initialGroupSessions)
64+
if err != nil {
65+
return fmt.Errorf("migration of non-linked session failed: %w",
66+
err)
67+
}
68+
69+
err = migrateSessionsToSQLAndValidate(ctx, tx, linkedSessions)
70+
if err != nil {
71+
return fmt.Errorf("migration of linked session failed: %w", err)
72+
}
73+
74+
total := len(initialGroupSessions) + len(linkedSessions)
75+
log.Infof("All sessions migrated from KV to SQL. Total number of "+
76+
"sessions migrated: %d", total)
77+
78+
return nil
79+
}
80+
81+
// migrateSessionsToSQLAndValidate runs the migration for the passed sessions
82+
// from the KV database to the SQL database, and validates that the migrated
83+
// sessions match the original sessions.
84+
func migrateSessionsToSQLAndValidate(ctx context.Context,
85+
tx SQLQueries, kvSessions []*Session) error {
86+
87+
for _, kvSession := range kvSessions {
88+
err := migrateSingleSessionToSQL(ctx, tx, kvSession)
89+
if err != nil {
90+
return fmt.Errorf("unable to migrate session(%v): %w",
91+
kvSession.ID, err)
92+
}
93+
94+
// Validate that the session was correctly migrated and matches
95+
// the original session in the kv store.
96+
sqlSess, err := tx.GetSessionByAlias(ctx, kvSession.ID[:])
97+
if err != nil {
98+
if errors.Is(err, sql.ErrNoRows) {
99+
err = ErrSessionNotFound
100+
}
101+
return fmt.Errorf("unable to get migrated session "+
102+
"from sql store: %w", err)
103+
}
104+
105+
migratedSession, err := unmarshalSession(ctx, tx, sqlSess)
106+
if err != nil {
107+
return fmt.Errorf("unable to unmarshal migrated "+
108+
"session: %w", err)
109+
}
110+
111+
overrideSessionTimeZone(kvSession)
112+
overrideSessionTimeZone(migratedSession)
113+
114+
if !reflect.DeepEqual(kvSession, migratedSession) {
115+
diff := difflib.UnifiedDiff{
116+
A: difflib.SplitLines(
117+
spew.Sdump(kvSession),
118+
),
119+
B: difflib.SplitLines(
120+
spew.Sdump(migratedSession),
121+
),
122+
FromFile: "Expected",
123+
FromDate: "",
124+
ToFile: "Actual",
125+
ToDate: "",
126+
Context: 3,
127+
}
128+
diffText, _ := difflib.GetUnifiedDiffString(diff)
129+
130+
return fmt.Errorf("%w: %v.\n%v", ErrMigrationMismatch,
131+
kvSession.ID, diffText)
132+
}
133+
}
134+
135+
return nil
136+
}
137+
138+
// migrateSingleSessionToSQL runs the migration for a single session from the
139+
// KV database to the SQL database. Note that if the session links to an
140+
// account, the linked accounts store MUST have been migrated before that
141+
// session is migrated.
142+
func migrateSingleSessionToSQL(ctx context.Context,
143+
tx SQLQueries, session *Session) error {
144+
145+
var (
146+
acctID sql.NullInt64
147+
err error
148+
remotePubKey []byte
149+
)
150+
151+
session.AccountID.WhenSome(func(alias accounts.AccountID) {
152+
// Check that the account exists in the SQL store, before
153+
// linking it.
154+
var acctAlias int64
155+
acctAlias, err = alias.ToInt64()
156+
if err != nil {
157+
return
158+
}
159+
160+
var acctDBID int64
161+
acctDBID, err = tx.GetAccountIDByAlias(ctx, acctAlias)
162+
if errors.Is(err, sql.ErrNoRows) {
163+
err = accounts.ErrAccNotFound
164+
return
165+
} else if err != nil {
166+
return
167+
}
168+
169+
acctID = sql.NullInt64{
170+
Int64: acctDBID,
171+
Valid: true,
172+
}
173+
})
174+
if err != nil {
175+
return err
176+
}
177+
178+
// The remote public key is currently only set for autopilot sessions,
179+
// else it's a nil byte array.
180+
if session.RemotePublicKey != nil {
181+
remotePubKey = session.RemotePublicKey.SerializeCompressed()
182+
}
183+
184+
// Proceed to insert the session into the sql db.
185+
insertSessionParams := sqlc.InsertSessionParams{
186+
Alias: session.ID[:],
187+
Label: session.Label,
188+
State: int16(session.State),
189+
Type: int16(session.Type),
190+
Expiry: session.Expiry.UTC(),
191+
CreatedAt: session.CreatedAt.UTC(),
192+
ServerAddress: session.ServerAddr,
193+
DevServer: session.DevServer,
194+
MacaroonRootKey: int64(session.MacaroonRootKey),
195+
PairingSecret: session.PairingSecret[:],
196+
LocalPrivateKey: session.LocalPrivateKey.Serialize(),
197+
LocalPublicKey: session.LocalPublicKey.SerializeCompressed(),
198+
RemotePublicKey: remotePubKey,
199+
Privacy: session.WithPrivacyMapper,
200+
AccountID: acctID,
201+
}
202+
203+
sqlId, err := tx.InsertSession(ctx, insertSessionParams)
204+
if err != nil {
205+
return err
206+
}
207+
208+
// Since the InsertSession query doesn't support that we set the revoked
209+
// field during the insert, we need to set the field after the session
210+
// has been created.
211+
if !session.RevokedAt.IsZero() {
212+
setSessionRevokedParams := sqlc.SetSessionRevokedAtParams{
213+
ID: sqlId,
214+
RevokedAt: sql.NullTime{
215+
Time: session.RevokedAt.UTC(),
216+
Valid: true,
217+
},
218+
}
219+
220+
err = tx.SetSessionRevokedAt(ctx, setSessionRevokedParams)
221+
if err != nil {
222+
return err
223+
}
224+
}
225+
226+
// After the session has been inserted, we need to update the session
227+
// with the group ID if it is linked to a group. We need to do this
228+
// after the session has been inserted, because the group ID can be the
229+
// session itself, and therefore the SQL id for the session won't exist
230+
// prior to inserting the session.
231+
groupID, err := tx.GetSessionIDByAlias(ctx, session.GroupID[:])
232+
if errors.Is(err, sql.ErrNoRows) {
233+
return ErrUnknownGroup
234+
} else if err != nil {
235+
return fmt.Errorf("unable to fetch group(%x): %w",
236+
session.GroupID[:], err)
237+
}
238+
239+
// Now lets set the group ID for the session.
240+
err = tx.SetSessionGroupID(ctx, sqlc.SetSessionGroupIDParams{
241+
ID: sqlId,
242+
GroupID: sql.NullInt64{
243+
Int64: groupID,
244+
Valid: true,
245+
},
246+
})
247+
if err != nil {
248+
return fmt.Errorf("unable to set group Alias: %w", err)
249+
}
250+
251+
// Once we have the sqlID for the session, we can proceed to insert rows
252+
// into the linked child tables.
253+
if session.MacaroonRecipe != nil {
254+
// We start by inserting the macaroon permissions.
255+
for _, sessionPerm := range session.MacaroonRecipe.Permissions {
256+
permParam := sqlc.InsertSessionMacaroonPermissionParams{
257+
SessionID: sqlId,
258+
Entity: sessionPerm.Entity,
259+
Action: sessionPerm.Action,
260+
}
261+
262+
err = tx.InsertSessionMacaroonPermission(
263+
ctx, permParam,
264+
)
265+
if err != nil {
266+
return err
267+
}
268+
}
269+
270+
// Next we insert the macaroon caveats.
271+
for _, sessCaveat := range session.MacaroonRecipe.Caveats {
272+
caveatParams := sqlc.InsertSessionMacaroonCaveatParams{
273+
SessionID: sqlId,
274+
CaveatID: sessCaveat.Id,
275+
VerificationID: sessCaveat.VerificationId,
276+
Location: sql.NullString{
277+
String: sessCaveat.Location,
278+
Valid: sessCaveat.Location != "",
279+
},
280+
}
281+
282+
err = tx.InsertSessionMacaroonCaveat(
283+
ctx, caveatParams,
284+
)
285+
if err != nil {
286+
return err
287+
}
288+
}
289+
}
290+
291+
// That's followed by the feature config.
292+
if session.FeatureConfig != nil {
293+
for featureName, config := range *session.FeatureConfig {
294+
fConfParams := sqlc.InsertSessionFeatureConfigParams{
295+
SessionID: sqlId,
296+
FeatureName: featureName,
297+
Config: config,
298+
}
299+
300+
err = tx.InsertSessionFeatureConfig(ctx, fConfParams)
301+
if err != nil {
302+
return err
303+
}
304+
}
305+
}
306+
307+
// Finally we insert the privacy flags.
308+
for _, privacyFlag := range session.PrivacyFlags {
309+
privacyFlagParams := sqlc.InsertSessionPrivacyFlagParams{
310+
SessionID: sqlId,
311+
Flag: int32(privacyFlag),
312+
}
313+
314+
err = tx.InsertSessionPrivacyFlag(
315+
ctx, privacyFlagParams,
316+
)
317+
if err != nil {
318+
return err
319+
}
320+
}
321+
322+
return nil
323+
}
324+
325+
// overrideSessionTimeZone overrides the time zone of the session to the local
326+
// time zone and chops off the nanosecond part for comparison. This is needed
327+
// because KV database stores times as-is which as an unwanted side effect would
328+
// fail migration due to time comparison expecting both the original and
329+
// migrated sessions to be in the same local time zone and in microsecond
330+
// precision. Note that PostgresSQL stores times in microsecond precision while
331+
// SQLite can store times in nanosecond precision if using TEXT storage class.
332+
func overrideSessionTimeZone(session *Session) {
333+
fixTime := func(t time.Time) time.Time {
334+
return t.In(time.Local).Truncate(time.Microsecond)
335+
}
336+
337+
if !session.Expiry.IsZero() {
338+
session.Expiry = fixTime(session.Expiry)
339+
}
340+
341+
if !session.CreatedAt.IsZero() {
342+
session.CreatedAt = fixTime(session.CreatedAt)
343+
}
344+
345+
if !session.RevokedAt.IsZero() {
346+
session.RevokedAt = fixTime(session.RevokedAt)
347+
}
348+
}

‎session/sql_migration_test.go

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
package session
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"testing"
8+
"time"
9+
10+
"github.com/lightninglabs/lightning-terminal/accounts"
11+
"github.com/lightninglabs/lightning-terminal/db"
12+
"github.com/lightningnetwork/lnd/clock"
13+
"github.com/lightningnetwork/lnd/macaroons"
14+
"github.com/lightningnetwork/lnd/sqldb"
15+
"github.com/stretchr/testify/require"
16+
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
17+
"gopkg.in/macaroon.v2"
18+
)
19+
20+
// TestSessionsStoreMigration tests the migration of session store from a bolt
21+
// backed to a SQL database. Note that this test does not attempt to be a
22+
// complete migration test.
23+
func TestSessionsStoreMigration(t *testing.T) {
24+
t.Parallel()
25+
26+
ctx := context.Background()
27+
clock := clock.NewTestClock(time.Now())
28+
29+
// When using build tags that creates a kvdb store for NewTestDB, we
30+
// skip this test as it is only applicable for postgres and sqlite tags.
31+
store := NewTestDB(t, clock)
32+
if _, ok := store.(*BoltStore); ok {
33+
t.Skipf("Skipping session store migration test for kvdb build")
34+
}
35+
36+
makeSQLDB := func(t *testing.T, acctStore accounts.Store) (*SQLStore,
37+
*db.TransactionExecutor[SQLQueries]) {
38+
39+
// Create a sql store with a linked account store.
40+
testDBStore := NewTestDBWithAccounts(t, clock, acctStore)
41+
t.Cleanup(func() {
42+
require.NoError(t, testDBStore.Close())
43+
})
44+
45+
store, ok := testDBStore.(*SQLStore)
46+
require.True(t, ok)
47+
48+
baseDB := store.BaseDB
49+
50+
genericExecutor := db.NewTransactionExecutor(
51+
baseDB, func(tx *sql.Tx) SQLQueries {
52+
return baseDB.WithTx(tx)
53+
},
54+
)
55+
56+
return store, genericExecutor
57+
}
58+
59+
// assertMigrationResults asserts that the sql store contains the
60+
// same sessions as the passed kv store sessions. This is intended to be
61+
// run after the migration.
62+
assertMigrationResults := func(t *testing.T, sqlStore *SQLStore,
63+
kvSessions []*Session) {
64+
65+
for _, kvSession := range kvSessions {
66+
// Fetch the migrated session from the sql store.
67+
sqlSession, err := sqlStore.GetSession(
68+
ctx, kvSession.ID,
69+
)
70+
require.NoError(t, err)
71+
72+
assertEqualSessions(t, kvSession, sqlSession)
73+
}
74+
75+
// Finally we ensure that the sql store doesn't contain more
76+
// sessions than the kv store.
77+
sqlSessions, err := sqlStore.ListAllSessions(ctx)
78+
require.NoError(t, err)
79+
require.Equal(t, len(kvSessions), len(sqlSessions))
80+
}
81+
82+
tests := []struct {
83+
name string
84+
populateDB func(
85+
t *testing.T, kvStore *BoltStore,
86+
accountStore accounts.Store,
87+
)
88+
}{
89+
{
90+
name: "empty",
91+
populateDB: func(t *testing.T, store *BoltStore,
92+
_ accounts.Store) {
93+
94+
// Don't populate the DB.
95+
},
96+
},
97+
{
98+
name: "one session no options",
99+
populateDB: func(t *testing.T, store *BoltStore,
100+
_ accounts.Store) {
101+
102+
_, err := store.NewSession(
103+
ctx, "test", TypeMacaroonAdmin,
104+
time.Unix(1000, 0), "",
105+
)
106+
require.NoError(t, err)
107+
},
108+
},
109+
{
110+
name: "multiple sessions no options",
111+
populateDB: func(t *testing.T, store *BoltStore,
112+
_ accounts.Store) {
113+
114+
_, err := store.NewSession(
115+
ctx, "session1", TypeMacaroonAdmin,
116+
time.Unix(1000, 0), "",
117+
)
118+
require.NoError(t, err)
119+
120+
_, err = store.NewSession(
121+
ctx, "session2", TypeMacaroonAdmin,
122+
time.Unix(1000, 0), "",
123+
)
124+
require.NoError(t, err)
125+
126+
_, err = store.NewSession(
127+
ctx, "session3", TypeMacaroonAdmin,
128+
time.Unix(1000, 0), "",
129+
)
130+
require.NoError(t, err)
131+
},
132+
},
133+
{
134+
name: "one session with one privacy flag",
135+
populateDB: func(t *testing.T, store *BoltStore,
136+
_ accounts.Store) {
137+
138+
_, err := store.NewSession(
139+
ctx, "test", TypeMacaroonAdmin,
140+
time.Unix(1000, 0), "",
141+
WithPrivacy(PrivacyFlags{ClearPubkeys}),
142+
)
143+
require.NoError(t, err)
144+
},
145+
},
146+
{
147+
name: "one session with multiple privacy flags",
148+
populateDB: func(t *testing.T, store *BoltStore,
149+
_ accounts.Store) {
150+
151+
_, err := store.NewSession(
152+
ctx, "test", TypeMacaroonAdmin,
153+
time.Unix(1000, 0), "",
154+
WithPrivacy(PrivacyFlags{
155+
ClearChanInitiator, ClearHTLCs,
156+
ClearClosingTxIds,
157+
}),
158+
)
159+
require.NoError(t, err)
160+
},
161+
},
162+
{
163+
name: "one session with a feature config",
164+
populateDB: func(t *testing.T, store *BoltStore,
165+
_ accounts.Store) {
166+
167+
featureConfig := map[string][]byte{
168+
"AutoFees": {1, 2, 3, 4},
169+
"AutoSomething": {4, 3, 4, 5, 6, 6},
170+
}
171+
172+
_, err := store.NewSession(
173+
ctx, "test", TypeMacaroonAdmin,
174+
time.Unix(1000, 0), "",
175+
WithFeatureConfig(featureConfig),
176+
)
177+
require.NoError(t, err)
178+
},
179+
},
180+
{
181+
name: "one session with dev server",
182+
populateDB: func(t *testing.T, store *BoltStore,
183+
_ accounts.Store) {
184+
185+
_, err := store.NewSession(
186+
ctx, "test", TypeMacaroonAdmin,
187+
time.Unix(1000, 0), "",
188+
WithDevServer(),
189+
)
190+
require.NoError(t, err)
191+
},
192+
},
193+
{
194+
name: "one session with macaroon recipe",
195+
populateDB: func(t *testing.T, store *BoltStore,
196+
_ accounts.Store) {
197+
198+
// this test uses caveats & perms from the
199+
// tlv_test.go
200+
_, err := store.NewSession(
201+
ctx, "test", TypeMacaroonAdmin,
202+
time.Unix(1000, 0), "foo.bar.baz:1234",
203+
WithMacaroonRecipe(caveats, perms),
204+
)
205+
require.NoError(t, err)
206+
},
207+
},
208+
{
209+
name: "one session with macaroon recipe nil caveats",
210+
populateDB: func(t *testing.T, store *BoltStore,
211+
_ accounts.Store) {
212+
213+
// this test uses perms from the tlv_test.go
214+
_, err := store.NewSession(
215+
ctx, "test", TypeMacaroonAdmin,
216+
time.Unix(1000, 0), "foo.bar.baz:1234",
217+
WithMacaroonRecipe(nil, perms),
218+
)
219+
require.NoError(t, err)
220+
},
221+
},
222+
{
223+
name: "one session with macaroon recipe nil perms",
224+
populateDB: func(t *testing.T, store *BoltStore,
225+
_ accounts.Store) {
226+
227+
// this test uses caveats from the tlv_test.go
228+
_, err := store.NewSession(
229+
ctx, "test", TypeMacaroonAdmin,
230+
time.Unix(1000, 0), "foo.bar.baz:1234",
231+
WithMacaroonRecipe(caveats, nil),
232+
)
233+
require.NoError(t, err)
234+
},
235+
},
236+
{
237+
name: "one session with a linked account",
238+
populateDB: func(t *testing.T, store *BoltStore,
239+
acctStore accounts.Store) {
240+
241+
// Create an account with balance
242+
acct, err := acctStore.NewAccount(
243+
ctx, 1234, time.Now().Add(time.Hour),
244+
"",
245+
)
246+
require.NoError(t, err)
247+
require.False(t, acct.HasExpired())
248+
249+
// For now, we manually add the account caveat
250+
// for bbolt compatibility.
251+
accountCaveat := checkers.Condition(
252+
macaroons.CondLndCustom,
253+
fmt.Sprintf("%s %x",
254+
accounts.CondAccount,
255+
acct.ID[:],
256+
),
257+
)
258+
259+
sessCaveats := []macaroon.Caveat{
260+
{
261+
Id: []byte(accountCaveat),
262+
},
263+
}
264+
265+
_, err = store.NewSession(
266+
ctx, "test", TypeMacaroonAccount,
267+
time.Unix(1000, 0), "",
268+
WithAccount(acct.ID),
269+
WithMacaroonRecipe(sessCaveats, nil),
270+
)
271+
require.NoError(t, err)
272+
},
273+
},
274+
{
275+
name: "linked session",
276+
populateDB: func(t *testing.T, store *BoltStore,
277+
_ accounts.Store) {
278+
279+
// First create the initial session for the
280+
// group.
281+
sess1, err := store.NewSession(
282+
ctx, "initSession", TypeMacaroonAdmin,
283+
time.Unix(1000, 0), "",
284+
)
285+
require.NoError(t, err)
286+
287+
// As the store won't allow us to link a
288+
// session before all sessions in the group have
289+
// been revoked, we revoke the session before
290+
// creating a new session that links to the
291+
// initial session.
292+
err = store.ShiftState(
293+
ctx, sess1.ID, StateCreated,
294+
)
295+
require.NoError(t, err)
296+
297+
err = store.ShiftState(
298+
ctx, sess1.ID, StateRevoked,
299+
)
300+
require.NoError(t, err)
301+
302+
_, err = store.NewSession(
303+
ctx, "linkedSession", TypeMacaroonAdmin,
304+
time.Unix(1000, 0), "",
305+
WithLinkedGroupID(&sess1.ID),
306+
)
307+
require.NoError(t, err)
308+
},
309+
},
310+
}
311+
312+
for _, test := range tests {
313+
t.Run(test.name, func(t *testing.T) {
314+
t.Parallel()
315+
316+
// First let's create an account store to link to in
317+
// the sessions store. Note that this is will be a sql
318+
// store due to the build tags enabled when running this
319+
// test, which means that we won't need to migrate the
320+
// account store in this test.
321+
accountStore := accounts.NewTestDB(t, clock)
322+
t.Cleanup(func() {
323+
require.NoError(t, accountStore.Close())
324+
})
325+
326+
kvStore, err := NewDB(
327+
t.TempDir(), DBFilename, clock, accountStore,
328+
)
329+
require.NoError(t, err)
330+
331+
t.Cleanup(func() {
332+
require.NoError(t, kvStore.Close())
333+
})
334+
335+
// populate the kvStore with the test data, in
336+
// preparation for the test.
337+
test.populateDB(t, kvStore, accountStore)
338+
339+
// Before we migrate the sessions, we fetch all sessions
340+
// from the kv store, to ensure that the migration
341+
// function doesn't mutate the bbolt store sessions.
342+
// We can then compare them to the sql sessions after
343+
// the migration has been executed.
344+
kvSessions, err := kvStore.ListAllSessions(ctx)
345+
require.NoError(t, err)
346+
347+
// Proceed to create the sql store and execute the
348+
// migration.
349+
sqlStore, txEx := makeSQLDB(t, accountStore)
350+
351+
var opts sqldb.MigrationTxOptions
352+
err = txEx.ExecTx(
353+
ctx, &opts, func(tx SQLQueries) error {
354+
return MigrateSessionStoreToSQL(
355+
ctx, kvStore, tx,
356+
)
357+
},
358+
)
359+
require.NoError(t, err)
360+
361+
// The migration function will check if the inserted
362+
// sessions equals the migrated ones, but as a sanity
363+
// check we'll also fetch migrated sessions from the sql
364+
// store and compare them to the original.
365+
assertMigrationResults(t, sqlStore, kvSessions)
366+
})
367+
}
368+
}

0 commit comments

Comments
 (0)
Please sign in to comment.