Skip to main content

nodedb_types/
identity.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Plane-neutral row identity.
4//!
5//! [`KeyRepr`] is the single representation of a row's identity within a
6//! collection, shared across planes and crates: the Data-Plane per-core
7//! write-version index keys committed writes by it, the Control-Plane
8//! transaction read-set keys observed reads by it, and the replicated Calvin
9//! `TxClass` carries it in its versioned read-set. Because read keys and write
10//! keys must compare in one namespace — and because that namespace now travels
11//! over the sequencer Raft log — the type lives in the shared `nodedb-types`
12//! crate: a pure `Send + Sync` value type with no plane affiliation.
13
14use serde::{Deserialize, Serialize};
15
16/// Identity of a row within a collection.
17///
18/// The engine that owns the row chooses the representation:
19/// - `Surrogate` for the cross-engine `u32` surrogate (schemaless + strict
20///   document rows, and vector-by-document upserts keyed on the owning doc).
21/// - `KvKey` for the raw Key-Value engine key bytes.
22/// - `Edge` for a graph edge, whose identity is the `(src, label, dst)` tuple
23///   rather than a surrogate.
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
25pub enum KeyRepr {
26    /// Cross-engine `u32` surrogate identity.
27    Surrogate(u32),
28    /// Raw Key-Value engine key bytes.
29    KvKey(Box<[u8]>),
30    /// Graph edge identity: `(source node, edge label, destination node)`.
31    Edge {
32        src: Box<str>,
33        label: Box<str>,
34        dst: Box<str>,
35    },
36}
37
38// Manual MessagePack impls: `KeyRepr` carries `Box<[u8]>` / `Box<str>` fields
39// which the derive cannot handle (zerompk has no impl for unsized-boxed
40// slices/strings), and keeping the boxed representation is a deliberate
41// footprint choice for the Data-Plane write index. The wire form is a
42// self-consistent `[tag, payload]` array mirroring how the derive encodes a
43// data enum.
44const KEY_REPR_TAG_SURROGATE: u32 = 0;
45const KEY_REPR_TAG_KV: u32 = 1;
46const KEY_REPR_TAG_EDGE: u32 = 2;
47
48impl zerompk::ToMessagePack for KeyRepr {
49    fn write<W: zerompk::Write>(&self, writer: &mut W) -> zerompk::Result<()> {
50        writer.write_array_len(2)?;
51        match self {
52            KeyRepr::Surrogate(v) => {
53                writer.write_u32(KEY_REPR_TAG_SURROGATE)?;
54                writer.write_u32(*v)?;
55            }
56            KeyRepr::KvKey(bytes) => {
57                writer.write_u32(KEY_REPR_TAG_KV)?;
58                writer.write_binary(bytes)?;
59            }
60            KeyRepr::Edge { src, label, dst } => {
61                writer.write_u32(KEY_REPR_TAG_EDGE)?;
62                writer.write_array_len(3)?;
63                writer.write_string(src)?;
64                writer.write_string(label)?;
65                writer.write_string(dst)?;
66            }
67        }
68        Ok(())
69    }
70}
71
72impl<'de> zerompk::FromMessagePack<'de> for KeyRepr {
73    fn read<R: zerompk::Read<'de>>(reader: &mut R) -> zerompk::Result<Self> {
74        let outer = reader.read_array_len()?;
75        if outer != 2 {
76            return Err(zerompk::Error::InvalidMarker(0));
77        }
78        let tag = reader.read_u32()?;
79        match tag {
80            KEY_REPR_TAG_SURROGATE => Ok(KeyRepr::Surrogate(reader.read_u32()?)),
81            KEY_REPR_TAG_KV => {
82                let bytes = reader.read_binary()?;
83                Ok(KeyRepr::KvKey(Box::from(bytes.as_ref())))
84            }
85            KEY_REPR_TAG_EDGE => {
86                let inner = reader.read_array_len()?;
87                if inner != 3 {
88                    return Err(zerompk::Error::InvalidMarker(0));
89                }
90                let src = reader.read_string()?;
91                let label = reader.read_string()?;
92                let dst = reader.read_string()?;
93                Ok(KeyRepr::Edge {
94                    src: Box::from(src.as_ref()),
95                    label: Box::from(label.as_ref()),
96                    dst: Box::from(dst.as_ref()),
97                })
98            }
99            _ => Err(zerompk::Error::InvalidMarker(0)),
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    fn roundtrip(k: &KeyRepr) -> KeyRepr {
109        let bytes = zerompk::to_msgpack_vec(k).expect("encode KeyRepr");
110        zerompk::from_msgpack(&bytes).expect("decode KeyRepr")
111    }
112
113    #[test]
114    fn surrogate_roundtrips() {
115        let k = KeyRepr::Surrogate(0xDEAD_BEEF);
116        assert_eq!(roundtrip(&k), k);
117    }
118
119    #[test]
120    fn kv_key_roundtrips() {
121        let k = KeyRepr::KvKey(Box::from(&b"\x00\x01\xffhello"[..]));
122        assert_eq!(roundtrip(&k), k);
123    }
124
125    #[test]
126    fn edge_roundtrips() {
127        let k = KeyRepr::Edge {
128            src: Box::from("alice"),
129            label: Box::from("FOLLOWS"),
130            dst: Box::from("bob"),
131        };
132        assert_eq!(roundtrip(&k), k);
133    }
134}