package db import ( _ "embed" "errors" "fmt" "os" "github.com/jmoiron/sqlx" _ "github.com/mattn/go-sqlite3" "git.offline-twitter.com/offline-labs/gas-stack/pkg/textutils" ) type DBConfig struct { // // Tracks whether the DB connector has been initialized // is_initialized bool // The SQL schema of the database under management sql_schema *string // Database starts at version 0. First migration brings us to version 1 migrations *[]Migration version_number uint } func Init(schema *string, migrationsList *[]Migration) DBConfig { return DBConfig{ sql_schema: schema, migrations: migrationsList, version_number: uint(len(*migrationsList)), } } func (c DBConfig) Create(path string) (*sqlx.DB, error) { // First check if the path already exists _, err := os.Stat(path) if err == nil { return nil, ErrDatabaseAlreadyExists } else if !errors.Is(err, os.ErrNotExist) { return nil, fmt.Errorf("path error: %w", err) } // Create DB file fmt.Printf("Creating............. %s\n", path) db, err := sqlx.Open("sqlite3", path) if err != nil { return nil, fmt.Errorf("opening db: %w", err) } // Initialize schema if _, err = db.Exec("pragma foreign_keys=on; pragma journal_mode=WAL;"); err != nil { return nil, fmt.Errorf("running pragma statements: %w", err) } if _, err = db.Exec(*c.sql_schema); err != nil { return nil, fmt.Errorf("creating schema: %w", err) } return db, nil } func (c DBConfig) Connect(path string) (*sqlx.DB, error) { db, err := sqlx.Open("sqlite3", path) if err != nil { return nil, fmt.Errorf("opening db: %w", err) } if _, err = db.Exec("pragma foreign_keys=on; pragma journal_mode=WAL;"); err != nil { return nil, fmt.Errorf("running pragma statements: %w", err) } err = c.CheckAndUpdateVersion(db) return db, err } func (c DBConfig) CheckAndUpdateVersion(db *sqlx.DB) error { var version uint err := db.Get(&version, "select version from db_version") if err != nil { return fmt.Errorf("couldn't check database version: %w", err) } if version > c.version_number { return VersionMismatchError{c.version_number, version} } if c.version_number > version { fmt.Print(textutils.ColorYellow) fmt.Printf("================================================\n") fmt.Printf("Database version is out of date. Upgrading database from version %d to version %d!\n", version, c.version_number) fmt.Print(textutils.ColorReset) for i := version; i < c.version_number; i++ { m := (*c.migrations)[i] if err := m.Apply(db); err != nil { return fmt.Errorf("migration %d: %w", m.ID, err) } fmt.Print(textutils.ColorYellow) fmt.Printf("Now at database schema version %d.\n", m.ID) fmt.Print(textutils.ColorReset) } fmt.Print(textutils.ColorGreen) fmt.Printf("================================================\n") fmt.Printf("Database version has been upgraded to version %d.\n", c.version_number) fmt.Print(textutils.ColorReset) } return nil } type VersionMismatchError struct { EngineVersion uint DatabaseVersion uint } func (e VersionMismatchError) Error() string { return fmt.Sprintf( `This profile was created with database schema version %d, which is newer than this application's database schema version, %d. Please upgrade this application to a newer version to use this profile. Or downgrade the profile's schema version, somehow.`, e.DatabaseVersion, e.EngineVersion, ) }