Files
fn_registry/fn_operations/db.go
T
egutierrez 67401cb967 feat: fn_operations library — entities, relations, types_snapshot con FTS y ciclos
Paquete Go completo con modelos (Entity, Relation, RelationInput, TypeSnapshot),
DB SQLite con WAL + FTS5 en entities, CRUD para las 4 tablas, validacion de
integridad, deteccion de ciclos solo en relaciones causales (via != ''), y
operaciones de alto nivel con snapshot automatico de tipos del registry.
9 tests, todos pasan.
2026-03-28 04:37:50 +01:00

141 lines
4.1 KiB
Go

package fn_operations
import (
"database/sql"
"fmt"
"os"
"path/filepath"
_ "github.com/mattn/go-sqlite3"
)
const schemaSQL = `
CREATE TABLE IF NOT EXISTS types_snapshot (
id TEXT PRIMARY KEY,
version TEXT NOT NULL DEFAULT '1.0.0',
lang TEXT NOT NULL,
algebraic TEXT NOT NULL CHECK(algebraic IN ('product','sum')),
definition TEXT NOT NULL DEFAULT '',
description TEXT NOT NULL DEFAULT '',
snapped_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS entities (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
type_ref TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','stale','corrupted','archived')),
description TEXT NOT NULL DEFAULT '',
domain TEXT NOT NULL DEFAULT '',
tags TEXT NOT NULL DEFAULT '[]',
source TEXT NOT NULL,
metadata TEXT NOT NULL DEFAULT '{}',
notes TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS relations (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
from_entity TEXT NOT NULL DEFAULT '',
to_entity TEXT NOT NULL,
via TEXT NOT NULL DEFAULT '',
description TEXT NOT NULL DEFAULT '',
purity TEXT NOT NULL DEFAULT '' CHECK(purity IN ('','pure','impure')),
direction TEXT NOT NULL DEFAULT 'unidirectional' CHECK(direction IN ('unidirectional','bidirectional','inverse')),
weight REAL,
status TEXT NOT NULL DEFAULT 'designed' CHECK(status IN ('designed','implemented','tested','running','deprecated')),
started_at TEXT,
ended_at TEXT,
"order" INTEGER,
tags TEXT NOT NULL DEFAULT '[]',
notes TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS relation_inputs (
id TEXT PRIMARY KEY,
relation_id TEXT NOT NULL REFERENCES relations(id) ON DELETE CASCADE,
entity_id TEXT NOT NULL REFERENCES entities(id),
role TEXT NOT NULL,
"order" INTEGER
);
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
id,
name,
description,
tags,
domain,
content='entities',
content_rowid='rowid'
);
-- Triggers to keep entities FTS in sync
CREATE TRIGGER IF NOT EXISTS entities_ai AFTER INSERT ON entities BEGIN
INSERT INTO entities_fts(rowid, id, name, description, tags, domain)
VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.domain);
END;
CREATE TRIGGER IF NOT EXISTS entities_ad AFTER DELETE ON entities BEGIN
INSERT INTO entities_fts(entities_fts, rowid, id, name, description, tags, domain)
VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.domain);
END;
CREATE TRIGGER IF NOT EXISTS entities_au AFTER UPDATE ON entities BEGIN
INSERT INTO entities_fts(entities_fts, rowid, id, name, description, tags, domain)
VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.domain);
INSERT INTO entities_fts(rowid, id, name, description, tags, domain)
VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.domain);
END;
`
// DB wraps a SQLite connection for an operations database.
type DB struct {
conn *sql.DB
path string
}
// Open opens or creates an operations database at the given path.
func Open(path string) (*DB, error) {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0o755); err != nil {
return nil, fmt.Errorf("creating db directory: %w", err)
}
conn, err := sql.Open("sqlite3", path+"?_foreign_keys=on")
if err != nil {
return nil, fmt.Errorf("opening database: %w", err)
}
if _, err := conn.Exec("PRAGMA journal_mode=WAL"); err != nil {
conn.Close()
return nil, fmt.Errorf("setting WAL mode: %w", err)
}
if _, err := conn.Exec(schemaSQL); err != nil {
conn.Close()
return nil, fmt.Errorf("applying schema: %w", err)
}
return &DB{conn: conn, path: path}, nil
}
// Conn returns the underlying sql.DB for transaction use.
func (db *DB) Conn() *sql.DB {
return db.conn
}
// Close closes the database connection.
func (db *DB) Close() error {
return db.conn.Close()
}
// Drop removes the database file.
func (db *DB) Drop() error {
db.Close()
return os.Remove(db.path)
}