Skip to main content

corium_db/
bootstrap.rs

1//! Attributes the engine installs in every database.
2//!
3//! Corium reserves the low `:db.part/db` sequence range — everything below the
4//! first user-installable attribute id — for attributes the engine owns. Today
5//! that is `:db/txInstant`, the commit timestamp the transactor asserts on
6//! every transaction entity. Storing it as an ordinary datom is what makes
7//! transaction time queryable data (`[?tx :db/txInstant ?inst]`) rather than
8//! log-only metadata, and what lets [`Db::as_of_instant`](crate::Db::as_of_instant)
9//! resolve a wall-clock instant to a basis.
10
11use corium_core::{
12    AttrId, Attribute, Cardinality, Datom, EntityId, Keyword, Partition, Schema, Value, ValueType,
13};
14
15use crate::Idents;
16
17/// Entity id of `:db/txInstant`, which is 50 as it is in Datomic.
18pub const TX_INSTANT: AttrId = EntityId::new(Partition::Db as u32, 50);
19
20/// The `:db/txInstant` keyword.
21#[must_use]
22pub fn tx_instant_ident() -> Keyword {
23    Keyword::new(Some("db"), "txInstant")
24}
25
26/// Schema record for `:db/txInstant`.
27///
28/// It is AVET-indexed so an instant resolves to a basis by index seek, and it
29/// keeps history: a transaction's timestamp is never retracted.
30#[must_use]
31pub const fn tx_instant_attribute() -> Attribute {
32    Attribute {
33        id: TX_INSTANT,
34        value_type: ValueType::Instant,
35        cardinality: Cardinality::One,
36        unique: None,
37        is_component: false,
38        indexed: true,
39        no_history: false,
40    }
41}
42
43/// Installs the engine's attributes into a schema and ident registry.
44///
45/// Idempotent: installing over a registry that already carries them (a schema
46/// decoded from a transactor handshake, say) leaves it unchanged.
47pub fn install(schema: &mut Schema, idents: &mut Idents) {
48    schema.insert(tx_instant_attribute());
49    idents.insert(tx_instant_ident(), TX_INSTANT);
50}
51
52/// Installs the engine's attributes into a schema alone.
53pub fn install_schema(schema: &mut Schema) {
54    schema.insert(tx_instant_attribute());
55}
56
57/// The `:db/txInstant` datom for transaction `t`.
58#[must_use]
59pub fn tx_instant_datom(t: u64, instant: i64) -> Datom {
60    let tx = EntityId::new(Partition::Tx as u32, t);
61    Datom {
62        e: tx,
63        a: TX_INSTANT,
64        v: Value::Instant(instant),
65        tx,
66        added: true,
67    }
68}
69
70/// The instant asserted for the transaction entity of `t` within `datoms`.
71///
72/// Commit paths use this to honour a `:db/txInstant` supplied as transaction
73/// metadata instead of stamping their own.
74#[must_use]
75pub fn asserted_instant(t: u64, datoms: &[Datom]) -> Option<i64> {
76    let tx = EntityId::new(Partition::Tx as u32, t);
77    let mut asserted = None;
78    for datom in datoms {
79        if let Datom {
80            e,
81            a,
82            v: Value::Instant(instant),
83            added,
84            ..
85        } = datom
86            && *e == tx
87            && *a == TX_INSTANT
88        {
89            if *added {
90                asserted = Some(*instant);
91            } else if asserted == Some(*instant) {
92                asserted = None;
93            }
94        }
95    }
96    asserted
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn install_is_idempotent() {
105        let mut schema = Schema::default();
106        let mut idents = Idents::default();
107        install(&mut schema, &mut idents);
108        install(&mut schema, &mut idents);
109        assert_eq!(schema.iter().count(), 1);
110        assert_eq!(idents.iter().count(), 1);
111        assert_eq!(idents.entid(&tx_instant_ident()), Some(TX_INSTANT));
112    }
113
114    #[test]
115    fn asserted_instant_finds_transaction_metadata() {
116        let datoms = vec![tx_instant_datom(7, 1_700_000_000_000)];
117        assert_eq!(asserted_instant(7, &datoms), Some(1_700_000_000_000));
118        assert_eq!(asserted_instant(8, &datoms), None);
119    }
120
121    #[test]
122    fn asserted_instant_uses_the_final_cardinality_one_value() {
123        let mut datoms = vec![tx_instant_datom(7, 2_000)];
124        let mut retract = tx_instant_datom(7, 2_000);
125        retract.added = false;
126        datoms.push(retract);
127        datoms.push(tx_instant_datom(7, 1_000));
128        assert_eq!(asserted_instant(7, &datoms), Some(1_000));
129    }
130}