Skip to main content

uqa_storage/mvcc/serializable/
predicates.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Logical predicate addresses supplied by execution, independent of physical record layouts.
8
9use std::ops::Bound;
10
11use uqa_core::memory::{BudgetedVec, MemoryBudget};
12
13use super::{VersionError, VersionResult};
14
15/// Rows, ordered index keys, canonical vector candidates, text and graph data occupy separate logical address spaces. Index identities must describe immutable incarnations, not reusable names or catalog OIDs. Access-path owners bind keys independently of approximate-index nodes, posting clusters or graph cache records.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum SerializableKeySpace {
18    Rows,
19    Index([u8; 16]),
20    Vectors,
21    Text,
22    Graph,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26enum Selection<'a> {
27    Object,
28    Point(SerializableKeySpace, &'a [u8]),
29    Range(SerializableKeySpace, Bound<&'a [u8]>, Bound<&'a [u8]>),
30}
31
32/// A logical read predicate or write address in one immutable object incarnation. Execution supplies order-preserving key bytes with the selected index's comparison/collation semantics; storage never interprets SQL values or physical posting records. An object predicate covers every key space belonging to that object.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct SerializablePredicate<'a> {
35    pub(super) object: [u8; 16],
36    selection: Selection<'a>,
37}
38
39impl<'a> SerializablePredicate<'a> {
40    pub const fn object(object: [u8; 16]) -> Self {
41        Self {
42            object,
43            selection: Selection::Object,
44        }
45    }
46
47    pub const fn point(object: [u8; 16], space: SerializableKeySpace, key: &'a [u8]) -> Self {
48        Self {
49            object,
50            selection: Selection::Point(space, key),
51        }
52    }
53
54    /// Bounds describe the actually observed key interval, including absent results, not just returned rows. Equal inclusive bounds observe one key; reversed or exclusively equal bounds observe an empty interval.
55    pub const fn range(
56        object: [u8; 16],
57        space: SerializableKeySpace,
58        lower: Bound<&'a [u8]>,
59        upper: Bound<&'a [u8]>,
60    ) -> Self {
61        Self {
62            object,
63            selection: Selection::Range(space, lower, upper),
64        }
65    }
66
67    pub(in crate::mvcc) fn validate(self, writing: bool) -> VersionResult<()> {
68        if self.object == [0; 16] {
69            return Err(VersionError::InvalidEncoding(
70                "serializable predicate requires an immutable object identity",
71            ));
72        }
73        let space = match self.selection {
74            Selection::Object => None,
75            Selection::Point(space, _) => Some(space),
76            Selection::Range(space, ..) if !writing => Some(space),
77            Selection::Range(..) => {
78                return Err(VersionError::InvalidEncoding(
79                    "serializable writes require logical points or an entire object",
80                ));
81            }
82        };
83        if space == Some(SerializableKeySpace::Index([0; 16])) {
84            return Err(VersionError::InvalidEncoding(
85                "serializable predicate requires an immutable index identity",
86            ));
87        }
88        Ok(())
89    }
90
91    pub(super) fn is_empty(self) -> bool {
92        let Selection::Range(_, lower, upper) = self.selection else {
93            return false;
94        };
95        match (lower, upper) {
96            (Bound::Unbounded, Bound::Excluded(right)) => right.is_empty(),
97            (Bound::Unbounded, _) | (_, Bound::Unbounded) => false,
98            (Bound::Included(left), Bound::Included(right)) => left > right,
99            (Bound::Excluded(left), Bound::Excluded(right)) => {
100                left >= right || right.strip_prefix(left) == Some(&[0][..])
101            }
102            (
103                Bound::Included(left) | Bound::Excluded(left),
104                Bound::Included(right) | Bound::Excluded(right),
105            ) => left >= right,
106        }
107    }
108
109    /// The writer was validated as a point or an object observation.
110    pub(super) fn overlaps_write(self, writer: Self) -> bool {
111        if self.object != writer.object || self.is_empty() {
112            return false;
113        }
114        match (self.selection, writer.selection) {
115            (Selection::Object, _) | (_, Selection::Object) => true,
116            (Selection::Point(space, key), Selection::Point(write_space, write_key)) => {
117                space == write_space && key == write_key
118            }
119            (Selection::Range(space, lower, upper), Selection::Point(write_space, key)) => {
120                space == write_space && after_lower(key, lower) && before_upper(key, upper)
121            }
122            (_, Selection::Range(..)) => false,
123        }
124    }
125}
126
127fn after_lower(key: &[u8], lower: Bound<&[u8]>) -> bool {
128    match lower {
129        Bound::Unbounded => true,
130        Bound::Included(bound) => key >= bound,
131        Bound::Excluded(bound) => key > bound,
132    }
133}
134
135fn before_upper(key: &[u8], upper: Bound<&[u8]>) -> bool {
136    match upper {
137        Bound::Unbounded => true,
138        Bound::Included(bound) => key <= bound,
139        Bound::Excluded(bound) => key < bound,
140    }
141}
142
143pub(in crate::mvcc) struct OwnedPredicate {
144    pub(super) object: [u8; 16],
145    pub(super) space: Option<SerializableKeySpace>,
146    pub(super) point: bool,
147    pub(super) lower: Bound<BudgetedVec<u8>>,
148    pub(super) upper: Bound<BudgetedVec<u8>>,
149}
150
151impl OwnedPredicate {
152    pub(in crate::mvcc) fn new(
153        predicate: SerializablePredicate<'_>,
154        memory: &MemoryBudget,
155    ) -> VersionResult<Self> {
156        let (space, point, lower, upper) = match predicate.selection {
157            Selection::Object => (None, false, Bound::Unbounded, Bound::Unbounded),
158            Selection::Point(space, key) => {
159                (Some(space), true, Bound::Included(key), Bound::Unbounded)
160            }
161            Selection::Range(space, lower, upper) => (Some(space), false, lower, upper),
162        };
163        Ok(Self {
164            object: predicate.object,
165            space,
166            point,
167            lower: own_bound(lower, memory)?,
168            upper: own_bound(upper, memory)?,
169        })
170    }
171
172    pub(in crate::mvcc) fn borrowed(&self) -> SerializablePredicate<'_> {
173        let selection = match self.space {
174            None => Selection::Object,
175            Some(space) if self.point => {
176                let Bound::Included(key) = &self.lower else {
177                    unreachable!("an owned point retains its exact key");
178                };
179                Selection::Point(space, key)
180            }
181            Some(space) => {
182                Selection::Range(space, borrow_bound(&self.lower), borrow_bound(&self.upper))
183            }
184        };
185        SerializablePredicate {
186            object: self.object,
187            selection,
188        }
189    }
190}
191
192fn own_bound(bound: Bound<&[u8]>, memory: &MemoryBudget) -> VersionResult<Bound<BudgetedVec<u8>>> {
193    let key = match bound {
194        Bound::Unbounded => return Ok(Bound::Unbounded),
195        Bound::Included(key) | Bound::Excluded(key) => key,
196    };
197    let mut owned = BudgetedVec::new(memory);
198    owned.extend_from_slice(key)?;
199    Ok(match bound {
200        Bound::Included(_) => Bound::Included(owned),
201        Bound::Excluded(_) => Bound::Excluded(owned),
202        Bound::Unbounded => unreachable!(),
203    })
204}
205
206fn borrow_bound(bound: &Bound<BudgetedVec<u8>>) -> Bound<&[u8]> {
207    match bound {
208        Bound::Unbounded => Bound::Unbounded,
209        Bound::Included(key) => Bound::Included(key),
210        Bound::Excluded(key) => Bound::Excluded(key),
211    }
212}