Skip to main content

riffdb_types/
columnar.rs

1//! Canonical schema-bound identities for columnar projection state.
2
3use std::fmt;
4
5use crate::{ContractLineage, EntityTypeId, FieldId};
6
7/// Maximum canonical bytes in one schema-bound columnar source identity.
8pub const MAX_COLUMNAR_PROJECTION_SOURCE_V1_BYTES: usize = 291;
9
10/// Positive replay ceilings bound into one columnar specification and control.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub struct ColumnarProjectionReplayLimitsV1 {
13    age_seconds: u64,
14    bytes: u64,
15    backlog: u64,
16}
17
18impl ColumnarProjectionReplayLimitsV1 {
19    /// Constructs exact positive replay ceilings.
20    #[must_use]
21    pub const fn new(age_seconds: u64, bytes: u64, backlog: u64) -> Option<Self> {
22        if age_seconds == 0 || bytes == 0 || backlog == 0 {
23            return None;
24        }
25        Some(Self {
26            age_seconds,
27            bytes,
28            backlog,
29        })
30    }
31
32    /// Maximum replay age in seconds.
33    #[must_use]
34    pub const fn age_seconds(self) -> u64 {
35        self.age_seconds
36    }
37
38    /// Maximum retained replay bytes.
39    #[must_use]
40    pub const fn bytes(self) -> u64 {
41        self.bytes
42    }
43
44    /// Maximum retained replay sequence backlog.
45    #[must_use]
46    pub const fn backlog(self) -> u64 {
47        self.backlog
48    }
49}
50
51/// Stable legacy columnar-definition fingerprint, preserved byte-for-byte.
52#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
53pub struct DefinitionFingerprint([u8; 32]);
54
55impl DefinitionFingerprint {
56    /// Returns the 32 digest bytes.
57    #[must_use]
58    pub const fn as_bytes(&self) -> &[u8; 32] {
59        &self.0
60    }
61
62    /// Reconstructs the accepted fingerprint from exact digest bytes.
63    #[must_use]
64    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
65        Self(bytes)
66    }
67}
68
69impl fmt::Display for DefinitionFingerprint {
70    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71        for byte in &self.0 {
72            write!(formatter, "{byte:02x}")?;
73        }
74        Ok(())
75    }
76}
77
78/// Stable logical identity of one compiler-declared vector projection.
79#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
80pub struct VectorProjectionSourceV1 {
81    lineage: ContractLineage,
82    entity_type: EntityTypeId,
83    vector_field: FieldId,
84}
85
86impl VectorProjectionSourceV1 {
87    /// Constructs a source identity from compiler-owned stable IDs.
88    #[must_use]
89    pub const fn new(
90        lineage: ContractLineage,
91        entity_type: EntityTypeId,
92        vector_field: FieldId,
93    ) -> Self {
94        Self {
95            lineage,
96            entity_type,
97            vector_field,
98        }
99    }
100
101    /// Contract lineage owning this projection.
102    #[must_use]
103    pub const fn lineage(&self) -> &ContractLineage {
104        &self.lineage
105    }
106
107    /// Compiler-assigned entity identity.
108    #[must_use]
109    pub const fn entity_type(&self) -> EntityTypeId {
110        self.entity_type
111    }
112
113    /// Compiler-assigned vector field identity.
114    #[must_use]
115    pub const fn vector_field(&self) -> FieldId {
116        self.vector_field
117    }
118}
119
120/// One canonical schema-bound scalar or vector source identity.
121#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
122pub enum ColumnarProjectionSourceV1 {
123    /// Scalar columnar state bound to the accepted physical definition fingerprint.
124    Scalar {
125        /// Contract lineage owning the schema.
126        lineage: ContractLineage,
127        /// Accepted legacy definition fingerprint.
128        definition_fingerprint: DefinitionFingerprint,
129    },
130    /// Vector columnar state retaining the accepted lineage/entity/field tuple.
131    Vector(VectorProjectionSourceV1),
132}
133
134impl ColumnarProjectionSourceV1 {
135    /// Constructs one scalar source identity.
136    #[must_use]
137    pub const fn scalar(
138        lineage: ContractLineage,
139        definition_fingerprint: DefinitionFingerprint,
140    ) -> Self {
141        Self::Scalar {
142            lineage,
143            definition_fingerprint,
144        }
145    }
146
147    /// Constructs one vector source identity.
148    #[must_use]
149    pub const fn vector(source: VectorProjectionSourceV1) -> Self {
150        Self::Vector(source)
151    }
152
153    /// Returns the owning contract lineage.
154    #[must_use]
155    pub const fn lineage(&self) -> &ContractLineage {
156        match self {
157            Self::Scalar { lineage, .. } => lineage,
158            Self::Vector(source) => source.lineage(),
159        }
160    }
161
162    /// Encodes the exact ADR-0192 canonical source bytes.
163    #[must_use]
164    pub fn to_canonical_bytes(&self) -> Vec<u8> {
165        let lineage = self.lineage().as_str().as_bytes();
166        let mut bytes = Vec::with_capacity(1 + 2 + lineage.len() + 32);
167        match self {
168            Self::Scalar {
169                definition_fingerprint,
170                ..
171            } => {
172                bytes.push(0x01);
173                bytes.extend_from_slice(&(lineage.len() as u16).to_be_bytes());
174                bytes.extend_from_slice(lineage);
175                bytes.extend_from_slice(definition_fingerprint.as_bytes());
176            }
177            Self::Vector(source) => {
178                bytes.push(0x02);
179                bytes.extend_from_slice(&(lineage.len() as u16).to_be_bytes());
180                bytes.extend_from_slice(lineage);
181                bytes.extend_from_slice(&source.entity_type().to_be_bytes());
182                bytes.extend_from_slice(&source.vector_field().to_be_bytes());
183            }
184        }
185        bytes
186    }
187
188    /// Decodes, fully consumes, and revalidates exact canonical source bytes.
189    pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ColumnarProjectionSourceError> {
190        if bytes.len() < 4 || bytes.len() > MAX_COLUMNAR_PROJECTION_SOURCE_V1_BYTES {
191            return Err(ColumnarProjectionSourceError);
192        }
193        let tag = bytes[0];
194        let lineage_len = usize::from(u16::from_be_bytes([bytes[1], bytes[2]]));
195        let suffix = match tag {
196            0x01 => 32,
197            0x02 => 8,
198            _ => return Err(ColumnarProjectionSourceError),
199        };
200        let expected = 3_usize
201            .checked_add(lineage_len)
202            .and_then(|value| value.checked_add(suffix))
203            .ok_or(ColumnarProjectionSourceError)?;
204        if expected != bytes.len() {
205            return Err(ColumnarProjectionSourceError);
206        }
207        let lineage_text = std::str::from_utf8(&bytes[3..3 + lineage_len])
208            .map_err(|_| ColumnarProjectionSourceError)?;
209        let lineage =
210            ContractLineage::new(lineage_text).map_err(|_| ColumnarProjectionSourceError)?;
211        let tail = &bytes[3 + lineage_len..];
212        let source = if tag == 0x01 {
213            let fingerprint: [u8; 32] =
214                tail.try_into().map_err(|_| ColumnarProjectionSourceError)?;
215            Self::scalar(lineage, DefinitionFingerprint::from_bytes(fingerprint))
216        } else {
217            let entity = EntityTypeId::new(u32::from_be_bytes(
218                tail[..4]
219                    .try_into()
220                    .map_err(|_| ColumnarProjectionSourceError)?,
221            ))
222            .ok_or(ColumnarProjectionSourceError)?;
223            let field = FieldId::new(u32::from_be_bytes(
224                tail[4..]
225                    .try_into()
226                    .map_err(|_| ColumnarProjectionSourceError)?,
227            ))
228            .ok_or(ColumnarProjectionSourceError)?;
229            Self::vector(VectorProjectionSourceV1::new(lineage, entity, field))
230        };
231        if source.to_canonical_bytes() != bytes {
232            return Err(ColumnarProjectionSourceError);
233        }
234        Ok(source)
235    }
236}
237
238/// Closed rejection of malformed canonical columnar source bytes.
239#[derive(Clone, Copy, Debug, Eq, PartialEq)]
240pub struct ColumnarProjectionSourceError;
241
242impl fmt::Display for ColumnarProjectionSourceError {
243    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
244        formatter.write_str("invalid canonical columnar projection source")
245    }
246}
247
248impl std::error::Error for ColumnarProjectionSourceError {}