Skip to main content

uqa_storage/key_value/
occurrence_format.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Typed addresses for providers projecting native rows into the common occurrence format.
8
9use super::codec::{other_error, read_segment, read_u64};
10use super::occurrence_keys::{
11    self as keys,
12    encoding::{controlled, Part},
13};
14use super::{
15    TAG_DOC_LENGTH, TAG_FIELD_STATS, TAG_OCCURRENCE_INDEX, TAG_POSTING,
16    TAG_POSTING_CLUSTER_POSITIONS, TAG_POSTING_CLUSTER_SCORE, TAG_POSTING_DOCUMENT,
17    TAG_REVERSE_POSTING,
18};
19use crate::{read_control::StorageReadControl, StorageBackendResult};
20use uqa_core::memory::BudgetedVec;
21use OccurrenceProjection::{
22    BlockMax, Document, Field, Format, LegacyDocument, LegacyField, LegacyLength, LegacyPositions,
23    LegacyPosting, LegacyReverse, LegacyScore, Length, Metadata, Positions, Score, Skip,
24};
25
26mod accelerators;
27pub use accelerators::BlockMaxValue;
28
29/// A value projection or a predecessor namespace whose presence requires a source rebuild.
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum OccurrenceProjection {
32    Score,
33    Positions,
34    Document,
35    Length,
36    Metadata,
37    Field,
38    Format,
39    Skip,
40    BlockMax,
41    LegacyPosting,
42    LegacyReverse,
43    LegacyScore,
44    LegacyPositions,
45    LegacyDocument,
46    LegacyLength,
47    LegacyField,
48}
49
50impl OccurrenceProjection {
51    fn tag(self) -> (u8, Option<u8>) {
52        match self {
53            Score => (TAG_OCCURRENCE_INDEX, Some(keys::SCORE)),
54            Positions => (TAG_OCCURRENCE_INDEX, Some(keys::POSITIONS)),
55            Document => (TAG_OCCURRENCE_INDEX, Some(keys::DOCUMENT)),
56            Length => (TAG_OCCURRENCE_INDEX, Some(keys::LENGTH)),
57            Metadata => (TAG_OCCURRENCE_INDEX, Some(keys::METADATA)),
58            Field => (TAG_OCCURRENCE_INDEX, Some(keys::FIELD)),
59            Format => (TAG_OCCURRENCE_INDEX, Some(keys::FORMAT)),
60            Skip => (TAG_OCCURRENCE_INDEX, Some(keys::SKIP)),
61            BlockMax => (TAG_OCCURRENCE_INDEX, Some(keys::BLOCK_MAX)),
62            LegacyPosting => (TAG_POSTING, None),
63            LegacyReverse => (TAG_REVERSE_POSTING, None),
64            LegacyScore => (TAG_POSTING_CLUSTER_SCORE, None),
65            LegacyPositions => (TAG_POSTING_CLUSTER_POSITIONS, None),
66            LegacyDocument => (TAG_POSTING_DOCUMENT, None),
67            LegacyLength => (TAG_DOC_LENGTH, None),
68            LegacyField => (TAG_FIELD_STATS, None),
69        }
70    }
71    pub fn is_legacy(self) -> bool {
72        self.tag().0 != TAG_OCCURRENCE_INDEX
73    }
74}
75
76/// A complete value address or a component-aligned prefix. Text and term bytes borrow their encoded key.
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub struct OccurrenceAddress<'a> {
79    pub table: &'a str,
80    pub projection: Option<OccurrenceProjection>,
81    pub field: Option<&'a str>,
82    pub term: Option<&'a [u8]>,
83    pub document: Option<u64>,
84    pub cluster: Option<u64>,
85    pub ordinal: Option<u64>,
86}
87
88impl<'a> OccurrenceAddress<'a> {
89    pub fn table(table: &'a str) -> Self {
90        Self {
91            table,
92            projection: None,
93            field: None,
94            term: None,
95            document: None,
96            cluster: None,
97            ordinal: None,
98        }
99    }
100
101    pub fn decode(key: &'a [u8]) -> StorageBackendResult<Self> {
102        let tag = *key
103            .first()
104            .ok_or_else(|| other_error("missing occurrence key tag"))?;
105        let mut offset = 1;
106        let table = text(read_segment(key, &mut offset)?)?;
107        let mut address = Self::table(table);
108        address.projection = match tag {
109            TAG_OCCURRENCE_INDEX => match key.get(offset) {
110                None => return Ok(address),
111                Some(&keys::SCORE) => Some(Score),
112                Some(&keys::POSITIONS) => Some(Positions),
113                Some(&keys::DOCUMENT) => Some(Document),
114                Some(&keys::LENGTH) => Some(Length),
115                Some(&keys::METADATA) => Some(Metadata),
116                Some(&keys::FIELD) => Some(Field),
117                Some(&keys::FORMAT) => Some(Format),
118                Some(&keys::SKIP) => Some(Skip),
119                Some(&keys::BLOCK_MAX) => Some(BlockMax),
120                _ => return Err(other_error("unknown occurrence projection")),
121            },
122            TAG_POSTING => Some(LegacyPosting),
123            TAG_REVERSE_POSTING => Some(LegacyReverse),
124            TAG_POSTING_CLUSTER_SCORE => Some(LegacyScore),
125            TAG_POSTING_CLUSTER_POSITIONS => Some(LegacyPositions),
126            TAG_POSTING_DOCUMENT => Some(LegacyDocument),
127            TAG_DOC_LENGTH => Some(LegacyLength),
128            TAG_FIELD_STATS => Some(LegacyField),
129            _ => return Err(other_error("unknown occurrence namespace")),
130        };
131        if tag == TAG_OCCURRENCE_INDEX {
132            offset += 1;
133        }
134        if offset == key.len() {
135            return Ok(address);
136        }
137        match address.projection {
138            Some(Score | Positions | Skip | BlockMax) => {
139                address.field = Some(text(read_segment(key, &mut offset)?)?);
140                if offset < key.len() {
141                    address.term = Some(read_segment(key, &mut offset)?);
142                }
143                if offset < key.len() {
144                    let number = Some(read_u64(key, &mut offset)?);
145                    match address.projection {
146                        Some(Skip) => address.document = number,
147                        Some(BlockMax) => address.ordinal = number,
148                        _ => address.cluster = number,
149                    }
150                }
151            }
152            Some(Document | Length) => {
153                address.document = Some(read_u64(key, &mut offset)?);
154                if offset < key.len() {
155                    address.field = Some(text(read_segment(key, &mut offset)?)?);
156                }
157            }
158            Some(Metadata | Field) => {
159                address.field = Some(text(read_segment(key, &mut offset)?)?);
160                if address.projection == Some(Metadata) && offset < key.len() {
161                    address.document = Some(read_u64(key, &mut offset)?);
162                }
163            }
164            _ => return Err(other_error("unexpected occurrence prefix components")),
165        }
166        if offset != key.len() {
167            return Err(other_error("trailing occurrence key bytes"));
168        }
169        Ok(address)
170    }
171
172    pub fn complete(self) -> bool {
173        match self.projection {
174            Some(Score | Positions) => {
175                self.field.is_some() && self.term.is_some() && self.cluster.is_some()
176            }
177            Some(Skip) => self.field.is_some() && self.term.is_some() && self.document.is_some(),
178            Some(BlockMax) => self.field.is_some() && self.term.is_some() && self.ordinal.is_some(),
179            Some(Document | Length | Metadata) => self.document.is_some() && self.field.is_some(),
180            Some(Field) => self.field.is_some(),
181            Some(Format) => true,
182            _ => false,
183        }
184    }
185
186    /// Encode an address or prefix under the caller's allowance. Reject invalid component combinations.
187    pub fn encode(self, control: &StorageReadControl) -> StorageBackendResult<BudgetedVec<u8>> {
188        let mut parts = [Part::Number(0); 3];
189        let mut len = 0;
190        let mut push = |part| {
191            parts[len] = part;
192            len += 1;
193        };
194        match self.projection {
195            Some(Score | Positions | Skip | BlockMax) => {
196                if let Some(field) = self.field {
197                    push(Part::Segment(field.as_bytes()));
198                }
199                if let Some(term) = self.term {
200                    push(Part::Segment(term));
201                }
202                if let Some(number) = match self.projection {
203                    Some(Skip) => self.document,
204                    Some(BlockMax) => self.ordinal,
205                    _ => self.cluster,
206                } {
207                    push(Part::Number(number));
208                }
209            }
210            Some(Document | Length) => {
211                if let Some(document) = self.document {
212                    push(Part::Number(document));
213                }
214                if let Some(field) = self.field {
215                    push(Part::Segment(field.as_bytes()));
216                }
217            }
218            Some(Metadata | Field) => {
219                if let Some(field) = self.field {
220                    push(Part::Segment(field.as_bytes()));
221                }
222                if let Some(document) = self.document {
223                    push(Part::Number(document));
224                }
225            }
226            _ => {}
227        }
228        let (tag, kind) = self
229            .projection
230            .map_or((TAG_OCCURRENCE_INDEX, None), OccurrenceProjection::tag);
231        let encoded = controlled(self.table, tag, kind, &parts[..len], control)?;
232        if OccurrenceAddress::decode(&encoded)? != self {
233            return Err(other_error("invalid occurrence address components"));
234        }
235        Ok(encoded)
236    }
237}
238
239fn text(bytes: &[u8]) -> StorageBackendResult<&str> {
240    std::str::from_utf8(bytes).map_err(|_| other_error("occurrence identifier is not UTF-8"))
241}
242
243#[cfg(test)]
244mod tests;