uqa_storage/mvcc/serializable/
predicates.rs1use std::ops::Bound;
10
11use uqa_core::memory::{BudgetedVec, MemoryBudget};
12
13use super::{VersionError, VersionResult};
14
15#[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#[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 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 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}