use rusqlite::{params, Connection};
pub const NOTE_OBSERVATION: &str = "11111111-1111-4111-8111-000000000001";
pub const NOTE_TASK: &str = "11111111-1111-4111-8111-000000000002";
pub const NOTE_DELETED: &str = "11111111-1111-4111-8111-000000000003";
pub const NOTE_TARGET_HOLDER: &str = "11111111-1111-4111-8111-000000000004";
pub const ENTITY: &str = "22222222-2222-4222-8222-000000000001";
pub const EDGE: &str = "33333333-3333-4333-8333-000000000001";
pub const EDGE_TRIPLE_HOLDER: &str = "33333333-3333-4333-8333-000000000002";
pub const ATOM: &str = "44444444-4444-4444-8444-000000000001";
pub const ATOM_SLUG_HOLDER: &str = "44444444-4444-4444-8444-000000000002";
pub const SECTION: &str = "55555555-5555-4555-8555-000000000001";
const STALE_FTS_ROWID: i64 = 90_001;
const T: i64 = 1_700_000_000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FixtureSpec {
pub source: String,
pub kg: String,
pub gtd: String,
pub knowledge: String,
pub collisions: bool,
pub stale_fts: bool,
}
impl FixtureSpec {
pub fn new(tenant: &str) -> Self {
Self {
source: tenant.to_string(),
kg: format!("{tenant}.kg"),
gtd: format!("{tenant}.gtd"),
knowledge: format!("{tenant}.knowledge"),
collisions: true,
stale_fts: true,
}
}
pub fn movable(tenant: &str) -> Self {
Self {
collisions: false,
stale_fts: false,
..Self::new(tenant)
}
}
}
impl Default for FixtureSpec {
fn default() -> Self {
Self::new("tenant:tnt_fixture")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fixture {
pub spec: FixtureSpec,
pub colliding_note: Option<&'static str>,
pub colliding_atom: Option<&'static str>,
pub colliding_edge_triple: Option<&'static str>,
pub stale_fts_subject: Option<&'static str>,
}
fn note(
conn: &Connection,
id: &str,
ns: &str,
kind: &str,
key: Option<&str>,
content: &str,
deleted: bool,
) -> rusqlite::Result<()> {
conn.execute(
"INSERT INTO notes (id, namespace, kind, key, content, created_at, updated_at, deleted_at) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?6, ?7)",
params![id, ns, kind, key, content, T, deleted.then_some(T)],
)?;
index_row(conn, "fts_notes", id, ns, kind, content)
}
fn index_row(
conn: &Connection,
table: &str,
id: &str,
ns: &str,
kind: &str,
body: &str,
) -> rusqlite::Result<()> {
conn.execute(
&format!(
"INSERT INTO {table} \
(subject_id, kind, title, body, tags, namespace, metadata, updated_at, record_kind) \
VALUES (?1, ?2, '', ?3, '[]', ?4, NULL, ?5, ?2)"
),
params![id, kind, body, ns, T],
)?;
conn.execute(
&format!(
"INSERT OR REPLACE INTO {table}_rowids (namespace, subject_id, rowid) \
VALUES (?1, ?2, last_insert_rowid())"
),
params![ns, id],
)?;
Ok(())
}
pub fn build(conn: &Connection, spec: &FixtureSpec) -> rusqlite::Result<Fixture> {
note(
conn,
NOTE_OBSERVATION,
&spec.source,
"observation",
Some("shared-key"),
"the ordinary move",
false,
)?;
note(
conn,
NOTE_TASK,
&spec.source,
"task",
None,
"a second target pack",
false,
)?;
note(
conn,
NOTE_DELETED,
&spec.source,
"observation",
Some("shared-key"),
"soft deleted",
true,
)?;
conn.execute(
"INSERT INTO entities (id, namespace, kind, name, created_at, updated_at) \
VALUES (?1, ?2, ?3, ?4, ?5, ?5)",
params![ENTITY, &spec.source, "concept", "fixture concept", T],
)?;
index_row(
conn,
"fts_entities",
ENTITY,
&spec.source,
"concept",
"fixture concept",
)?;
conn.execute(
"INSERT INTO graph_edges (id, namespace, source_id, target_id, relation, created_at, updated_at) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?6)",
params![EDGE, &spec.source, ENTITY, NOTE_OBSERVATION, "annotates", T],
)?;
conn.execute(
"INSERT INTO knowledge_atoms (id, namespace, slug, name, created_at, updated_at) \
VALUES (?1, ?2, ?3, ?4, ?5, ?5)",
params![ATOM, &spec.source, "shared-slug", "fixture atom", T],
)?;
conn.execute(
"INSERT INTO knowledge_sections \
(id, atom_id, namespace, section_type, heading, content, content_hash, created_at, updated_at) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?8)",
params![SECTION, ATOM, &spec.source, "body", "a heading", "section body", "hash-1", T],
)?;
let mut fixture = Fixture {
spec: spec.clone(),
colliding_note: None,
colliding_atom: None,
colliding_edge_triple: None,
stale_fts_subject: None,
};
if spec.collisions {
note(
conn,
NOTE_TARGET_HOLDER,
&spec.kg,
"observation",
Some("shared-key"),
"already here",
false,
)?;
fixture.colliding_note = Some(NOTE_TARGET_HOLDER);
conn.execute(
"INSERT INTO knowledge_atoms (id, namespace, slug, name, created_at, updated_at) \
VALUES (?1, ?2, ?3, ?4, ?5, ?5)",
params![
ATOM_SLUG_HOLDER,
&spec.knowledge,
"shared-slug",
"already here",
T
],
)?;
fixture.colliding_atom = Some(ATOM_SLUG_HOLDER);
conn.execute(
"INSERT INTO graph_edges (id, namespace, source_id, target_id, relation, created_at, updated_at) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?6)",
params![EDGE_TRIPLE_HOLDER, &spec.kg, ENTITY, NOTE_OBSERVATION, "annotates", T],
)?;
fixture.colliding_edge_triple = Some(EDGE_TRIPLE_HOLDER);
}
if spec.stale_fts {
conn.execute(
"INSERT INTO fts_notes_rowids (namespace, subject_id, rowid) VALUES (?1, ?2, ?3)",
params![&spec.kg, NOTE_OBSERVATION, STALE_FTS_ROWID],
)?;
fixture.stale_fts_subject = Some(NOTE_OBSERVATION);
}
Ok(fixture)
}
pub fn routes(spec: &FixtureSpec) -> Vec<(&'static str, String)> {
vec![
("note:observation", spec.kg.clone()),
("note:task", spec.gtd.clone()),
("entity:concept", spec.kg.clone()),
("edge", spec.kg.clone()),
("atom", spec.knowledge.clone()),
]
}
pub fn add_vector_row(
conn: &Connection,
model_key: &str,
namespace: &str,
subject_id: &str,
) -> rusqlite::Result<()> {
conn.execute_batch(&format!(
"CREATE VIRTUAL TABLE IF NOT EXISTS vec_{model_key} USING vec0(\
subject_id TEXT PRIMARY KEY, \
namespace TEXT NOT NULL, \
kind TEXT NOT NULL, \
field TEXT NOT NULL, \
embedding_model TEXT NOT NULL, \
embedding float[4] distance_metric=cosine)"
))?;
conn.execute(
&format!(
"INSERT INTO vec_{model_key} \
(subject_id, namespace, kind, field, embedding_model, embedding) \
VALUES (?1, ?2, ?3, ?4, ?5, '[0.1, 0.2, 0.3, 0.4]')"
),
params![subject_id, namespace, "note", "content", model_key],
)?;
Ok(())
}