#![allow(dead_code)]
use macrame::schema::ddl;
const TS_GLOB: &str = "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9][0-9][0-9][0-9]Z";
const V12_ONLY_TRIGGERS: &[&str] = &[
"trg_concepts_cross_lineage",
"trg_concepts_branch_immutable",
"trg_branches_frozen_update",
"trg_branches_frozen_delete",
];
const POST_V12_TRIGGERS: &[&str] = &["trg_txlog_mark_gap"];
const V12_CHANGED_TRIGGERS: &[&str] = &[
"trg_links_current_sync",
"trg_links_single_open",
"trg_concepts_log_insert",
"trg_concepts_log_update",
"trg_links_log_insert",
];
pub fn concepts_v11() -> String {
format!(
"CREATE TABLE IF NOT EXISTS concepts (
rowid_pk INTEGER PRIMARY KEY,
id TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
embedding_model TEXT,
valid_from TEXT NOT NULL,
valid_to TEXT NOT NULL DEFAULT '9999-12-31T23:59:59.999999Z',
recorded_at TEXT NOT NULL,
retired INTEGER NOT NULL DEFAULT 0,
CHECK (valid_from GLOB '{TS_GLOB}' AND valid_to GLOB '{TS_GLOB}' \
AND recorded_at GLOB '{TS_GLOB}' AND 1)
);"
)
}
pub fn links_v11() -> String {
format!(
"CREATE TABLE IF NOT EXISTS links (
source_id TEXT NOT NULL REFERENCES concepts(id),
target_id TEXT NOT NULL REFERENCES concepts(id),
edge_type TEXT NOT NULL,
valid_from TEXT NOT NULL,
recorded_at TEXT NOT NULL,
valid_to TEXT NOT NULL DEFAULT '9999-12-31T23:59:59.999999Z',
weight REAL NOT NULL DEFAULT 1.0,
properties TEXT NOT NULL DEFAULT '{{}}',
PRIMARY KEY (source_id, target_id, edge_type, valid_from, recorded_at),
CHECK (weight >= 0.0 AND weight < 9e999 AND typeof(weight) = 'real'),
CHECK (valid_from GLOB '{TS_GLOB}' AND valid_to GLOB '{TS_GLOB}' \
AND recorded_at GLOB '{TS_GLOB}' AND 1)
);"
)
}
pub fn links_current_v11() -> String {
format!(
"CREATE TABLE IF NOT EXISTS links_current (
source_id TEXT NOT NULL,
target_id TEXT NOT NULL,
edge_type TEXT NOT NULL,
valid_from TEXT NOT NULL,
valid_to TEXT NOT NULL,
weight REAL NOT NULL,
properties TEXT NOT NULL,
recorded_at TEXT NOT NULL,
PRIMARY KEY (source_id, target_id, edge_type, valid_from),
CHECK (valid_from GLOB '{TS_GLOB}' AND valid_to GLOB '{TS_GLOB}' \
AND recorded_at GLOB '{TS_GLOB}' AND 1)
);"
)
}
pub fn transaction_log_v11() -> String {
format!(
"CREATE TABLE IF NOT EXISTS transaction_log (
seq_id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT NOT NULL,
entity_id TEXT NOT NULL,
operation TEXT NOT NULL,
payload TEXT NOT NULL,
recorded_at TEXT NOT NULL,
CHECK (recorded_at GLOB '{TS_GLOB}' AND 1)
);"
)
}
pub const TRIGGERS_V11: &[&str] = &[
r#"
CREATE TRIGGER IF NOT EXISTS trg_links_current_sync
AFTER INSERT ON links
BEGIN
INSERT INTO links_current
(source_id, target_id, edge_type, valid_from, valid_to,
weight, properties, recorded_at)
VALUES
(NEW.source_id, NEW.target_id, NEW.edge_type, NEW.valid_from,
NEW.valid_to, NEW.weight, NEW.properties, NEW.recorded_at)
ON CONFLICT(source_id, target_id, edge_type, valid_from) DO UPDATE SET
valid_to = excluded.valid_to,
weight = excluded.weight,
properties = excluded.properties,
recorded_at = excluded.recorded_at
WHERE excluded.recorded_at > links_current.recorded_at;
END;
"#,
r#"
CREATE TRIGGER IF NOT EXISTS trg_links_single_open
BEFORE INSERT ON links
WHEN NEW.valid_to = '9999-12-31T23:59:59.999999Z'
AND EXISTS (
SELECT 1 FROM links_current
WHERE source_id = NEW.source_id
AND target_id = NEW.target_id
AND edge_type = NEW.edge_type
AND valid_from <> NEW.valid_from
AND valid_to = '9999-12-31T23:59:59.999999Z'
)
BEGIN
SELECT RAISE(ABORT, 'macrame: edge already has an open interval; retire it first');
END;
"#,
r#"
CREATE TRIGGER IF NOT EXISTS trg_concepts_log_insert
AFTER INSERT ON concepts
WHEN NOT EXISTS (
SELECT 1 FROM sqlite_master
WHERE type = 'table' AND name = 'macrame_archive_session'
)
BEGIN
INSERT INTO transaction_log (table_name, entity_id, operation, payload, recorded_at)
VALUES ('concepts', NEW.id, 'I',
json_object('v', 2, 'title', NEW.title, 'content', NEW.content,
'valid_from', NEW.valid_from, 'valid_to', NEW.valid_to,
'retired', NEW.retired,
'embedding_model', NEW.embedding_model),
NEW.recorded_at);
END;
"#,
r#"
CREATE TRIGGER IF NOT EXISTS trg_concepts_log_update
AFTER UPDATE ON concepts
BEGIN
INSERT INTO transaction_log (table_name, entity_id, operation, payload, recorded_at)
VALUES ('concepts', NEW.id, 'U',
json_object('v', 2, 'title', NEW.title, 'content', NEW.content,
'valid_from', NEW.valid_from, 'valid_to', NEW.valid_to,
'retired', NEW.retired,
'embedding_model', NEW.embedding_model),
NEW.recorded_at);
END;
"#,
r#"
CREATE TRIGGER IF NOT EXISTS trg_links_log_insert
AFTER INSERT ON links
BEGIN
INSERT INTO transaction_log (table_name, entity_id, operation, payload, recorded_at)
VALUES ('links',
NEW.source_id || '|' || NEW.target_id || '|' || NEW.edge_type || '|' || NEW.valid_from,
'I',
json_object('v', 1, 'source_id', NEW.source_id, 'target_id', NEW.target_id,
'edge_type', NEW.edge_type, 'valid_from', NEW.valid_from,
'valid_to', NEW.valid_to, 'weight', NEW.weight,
'properties', json(NEW.properties)),
NEW.recorded_at);
END;
"#,
];
pub fn triggers_v11() -> Vec<&'static str> {
ddl::CREATE_TRIGGERS
.iter()
.copied()
.filter(|t| {
!V12_ONLY_TRIGGERS.iter().any(|n| t.contains(n))
&& !V12_CHANGED_TRIGGERS.iter().any(|n| t.contains(n))
&& !POST_V12_TRIGGERS.iter().any(|n| t.contains(n))
})
.chain(TRIGGERS_V11.iter().copied())
.collect()
}
pub fn tables_v11() -> Vec<String> {
vec![
concepts_v11(),
links_v11(),
links_current_v11(),
transaction_log_v11(),
]
}
pub fn indices_v11() -> Vec<&'static str> {
ddl::CREATE_INDICES
.iter()
.copied()
.filter(|sql| {
!sql.contains("idx_lc_lineage_cut")
&& !sql.contains("idx_txlog_fold_partition")
&& !sql.contains("idx_links_branch")
&& !sql.contains("idx_txlog_branch")
})
.collect()
}
pub async fn v11_schema(conn: &libsql::Connection) {
for table in tables_v11() {
conn.execute(&table, ()).await.unwrap();
}
conn.execute(ddl::CREATE_ANALYTICS_ANNOTATIONS_TABLE, ())
.await
.unwrap();
conn.execute(ddl::CREATE_CONCEPTS_FTS, ()).await.unwrap();
for index_ddl in indices_v11() {
conn.execute(index_ddl, ()).await.unwrap();
}
for trigger_ddl in triggers_v11() {
conn.execute(trigger_ddl, ()).await.unwrap();
}
}
pub async fn wind_back_to_v11(conn: &libsql::Connection) {
for trigger in V12_ONLY_TRIGGERS
.iter()
.chain(V12_CHANGED_TRIGGERS)
.chain(POST_V12_TRIGGERS)
{
conn.execute(&format!("DROP TRIGGER IF EXISTS {trigger}"), ())
.await
.unwrap();
}
conn.execute("DROP TABLE IF EXISTS log_integrity", ())
.await
.unwrap();
conn.execute("DROP INDEX IF EXISTS idx_txlog_fold_partition", ())
.await
.unwrap();
for index in ["idx_links_branch", "idx_txlog_branch"] {
conn.execute(&format!("DROP INDEX IF EXISTS {index}"), ())
.await
.unwrap();
}
for table in ["concepts", "transaction_log"] {
conn.execute(&format!("ALTER TABLE {table} DROP COLUMN branch_id"), ())
.await
.unwrap_or_else(|e| panic!("DROP COLUMN on {table}: {e}"));
}
conn.execute("ALTER TABLE links RENAME TO links_wound_back", ())
.await
.unwrap();
conn.execute("DROP TRIGGER IF EXISTS trg_links_guard_delete", ())
.await
.unwrap();
conn.execute(&links_v11(), ()).await.unwrap();
conn.execute(
"INSERT INTO links (source_id, target_id, edge_type, valid_from, \
recorded_at, valid_to, weight, properties) \
SELECT source_id, target_id, edge_type, valid_from, recorded_at, \
valid_to, weight, properties FROM links_wound_back",
(),
)
.await
.unwrap();
conn.execute("DROP TABLE links_wound_back", ())
.await
.unwrap();
conn.execute(ddl::CREATE_LINKS_GUARD_DELETE, ())
.await
.unwrap();
for index in [
"CREATE INDEX idx_links_recorded_at ON links (recorded_at);",
"CREATE INDEX idx_links_target ON links (target_id);",
] {
conn.execute(index, ()).await.unwrap();
}
conn.execute("DROP TABLE links_current", ()).await.unwrap();
conn.execute(&links_current_v11(), ()).await.unwrap();
for index in [
"CREATE INDEX idx_lc_traversal_cover ON links_current \
(source_id, valid_from, valid_to, weight, edge_type, target_id);",
"CREATE INDEX idx_lc_open_interval ON links_current \
(source_id, target_id, edge_type, valid_to, valid_from);",
] {
conn.execute(index, ()).await.unwrap();
}
conn.execute("DROP TABLE branches", ()).await.unwrap();
for body in TRIGGERS_V11 {
conn.execute(body, ()).await.unwrap();
}
conn.execute(
"INSERT INTO links_current (source_id, target_id, edge_type, valid_from, \
valid_to, weight, properties, recorded_at) \
SELECT source_id, target_id, edge_type, valid_from, valid_to, weight, \
properties, recorded_at FROM links \
WHERE true ON CONFLICT DO NOTHING",
(),
)
.await
.unwrap();
}