1use crate::cursor::column_batch::ColumnValues;
2use std::sync::Arc;
3
4#[derive(Debug, Clone, PartialEq)]
6pub enum IndexPredicate {
7 Equal(linkrs_core::Value),
8 Range {
9 lower: Option<linkrs_core::Value>,
10 upper: Option<linkrs_core::Value>,
11 include_lower: bool,
12 include_upper: bool,
13 },
14 Prefix(linkrs_core::Value),
15 All,
16}
17
18#[derive(Debug, Clone, PartialEq)]
28pub enum ScanPredicate {
29 ColumnEqual {
31 column: Arc<str>,
32 value: linkrs_core::Value,
33 },
34 ColumnRange {
36 column: Arc<str>,
37 lower: Option<linkrs_core::Value>,
38 upper: Option<linkrs_core::Value>,
39 include_lower: bool,
40 include_upper: bool,
41 },
42}
43
44impl ScanPredicate {
45 pub fn matches(&self, props: &[(std::sync::Arc<str>, linkrs_core::Value)]) -> bool {
50 let Some(value) = props
51 .iter()
52 .find(|(name, _)| name.as_ref() == self.column())
53 .map(|(_, v)| v)
54 else {
55 return false;
56 };
57 match self {
58 ScanPredicate::ColumnEqual {
59 value: expected, ..
60 } => compare_scalar(value, expected) == std::cmp::Ordering::Equal,
61 ScanPredicate::ColumnRange {
62 lower,
63 upper,
64 include_lower,
65 include_upper,
66 ..
67 } => {
68 if let Some(lower) = lower {
69 let ord = compare_scalar(value, lower);
70 let passes = if *include_lower {
71 ord != std::cmp::Ordering::Less
72 } else {
73 ord == std::cmp::Ordering::Greater
74 };
75 if !passes {
76 return false;
77 }
78 }
79 if let Some(upper) = upper {
80 let ord = compare_scalar(value, upper);
81 let passes = if *include_upper {
82 ord != std::cmp::Ordering::Greater
83 } else {
84 ord == std::cmp::Ordering::Less
85 };
86 if !passes {
87 return false;
88 }
89 }
90 true
91 }
92 }
93 }
94
95 pub fn column(&self) -> &str {
97 match self {
98 ScanPredicate::ColumnEqual { column, .. } => column,
99 ScanPredicate::ColumnRange { column, .. } => column,
100 }
101 }
102
103 pub fn column_arc(&self) -> Arc<str> {
104 match self {
105 ScanPredicate::ColumnEqual { column, .. } => column.clone(),
106 ScanPredicate::ColumnRange { column, .. } => column.clone(),
107 }
108 }
109
110 pub fn merged_ranges(predicates: &[ScanPredicate]) -> Vec<PredicateRange> {
117 let mut ranges: Vec<PredicateRange> = Vec::new();
118 for predicate in predicates {
119 let (lower, include_lower, upper, include_upper) = match predicate {
120 ScanPredicate::ColumnEqual { value, .. } => {
121 (Some(value.clone()), true, Some(value.clone()), true)
122 }
123 ScanPredicate::ColumnRange {
124 lower,
125 include_lower,
126 upper,
127 include_upper,
128 ..
129 } => (lower.clone(), *include_lower, upper.clone(), *include_upper),
130 };
131 let column = predicate.column_arc();
132 match ranges.iter_mut().find(|r| r.column == column) {
133 Some(range) => range.intersect(lower, include_lower, upper, include_upper),
134 None => ranges.push(PredicateRange {
135 column,
136 lower,
137 include_lower,
138 upper,
139 include_upper,
140 }),
141 }
142 }
143 ranges
144 }
145}
146
147#[derive(Debug, Clone)]
151pub struct PredicateRange {
152 pub column: Arc<str>,
153 pub lower: Option<linkrs_core::Value>,
154 pub include_lower: bool,
155 pub upper: Option<linkrs_core::Value>,
156 pub include_upper: bool,
157}
158
159impl PredicateRange {
160 fn intersect(
162 &mut self,
163 lower: Option<linkrs_core::Value>,
164 include_lower: bool,
165 upper: Option<linkrs_core::Value>,
166 include_upper: bool,
167 ) {
168 if let (_, Some(new_lower)) = (&self.lower, &lower) {
169 let replace = match &self.lower {
170 Some(old) => match compare_scalar(old, new_lower) {
171 std::cmp::Ordering::Equal => self.include_lower && !include_lower,
172 std::cmp::Ordering::Less => true,
173 std::cmp::Ordering::Greater => false,
174 },
175 None => true,
176 };
177 if replace {
178 self.lower = Some(new_lower.clone());
179 self.include_lower = include_lower;
180 }
181 }
182 if let (_, Some(new_upper)) = (&self.upper, &upper) {
183 let replace = match &self.upper {
184 Some(old) => match compare_scalar(old, new_upper) {
185 std::cmp::Ordering::Equal => self.include_upper && !include_upper,
186 std::cmp::Ordering::Greater => true,
187 std::cmp::Ordering::Less => false,
188 },
189 None => true,
190 };
191 if replace {
192 self.upper = Some(new_upper.clone());
193 self.include_upper = include_upper;
194 }
195 }
196 }
197
198 pub fn overlaps(&self, min: &linkrs_core::Value, max: &linkrs_core::Value) -> bool {
202 if let Some(ref lower) = self.lower {
203 let ord = compare_scalar(max, lower);
205 let reaches = if self.include_lower {
206 ord != std::cmp::Ordering::Less
207 } else {
208 ord == std::cmp::Ordering::Greater
209 };
210 if !reaches {
211 return false;
212 }
213 }
214 if let Some(ref upper) = self.upper {
215 let ord = compare_scalar(min, upper);
217 let under = if self.include_upper {
218 ord != std::cmp::Ordering::Greater
219 } else {
220 ord == std::cmp::Ordering::Less
221 };
222 if !under {
223 return false;
224 }
225 }
226 true
227 }
228
229 pub fn equality_len(&self) -> Option<usize> {
235 if !self.include_lower || !self.include_upper {
236 return None;
237 }
238 let lower = self.lower.as_ref()?;
239 let upper = self.upper.as_ref()?;
240 if compare_scalar(lower, upper) != std::cmp::Ordering::Equal {
241 return None;
242 }
243 crate::vertex::column_store::complex_len(lower)
244 }
245
246 pub fn equality_leaf_range(&self) -> Option<(linkrs_core::Value, linkrs_core::Value)> {
252 if !self.include_lower || !self.include_upper {
253 return None;
254 }
255 let lower = self.lower.as_ref()?;
256 let upper = self.upper.as_ref()?;
257 if compare_scalar(lower, upper) != std::cmp::Ordering::Equal {
258 return None;
259 }
260 crate::vertex::column_store::complex_leaf_range(lower)
261 }
262
263 pub fn equality_key_fp(&self) -> Option<u64> {
266 if !self.include_lower || !self.include_upper {
267 return None;
268 }
269 let lower = self.lower.as_ref()?;
270 let upper = self.upper.as_ref()?;
271 if compare_scalar(lower, upper) != std::cmp::Ordering::Equal {
272 return None;
273 }
274 let fp = crate::vertex::column_store::complex_key_fp(lower);
275 (fp != 0).then_some(fp)
276 }
277}
278
279impl ScanPredicate {
280 pub fn matches_column(&self, column: &ColumnValues, idx: usize) -> bool {
284 let Some(value) = column.value_at(idx) else {
285 return false;
286 };
287 self.matches_scalar(&value)
288 }
289
290 pub fn matches_value(&self, value: &linkrs_core::Value) -> bool {
297 self.matches_scalar(value)
298 }
299
300 fn matches_scalar(&self, value: &linkrs_core::Value) -> bool {
302 match self {
303 ScanPredicate::ColumnEqual {
304 value: expected, ..
305 } => compare_scalar(value, expected) == std::cmp::Ordering::Equal,
306 ScanPredicate::ColumnRange {
307 lower,
308 upper,
309 include_lower,
310 include_upper,
311 ..
312 } => {
313 if let Some(lower) = lower {
314 let ord = compare_scalar(value, lower);
315 let passes = if *include_lower {
316 ord != std::cmp::Ordering::Less
317 } else {
318 ord == std::cmp::Ordering::Greater
319 };
320 if !passes {
321 return false;
322 }
323 }
324 if let Some(upper) = upper {
325 let ord = compare_scalar(value, upper);
326 let passes = if *include_upper {
327 ord != std::cmp::Ordering::Greater
328 } else {
329 ord == std::cmp::Ordering::Less
330 };
331 if !passes {
332 return false;
333 }
334 }
335 true
336 }
337 }
338 }
339}
340
341pub fn compare_stat_values(a: &linkrs_core::Value, b: &linkrs_core::Value) -> std::cmp::Ordering {
350 compare_scalar(a, b)
351}
352
353fn compare_scalar(a: &linkrs_core::Value, b: &linkrs_core::Value) -> std::cmp::Ordering {
354 match (as_i64(a), as_i64(b)) {
355 (Some(x), Some(y)) => x.cmp(&y),
356 _ => match (as_f64(a), as_f64(b)) {
357 (Some(x), Some(y)) => x.partial_cmp(&y).unwrap_or(std::cmp::Ordering::Equal),
358 _ => linkrs_core::Value::cmp(a, b),
359 },
360 }
361}
362
363fn as_i64(value: &linkrs_core::Value) -> Option<i64> {
364 match value {
365 linkrs_core::Value::SmallInt(v) => Some(*v as i64),
366 linkrs_core::Value::Int(v) => Some(*v as i64),
367 linkrs_core::Value::BigInt(v) => Some(*v),
368 _ => None,
369 }
370}
371
372fn as_f64(value: &linkrs_core::Value) -> Option<f64> {
373 match value {
374 linkrs_core::Value::Float(v) => Some(*v as f64),
375 linkrs_core::Value::Double(v) => Some(*v),
376 _ => None,
377 }
378}