1use crate::{
4 AttrId, EntityId, TxId, Value,
5 encoding::{DecodeError, Encodable, decode_entity_id, decode_u64, decode_value},
6};
7
8#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct Datom {
11 pub e: EntityId,
13 pub a: AttrId,
15 pub v: Value,
17 pub tx: TxId,
19 pub added: bool,
21}
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum IndexOrder {
26 Eavt,
28 Aevt,
30 Avet,
32 Vaet,
34}
35
36pub const KEY_TX_SUFFIX_LEN: usize = 8;
39
40#[must_use]
48pub fn key_components(key: &[u8]) -> &[u8] {
49 &key[..key.len().saturating_sub(KEY_TX_SUFFIX_LEN)]
50}
51
52impl Datom {
53 #[must_use]
55 pub fn key(&self, order: IndexOrder) -> Vec<u8> {
56 let mut out = Vec::new();
57 match order {
58 IndexOrder::Eavt => {
59 self.e.encode_into(&mut out);
60 self.a.encode_into(&mut out);
61 self.v.encode_into(&mut out);
62 }
63 IndexOrder::Aevt => {
64 self.a.encode_into(&mut out);
65 self.e.encode_into(&mut out);
66 self.v.encode_into(&mut out);
67 }
68 IndexOrder::Avet => {
69 self.a.encode_into(&mut out);
70 self.v.encode_into(&mut out);
71 self.e.encode_into(&mut out);
72 }
73 IndexOrder::Vaet => {
74 self.v.encode_into(&mut out);
75 self.a.encode_into(&mut out);
76 self.e.encode_into(&mut out);
77 }
78 }
79 encode_tx_added(self.tx, self.added, &mut out);
80 out
81 }
82
83 pub fn from_key(order: IndexOrder, input: &[u8]) -> Result<Self, DecodeError> {
88 let mut offset = 0;
89 let entity = |offset: &mut usize| {
90 let (value, used) = decode_entity_id(&input[*offset..])?;
91 *offset += used;
92 Ok::<_, DecodeError>(value)
93 };
94 let (e, a, v) = match order {
95 IndexOrder::Eavt => {
96 let e = entity(&mut offset)?;
97 let a = entity(&mut offset)?;
98 let (v, used) = decode_value(&input[offset..])?;
99 offset += used;
100 (e, a, v)
101 }
102 IndexOrder::Aevt => {
103 let a = entity(&mut offset)?;
104 let e = entity(&mut offset)?;
105 let (v, used) = decode_value(&input[offset..])?;
106 offset += used;
107 (e, a, v)
108 }
109 IndexOrder::Avet => {
110 let a = entity(&mut offset)?;
111 let (v, used) = decode_value(&input[offset..])?;
112 offset += used;
113 let e = entity(&mut offset)?;
114 (e, a, v)
115 }
116 IndexOrder::Vaet => {
117 let (v, used) = decode_value(&input[offset..])?;
118 offset += used;
119 let a = entity(&mut offset)?;
120 let e = entity(&mut offset)?;
121 (e, a, v)
122 }
123 };
124 let (tx_added, used) = decode_u64(&input[offset..])?;
125 offset += used;
126 if offset != input.len() {
127 return Err(DecodeError::Trailing);
128 }
129 Ok(Self {
130 e,
131 a,
132 v,
133 tx: EntityId::from_raw(tx_added >> 1),
134 added: tx_added & 1 == 1,
135 })
136 }
137}
138
139fn encode_tx_added(tx: TxId, added: bool, out: &mut Vec<u8>) {
140 ((tx.raw() << 1) | u64::from(added)).encode_into(out);
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn covering_keys_round_trip() {
149 let datom = Datom {
150 e: EntityId::from_raw(42),
151 a: EntityId::from_raw(7),
152 v: Value::Str("a\0z".into()),
153 tx: EntityId::from_raw(19),
154 added: true,
155 };
156 for order in [
157 IndexOrder::Eavt,
158 IndexOrder::Aevt,
159 IndexOrder::Avet,
160 IndexOrder::Vaet,
161 ] {
162 let key = datom.key(order);
163 assert_eq!(Datom::from_key(order, &key), Ok(datom.clone()));
164 }
165 }
166
167 #[test]
168 fn key_components_strips_only_the_transaction_suffix() {
169 let datom = Datom {
170 e: EntityId::from_raw(42),
171 a: EntityId::from_raw(7),
172 v: Value::Str("a\0z".into()),
173 tx: EntityId::from_raw(19),
174 added: true,
175 };
176 let retraction = Datom {
179 tx: EntityId::from_raw(20),
180 added: false,
181 ..datom.clone()
182 };
183 for order in [
184 IndexOrder::Eavt,
185 IndexOrder::Aevt,
186 IndexOrder::Avet,
187 IndexOrder::Vaet,
188 ] {
189 let key = datom.key(order);
190 assert_eq!(key_components(&key).len(), key.len() - KEY_TX_SUFFIX_LEN);
191 assert!(key.starts_with(key_components(&key)));
192 assert_eq!(
193 key_components(&key),
194 key_components(&retraction.key(order)),
195 "a retraction must share the asserted fact's prefix"
196 );
197 }
198 }
199
200 #[test]
201 fn covering_key_rejects_trailing_bytes() {
202 let datom = Datom {
203 e: EntityId::from_raw(1),
204 a: EntityId::from_raw(2),
205 v: Value::Long(3),
206 tx: EntityId::from_raw(4),
207 added: false,
208 };
209 let mut key = datom.key(IndexOrder::Eavt);
210 key.push(0);
211 assert_eq!(
212 Datom::from_key(IndexOrder::Eavt, &key),
213 Err(DecodeError::Trailing)
214 );
215 }
216}