@@ -16,47 +16,70 @@ import (
1616//go:embed init.sql
1717var initDatabase string
1818
19+ func initializeSettings (tx * txn , target int64 ) error {
20+ _ , err := tx .Exec (`INSERT INTO global_settings (id, db_version) VALUES (0, ?)` , target )
21+ return err
22+ }
23+
1924func (s * Store ) initNewDatabase (tx * txn , target int64 ) error {
2025 if _ , err := tx .Exec (initDatabase ); err != nil {
2126 return fmt .Errorf ("failed to initialize database: %w" , err )
22- } else if err := setDBVersion (tx , target ); err != nil {
23- return fmt .Errorf ("failed to set initial database version : %w" , err )
27+ } else if err := initializeSettings (tx , target ); err != nil {
28+ return fmt .Errorf ("failed to initialize global settings : %w" , err )
2429 }
2530 return nil
2631}
2732
2833func (s * Store ) upgradeDatabase (current , target int64 ) error {
29- log := s .log .Named ("migrations" )
30- log .Info ("migrating database" , zap .Int64 ("current" , current ), zap .Int64 ("target" , target ))
34+ log := s .log .Named ("migrations" ).With (zap .Int64 ("target" , target ))
35+ for ; current < target ; current ++ {
36+ version := current + 1 // initial schema is version 1, migration 0 is version 2, etc.
37+ log := log .With (zap .Int64 ("version" , version ))
38+ start := time .Now ()
39+ fn := migrations [current - 1 ]
40+ err := s .transaction (func (tx * txn ) error {
41+ if _ , err := tx .Exec ("PRAGMA defer_foreign_keys=ON" ); err != nil {
42+ return fmt .Errorf ("failed to enable foreign key deferral: %w" , err )
43+ } else if err := fn (tx , log ); err != nil {
44+ return err
45+ } else if err := foreignKeyCheck (tx , log ); err != nil {
46+ return fmt .Errorf ("failed foreign key check: %w" , err )
47+ }
48+ return setDBVersion (tx , version )
49+ })
50+ if err != nil {
51+ return fmt .Errorf ("migration %d failed: %w" , version , err )
52+ }
53+ log .Info ("migration complete" , zap .Duration ("elapsed" , time .Since (start )))
54+ }
55+ return nil
56+ }
3157
32- // disable foreign key constraints during migration
33- if _ , err := s .db .Exec ("PRAGMA foreign_keys = OFF" ); err != nil {
34- return fmt .Errorf ("failed to disable foreign key constraints: %w" , err )
58+ func foreignKeyCheck (txn * txn , log * zap.Logger ) error {
59+ rows , err := txn .Query ("PRAGMA foreign_key_check" )
60+ if err != nil {
61+ return fmt .Errorf ("failed to run foreign key check: %w" , err )
3562 }
36- defer func () {
37- // re-enable foreign key constraints
38- if _ , err := s .db .Exec ("PRAGMA foreign_keys = ON" ); err != nil {
39- log .Panic ("failed to enable foreign key constraints" , zap .Error (err ))
40- }
41- }()
63+ defer rows .Close ()
64+ var hasErrors bool
65+ for rows .Next () {
66+ var table string
67+ var rowid sql.NullInt64
68+ var fkTable string
69+ var fkRowid sql.NullInt64
4270
43- return s .transaction (func (tx * txn ) error {
44- for _ , fn := range migrations [current - 1 :] {
45- current ++
46- start := time .Now ()
47- if err := fn (tx , log .With (zap .Int64 ("version" , current ))); err != nil {
48- return fmt .Errorf ("failed to migrate database to version %v: %w" , current , err )
49- }
50- // check that no foreign key constraints were violated
51- if err := tx .QueryRow ("PRAGMA foreign_key_check" ).Scan (); ! errors .Is (err , sql .ErrNoRows ) {
52- return fmt .Errorf ("foreign key constraints are not satisfied" )
53- }
54- log .Debug ("migration complete" , zap .Int64 ("current" , current ), zap .Int64 ("target" , target ), zap .Duration ("elapsed" , time .Since (start )))
71+ if err := rows .Scan (& table , & rowid , & fkTable , & fkRowid ); err != nil {
72+ return fmt .Errorf ("failed to scan foreign key check result: %w" , err )
5573 }
56-
57- // set the final database version
58- return setDBVersion (tx , target )
59- })
74+ hasErrors = true
75+ log .Error ("foreign key constraint violated" , zap .String ("table" , table ), zap .Int64 ("rowid" , rowid .Int64 ), zap .String ("fkTable" , fkTable ), zap .Int64 ("fkRowid" , fkRowid .Int64 ))
76+ }
77+ if err := rows .Err (); err != nil {
78+ return fmt .Errorf ("failed to iterate foreign key check results: %w" , err )
79+ } else if hasErrors {
80+ return errors .New ("foreign key constraint violated" )
81+ }
82+ return nil
6083}
6184
6285func (s * Store ) init () error {
0 commit comments