#[path = "common/harness.rs"]
mod harness;
use std::future::Future;
use std::pin::Pin;
use std::task::Poll;
use std::time::Duration;
use harness::TestHarness;
use macrame::connection::chunk_rows;
use macrame::prelude::*;
const T1: &str = "2026-01-01T00:00:00.000000Z";
const T2: &str = "2026-02-01T00:00:00.000000Z";
async fn count(db: &Database, sql: &str) -> i64 {
db.read_conn()
.query(sql, ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap()
}
async fn scalar(db: &Database, sql: &str) -> Option<String> {
db.read_conn()
.query(sql, ())
.await
.unwrap()
.next()
.await
.unwrap()
.and_then(|row| row.get(0).ok())
}
async fn seed_nodes(db: &Database, ids: impl IntoIterator<Item = String>) {
let concepts: Vec<ConceptUpsert> = ids
.into_iter()
.map(|id| {
let title = format!("Node {id}");
ConceptUpsert::new(id, title).valid_from(T1)
})
.collect();
db.write_concepts(concepts).await.unwrap();
}
fn edge(source: &str, target: &str, edge_type: &str, valid_from: &str) -> EdgeAssertion {
EdgeAssertion::new(source, target, edge_type).valid_from(valid_from)
}
async fn poll_once_each<T>(futures: &mut [Pin<Box<dyn Future<Output = T> + '_>>]) {
std::future::poll_fn(|cx| {
for f in futures.iter_mut() {
let _ = f.as_mut().poll(cx);
}
Poll::Ready(())
})
.await
}
#[tokio::test]
async fn high_priority_writes_are_serviced_before_a_low_priority_backlog() {
const BACKLOG: usize = 60;
const PROBES: usize = 8;
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let nodes = std::iter::once("SRC".to_string())
.chain((0..BACKLOG).map(|i| format!("B{i}")))
.chain((0..PROBES).map(|i| format!("P{i}")));
seed_nodes(&db, nodes).await;
let mut backlog: Vec<Pin<Box<dyn Future<Output = Result<usize>>>>> = (0..BACKLOG)
.map(|i| {
let target = format!("B{i}");
let db = &db;
Box::pin(async move {
db.bulk_import(vec![edge("SRC", &target, "BACKLOG", T1)])
.await
.map_err(DbError::from)
}) as Pin<Box<dyn Future<Output = _>>>
})
.collect();
poll_once_each(&mut backlog).await;
let mut probes: Vec<Pin<Box<dyn Future<Output = Result<()>>>>> = (0..PROBES)
.map(|i| {
let target = format!("P{i}");
Box::pin(db.assert_edge(edge("SRC", &target, "PROBE", T1)))
as Pin<Box<dyn Future<Output = _>>>
})
.collect();
poll_once_each(&mut probes).await;
for probe in probes {
probe.await.unwrap();
}
for chunk in backlog {
chunk.await.unwrap();
}
assert_eq!(
count(&db, "SELECT COUNT(*) FROM links WHERE edge_type = 'PROBE'").await,
PROBES as i64
);
assert_eq!(
count(
&db,
"SELECT COUNT(*) FROM links WHERE edge_type = 'BACKLOG'"
)
.await,
BACKLOG as i64
);
let last_probe = scalar(
&db,
"SELECT MAX(recorded_at) FROM links WHERE edge_type = 'PROBE'",
)
.await
.unwrap();
let first_backlog = scalar(
&db,
"SELECT MIN(recorded_at) FROM links WHERE edge_type = 'BACKLOG'",
)
.await
.unwrap();
assert!(
last_probe < first_backlog,
"a high-priority write queued behind {BACKLOG} background chunks was serviced after \
one of them: last probe {last_probe} >= first backlog chunk {first_backlog}"
);
}
#[tokio::test]
async fn a_lone_high_priority_write_is_still_serviced_before_a_saturated_backlog() {
const BACKLOG: usize = 40;
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let nodes = std::iter::once("SRC".to_string())
.chain((0..BACKLOG).map(|i| format!("B{i}")))
.chain(std::iter::once("PROBE".to_string()));
seed_nodes(&db, nodes).await;
let mut backlog: Vec<Pin<Box<dyn Future<Output = Result<usize>>>>> = (0..BACKLOG)
.map(|i| {
let target = format!("B{i}");
let db = &db;
Box::pin(async move {
db.bulk_import(vec![edge("SRC", &target, "BACKLOG", T1)])
.await
.map_err(DbError::from)
}) as Pin<Box<dyn Future<Output = _>>>
})
.collect();
poll_once_each(&mut backlog).await;
let mut probe: Vec<Pin<Box<dyn Future<Output = Result<()>>>>> =
vec![Box::pin(db.assert_edge(edge("SRC", "PROBE", "PROBE", T1)))
as Pin<Box<dyn Future<Output = _>>>];
poll_once_each(&mut probe).await;
for p in probe {
tokio::time::timeout(Duration::from_secs(5), p)
.await
.expect("high-priority write never completed behind the backlog")
.unwrap();
}
assert_eq!(
count(&db, "SELECT COUNT(*) FROM links WHERE edge_type = 'PROBE'").await,
1
);
for chunk in backlog {
chunk.await.unwrap();
}
assert_eq!(
count(
&db,
"SELECT COUNT(*) FROM links WHERE edge_type = 'BACKLOG'"
)
.await,
BACKLOG as i64,
"the backlog must still be serviced, only later"
);
let probe_at = scalar(
&db,
"SELECT MAX(recorded_at) FROM links WHERE edge_type = 'PROBE'",
)
.await
.unwrap();
let first_backlog = scalar(
&db,
"SELECT MIN(recorded_at) FROM links WHERE edge_type = 'BACKLOG'",
)
.await
.unwrap();
assert!(
probe_at < first_backlog,
"a lone high-priority write queued behind {BACKLOG} background chunks was serviced \
after one of them: probe {probe_at} >= first backlog chunk {first_backlog}"
);
}
#[tokio::test]
async fn bulk_import_is_atomic_per_chunk_not_overall() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let n = 400usize;
let nodes = std::iter::once("SRC".to_string()).chain((0..=n).map(|i| format!("T{i}")));
seed_nodes(&db, nodes).await;
let mut edges: Vec<EdgeAssertion> = (0..n)
.map(|i| edge("SRC", &format!("T{i}"), "KNOWS", T1))
.collect();
edges.push(edge("SRC", &format!("T{n}"), "KNOWS", T1));
edges.push(edge("SRC", "T0", "KNOWS", T2));
let edge_count = edges.len();
let err = db.bulk_import(edges).await.unwrap_err();
assert!(
matches!(err.cause, DbError::SingleOpenViolation { .. }),
"got {err:?}"
);
let committed = count(&db, "SELECT COUNT(*) FROM links").await;
assert_eq!(
err.written as i64, committed,
"the error's partial count and the committed rows must be the same number, or the count is worse than not having one"
);
assert!(
committed > 0,
"nothing committed -- `bulk_import` is not all-or-nothing, so a failure \
at row {n} must leave the chunks before it in place"
);
assert!(
committed < edge_count as i64,
"everything committed ({committed} of {edge_count} rows), so nothing \
rolled back and the violation was not caught"
);
let in_prefix = count(
&db,
&format!(
"SELECT COUNT(*) FROM links WHERE CAST(SUBSTR(target_id, 2) AS INTEGER) < {committed}"
),
)
.await;
assert_eq!(
in_prefix, committed,
"the committed rows are not the contiguous prefix of the batch"
);
let stamps = count(&db, "SELECT COUNT(DISTINCT recorded_at) FROM links").await;
let runs = count(
&db,
"SELECT COUNT(*) FROM ( \
SELECT recorded_at, LAG(recorded_at) OVER ( \
ORDER BY CAST(SUBSTR(target_id, 2) AS INTEGER)) AS prev \
FROM links \
) WHERE prev IS NULL OR prev <> recorded_at",
)
.await;
assert!(stamps >= 1, "the committed prefix carries no stamp at all");
assert_eq!(
stamps, runs,
"a stamp spans a discontiguous set of rows: {stamps} distinct stamps \
across {runs} runs, so a chunk is not one transaction under one stamp"
);
assert_eq!(audit_current(db.read_conn()).await.unwrap(), 0);
}
#[tokio::test]
async fn a_violation_in_a_single_chunk_batch_commits_nothing() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
seed_nodes(&db, ["SRC", "T0", "T1"].map(String::from)).await;
let edges = vec![
edge("SRC", "T1", "KNOWS", T1),
edge("SRC", "T0", "KNOWS", T1),
edge("SRC", "T0", "KNOWS", T2),
];
let err = db.bulk_import(edges).await.unwrap_err();
assert!(
matches!(err.cause, DbError::SingleOpenViolation { .. }),
"got {err:?}"
);
assert_eq!(
err.written, 0,
"one chunk failed and it was the only chunk, so nothing was written"
);
assert_eq!(
count(&db, "SELECT COUNT(*) FROM links").await,
0,
"the good rows that shared a chunk with the failure must have rolled \
back with it"
);
assert_eq!(audit_current(db.read_conn()).await.unwrap(), 0);
}
#[tokio::test]
async fn write_concepts_commits_earlier_chunks_when_a_later_one_fails() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let n = chunk_rows::CONCEPTS;
let mut concepts: Vec<ConceptUpsert> = (0..n)
.map(|i| ConceptUpsert::new(format!("C{i}"), format!("Concept {i}")).valid_from(T1))
.collect();
concepts.push(ConceptUpsert::new("KEEP_ME", "Good row in a doomed chunk").valid_from(T1));
concepts.push(ConceptUpsert::new("DUP", "First").valid_from(T1));
concepts.push(ConceptUpsert::new("DUP", "Second").valid_from(T1));
let err = db.write_concepts(concepts).await.unwrap_err();
assert!(
matches!(err.cause, DbError::RecordedAtRegression { .. }),
"got {err:?}"
);
assert_eq!(
err.written, n,
"chunk one committed {n} rows and the error must say so (W7.6)"
);
assert_eq!(
count(&db, "SELECT COUNT(*) FROM concepts").await,
n as i64,
"chunk one is committed and stays committed"
);
assert_eq!(
count(
&db,
"SELECT COUNT(*) FROM concepts WHERE id IN ('KEEP_ME', 'DUP')"
)
.await,
0,
"the failing chunk must roll back whole, including the rows before the failure"
);
}
#[tokio::test]
async fn the_read_connection_refuses_writes() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
seed_nodes(&db, ["A".to_string(), "B".to_string()]).await;
db.assert_edge(edge("A", "B", "KNOWS", T1)).await.unwrap();
let conn = db.read_conn();
let insert = conn
.execute(
"INSERT INTO concepts (id, title, content, valid_from, valid_to, recorded_at, retired) \
VALUES ('SNEAK', 'Bypassed the actor', '', ?1, '9999-12-31T23:59:59.999999Z', ?1, 0)",
libsql::params![T1],
)
.await;
assert!(insert.is_err(), "the read connection accepted an INSERT");
let update = conn
.execute("UPDATE concepts SET title = 'Rewritten' WHERE id = 'A'", ())
.await;
assert!(update.is_err(), "the read connection accepted an UPDATE");
let delete = conn.execute("DELETE FROM links", ()).await;
assert!(delete.is_err(), "the read connection accepted a DELETE");
let ddl = conn.execute("CREATE TABLE sneak (x TEXT)", ()).await;
assert!(ddl.is_err(), "the read connection accepted DDL");
assert_eq!(
count(&db, "SELECT COUNT(*) FROM concepts WHERE id = 'SNEAK'").await,
0
);
assert_eq!(count(&db, "SELECT COUNT(*) FROM links").await, 1);
assert_eq!(
scalar(&db, "SELECT title FROM concepts WHERE id = 'A'")
.await
.unwrap(),
"Node A"
);
}
#[tokio::test]
async fn open_readers_do_not_block_the_writer() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let nodes = std::iter::once("SRC".to_string()).chain((0..32).map(|i| format!("N{i}")));
seed_nodes(&db, nodes).await;
db.bulk_import(
(0..32)
.map(|i| edge("SRC", &format!("N{i}"), "KNOWS", T1))
.collect(),
)
.await
.unwrap();
let mut streams = Vec::new();
for _ in 0..4 {
let mut rows = db
.read_conn()
.query(
"SELECT source_id, target_id FROM links ORDER BY target_id",
(),
)
.await
.unwrap();
rows.next().await.unwrap().expect("seeded rows expected");
streams.push(rows);
}
let limit = Duration::from_secs(5);
tokio::time::timeout(limit, db.assert_edge(edge("SRC", "N0", "LIKES", T1)))
.await
.expect("assert_edge blocked behind open readers")
.unwrap();
let written = tokio::time::timeout(
limit,
db.write_bulk_atomic(
(0..32)
.map(|i| edge("SRC", &format!("N{i}"), "CITES", T1))
.collect(),
),
)
.await
.expect("write_bulk_atomic blocked behind open readers")
.unwrap();
assert_eq!(written, 32);
for mut rows in streams {
let mut seen = 1;
while tokio::time::timeout(limit, rows.next())
.await
.expect("an open reader stalled after the writer committed")
.unwrap()
.is_some()
{
seen += 1;
}
assert!(
seen >= 32,
"reader saw {seen} rows, expected its 32-row snapshot at least"
);
}
assert_eq!(count(&db, "SELECT COUNT(*) FROM links").await, 32 + 1 + 32);
assert_eq!(audit_current(db.read_conn()).await.unwrap(), 0);
}
fn multi_chunk_edges(n: usize) -> (Vec<String>, Vec<EdgeAssertion>) {
let nodes: Vec<String> = std::iter::once("SRC".to_string())
.chain((0..n).map(|i| format!("T{i}")))
.collect();
let edges = (0..n)
.map(|i| edge("SRC", &format!("T{i}"), "KNOWS", T1))
.collect();
(nodes, edges)
}
#[tokio::test]
async fn a_cancelled_token_stops_the_import_at_the_next_chunk_boundary() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let (nodes, edges) = multi_chunk_edges(chunk_rows::EDGES * 3);
let total = edges.len();
seed_nodes(&db, nodes).await;
let token = CancelToken::new();
let seen = std::sync::Arc::new(std::sync::Mutex::new(Vec::<BulkProgress>::new()));
let control = BulkControl::new().cancel_with(token.clone()).on_progress({
let seen = std::sync::Arc::clone(&seen);
move |p| {
seen.lock().unwrap().push(p);
token.cancel();
}
});
let err = db.bulk_import_with(edges, control).await.unwrap_err();
assert!(err.was_cancelled(), "got {err:?}");
let seen: Vec<BulkProgress> = seen.lock().unwrap().clone();
assert_eq!(
seen.len(),
1,
"the token was cancelled from the first chunk's callback, so the loop \
must not have sent a second chunk"
);
assert_eq!(
err.written, seen[0].written,
"the error's count and the last progress report are the same number"
);
assert!(
err.written > 0 && err.written < total,
"{} of {total} rows -- a cancellation that wrote nothing, or wrote \
everything, is not testing the boundary",
err.written
);
assert_eq!(
count(&db, "SELECT COUNT(*) FROM links").await,
err.written as i64,
"the rows the error claims are committed must actually be committed: \
cancellation is a boundary, not a rollback"
);
assert_eq!(audit_current(db.read_conn()).await.unwrap(), 0);
}
#[tokio::test]
async fn progress_covers_every_row_exactly_once_and_ends_at_the_total() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let (nodes, edges) = multi_chunk_edges(chunk_rows::EDGES * 3);
let total = edges.len();
seed_nodes(&db, nodes).await;
let seen = std::sync::Arc::new(std::sync::Mutex::new(Vec::<BulkProgress>::new()));
let control = BulkControl::new().on_progress({
let seen = std::sync::Arc::clone(&seen);
move |p| seen.lock().unwrap().push(p)
});
let written = db.bulk_import_with(edges, control).await.unwrap();
assert_eq!(written, total);
let seen = seen.lock().unwrap();
assert!(
seen.len() > 1,
"{total} rows arrived in one chunk, so this test is not watching a loop"
);
for report in seen.iter() {
assert_eq!(
report.total, total,
"`total` is the batch, and does not move"
);
}
for pair in seen.windows(2) {
assert!(
pair[1].written > pair[0].written,
"progress went backwards or stalled: {pair:?}"
);
}
assert_eq!(
seen.iter().map(|p| p.rows).sum::<usize>(),
total,
"the per-chunk counts must partition the batch"
);
assert_eq!(
seen.last().unwrap().written,
total,
"the last report is the one a progress bar finishes on"
);
}
#[tokio::test]
async fn a_token_cancelled_before_the_call_commits_nothing() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let (nodes, edges) = multi_chunk_edges(chunk_rows::EDGES * 2);
seed_nodes(&db, nodes).await;
let token = CancelToken::new();
token.cancel();
let err = db
.bulk_import_with(edges, BulkControl::new().cancel_with(token))
.await
.unwrap_err();
assert!(err.was_cancelled(), "got {err:?}");
assert_eq!(err.written, 0);
assert_eq!(count(&db, "SELECT COUNT(*) FROM links").await, 0);
}
#[tokio::test]
async fn cancelling_a_batch_with_no_work_left_still_succeeds() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let token = CancelToken::new();
token.cancel();
let written = db
.bulk_import_with(vec![], BulkControl::new().cancel_with(token))
.await
.expect("an empty batch has nothing to cancel");
assert_eq!(written, 0);
}
#[tokio::test]
async fn every_chunked_path_reports_its_partial_count() {
let harness = TestHarness::new();
let db = Database::open(&harness.db_path).await.unwrap();
let n = chunk_rows::ANNOTATIONS * 2;
let concepts: Vec<ConceptUpsert> = (0..n)
.map(|i| ConceptUpsert::new(format!("C{i}"), format!("Concept {i}")).valid_from(T1))
.collect();
let token = CancelToken::new();
let control = BulkControl::new().cancel_with(token.clone()).on_progress({
let token = token.clone();
move |_| token.cancel()
});
let err = db.write_concepts_with(concepts, control).await.unwrap_err();
assert!(err.was_cancelled(), "got {err:?}");
assert_eq!(
count(&db, "SELECT COUNT(*) FROM concepts").await,
err.written as i64
);
let rest: Vec<ConceptUpsert> = (err.written..n)
.map(|i| ConceptUpsert::new(format!("C{i}"), format!("Concept {i}")).valid_from(T1))
.collect();
db.write_concepts(rest).await.unwrap();
let annotations: Vec<Annotation> = (0..n)
.map(|i| Annotation::new(format!("C{i}"), "louvain.community", "7"))
.collect();
let token = CancelToken::new();
let control = BulkControl::new().cancel_with(token.clone()).on_progress({
let token = token.clone();
move |_| token.cancel()
});
let err = db
.write_analytics_annotations_with(annotations, control)
.await
.unwrap_err();
assert!(err.was_cancelled(), "got {err:?}");
assert_eq!(
count(&db, "SELECT COUNT(*) FROM analytics_annotations").await,
err.written as i64
);
}
#[tokio::test]
async fn a_database_is_shared_by_arc_and_is_deliberately_not_clone() {
use std::marker::PhantomData;
use std::sync::Arc;
struct Probe<T>(PhantomData<T>);
trait NotClone {
fn is_clone(&self) -> bool {
false
}
}
impl<T> NotClone for Probe<T> {}
impl<T: Clone> Probe<T> {
fn is_clone(&self) -> bool {
true
}
}
assert!(
Probe::<String>(PhantomData).is_clone(),
"the probe answers `false` for a type that is `Clone`, so the assertion \
below proves nothing -- fix the probe before trusting it"
);
assert!(
!Probe::<Database>(PhantomData).is_clone(),
"`Database` gained `Clone`, which duplicates the right to `close()`: the \
copy's `writer` cannot be joined so its exit status goes unchecked, its \
`cadence_stop` keeps the snapshot task alive against a closing database, \
and whichever copy closes second writes a \"final\" snapshot with the \
actor still running. Share it as `Arc<Database>` instead -- D-203."
);
assert!(
Probe::<tokio::sync::watch::Sender<bool>>(PhantomData).is_clone(),
"`watch::Sender` is no longer `Clone`, so D-203's argument about \
`cadence_stop` no longer holds as written and wants revisiting"
);
let harness = TestHarness::new();
let db = Arc::new(Database::open(&harness.db_path).await.unwrap());
seed_nodes(&db, ["A".to_string(), "B".to_string()]).await;
let mut tasks = Vec::new();
for i in 0..4 {
let db = Arc::clone(&db);
tasks.push(tokio::spawn(async move {
let concept = ConceptUpsert::new(format!("N{i}"), format!("Node {i}")).valid_from(T1);
db.write_concepts(vec![concept]).await.unwrap();
count(&db, "SELECT COUNT(*) FROM concepts").await
}));
}
for task in tasks {
task.await.unwrap();
}
assert_eq!(
count(&db, "SELECT COUNT(*) FROM concepts").await,
6,
"two seeded plus one per task"
);
let db = Arc::into_inner(db).expect("every task has finished, so this is the last handle");
db.close().await.unwrap();
}