Files
DoGaMa-serv/internal/persistence/sqlite/store.go
T
codex 75d5504729
CI / validate (pull_request) Canceled after 3m32s
refactor(modules): load wasm modules from templates
2026-08-25 17:52:28 +02:00

119 lines
3.7 KiB
Go

// Package sqlite owns the SQLite implementation of application persistence.
package sqlite
import (
"context"
"database/sql"
_ "embed"
"fmt"
"strings"
_ "modernc.org/sqlite"
)
const driverName = "sqlite"
//go:embed schema.sql
var schema string
// Open opens a SQLite database and initializes a new database with the current schema.
func Open(ctx context.Context, path string) (*sql.DB, error) {
dsn := path
if path != ":memory:" && !strings.HasPrefix(path, "file:") {
dsn = "file:" + path
}
db, err := sql.Open(driverName, dsn)
if err != nil {
return nil, fmt.Errorf("open sqlite: %w", err)
}
db.SetMaxOpenConns(1)
if err := configure(ctx, db); err != nil {
_ = db.Close()
return nil, err
}
if err := initialize(ctx, db); err != nil {
_ = db.Close()
return nil, err
}
return db, nil
}
func configure(ctx context.Context, db *sql.DB) error {
for _, statement := range []string{
"PRAGMA foreign_keys = ON",
"PRAGMA journal_mode = WAL",
"PRAGMA busy_timeout = 5000",
} {
if _, err := db.ExecContext(ctx, statement); err != nil {
return fmt.Errorf("configure sqlite: %w", err)
}
}
return nil
}
func initialize(ctx context.Context, db *sql.DB) error {
var existing int
if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='system_state'").Scan(&existing); err != nil {
return fmt.Errorf("inspect sqlite schema: %w", err)
}
if existing == 1 {
if err := ensureDiagnosticSchema(ctx, db); err != nil {
return err
}
return ensureTemplateModuleSchema(ctx, db)
}
if _, err := db.ExecContext(ctx, schema); err != nil {
return fmt.Errorf("initialize sqlite schema: %w", err)
}
if err := ensureDiagnosticSchema(ctx, db); err != nil {
return err
}
return ensureTemplateModuleSchema(ctx, db)
}
// ensureTemplateModuleSchema preserves the complete module bundle with an
// immutable template snapshot for stores created before template-local modules.
func ensureTemplateModuleSchema(ctx context.Context, db *sql.DB) error {
rows, err := db.QueryContext(ctx, "PRAGMA table_info(template_versions)")
if err != nil {
return fmt.Errorf("inspect template module schema: %w", err)
}
defer rows.Close()
for rows.Next() {
var cid, notNull, primaryKey int
var name, typ string
var defaultValue any
if err := rows.Scan(&cid, &name, &typ, &notNull, &defaultValue, &primaryKey); err != nil {
return err
}
if name == "module_bundle" {
return nil
}
}
if err := rows.Err(); err != nil {
return err
}
if _, err := db.ExecContext(ctx, "ALTER TABLE template_versions ADD COLUMN module_bundle BLOB"); err != nil {
return fmt.Errorf("migrate template module schema: %w", err)
}
return nil
}
// This additive migration is safe for existing V1 databases and keeps the
// original embedded schema immutable for fresh installs.
func ensureDiagnosticSchema(ctx context.Context, db *sql.DB) error {
_, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS operation_diagnostics (
operation_id TEXT PRIMARY KEY REFERENCES instance_operations(id) ON DELETE CASCADE,
instance_id TEXT NOT NULL REFERENCES instances(id) ON DELETE CASCADE,
step TEXT NOT NULL,
error_code TEXT NOT NULL,
details TEXT NOT NULL,
created_at TEXT NOT NULL
); CREATE INDEX IF NOT EXISTS operation_diagnostics_instance_idx ON operation_diagnostics(instance_id, created_at DESC);
CREATE TABLE IF NOT EXISTS operation_steps (operation_id TEXT NOT NULL REFERENCES instance_operations(id) ON DELETE CASCADE, step_id TEXT NOT NULL, position INTEGER NOT NULL, status TEXT NOT NULL, PRIMARY KEY(operation_id, step_id));`)
if err != nil {
return fmt.Errorf("migrate diagnostic schema: %w", err)
}
return nil
}