use std::sync::Arc;
use armdb::armour::Db;
use armdb::{CollectionMeta, Config, FixedConfig, NoHook, RapiraCodec, TtlHook, TypedMap, ZeroMap};
use armour_core::GetType;
use rapira::Rapira;
use zerocopy::{FromBytes, Immutable, IntoBytes};
type Id = [u8; 8];
fn id(n: u64) -> Id {
n.to_be_bytes()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, GetType, Rapira)]
struct Message {
len: u32,
}
impl CollectionMeta for Message {
type SelfId = Id;
const NAME: &'static str = "messages";
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, GetType, IntoBytes, FromBytes, Immutable)]
#[repr(C)]
struct SeenMarker {
at: u64,
}
impl CollectionMeta for SeenMarker {
type SelfId = Id;
const NAME: &'static str = "seen";
}
type Messages = TypedMap<Id, Message, RapiraCodec, TtlHook<Message, NoHook>>;
type Seen = ZeroMap<Id, 8, SeenMarker, TtlHook<SeenMarker, NoHook>, armdb::durability::Fixed>;
fn open_pair(db: &Arc<Db>) -> (Arc<Messages>, Arc<Seen>) {
let messages = db
.open_typed_map::<Message, RapiraCodec, _>(Config::test(), NoHook, &[])
.unwrap();
let seen = db
.open_zero_map_fixed::<SeenMarker, 8, _>(FixedConfig::test(), NoHook, &[])
.unwrap();
(messages, seen)
}
#[test]
fn tx_mixed_backends_commit_together() {
let dir = tempfile::tempdir().unwrap();
let db = Arc::new(Db::open_test(dir.path()).unwrap());
let (messages, seen) = open_pair(&db);
db.tx()
.put(&messages, &id(1), &Message { len: 42 })
.unwrap()
.put(&seen, &id(1), &SeenMarker { at: 7 })
.unwrap()
.durable()
.commit()
.unwrap();
assert_eq!(messages.get(&id(1)).map(|m| m.len), Some(42));
assert_eq!(seen.get(&id(1)).map(|s| s.at), Some(7));
}
#[test]
fn tx_mixed_backends_roll_back_together() {
let dir = tempfile::tempdir().unwrap();
let db = Arc::new(Db::open_test(dir.path()).unwrap());
let (messages, seen) = open_pair(&db);
let err = db
.tx()
.put(&messages, &id(2), &Message { len: 1 })
.unwrap()
.put(&seen, &id(2), &SeenMarker { at: 1 })
.unwrap()
.guard(&messages, &id(999), |cur: Option<&Message>| {
if cur.is_none() {
Err(armdb::DbError::TxConflict)
} else {
Ok(())
}
})
.unwrap()
.commit()
.unwrap_err();
assert!(matches!(err, armdb::DbError::TxConflict), "{err}");
assert!(messages.get(&id(2)).is_none());
assert!(
seen.get(&id(2)).is_none(),
"the Fixed half must not be written either"
);
assert_eq!(
db.pending_transactions().len(),
0,
"nothing reached the log"
);
}
#[test]
fn tx_insert_on_a_fixed_collection() {
let dir = tempfile::tempdir().unwrap();
let db = Arc::new(Db::open_test(dir.path()).unwrap());
let (messages, seen) = open_pair(&db);
db.tx()
.insert(&seen, &id(1), &SeenMarker { at: 7 })
.unwrap()
.insert(&messages, &id(1), &Message { len: 42 })
.unwrap()
.commit()
.unwrap();
assert_eq!(seen.get(&id(1)).map(|s| s.at), Some(7));
assert_eq!(messages.get(&id(1)).map(|m| m.len), Some(42));
let pending_before_refused_insert = db.pending_transactions().len();
let err = db
.tx()
.insert(&seen, &id(1), &SeenMarker { at: 9 })
.unwrap()
.put(&messages, &id(2), &Message { len: 1 })
.unwrap()
.commit()
.unwrap_err();
assert!(matches!(err, armdb::DbError::KeyExists), "{err}");
assert_eq!(
seen.get(&id(1)).map(|s| s.at),
Some(7),
"the Fixed row keeps its value"
);
assert!(
messages.get(&id(2)).is_none(),
"the Bitcask half of the refused transaction is not written either"
);
assert_eq!(
db.pending_transactions().len(),
pending_before_refused_insert,
"the refused transaction adds nothing to the log"
);
}