#[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::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}");
Box::pin(db.bulk_import(vec![edge("SRC", &target, "BACKLOG", T1)]))
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}");
Box::pin(db.bulk_import(vec![edge("SRC", &target, "BACKLOG", T1)]))
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 = chunk_rows::EDGES;
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 err = db.bulk_import(edges).await.unwrap_err();
assert!(
matches!(err, DbError::SingleOpenViolation { .. }),
"got {err:?}"
);
assert_eq!(
count(&db, "SELECT COUNT(*) FROM links").await,
n as i64,
"the first chunk must stay committed -- `bulk_import` is not all-or-nothing"
);
assert_eq!(
count(
&db,
&format!("SELECT COUNT(*) FROM links WHERE target_id = 'T{n}'")
)
.await,
0,
"the good row that shared a chunk with the failure must have rolled back with it"
);
assert_eq!(
count(&db, "SELECT COUNT(DISTINCT recorded_at) FROM links").await,
1,
"a chunk is one transaction under one stamp"
);
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, DbError::RecordedAtRegression { .. }),
"got {err:?}"
);
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);
}