Skip to main content

uqa_storage/key_value/
occurrence_records.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Common Key/Value occurrence addresses and wire values for MVCC publication.
8
9use super::inverted_index::FieldStats;
10use super::occurrence_format::{OccurrenceAddress as Address, OccurrenceProjection as Projection};
11use super::occurrence_keys::{
12    self as keys,
13    encoding::{controlled, Part},
14};
15use crate::mvcc::{
16    OccurrenceRecordKind as Kind, OccurrenceRecordLayout, OccurrenceRecordValue as Value,
17    OccurrenceRelatedKey as Related, VersionError, VersionResult,
18};
19use crate::read_control::StorageReadControl;
20use uqa_core::memory::BudgetedVec;
21
22// Keep guards outside the data namespace so drop/recreation cannot reuse a structural boundary.
23const GUARDS: u8 = b'z';
24
25pub(crate) fn guard(
26    table: &str,
27    document: Option<u64>,
28    control: &StorageReadControl,
29) -> VersionResult<BudgetedVec<u8>> {
30    Ok(match document {
31        Some(document) => controlled(
32            table,
33            GUARDS,
34            Some(b'd'),
35            &[Part::Number(document)],
36            control,
37        )?,
38        None => controlled(table, GUARDS, Some(b's'), &[], control)?,
39    })
40}
41
42pub(crate) fn format(table: &str, control: &StorageReadControl) -> VersionResult<BudgetedVec<u8>> {
43    Ok(controlled(
44        table,
45        super::TAG_OCCURRENCE_INDEX,
46        Some(keys::FORMAT),
47        &[],
48        control,
49    )?)
50}
51
52fn address(key: &[u8]) -> VersionResult<Address<'_>> {
53    let address = Address::decode(key)?;
54    if !address.complete() {
55        return Err(VersionError::InvalidEncoding(
56            "incomplete occurrence replacement",
57        ));
58    }
59    Ok(address)
60}
61
62pub struct KeyValueOccurrenceRecords;
63
64impl OccurrenceRecordLayout for KeyValueOccurrenceRecords {
65    fn kind(&self, key: &[u8], control: &StorageReadControl) -> VersionResult<Kind> {
66        control.cancellation().check()?;
67        let address = address(key)?;
68        Ok(match address.projection {
69            Some(Projection::Score) => Kind::Score(address.cluster.expect("complete cluster")),
70            Some(Projection::Positions) => Kind::Positions,
71            Some(Projection::Field) => Kind::Statistics,
72            Some(Projection::Format) => Kind::Format,
73            Some(Projection::Skip | Projection::BlockMax) => Kind::Cache,
74            _ => {
75                return Err(VersionError::InvalidEncoding(
76                    "occurrence merge targets a canonical record",
77                ))
78            }
79        })
80    }
81
82    fn related_key(
83        &self,
84        key: &[u8],
85        related: Related,
86        control: &StorageReadControl,
87    ) -> VersionResult<BudgetedVec<u8>> {
88        let mut address = address(key)?;
89        match related {
90            Related::Structure => guard(address.table, None, control),
91            Related::Format => format(address.table, control),
92            Related::Score | Related::Positions => {
93                address.projection = Some(if matches!(related, Related::Score) {
94                    Projection::Score
95                } else {
96                    Projection::Positions
97                });
98                if !address.complete() {
99                    return Err(VersionError::InvalidEncoding(
100                        "invalid occurrence cluster peer",
101                    ));
102                }
103                Ok(address.encode(control)?)
104            }
105            Related::Skips | Related::BlockMax => Ok(controlled(
106                address.table,
107                super::TAG_OCCURRENCE_INDEX,
108                Some(if matches!(related, Related::Skips) {
109                    keys::SKIP
110                } else {
111                    keys::BLOCK_MAX
112                }),
113                &[],
114                control,
115            )?),
116        }
117    }
118
119    fn decode<'a>(
120        &self,
121        key: &[u8],
122        value: &'a [u8],
123        control: &StorageReadControl,
124    ) -> VersionResult<Value<'a>> {
125        Ok(match self.kind(key, control)? {
126            Kind::Score(_) => Value::Score(value),
127            Kind::Positions => Value::Positions(value),
128            Kind::Statistics => {
129                let stats = FieldStats::from_bytes(value)?;
130                Value::Statistics {
131                    revision: stats.revision,
132                    doc_count: stats.doc_count,
133                    total_length: stats.total_length,
134                }
135            }
136            Kind::Format => Value::Format(value),
137            _ => {
138                return Err(VersionError::InvalidEncoding(
139                    "invalid occurrence value projection",
140                ))
141            }
142        })
143    }
144
145    fn encode(
146        &self,
147        key: &[u8],
148        _template: &[u8],
149        value: Value<'_>,
150        control: &StorageReadControl,
151    ) -> VersionResult<BudgetedVec<u8>> {
152        let mut bytes = BudgetedVec::new(control.memory());
153        match (self.kind(key, control)?, value) {
154            (Kind::Score(_), Value::Score(value))
155            | (Kind::Positions, Value::Positions(value))
156            | (Kind::Format, Value::Format(value)) => bytes.extend_from_slice(value)?,
157            (
158                Kind::Statistics,
159                Value::Statistics {
160                    revision,
161                    doc_count,
162                    total_length,
163                },
164            ) => bytes.extend_from_slice(
165                &FieldStats {
166                    revision,
167                    doc_count,
168                    total_length,
169                }
170                .to_bytes()?,
171            )?,
172            _ => {
173                return Err(VersionError::InvalidEncoding(
174                    "occurrence key and value disagree",
175                ))
176            }
177        }
178        Ok(bytes)
179    }
180}