diff --git a/libpod/sqlite_state.go b/libpod/sqlite_state.go index 8317fe4b7c..e1007dbc37 100644 --- a/libpod/sqlite_state.go +++ b/libpod/sqlite_state.go @@ -29,6 +29,30 @@ type SQLiteState struct { runtime *Runtime } +const ( + // Deal with timezone automatically. + sqliteOptionLocation = "_loc=auto" + // Set the journal mode (https://www.sqlite.org/pragma.html#pragma_journal_mode). + sqliteOptionJournal = "&_journal=WAL" + // Force WAL mode to fsync after each transaction (https://www.sqlite.org/pragma.html#pragma_synchronous). + sqliteOptionSynchronous = "&_sync=FULL" + // Allow foreign keys (https://www.sqlite.org/pragma.html#pragma_foreign_keys). + sqliteOptionForeignKeys = "&_foreign_keys=1" + // Enable cache sharing for threads within a process + sqliteOptionSharedCache = "&cache=shared" + // Make sure that transactions happen exclusively. + sqliteOptionTXLock = "&_txlock=exclusive" + + // Assembled sqlite options used when opening the database. + sqliteOptions = "db.sql?" + + sqliteOptionLocation + + sqliteOptionJournal + + sqliteOptionSynchronous + + sqliteOptionForeignKeys + + sqliteOptionSharedCache + + sqliteOptionTXLock +) + // NewSqliteState creates a new SQLite-backed state database. func NewSqliteState(runtime *Runtime) (_ State, defErr error) { state := new(SQLiteState) @@ -45,7 +69,7 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) { return nil, fmt.Errorf("creating root directory: %w", err) } - conn, err := sql.Open("sqlite3", filepath.Join(basePath, "db.sql?_loc=auto")) + conn, err := sql.Open("sqlite3", filepath.Join(basePath, sqliteOptions)) if err != nil { return nil, fmt.Errorf("initializing sqlite database: %w", err) } @@ -63,18 +87,9 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) { return nil, fmt.Errorf("cannot connect to database: %w", err) } - // Enable foreign keys constraints, which we use extensively in our - // tables. - if _, err := state.conn.Exec("PRAGMA foreign_keys = ON;"); err != nil { - return nil, fmt.Errorf("enabling foreign key support in database: %w", err) - } - // Enable WAL mode for performance - https://www.sqlite.org/wal.html - if _, err := state.conn.Exec("PRAGMA journal_mode = WAL;"); err != nil { - return nil, fmt.Errorf("switching journal to WAL mode: %w", err) - } - // Force WAL mode to fsync after every transaction, for reboot safety. - if _, err := state.conn.Exec("PRAGMA synchronous = FULL;"); err != nil { - return nil, fmt.Errorf("setting full fsync mode in db: %w", err) + // Migrate schema (if necessary) + if err := state.migrateSchemaIfNecessary(); err != nil { + return nil, err } // Set up tables @@ -82,10 +97,6 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) { return nil, fmt.Errorf("creating tables: %w", err) } - if err := state.migrateSchemaIfNecessary(); err != nil { - return nil, err - } - state.valid = true state.runtime = runtime diff --git a/libpod/sqlite_state_internal.go b/libpod/sqlite_state_internal.go index 1f62d346bd..184b96971e 100644 --- a/libpod/sqlite_state_internal.go +++ b/libpod/sqlite_state_internal.go @@ -16,6 +16,20 @@ import ( ) func (s *SQLiteState) migrateSchemaIfNecessary() (defErr error) { + // First, check if the DBConfig table exists + checkRow := s.conn.QueryRow("SELECT 1 FROM sqlite_master WHERE type='table' AND name='DBConfig';") + var check int + if err := checkRow.Scan(&check); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil + } + return fmt.Errorf("checking if DB config table exists: %w", err) + } + if check != 1 { + // Table does not exist, fresh database, no need to migrate. + return nil + } + row := s.conn.QueryRow("SELECT SchemaVersion FROM DBConfig;") var schemaVer int if err := row.Scan(&schemaVer); err != nil { @@ -24,6 +38,7 @@ func (s *SQLiteState) migrateSchemaIfNecessary() (defErr error) { // Schema was just created, so it has to be the latest. return nil } + return fmt.Errorf("scanning schema version from DB config: %w", err) } // If the schema version 0 or less, it's invalid