Files
web/cmssqlite/schema.go
T

54 lines
3.6 KiB
Go

// SPDX-License-Identifier: MPL-2.0
// Package cmssqlite stores cms values in an application's SQLite transaction.
// Schema installation is explicit. Callers own database opening, migration
// versions, authorization and audits. Every mutation must use a caller-owned
// transaction, committing its domain change and audit together; never use a
// pooled *sql.DB for multi-statement writes. Namespaces isolate application data.
package cmssqlite
import (
"context"
"database/sql"
"gamertan.com/web/cms"
)
type Queryer interface {
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
// Reader may use a database or read transaction. Writers below require *sql.Tx.
type Reader struct {
db Queryer
scope string
}
func New(db Queryer, scope string) (*Reader, error) {
if db == nil || !cms.ValidID(scope) {
return nil, cms.ErrInvalid
}
return &Reader{db: db, scope: scope}, nil
}
// CreateSchema must be called from the application's explicit migration.
// It never changes an existing publishing schema or starts a transaction.
func CreateSchema(ctx context.Context, tx *sql.Tx) error {
for _, statement := range []string{
`CREATE TABLE IF NOT EXISTS gwf_cms_taxonomies (scope TEXT NOT NULL,id TEXT NOT NULL,slug TEXT NOT NULL,name TEXT NOT NULL,description TEXT NOT NULL,revision INTEGER NOT NULL CHECK(revision>0),active INTEGER NOT NULL CHECK(active IN (0,1)),PRIMARY KEY(scope,id),UNIQUE(scope,slug))`,
`CREATE TABLE IF NOT EXISTS gwf_cms_terms (scope TEXT NOT NULL,id TEXT NOT NULL,taxonomy_id TEXT NOT NULL,slug TEXT NOT NULL,name TEXT NOT NULL,description TEXT NOT NULL,revision INTEGER NOT NULL CHECK(revision>0),active INTEGER NOT NULL CHECK(active IN (0,1)),PRIMARY KEY(scope,id),UNIQUE(scope,taxonomy_id,slug),FOREIGN KEY(scope,taxonomy_id) REFERENCES gwf_cms_taxonomies(scope,id))`,
`CREATE TABLE IF NOT EXISTS gwf_cms_term_slugs (scope TEXT NOT NULL,taxonomy_id TEXT NOT NULL,slug TEXT NOT NULL,term_id TEXT NOT NULL,PRIMARY KEY(scope,taxonomy_id,slug),FOREIGN KEY(scope,term_id) REFERENCES gwf_cms_terms(scope,id))`,
`CREATE TABLE IF NOT EXISTS gwf_cms_resources (scope TEXT NOT NULL,kind TEXT NOT NULL,id TEXT NOT NULL,published_revision INTEGER NOT NULL DEFAULT 0 CHECK(published_revision>=0),PRIMARY KEY(scope,kind,id))`,
`CREATE TABLE IF NOT EXISTS gwf_cms_associations (scope TEXT NOT NULL,kind TEXT NOT NULL,id TEXT NOT NULL,revision INTEGER NOT NULL CHECK(revision>0),document TEXT NOT NULL,PRIMARY KEY(scope,kind,id,revision),FOREIGN KEY(scope,kind,id) REFERENCES gwf_cms_resources(scope,kind,id))`,
`CREATE TABLE IF NOT EXISTS gwf_cms_memberships (scope TEXT NOT NULL,kind TEXT NOT NULL,id TEXT NOT NULL,revision INTEGER NOT NULL,term_id TEXT NOT NULL,PRIMARY KEY(scope,kind,id,revision,term_id),FOREIGN KEY(scope,kind,id,revision) REFERENCES gwf_cms_associations(scope,kind,id,revision),FOREIGN KEY(scope,term_id) REFERENCES gwf_cms_terms(scope,id))`,
`CREATE INDEX IF NOT EXISTS gwf_cms_memberships_term ON gwf_cms_memberships(scope,term_id,kind,id,revision)`,
`CREATE TABLE IF NOT EXISTS gwf_cms_links (scope TEXT NOT NULL,kind TEXT NOT NULL,id TEXT NOT NULL,revision INTEGER NOT NULL,target_kind TEXT NOT NULL,target_id TEXT NOT NULL,PRIMARY KEY(scope,kind,id,revision,target_kind,target_id),FOREIGN KEY(scope,kind,id,revision) REFERENCES gwf_cms_associations(scope,kind,id,revision))`,
`CREATE INDEX IF NOT EXISTS gwf_cms_links_target ON gwf_cms_links(scope,target_kind,target_id,kind,id,revision)`,
} {
if _, err := tx.ExecContext(ctx, statement); err != nil {
return err
}
}
return nil
}