use crate::{
StoreError,
error::database_error,
schema::{
Layout,
layout::{SchemaObject, compare, objects_blocking},
},
};
use rusqlite::Connection;
use std::path::Path;
pub const RUN_STORE_VERSION: i64 = 1;
const UNSTAMPED_VERSION: i64 = 0;
const BINDING: &str = "CREATE TABLE run_binding (
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
workspace_id TEXT NOT NULL,
lineage_id TEXT NOT NULL,
archived INTEGER NOT NULL CHECK (archived IN (0, 1))
)";
const RUNS: &str = "CREATE TABLE runs (
run_id TEXT PRIMARY KEY,
work_id TEXT NOT NULL,
record_json TEXT NOT NULL
)";
const LIFECYCLE: &str = "CREATE TABLE run_lifecycle (
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
event_id TEXT NOT NULL UNIQUE,
run_id TEXT NOT NULL REFERENCES runs(run_id),
state TEXT NOT NULL,
detail TEXT,
observed_at TEXT,
recorded_by_json TEXT NOT NULL,
recorded_at TEXT NOT NULL
)";
const LIFECYCLE_BY_RUN: &str =
"CREATE INDEX run_lifecycle_by_run ON run_lifecycle(run_id, sequence)";
const ACTIVITY: &str = "CREATE TABLE run_activity (
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT NOT NULL REFERENCES runs(run_id),
source_sequence INTEGER NOT NULL,
kind TEXT NOT NULL,
text TEXT,
truncated INTEGER NOT NULL CHECK (truncated IN (0, 1)),
text_digest TEXT,
observed_at TEXT,
recorded_by_json TEXT NOT NULL,
recorded_at TEXT NOT NULL,
UNIQUE (run_id, source_sequence)
)";
const ACTIVITY_BY_RUN: &str = "CREATE INDEX run_activity_by_run ON run_activity(run_id, sequence)";
const TALLY: &str = "CREATE TABLE run_activity_tally (
run_id TEXT PRIMARY KEY REFERENCES runs(run_id),
recorded INTEGER NOT NULL,
last_kind TEXT NOT NULL,
high_water INTEGER NOT NULL,
last_at TEXT NOT NULL
)";
const RETENTION: &str = "CREATE TABLE run_activity_retention (
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
pruned_through INTEGER NOT NULL
)";
const LINKS: &str = "CREATE TABLE run_links (
operation_id TEXT PRIMARY KEY,
run_id TEXT NOT NULL REFERENCES runs(run_id),
work_id TEXT NOT NULL,
linked_by_json TEXT NOT NULL,
linked_at TEXT NOT NULL
)";
const FORKS: &str = "CREATE TABLE run_lineage_forks (
lineage_id TEXT PRIMARY KEY,
parent_lineage_id TEXT NOT NULL,
revision INTEGER NOT NULL
)";
const SEQUENCE: &str = "CREATE TABLE sqlite_sequence(name,seq)";
fn expected() -> Vec<SchemaObject> {
let table = |name: &str, sql| SchemaObject::new("table", name, name, Some(sql));
let index = |name: &str, on: &str, sql| SchemaObject::new("index", name, on, Some(sql));
let automatic =
|on: &str| SchemaObject::new("index", &format!("sqlite_autoindex_{on}_1"), on, None);
let mut objects = vec![
table("run_binding", BINDING),
table("runs", RUNS),
table("run_lifecycle", LIFECYCLE),
table("run_activity", ACTIVITY),
table("run_activity_tally", TALLY),
table("run_activity_retention", RETENTION),
table("run_links", LINKS),
table("run_lineage_forks", FORKS),
table("sqlite_sequence", SEQUENCE),
index("run_lifecycle_by_run", "run_lifecycle", LIFECYCLE_BY_RUN),
index("run_activity_by_run", "run_activity", ACTIVITY_BY_RUN),
automatic("runs"),
automatic("run_lifecycle"),
automatic("run_activity"),
automatic("run_activity_tally"),
automatic("run_links"),
automatic("run_lineage_forks"),
];
objects.sort();
objects
}
pub(super) fn check_blocking(connection: &Connection, path: &Path) -> Result<Layout, StoreError> {
let found: i64 = connection
.query_row("PRAGMA user_version", [], |row| row.get(0))
.map_err(database_error(path, "read run store version"))?;
let objects = objects_blocking(connection, path)?;
match found {
UNSTAMPED_VERSION if objects.is_empty() => Ok(Layout::Empty),
UNSTAMPED_VERSION => Err(StoreError::UnrecognizedSchema {
path: path.to_path_buf(),
detail: "not a DPM run store: it has tables but no run store version".into(),
}),
RUN_STORE_VERSION => {
compare(path, found, &objects, expected())?;
Ok(Layout::Current)
}
_ => Err(StoreError::UnsupportedSchemaVersion {
path: path.to_path_buf(),
found,
supported: RUN_STORE_VERSION,
}),
}
}
pub(super) fn create_blocking(connection: &Connection, path: &Path) -> Result<(), StoreError> {
connection
.execute_batch(&format!(
"{BINDING}; {RUNS}; {LIFECYCLE}; {LIFECYCLE_BY_RUN}; {ACTIVITY}; {ACTIVITY_BY_RUN}; \
{TALLY}; {RETENTION}; {LINKS}; {FORKS}; PRAGMA user_version = {RUN_STORE_VERSION};"
))
.map_err(database_error(path, "initialize run store schema"))
}