@@ -23,19 +23,26 @@ export function apply(db: Database) {
2323 )
2424 if ( tables . some ( ( table ) => table . name === "session" ) ) return yield * applyOnly ( db , migrations )
2525 if ( tables . length > 0 ) return yield * Effect . die ( "Database is not empty and has no session table" )
26- yield * db . transaction ( ( tx ) =>
26+ // Bootstrap DDL cannot replay, so the emptiness decision must be re-verified
27+ // inside this immediate write transaction: another process may have
28+ // bootstrapped the same file after the read above.
29+ const bootstrapped = yield * db . transaction ( ( tx ) =>
2730 Effect . gen ( function * ( ) {
28- yield * schema . up ( tx )
2931 yield * tx . run (
30- sql `CREATE TABLE ${ sql . identifier ( "migration" ) } (id TEXT PRIMARY KEY, time_completed INTEGER NOT NULL)` ,
32+ sql `CREATE TABLE IF NOT EXISTS ${ sql . identifier ( "migration" ) } (id TEXT PRIMARY KEY, time_completed INTEGER NOT NULL)` ,
3133 )
34+ if ( yield * tx . get ( sql `SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${ "session" } ` ) )
35+ return false
36+ yield * schema . up ( tx )
3237 yield * Effect . forEach ( migrations , ( migration ) =>
3338 tx . run (
3439 sql `INSERT OR IGNORE INTO ${ sql . identifier ( "migration" ) } (id, time_completed) VALUES (${ migration . id } , ${ Date . now ( ) } )` ,
3540 ) ,
3641 )
42+ return true
3743 } ) ,
3844 )
45+ if ( ! bootstrapped ) return yield * applyOnly ( db , migrations )
3946 } ) ,
4047 )
4148}
@@ -68,8 +75,12 @@ export function applyOnly(db: Database, input: Migration[]) {
6875
6976 for ( const migration of input ) {
7077 if ( completed . has ( migration . id ) ) continue
78+ // Migration DDL cannot replay, so completion must be re-verified inside
79+ // this immediate write transaction: another process may have claimed the
80+ // migration after the journal read above.
7181 yield * db . transaction ( ( tx ) =>
7282 Effect . gen ( function * ( ) {
83+ if ( yield * tx . get ( sql `SELECT id FROM ${ sql . identifier ( "migration" ) } WHERE id = ${ migration . id } ` ) ) return
7384 yield * migration . up ( tx )
7485 yield * tx . run (
7586 sql `INSERT OR IGNORE INTO ${ sql . identifier ( "migration" ) } (id, time_completed) VALUES (${ migration . id } , ${ Date . now ( ) } )` ,
0 commit comments