use crate::{changes::Change, types::RunId};
use rusqlite::{Connection, OptionalExtension};
const TOUCHED: &str = "CREATE TEMP TABLE IF NOT EXISTS touched(run TEXT, list INTEGER NOT NULL);
CREATE TEMP TRIGGER IF NOT EXISTS touched_run_insert AFTER INSERT ON main.runs BEGIN INSERT INTO touched VALUES(NEW.id,1); END;
CREATE TEMP TRIGGER IF NOT EXISTS touched_run_delete AFTER DELETE ON main.runs BEGIN INSERT INTO touched VALUES(OLD.id,1); END;
CREATE TEMP TRIGGER IF NOT EXISTS touched_run_update AFTER UPDATE ON main.runs BEGIN INSERT INTO touched VALUES(NEW.id,0); END;
CREATE TEMP TRIGGER IF NOT EXISTS touched_request_insert AFTER INSERT ON main.requests BEGIN INSERT INTO touched VALUES(NEW.run_id,0); END;
CREATE TEMP TRIGGER IF NOT EXISTS touched_request_update AFTER UPDATE ON main.requests BEGIN INSERT INTO touched VALUES(NEW.run_id,0); END;
CREATE TEMP TRIGGER IF NOT EXISTS touched_request_delete AFTER DELETE ON main.requests BEGIN INSERT INTO touched VALUES(OLD.run_id,0); END;
CREATE TEMP TRIGGER IF NOT EXISTS touched_execution_insert AFTER INSERT ON main.executions BEGIN
INSERT INTO touched SELECT DISTINCT run_id,0 FROM requests WHERE execution_id=NEW.id;
INSERT INTO touched SELECT json_extract(NEW.data,'$.provenance.runId'),0 WHERE json_valid(NEW.data) AND json_extract(NEW.data,'$.provenance.runId') IS NOT NULL; END;
CREATE TEMP TRIGGER IF NOT EXISTS touched_execution_update AFTER UPDATE ON main.executions BEGIN
INSERT INTO touched SELECT DISTINCT run_id,0 FROM requests WHERE execution_id=NEW.id;
INSERT INTO touched SELECT json_extract(NEW.data,'$.provenance.runId'),0 WHERE json_valid(NEW.data) AND json_extract(NEW.data,'$.provenance.runId') IS NOT NULL; END;
CREATE TEMP TRIGGER IF NOT EXISTS touched_execution_delete AFTER DELETE ON main.executions BEGIN
INSERT INTO touched SELECT DISTINCT run_id,0 FROM requests WHERE execution_id=OLD.id;
INSERT INTO touched SELECT json_extract(OLD.data,'$.provenance.runId'),0 WHERE json_valid(OLD.data) AND json_extract(OLD.data,'$.provenance.runId') IS NOT NULL; END;";
pub(super) fn install(db: &Connection) -> rusqlite::Result<bool> {
let version: u32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
if version != super::STATE_SCHEMA_VERSION {
return Ok(false);
}
db.execute_batch(TOUCHED)?;
Ok(true)
}
pub(super) fn take(db: &Connection) -> Vec<Change> {
let touched = || -> rusqlite::Result<Option<Vec<RunId>>> {
let exists = db
.query_row(
"SELECT 1 FROM temp.sqlite_master WHERE type='table' AND name='touched'",
[],
|_| Ok(()),
)
.optional()?;
if exists.is_none() {
return Ok(None);
}
let rows = db
.prepare_cached("SELECT run,max(list) FROM temp.touched GROUP BY run")?
.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, bool>(1)?))
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
db.execute("DELETE FROM temp.touched", [])?;
if rows.is_empty() || rows.iter().any(|(_, list)| *list) {
return Ok(None);
}
Ok(rows
.into_iter()
.map(|(run, _)| run.parse().ok())
.collect::<Option<Vec<RunId>>>())
};
match touched() {
Ok(Some(runs)) => runs.into_iter().map(Change::RunInvalidated).collect(),
Ok(None) | Err(_) => vec![Change::StateInvalidated],
}
}