package main import ( "database/sql" "fmt" "strings" _ "github.com/go-sql-driver/mysql" _ "modernc.org/sqlite" ) const sqliteSchema = ` CREATE TABLE IF NOT EXISTS lists ( id TEXT PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS sublists ( id INTEGER PRIMARY KEY AUTOINCREMENT, list_id TEXT NOT NULL REFERENCES lists(id), name TEXT NOT NULL, color TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, list_id TEXT NOT NULL REFERENCES lists(id), name TEXT NOT NULL, quantity INTEGER NOT NULL DEFAULT 1, done INTEGER NOT NULL DEFAULT 0, position REAL NOT NULL DEFAULT 0, sublist_id INTEGER REFERENCES sublists(id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );` const mysqlSchema = ` CREATE TABLE IF NOT EXISTS lists ( id VARCHAR(64) PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS sublists ( id INTEGER PRIMARY KEY AUTO_INCREMENT, list_id VARCHAR(64) NOT NULL, name VARCHAR(255) NOT NULL, color VARCHAR(32) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (list_id) REFERENCES lists(id) ); CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTO_INCREMENT, list_id VARCHAR(64) NOT NULL, name VARCHAR(255) NOT NULL, quantity INTEGER NOT NULL DEFAULT 1, done BOOLEAN NOT NULL DEFAULT FALSE, position DOUBLE NOT NULL DEFAULT 0, sublist_id INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (list_id) REFERENCES lists(id), FOREIGN KEY (sublist_id) REFERENCES sublists(id) );` func openDB(databaseURL string) (*sql.DB, error) { driver, dsn, found := strings.Cut(databaseURL, "://") if !found { return nil, fmt.Errorf("DATABASE_URL must be in the form driver://dsn, got %q", databaseURL) } var schema string switch driver { case "sqlite": schema = sqliteSchema case "mysql": driver = "mysql" schema = mysqlSchema default: return nil, fmt.Errorf("unsupported database driver %q, expected sqlite or mysql", driver) } db, err := sql.Open(driver, dsn) if err != nil { return nil, fmt.Errorf("opening database: %w", err) } if err := db.Ping(); err != nil { return nil, fmt.Errorf("connecting to database: %w", err) } if _, err := db.Exec(schema); err != nil { return nil, fmt.Errorf("running migration: %w", err) } // Best-effort: adds columns for databases created before they existed. // Fails harmlessly if a column is already there. db.Exec("ALTER TABLE items ADD COLUMN position REAL NOT NULL DEFAULT 0") db.Exec("ALTER TABLE items ADD COLUMN sublist_id INTEGER") return db, nil }