use std::sync::Arc;
use corium_protocol::codec::encode_edn;
use corium_query::edn::Edn;
use corium_transactor::StoreSpec;
use corium_transactor::node::{NodeConfig, TransactorNode};
fn schema() -> Vec<u8> {
encode_edn(&Edn::Vector(vec![Edn::Map(vec![
(Edn::keyword("db/ident"), Edn::keyword("item/key")),
(Edn::keyword("db/valueType"), Edn::keyword("db.type/long")),
(
Edn::keyword("db/cardinality"),
Edn::keyword("db.cardinality/one"),
),
(
Edn::keyword("db/unique"),
Edn::keyword("db.unique/identity"),
),
])]))
}
fn tx(key: i64) -> Vec<u8> {
encode_edn(&Edn::Vector(vec![Edn::Map(vec![
(Edn::keyword("db/id"), Edn::Str("e".into())),
(Edn::keyword("item/key"), Edn::Long(key)),
])]))
}
fn bad_tx() -> Vec<u8> {
encode_edn(&Edn::Vector(vec![Edn::Map(vec![
(Edn::keyword("db/id"), Edn::Str("e".into())),
(Edn::keyword("item/key"), Edn::Str("not-a-long".into())),
])]))
}
async fn mem_node() -> Arc<TransactorNode> {
mem_node_with_batch(None).await
}
async fn mem_node_with_batch(max_commit_batch: Option<usize>) -> Arc<TransactorNode> {
let mut config = NodeConfig::new(std::path::PathBuf::from("/nonexistent-mem-node"));
config.store = StoreSpec::Memory;
if let Some(max) = max_commit_batch {
config.max_commit_batch = max;
}
TransactorNode::open(config).await.expect("open mem node")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn concurrent_transactions_all_commit_with_contiguous_basis() {
let node = mem_node().await;
node.create_db("items", &schema()).await.expect("create");
let count: u64 = 200;
let mut handles = Vec::new();
for key in 0..count {
let node = Arc::clone(&node);
let key = i64::try_from(key).expect("key fits i64");
handles.push(tokio::spawn(async move {
node.transact("items", &tx(key))
.await
.expect("transact")
.basis_t
}));
}
let mut bases = Vec::new();
for handle in handles {
bases.push(handle.await.expect("join"));
}
bases.sort_unstable();
assert_eq!(bases, (1..=count).collect::<Vec<_>>());
let db = node.db_state("items").await.expect("state").db();
assert_eq!(db.basis_t(), count);
assert_eq!(db.stats().entities, usize::try_from(count).expect("fits"));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_rejected_transaction_does_not_abort_its_batchmates() {
let node = mem_node().await;
node.create_db("items", &schema()).await.expect("create");
let bad_node = Arc::clone(&node);
let bad = tokio::spawn(async move { bad_node.transact("items", &bad_tx()).await });
let good_node = Arc::clone(&node);
let good = tokio::spawn(async move { good_node.transact("items", &tx(7)).await });
assert!(bad.await.expect("join").is_err(), "type mismatch must fail");
assert!(
good.await.expect("join").is_ok(),
"batchmate must still commit"
);
let db = node.db_state("items").await.expect("state").db();
assert_eq!(db.stats().entities, 1);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_batch_cap_of_one_disables_batching_but_stays_correct() {
let node = mem_node_with_batch(Some(1)).await;
node.create_db("items", &schema()).await.expect("create");
let count: u64 = 50;
let mut handles = Vec::new();
for key in 0..count {
let node = Arc::clone(&node);
let key = i64::try_from(key).expect("key fits i64");
handles.push(tokio::spawn(async move {
node.transact("items", &tx(key))
.await
.expect("transact")
.basis_t
}));
}
let mut bases = Vec::new();
for handle in handles {
bases.push(handle.await.expect("join"));
}
bases.sort_unstable();
assert_eq!(bases, (1..=count).collect::<Vec<_>>());
let db = node.db_state("items").await.expect("state").db();
assert_eq!(db.basis_t(), count);
assert_eq!(db.stats().entities, usize::try_from(count).expect("fits"));
}