Files
meta-core/internal/storage/schema_version.go
T

77 lines
2.6 KiB
Go

package storage
import (
"context"
"database/sql"
"errors"
"fmt"
"strconv"
"github.com/metazla/meta-core/internal/kv"
)
// SchemaVersion is the truth-database layout this build of meta-core expects.
//
// 2 — v1.x: one kv table holding records, the CID index and UDL cells
// 3 — v2.0.0: the claims model (truth: principal, claim, cid_group, meta)
//
// Bump only when a layout change can't be detected from the data itself. Each
// bump is a flag day: there is no migration code in meta-core (the one-off
// v2 → v3 conversion of watch.nsl.sh is a manual tool, tools/claims-migrate).
const SchemaVersion = 3
// SchemaVersionKey is the truth `meta` row recording SchemaVersion.
const SchemaVersionKey = "schema-version"
// EnsureSchemaVersion verifies the truth database matches the expected
// version. Called once at startup, before Start.
//
// - a v1.x store (the old kv table, which the default path still points at)
// → refuse
// - sentinel present and matching → OK
// - sentinel missing and no claims → fresh store, stamp it
// - sentinel missing with claims, or mismatched → refuse
func (c *Client) EnsureSchemaVersion(expected int) error {
c.mu.RLock()
truth := c.truth
c.mu.RUnlock()
if truth == nil {
return fmt.Errorf("not connected")
}
ctx, cancel := context.WithTimeout(context.Background(), opTimeout)
defer cancel()
return truth.Update(ctx, func(tx *kv.Tx) error {
var name string
err := tx.QueryRow(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'kv'`).Scan(&name)
if err == nil {
return fmt.Errorf(
"%s holds a meta-core v1.x store (schema version 2, the kv table). v2.0.0 "+
"does not migrate it: convert it with tools/claims-migrate, or move it "+
"away to start empty", truth.Path())
}
if !errors.Is(err, sql.ErrNoRows) {
return err
}
var have string
err = tx.QueryRow(`SELECT value FROM meta WHERE name = ?`, SchemaVersionKey).Scan(&have)
if errors.Is(err, sql.ErrNoRows) {
var n int64
if err := tx.QueryRow(`SELECT count(*) FROM claim`).Scan(&n); err != nil {
return err
}
if n > 0 {
return fmt.Errorf("truth database has %d claims but no %q sentinel; refusing to guess its layout", n, SchemaVersionKey)
}
_, err := tx.Exec(`INSERT INTO meta(name, value) VALUES(?, ?)`, SchemaVersionKey, strconv.Itoa(expected))
return err
}
if err != nil {
return fmt.Errorf("get schema version: %w", err)
}
if have == strconv.Itoa(expected) {
return nil
}
return fmt.Errorf("truth schema version is %q, this build of meta-core expects %d", have, expected)
})
}