Skip to main content

corium_core/
datom.rs

1//! Datoms and covering index key composition.
2
3use crate::{
4    AttrId, EntityId, TxId, Value,
5    encoding::{DecodeError, Encodable, decode_entity_id, decode_u64, decode_value},
6};
7
8/// A single immutable fact assertion or retraction.
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct Datom {
11    /// Entity id.
12    pub e: EntityId,
13    /// Attribute id.
14    pub a: AttrId,
15    /// Value.
16    pub v: Value,
17    /// Transaction id.
18    pub tx: TxId,
19    /// Assertion (`true`) or retraction (`false`).
20    pub added: bool,
21}
22
23/// Covering index orders.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum IndexOrder {
26    /// Entity, attribute, value, transaction.
27    Eavt,
28    /// Attribute, entity, value, transaction.
29    Aevt,
30    /// Attribute, value, entity, transaction.
31    Avet,
32    /// Value, attribute, entity, transaction.
33    Vaet,
34}
35
36impl Datom {
37    /// Returns this datom's byte key for the requested index order.
38    #[must_use]
39    pub fn key(&self, order: IndexOrder) -> Vec<u8> {
40        let mut out = Vec::new();
41        match order {
42            IndexOrder::Eavt => {
43                self.e.encode_into(&mut out);
44                self.a.encode_into(&mut out);
45                self.v.encode_into(&mut out);
46            }
47            IndexOrder::Aevt => {
48                self.a.encode_into(&mut out);
49                self.e.encode_into(&mut out);
50                self.v.encode_into(&mut out);
51            }
52            IndexOrder::Avet => {
53                self.a.encode_into(&mut out);
54                self.v.encode_into(&mut out);
55                self.e.encode_into(&mut out);
56            }
57            IndexOrder::Vaet => {
58                self.v.encode_into(&mut out);
59                self.a.encode_into(&mut out);
60                self.e.encode_into(&mut out);
61            }
62        }
63        encode_tx_added(self.tx, self.added, &mut out);
64        out
65    }
66
67    /// Decodes a complete covering-index key back into its datom.
68    ///
69    /// # Errors
70    /// Returns [`DecodeError`] when a component is truncated or malformed.
71    pub fn from_key(order: IndexOrder, input: &[u8]) -> Result<Self, DecodeError> {
72        let mut offset = 0;
73        let entity = |offset: &mut usize| {
74            let (value, used) = decode_entity_id(&input[*offset..])?;
75            *offset += used;
76            Ok::<_, DecodeError>(value)
77        };
78        let (e, a, v) = match order {
79            IndexOrder::Eavt => {
80                let e = entity(&mut offset)?;
81                let a = entity(&mut offset)?;
82                let (v, used) = decode_value(&input[offset..])?;
83                offset += used;
84                (e, a, v)
85            }
86            IndexOrder::Aevt => {
87                let a = entity(&mut offset)?;
88                let e = entity(&mut offset)?;
89                let (v, used) = decode_value(&input[offset..])?;
90                offset += used;
91                (e, a, v)
92            }
93            IndexOrder::Avet => {
94                let a = entity(&mut offset)?;
95                let (v, used) = decode_value(&input[offset..])?;
96                offset += used;
97                let e = entity(&mut offset)?;
98                (e, a, v)
99            }
100            IndexOrder::Vaet => {
101                let (v, used) = decode_value(&input[offset..])?;
102                offset += used;
103                let a = entity(&mut offset)?;
104                let e = entity(&mut offset)?;
105                (e, a, v)
106            }
107        };
108        let (tx_added, used) = decode_u64(&input[offset..])?;
109        offset += used;
110        if offset != input.len() {
111            return Err(DecodeError::Trailing);
112        }
113        Ok(Self {
114            e,
115            a,
116            v,
117            tx: EntityId::from_raw(tx_added >> 1),
118            added: tx_added & 1 == 1,
119        })
120    }
121}
122
123fn encode_tx_added(tx: TxId, added: bool, out: &mut Vec<u8>) {
124    ((tx.raw() << 1) | u64::from(added)).encode_into(out);
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn covering_keys_round_trip() {
133        let datom = Datom {
134            e: EntityId::from_raw(42),
135            a: EntityId::from_raw(7),
136            v: Value::Str("a\0z".into()),
137            tx: EntityId::from_raw(19),
138            added: true,
139        };
140        for order in [
141            IndexOrder::Eavt,
142            IndexOrder::Aevt,
143            IndexOrder::Avet,
144            IndexOrder::Vaet,
145        ] {
146            let key = datom.key(order);
147            assert_eq!(Datom::from_key(order, &key), Ok(datom.clone()));
148        }
149    }
150
151    #[test]
152    fn covering_key_rejects_trailing_bytes() {
153        let datom = Datom {
154            e: EntityId::from_raw(1),
155            a: EntityId::from_raw(2),
156            v: Value::Long(3),
157            tx: EntityId::from_raw(4),
158            added: false,
159        };
160        let mut key = datom.key(IndexOrder::Eavt);
161        key.push(0);
162        assert_eq!(
163            Datom::from_key(IndexOrder::Eavt, &key),
164            Err(DecodeError::Trailing)
165        );
166    }
167}