Files
kandev/apps/backend/internal/persistence/meta.go
T
2026-05-16 20:07:57 +01:00

101 lines
2.7 KiB
Go

package persistence
import (
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
)
const metaTableDDL = `
CREATE TABLE IF NOT EXISTS kandev_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL DEFAULT ''
)`
// ensureMetaTable creates the kandev_meta table if it does not exist.
func ensureMetaTable(db *sqlx.DB) error {
if _, err := db.Exec(metaTableDDL); err != nil {
return fmt.Errorf("create kandev_meta: %w", err)
}
return nil
}
// readKey returns the value for key, or "" when the key is absent.
func readKey(db *sqlx.DB, key string) (string, error) {
var value string
err := db.QueryRow(`SELECT value FROM kandev_meta WHERE key = ?`, key).Scan(&value)
if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
if err != nil {
return "", fmt.Errorf("read meta key %q: %w", key, err)
}
return value, nil
}
// writeKey upserts key=value into kandev_meta.
func writeKey(db *sqlx.DB, key, value string) error {
_, err := db.Exec(
`INSERT INTO kandev_meta (key, value) VALUES (?, ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
key, value,
)
if err != nil {
return fmt.Errorf("write meta key %q: %w", key, err)
}
return nil
}
// WriteVersion records currentVersion as the binary version that last
// successfully completed boot against this DB. Call this only after every
// repository initSchema has succeeded.
func WriteVersion(db *sqlx.DB, version string) error {
if err := writeKey(db, "kandev_version", version); err != nil {
return err
}
return nil
}
// hasUserTables returns true when the DB contains at least one table that is
// not part of the SQLite internal schema and not kandev_meta itself. This
// distinguishes a genuinely fresh DB from a pre-meta DB being upgraded.
func hasUserTables(db *sqlx.DB) (bool, error) {
var count int
err := db.QueryRow(`
SELECT COUNT(*) FROM sqlite_master
WHERE type = 'table'
AND name NOT LIKE 'sqlite_%'
AND name != 'kandev_meta'
`).Scan(&count)
if err != nil {
return false, fmt.Errorf("check user tables: %w", err)
}
return count > 0, nil
}
// shouldBackup returns true when a pre-migration backup should be taken.
//
// - fresh DB with no user tables: no backup (clean first install)
// - any tables exist but stored version is empty: pre-meta upgrade, back up
// - stored version differs from current binary version: upgrade, back up
// - stored version matches current: same release re-launched, no backup
func shouldBackup(stored, current string, userTables bool) bool {
if stored == "" && !userTables {
return false // fresh install
}
if stored != current {
return true // upgrade (or pre-meta DB)
}
return false
}
// fallback returns s if non-empty, otherwise def.
func fallback(s, def string) string {
if s != "" {
return s
}
return def
}