1use std::sync::Arc;
25
26use radixdb_core::{CompactArc, I64Set};
27use radixdb_core::{Result, Row, RowVec, Value, ValueSet};
28use radixdb_sql::ast::*;
29use radixdb_storage::traits::{Index, QueryResult, Table};
30
31use super::context::{
32 cache_in_subquery, extract_table_names_for_cache, get_cached_in_subquery, ExecutionContext,
33};
34use super::expression::{ExpressionEval, RowFilter};
35use super::planner::QueryPlanner;
36use super::query_classification::QueryClassification;
37use super::result::ExecutorResult;
38use crate::lookup_key::exact_integer_pk_value;
39
40#[doc(hidden)]
43pub trait IndexOptimizerHost {
44 fn index_project_rows(
45 &self,
46 select_exprs: &[Expression],
47 rows: RowVec,
48 all_columns: &[String],
49 ctx: &ExecutionContext,
50 ) -> Result<RowVec>;
51
52 fn index_project_rows_with_alias(
53 &self,
54 select_exprs: &[Expression],
55 rows: RowVec,
56 all_columns: &[String],
57 all_columns_lower: Option<&[String]>,
58 ctx: &ExecutionContext,
59 table_alias: Option<&str>,
60 ) -> Result<RowVec>;
61
62 fn index_output_column_names(
63 &self,
64 select_exprs: &[Expression],
65 all_columns: &[String],
66 table_alias: Option<&str>,
67 ) -> Vec<String>;
68
69 fn index_query_planner(&self) -> &QueryPlanner;
70
71 fn index_execute_select(
72 &self,
73 stmt: &SelectStatement,
74 ctx: &ExecutionContext,
75 ) -> Result<Box<dyn QueryResult>>;
76
77 fn index_process_where_subqueries(
78 &self,
79 expr: &Expression,
80 ctx: &ExecutionContext,
81 ) -> Result<Expression>;
82
83 fn index_has_subqueries(expr: &Expression) -> bool;
84
85 fn index_is_subquery_correlated(subquery: &SelectStatement) -> bool;
86}
87
88#[doc(hidden)]
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum IntegerPkKeysetBound {
91 After(i64),
92 From(i64),
93 Empty,
94}
95
96impl IntegerPkKeysetBound {
97 fn strongest_lower(self, right: Self) -> Self {
101 match (self, right) {
102 (Self::Empty, _) | (_, Self::Empty) => Self::Empty,
103 (Self::After(left), Self::After(right)) => Self::After(left.max(right)),
104 (Self::From(left), Self::From(right)) => Self::From(left.max(right)),
105 (Self::After(exclusive), Self::From(inclusive)) => {
106 if exclusive >= inclusive {
107 Self::After(exclusive)
108 } else {
109 Self::From(inclusive)
110 }
111 }
112 (Self::From(inclusive), Self::After(exclusive)) => {
113 if exclusive >= inclusive {
114 Self::After(exclusive)
115 } else {
116 Self::From(inclusive)
117 }
118 }
119 }
120 }
121
122 fn from_parts(start_after: Option<i64>, start_from: Option<i64>) -> Option<Self> {
123 match (start_after, start_from) {
124 (Some(after), Some(from)) => Some(Self::After(after).strongest_lower(Self::From(from))),
125 (Some(after), None) => Some(Self::After(after)),
126 (None, Some(from)) => Some(Self::From(from)),
127 (None, None) => None,
128 }
129 }
130
131 fn into_parts(self) -> (Option<i64>, Option<i64>, bool) {
132 match self {
133 Self::After(value) => (Some(value), None, false),
134 Self::From(value) => (None, Some(value), false),
135 Self::Empty => (None, None, true),
136 }
137 }
138}
139
140#[doc(hidden)]
143pub enum VectorProjectionSlot {
144 Star,
146 Column(usize),
148 Distance,
150 Compiled(Box<ExpressionEval>),
152}
153
154#[doc(hidden)]
156pub trait IndexOptimizerExt: IndexOptimizerHost {
157 #[allow(clippy::type_complexity)]
162 fn try_min_max_index_optimization(
163 &self,
164 stmt: &SelectStatement,
165 table: &dyn Table,
166 _all_columns: &[String],
167 ) -> Result<Option<(Box<dyn QueryResult>, CompactArc<Vec<String>>)>> {
168 if stmt.columns.len() != 1 {
170 return Ok(None);
171 }
172
173 let col_expr = &stmt.columns[0];
174
175 let (func, alias) = match col_expr {
177 Expression::FunctionCall(func) => (func, None),
178 Expression::Aliased(aliased) => {
179 if let Expression::FunctionCall(func) = aliased.expression.as_ref() {
180 (func, Some(aliased.alias.value.to_string()))
181 } else {
182 return Ok(None);
183 }
184 }
185 _ => return Ok(None),
186 };
187
188 if func.function != "MIN" && func.function != "MAX" {
191 return Ok(None);
192 }
193
194 if func.is_distinct
198 || func.filter.is_some()
199 || func.arguments.len() != 1
200 || stmt.having.is_some()
201 {
202 return Ok(None);
203 }
204
205 let column_name = match &func.arguments[0] {
206 Expression::Identifier(id) => id.value.to_string(),
207 Expression::QualifiedIdentifier(qid) => qid.name.value.to_string(),
208 _ => return Ok(None),
209 };
210
211 let value = if func.function == "MIN" {
213 table.get_index_min_value(&column_name)
214 } else {
215 table.get_index_max_value(&column_name)
216 };
217
218 if let Some(val) = value {
219 let col_name = alias.unwrap_or_else(|| format!("{}({})", func.function, column_name));
221 let columns = CompactArc::new(vec![col_name]);
222 let mut rows = RowVec::with_capacity(1);
223 rows.push((0, Row::from_values(vec![val])));
224 let result: Box<dyn QueryResult> = Box::new(ExecutorResult::with_arc_columns(
225 CompactArc::clone(&columns),
226 rows,
227 ));
228 return Ok(Some((result, columns)));
229 }
230
231 Ok(None)
232 }
233
234 #[allow(clippy::type_complexity)]
239 fn try_count_star_optimization(
240 &self,
241 stmt: &SelectStatement,
242 table: &dyn Table,
243 ) -> Result<Option<(Box<dyn QueryResult>, CompactArc<Vec<String>>)>> {
244 if stmt.columns.len() != 1 {
246 return Ok(None);
247 }
248
249 let col_expr = &stmt.columns[0];
250
251 let (func, alias) = match col_expr {
253 Expression::FunctionCall(func) => (func, None),
254 Expression::Aliased(aliased) => {
255 if let Expression::FunctionCall(func) = aliased.expression.as_ref() {
256 (func, Some(aliased.alias.value.to_string()))
257 } else {
258 return Ok(None);
259 }
260 }
261 _ => return Ok(None),
262 };
263
264 if func.function != "COUNT" {
267 return Ok(None);
268 }
269
270 if func.is_distinct || stmt.having.is_some() {
272 return Ok(None);
273 }
274
275 if func.filter.is_some() {
277 return Ok(None);
278 }
279
280 let is_count_star = func.arguments.is_empty()
282 || (func.arguments.len() == 1 && matches!(func.arguments[0], Expression::Star(_)));
283
284 if !is_count_star {
285 return Ok(None);
286 }
287
288 let count = table.row_count();
290
291 let col_name = alias.unwrap_or_else(|| "COUNT(*)".to_string());
293 let columns = CompactArc::new(vec![col_name]);
294 let mut rows = RowVec::with_capacity(1);
295 rows.push((0, Row::from_values(vec![Value::Integer(count as i64)])));
296 let result: Box<dyn QueryResult> = Box::new(ExecutorResult::with_arc_columns(
297 CompactArc::clone(&columns),
298 rows,
299 ));
300 Ok(Some((result, columns)))
301 }
302
303 #[allow(clippy::type_complexity)]
308 fn try_order_by_index_optimization(
309 &self,
310 stmt: &SelectStatement,
311 table: &dyn Table,
312 all_columns: &[String],
313 ctx: &ExecutionContext,
314 ) -> Result<Option<(Box<dyn QueryResult>, CompactArc<Vec<String>>)>> {
315 let order_by = &stmt.order_by[0];
317 let column_name = match &order_by.expression {
318 Expression::Identifier(id) => id.value.clone(),
319 Expression::QualifiedIdentifier(qid) => qid.name.value.clone(),
320 _ => return Ok(None), };
322
323 let ascending = order_by.ascending;
325
326 let requested_nulls_first = order_by.nulls_first.unwrap_or(!ascending);
333 let structural_nulls_first = ascending;
334 if requested_nulls_first != structural_nulls_first
335 && table
336 .schema()
337 .find_column(&column_name)
338 .is_some_and(|(_, column)| column.nullable)
339 {
340 return Ok(None);
341 }
342
343 let limit = if let Some(ref limit_expr) = stmt.limit {
345 match ExpressionEval::compile(limit_expr, &[])
346 .ok()
347 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
348 {
349 Some(Value::Integer(l)) => l as usize,
350 Some(Value::Float(f)) => f as usize,
351 _ => return Ok(None),
352 }
353 } else {
354 return Ok(None);
355 };
356
357 let offset = if let Some(ref offset_expr) = stmt.offset {
358 match ExpressionEval::compile(offset_expr, &[])
359 .ok()
360 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
361 {
362 Some(Value::Integer(o)) => o as usize,
363 Some(Value::Float(f)) => f as usize,
364 _ => 0,
365 }
366 } else {
367 0
368 };
369
370 if let Some(rows) =
372 table.collect_rows_ordered_by_index(&column_name, ascending, limit, offset)
373 {
374 let projected_rows = self.index_project_rows(&stmt.columns, rows, all_columns, ctx)?;
376
377 let output_columns =
380 CompactArc::new(self.index_output_column_names(&stmt.columns, all_columns, None));
381
382 let result = ExecutorResult::with_arc_columns(
383 CompactArc::clone(&output_columns),
384 projected_rows,
385 );
386 return Ok(Some((Box::new(result), output_columns)));
387 }
388
389 Ok(None)
390 }
391
392 #[allow(clippy::type_complexity)]
398 fn try_keyset_pagination_optimization(
399 &self,
400 stmt: &SelectStatement,
401 where_expr: Option<&Expression>,
402 table: &dyn Table,
403 all_columns: &[String],
404 table_alias: Option<&str>,
405 ctx: &ExecutionContext,
406 ) -> Result<Option<(Box<dyn QueryResult>, CompactArc<Vec<String>>)>> {
407 let where_clause = match where_expr {
409 Some(expr) => expr,
410 None => return Ok(None),
411 };
412
413 let order_by = &stmt.order_by[0];
415 let order_column = match &order_by.expression {
416 Expression::Identifier(id) => id.value.clone(),
417 Expression::QualifiedIdentifier(qid) => qid.name.value.clone(),
418 _ => return Ok(None),
419 };
420
421 let ascending = order_by.ascending;
423
424 let schema = table.schema();
426 let pk_indices = schema.primary_key_indices();
427 if pk_indices.len() != 1 {
428 return Ok(None); }
430
431 let pk_idx = pk_indices[0];
432 if schema.columns[pk_idx].data_type != radixdb_core::DataType::Integer {
433 return Ok(None);
434 }
435 let pk_column = &schema.columns[pk_idx].name;
436
437 if !order_column.eq_ignore_ascii_case(pk_column) {
439 return Ok(None);
440 }
441
442 let (start_after, start_from, is_pure_keyset, is_empty) =
445 self.extract_pk_keyset_bounds(where_clause, pk_column, ctx)?;
446
447 if is_empty && is_pure_keyset {
448 let output_columns = CompactArc::new(self.index_output_column_names(
449 &stmt.columns,
450 all_columns,
451 table_alias,
452 ));
453 return Ok(Some((
454 Box::new(ExecutorResult::with_arc_columns(
455 CompactArc::clone(&output_columns),
456 RowVec::new(),
457 )),
458 output_columns,
459 )));
460 }
461
462 if start_after.is_none() && start_from.is_none() {
465 return Ok(None);
466 }
467 if !is_pure_keyset {
468 return Ok(None);
469 }
470
471 let limit = if let Some(ref limit_expr) = stmt.limit {
473 match ExpressionEval::compile(limit_expr, &[])
474 .ok()
475 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
476 {
477 Some(Value::Integer(l)) => l as usize,
478 Some(Value::Float(f)) => f as usize,
479 _ => return Ok(None),
480 }
481 } else {
482 return Ok(None);
483 };
484
485 if let Some(rows) = table.collect_rows_pk_keyset(start_after, start_from, ascending, limit)
487 {
488 let all_columns_lower = schema.column_names_lower_arc();
491 let projected_rows = self.index_project_rows_with_alias(
492 &stmt.columns,
493 rows,
494 all_columns,
495 Some(&all_columns_lower),
496 ctx,
497 table_alias,
498 )?;
499 let output_columns = CompactArc::new(self.index_output_column_names(
500 &stmt.columns,
501 all_columns,
502 table_alias,
503 ));
504
505 let result = ExecutorResult::with_arc_columns(
506 CompactArc::clone(&output_columns),
507 projected_rows,
508 );
509 return Ok(Some((Box::new(result), output_columns)));
510 }
511
512 Ok(None)
513 }
514
515 #[allow(clippy::type_complexity)]
517 fn try_composite_ordered_range_optimization(
518 &self,
519 stmt: &SelectStatement,
520 where_expr: &dyn radixdb_storage::expression::Expression,
521 table: &dyn Table,
522 all_columns: &[String],
523 table_alias: Option<&str>,
524 ctx: &ExecutionContext,
525 ) -> Result<Option<(Box<dyn QueryResult>, CompactArc<Vec<String>>)>> {
526 let order_by = &stmt.order_by[0];
527 let order_column = match &order_by.expression {
528 Expression::Identifier(id) => id.value.as_str(),
529 Expression::QualifiedIdentifier(qid) => qid.name.value.as_str(),
530 _ => return Ok(None),
531 };
532 let limit = match stmt
533 .limit
534 .as_ref()
535 .and_then(|expr| ExpressionEval::compile(expr, &[]).ok())
536 .and_then(|expr| expr.with_context(ctx).eval_slice(&Row::new()).ok())
537 {
538 Some(Value::Integer(value)) if value >= 0 => value as usize,
539 Some(Value::Float(value)) if value >= 0.0 => value as usize,
540 _ => return Ok(None),
541 };
542 let offset = match stmt
543 .offset
544 .as_ref()
545 .and_then(|expr| ExpressionEval::compile(expr, &[]).ok())
546 .and_then(|expr| expr.with_context(ctx).eval_slice(&Row::new()).ok())
547 {
548 Some(Value::Integer(value)) if value >= 0 => value as usize,
549 Some(Value::Float(value)) if value >= 0.0 => value as usize,
550 None => 0,
551 _ => return Ok(None),
552 };
553
554 let Some(rows) = table.collect_rows_composite_ordered_range(
555 where_expr,
556 order_column,
557 order_by.ascending,
558 limit,
559 offset,
560 ) else {
561 return Ok(None);
562 };
563 let rows = rows?;
564 let lower = table.schema().column_names_lower_arc();
565 let projected_rows = self.index_project_rows_with_alias(
566 &stmt.columns,
567 rows,
568 all_columns,
569 Some(&lower),
570 ctx,
571 table_alias,
572 )?;
573 let output_columns = CompactArc::new(self.index_output_column_names(
574 &stmt.columns,
575 all_columns,
576 table_alias,
577 ));
578 let result =
579 ExecutorResult::with_arc_columns(CompactArc::clone(&output_columns), projected_rows);
580 Ok(Some((Box::new(result), output_columns)))
581 }
582
583 fn extract_pk_keyset_bounds(
591 &self,
592 expr: &Expression,
593 pk_column: &str,
594 ctx: &ExecutionContext,
595 ) -> Result<(Option<i64>, Option<i64>, bool, bool)> {
596 if let Expression::Infix(infix) = expr {
597 let op = infix.operator.as_str();
598
599 match op {
601 ">" | ">=" | "<" | "<=" => {
602 let left_is_pk = match &*infix.left {
604 Expression::Identifier(id) => id.value.eq_ignore_ascii_case(pk_column),
605 Expression::QualifiedIdentifier(qid) => {
606 qid.name.value.eq_ignore_ascii_case(pk_column)
607 }
608 _ => false,
609 };
610
611 if left_is_pk {
612 if matches!(op, ">" | ">=") {
614 if let Some(value) = self.eval_pk_keyset_scalar(&infix.right, ctx) {
615 if let Some(bound) =
616 Self::integer_pk_keyset_lower_bound(&value, op == ">=")
617 {
618 return Ok(match bound {
619 IntegerPkKeysetBound::After(value) => {
620 (Some(value), None, true, false)
621 }
622 IntegerPkKeysetBound::From(value) => {
623 (None, Some(value), true, false)
624 }
625 IntegerPkKeysetBound::Empty => (None, None, true, true),
626 });
627 }
628 }
629 }
630 } else {
631 let right_is_pk = match &*infix.right {
633 Expression::Identifier(id) => id.value.eq_ignore_ascii_case(pk_column),
634 Expression::QualifiedIdentifier(qid) => {
635 qid.name.value.eq_ignore_ascii_case(pk_column)
636 }
637 _ => false,
638 };
639
640 if right_is_pk && matches!(op, "<" | "<=") {
641 if let Some(value) = self.eval_pk_keyset_scalar(&infix.left, ctx) {
642 if let Some(bound) =
643 Self::integer_pk_keyset_lower_bound(&value, op == "<=")
644 {
645 return Ok(match bound {
646 IntegerPkKeysetBound::After(value) => {
647 (Some(value), None, true, false)
648 }
649 IntegerPkKeysetBound::From(value) => {
650 (None, Some(value), true, false)
651 }
652 IntegerPkKeysetBound::Empty => (None, None, true, true),
653 });
654 }
655 }
656 }
657 }
658 }
659 "AND" => {
660 let (left_after, left_from, left_pure, left_empty) =
662 self.extract_pk_keyset_bounds(&infix.left, pk_column, ctx)?;
663 let (right_after, right_from, right_pure, right_empty) =
664 self.extract_pk_keyset_bounds(&infix.right, pk_column, ctx)?;
665
666 let is_pure = left_pure && right_pure;
670
671 if left_empty || right_empty {
672 return Ok((None, None, is_pure, true));
673 }
674
675 let left_bound = IntegerPkKeysetBound::from_parts(left_after, left_from);
676 let right_bound = IntegerPkKeysetBound::from_parts(right_after, right_from);
677 let strongest = match (left_bound, right_bound) {
678 (Some(left), Some(right)) => Some(left.strongest_lower(right)),
679 (Some(bound), None) | (None, Some(bound)) => Some(bound),
680 (None, None) => None,
681 };
682
683 let (start_after, start_from, is_empty) = strongest
684 .map(IntegerPkKeysetBound::into_parts)
685 .unwrap_or((None, None, false));
686
687 return Ok((start_after, start_from, is_pure, is_empty));
688 }
689 _ => {}
690 }
691 }
692
693 Ok((None, None, false, false))
695 }
696
697 fn eval_pk_keyset_scalar(&self, expr: &Expression, ctx: &ExecutionContext) -> Option<Value> {
699 match expr {
700 Expression::IntegerLiteral(lit) => Some(Value::Integer(lit.value)),
701 Expression::FloatLiteral(lit) => Some(Value::Float(lit.value)),
702 Expression::Parameter(param) => {
703 if param.name.starts_with(':') {
706 let name = ¶m.name[1..];
707 ctx.get_named_param(name).cloned()
708 } else {
709 let params = ctx.params();
710 let param_idx = if param.index > 0 {
711 param.index - 1
712 } else {
713 return None;
714 };
715 if param_idx < params.len() {
716 Some(params[param_idx].clone())
717 } else {
718 None
719 }
720 }
721 }
722 _ => {
723 ExpressionEval::compile(expr, &[])
725 .ok()
726 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
727 }
728 }
729 }
730
731 fn integer_pk_keyset_lower_bound(
734 value: &Value,
735 inclusive: bool,
736 ) -> Option<IntegerPkKeysetBound> {
737 if let Some(integer) = exact_integer_pk_value(value) {
738 return Some(if inclusive {
739 IntegerPkKeysetBound::From(integer)
740 } else {
741 IntegerPkKeysetBound::After(integer)
742 });
743 }
744
745 let Value::Float(float) = value else {
746 return None;
747 };
748 if float.is_nan() || *float == f64::INFINITY {
749 return Some(IntegerPkKeysetBound::Empty);
750 }
751 if *float == f64::NEG_INFINITY || *float < i64::MIN as f64 {
752 return Some(IntegerPkKeysetBound::From(i64::MIN));
753 }
754 if *float >= i64::MAX as f64 {
755 return Some(IntegerPkKeysetBound::Empty);
756 }
757
758 debug_assert!(float.is_finite() && float.fract() != 0.0);
759 Some(IntegerPkKeysetBound::From(float.ceil() as i64))
760 }
761
762 fn extract_window_order_info(stmt: &SelectStatement) -> Option<(String, bool)> {
765 for col_expr in &stmt.columns {
767 if let Some(info) = Self::find_window_order_in_expr(col_expr) {
768 return Some(info);
769 }
770 }
771 None
772 }
773
774 fn is_window_safe_for_limit_pushdown(stmt: &SelectStatement) -> bool {
786 for col_expr in &stmt.columns {
787 if !Self::is_expr_window_safe(col_expr) {
788 return false;
789 }
790 }
791 true
792 }
793
794 fn is_expr_window_safe(expr: &Expression) -> bool {
797 match expr {
798 Expression::Window(window_expr) => {
799 let func_name = window_expr.function.function.to_uppercase();
800 !matches!(func_name.as_str(), "NTILE" | "PERCENT_RANK" | "CUME_DIST")
802 }
803 Expression::Aliased(aliased) => Self::is_expr_window_safe(&aliased.expression),
804 Expression::Case(case_expr) => {
806 if let Some(value) = &case_expr.value {
808 if !Self::is_expr_window_safe(value) {
809 return false;
810 }
811 }
812 for when_clause in &case_expr.when_clauses {
814 if !Self::is_expr_window_safe(&when_clause.condition)
815 || !Self::is_expr_window_safe(&when_clause.then_result)
816 {
817 return false;
818 }
819 }
820 if let Some(else_expr) = &case_expr.else_value {
822 if !Self::is_expr_window_safe(else_expr) {
823 return false;
824 }
825 }
826 true
827 }
828 Expression::FunctionCall(func) => {
829 func.arguments.iter().all(Self::is_expr_window_safe)
831 }
832 Expression::Infix(infix) => {
833 Self::is_expr_window_safe(&infix.left) && Self::is_expr_window_safe(&infix.right)
835 }
836 Expression::Cast(cast) => Self::is_expr_window_safe(&cast.expr),
837 Expression::Prefix(prefix) => Self::is_expr_window_safe(&prefix.right),
838 Expression::ScalarSubquery(_) => {
839 true
842 }
843 Expression::In(in_expr) => {
844 Self::is_expr_window_safe(&in_expr.left)
846 && Self::is_expr_window_safe(&in_expr.right)
847 }
848 Expression::Between(between) => {
849 Self::is_expr_window_safe(&between.expr)
850 && Self::is_expr_window_safe(&between.lower)
851 && Self::is_expr_window_safe(&between.upper)
852 }
853 Expression::List(list) => {
854 list.elements.iter().all(Self::is_expr_window_safe)
856 }
857 Expression::ExpressionList(expr_list) => {
858 expr_list.expressions.iter().all(Self::is_expr_window_safe)
860 }
861 Expression::Like(like) => {
862 Self::is_expr_window_safe(&like.left)
863 && Self::is_expr_window_safe(&like.pattern)
864 && like
865 .escape
866 .as_ref()
867 .is_none_or(|e| Self::is_expr_window_safe(e))
868 }
869 Expression::Exists(_) | Expression::AllAny(_) => {
870 true
872 }
873 Expression::Distinct(distinct) => Self::is_expr_window_safe(&distinct.expr),
874 Expression::Identifier(_)
876 | Expression::QualifiedIdentifier(_)
877 | Expression::IntegerLiteral(_)
878 | Expression::FloatLiteral(_)
879 | Expression::StringLiteral(_)
880 | Expression::BooleanLiteral(_)
881 | Expression::NullLiteral(_)
882 | Expression::Parameter(_)
883 | Expression::IntervalLiteral(_)
884 | Expression::BoundValue(_)
885 | Expression::Star(_)
886 | Expression::QualifiedStar(_)
887 | Expression::Default(_)
888 | Expression::InHashSet(_)
889 | Expression::TableSource(_)
890 | Expression::JoinSource(_)
891 | Expression::SubquerySource(_)
892 | Expression::ValuesSource(_)
893 | Expression::CteReference(_)
894 | Expression::FunctionTableSource(_) => true,
895 }
896 }
897
898 fn has_equality_condition(expr: &Expression) -> bool {
904 match expr {
905 Expression::Infix(infix) => {
907 let op = infix.operator.as_str();
908 if op == "=" {
909 return true;
911 }
912 if op.eq_ignore_ascii_case("AND") || op.eq_ignore_ascii_case("OR") {
914 return Self::has_equality_condition(&infix.left)
915 || Self::has_equality_condition(&infix.right);
916 }
917 false
918 }
919 _ => false,
921 }
922 }
923
924 fn estimate_table_expr_cardinality(
931 &self,
932 expr: &Expression,
933 filter: Option<&Expression>,
934 ) -> u64 {
935 let table_name: &str = match expr {
937 Expression::TableSource(ts) => ts.name.value_lower.as_str(),
938 Expression::Aliased(aliased) => {
939 if let Expression::TableSource(ts) = aliased.expression.as_ref() {
940 ts.name.value_lower.as_str()
941 } else {
942 return u64::MAX; }
944 }
945 _ => return u64::MAX, };
947
948 self.index_query_planner()
950 .estimate_scan_rows(table_name, filter)
951 .unwrap_or(u64::MAX)
952 }
953
954 fn find_window_order_in_expr(expr: &Expression) -> Option<(String, bool)> {
956 match expr {
957 Expression::Window(window_expr) => {
958 if !window_expr.partition_by.is_empty() {
960 return None; }
962
963 if window_expr.window_ref.is_some() {
965 return None;
966 }
967
968 let order_by = &window_expr.order_by;
970
971 if order_by.len() != 1 {
973 return None;
974 }
975
976 let order = &order_by[0];
977 let column_name = match &order.expression {
978 Expression::Identifier(id) => id.value.to_string(),
979 Expression::QualifiedIdentifier(qid) => qid.name.value.to_string(),
980 _ => return None, };
982
983 Some((column_name, order.ascending))
984 }
985 Expression::Aliased(aliased) => Self::find_window_order_in_expr(&aliased.expression),
986 _ => None,
987 }
988 }
989
990 fn extract_window_partition_info(stmt: &SelectStatement) -> Option<String> {
993 let mut canonical: Option<String> = None;
997 for col_expr in &stmt.columns {
998 if !Self::collect_window_partitions(col_expr, &mut canonical) {
999 return None;
1000 }
1001 }
1002 canonical
1003 }
1004
1005 fn collect_window_partitions(expr: &Expression, canonical: &mut Option<String>) -> bool {
1009 match expr {
1010 Expression::Window(window_expr) => {
1011 if window_expr.partition_by.is_empty()
1012 || window_expr.partition_by.len() != 1
1013 || window_expr.window_ref.is_some()
1014 {
1015 return false;
1016 }
1017 let col_name = match &window_expr.partition_by[0] {
1018 Expression::Identifier(id) => id.value.to_string(),
1019 Expression::QualifiedIdentifier(qid) => qid.name.value.to_string(),
1020 _ => return false,
1021 };
1022 match canonical {
1023 None => *canonical = Some(col_name),
1024 Some(c) if !c.eq_ignore_ascii_case(&col_name) => return false,
1025 _ => {}
1026 }
1027 true
1028 }
1029 Expression::Aliased(aliased) => {
1030 Self::collect_window_partitions(&aliased.expression, canonical)
1031 }
1032 Expression::Infix(infix) => {
1033 Self::collect_window_partitions(&infix.left, canonical)
1034 && Self::collect_window_partitions(&infix.right, canonical)
1035 }
1036 Expression::Prefix(prefix) => Self::collect_window_partitions(&prefix.right, canonical),
1037 Expression::Case(case) => {
1038 if let Some(v) = &case.value {
1039 if !Self::collect_window_partitions(v, canonical) {
1040 return false;
1041 }
1042 }
1043 for w in &case.when_clauses {
1044 if !Self::collect_window_partitions(&w.condition, canonical)
1045 || !Self::collect_window_partitions(&w.then_result, canonical)
1046 {
1047 return false;
1048 }
1049 }
1050 if let Some(e) = &case.else_value {
1051 if !Self::collect_window_partitions(e, canonical) {
1052 return false;
1053 }
1054 }
1055 true
1056 }
1057 Expression::Cast(cast) => Self::collect_window_partitions(&cast.expr, canonical),
1058 Expression::FunctionCall(func) => {
1059 for arg in &func.arguments {
1060 if !Self::collect_window_partitions(arg, canonical) {
1061 return false;
1062 }
1063 }
1064 true
1065 }
1066 Expression::Between(between) => {
1067 Self::collect_window_partitions(&between.expr, canonical)
1068 && Self::collect_window_partitions(&between.lower, canonical)
1069 && Self::collect_window_partitions(&between.upper, canonical)
1070 }
1071 Expression::In(in_expr) => {
1072 Self::collect_window_partitions(&in_expr.left, canonical)
1073 && Self::collect_window_partitions(&in_expr.right, canonical)
1074 }
1075 Expression::Like(l) => {
1076 Self::collect_window_partitions(&l.left, canonical)
1077 && Self::collect_window_partitions(&l.pattern, canonical)
1078 && l.escape
1079 .as_ref()
1080 .is_none_or(|e| Self::collect_window_partitions(e, canonical))
1081 }
1082 Expression::Distinct(d) => Self::collect_window_partitions(&d.expr, canonical),
1083 Expression::List(l) => l
1084 .elements
1085 .iter()
1086 .all(|e| Self::collect_window_partitions(e, canonical)),
1087 Expression::ExpressionList(l) => l
1088 .expressions
1089 .iter()
1090 .all(|e| Self::collect_window_partitions(e, canonical)),
1091 _ => true,
1093 }
1094 }
1095
1096 #[allow(clippy::type_complexity)]
1102 #[allow(clippy::too_many_arguments)]
1103 fn try_in_subquery_index_optimization(
1104 &self,
1105 stmt: &SelectStatement,
1106 where_expr: &Expression,
1107 table: &dyn Table,
1108 all_columns: &[String],
1109 table_alias: Option<&str>,
1110 ctx: &ExecutionContext,
1111 classification: &Arc<QueryClassification>,
1112 ) -> Result<Option<(Box<dyn QueryResult>, CompactArc<Vec<String>>)>> {
1113 let (column_name, subquery, is_negated, remaining_predicate) =
1115 match Self::extract_in_subquery_info(where_expr) {
1116 Some(info) => info,
1117 None => return Ok(None),
1118 };
1119
1120 if is_negated {
1124 return Ok(None);
1125 }
1126
1127 if <Self as IndexOptimizerHost>::index_is_subquery_correlated(&subquery.subquery) {
1129 return Ok(None);
1130 }
1131
1132 if classification.select_has_correlated_subqueries {
1135 return Ok(None);
1136 }
1137
1138 let schema = table.schema();
1140 let pk_indices = schema.primary_key_indices();
1141 let is_pk_column = pk_indices.len() == 1 && {
1142 let pk_col_idx = pk_indices[0];
1143 schema.columns[pk_col_idx].data_type == radixdb_core::DataType::Integer
1144 && schema.columns[pk_col_idx].name_lower == column_name
1145 };
1146
1147 let cache_key = subquery.subquery.to_string();
1149 let values = if let Some(cached) = get_cached_in_subquery(&cache_key) {
1150 cached
1151 } else {
1152 let subquery_ctx = ctx.with_incremented_query_depth();
1153 let mut result = self.index_execute_select(&subquery.subquery, &subquery_ctx)?;
1154
1155 let mut values = Vec::new();
1157 while result.next() {
1158 let row = result.row();
1159 if !row.is_empty() {
1160 values.push(
1161 row.get(0)
1162 .cloned()
1163 .unwrap_or_else(radixdb_core::Value::null_unknown),
1164 );
1165 }
1166 }
1167 if let Some(err) = result.last_error() {
1168 return Err(err);
1169 }
1170 cache_in_subquery(
1172 cache_key,
1173 extract_table_names_for_cache(&subquery.subquery),
1174 values.clone(),
1175 );
1176 values
1177 };
1178
1179 let indexed_row_ids = if !is_pk_column && !is_negated {
1184 match table.collect_row_ids_by_index_values(&column_name, &values) {
1185 Some(row_ids) => Some(row_ids?),
1186 None => return Ok(None),
1187 }
1188 } else {
1189 None
1190 };
1191
1192 if values.is_empty() {
1193 if is_negated {
1195 return Ok(None);
1197 } else {
1198 let output_columns = CompactArc::new(self.index_output_column_names(
1200 &stmt.columns,
1201 all_columns,
1202 table_alias,
1203 ));
1204 let result = ExecutorResult::with_arc_columns(
1205 CompactArc::clone(&output_columns),
1206 RowVec::new(),
1207 );
1208 return Ok(Some((Box::new(result), output_columns)));
1209 }
1210 }
1211
1212 let mut all_row_ids = Vec::with_capacity(values.len());
1215 if is_pk_column {
1216 if is_negated {
1217 let exclusion_set: I64Set =
1222 values.iter().filter_map(exact_integer_pk_value).collect();
1223
1224 let offset = stmt
1226 .offset
1227 .as_ref()
1228 .and_then(|e| {
1229 ExpressionEval::compile(e, &[])
1230 .ok()
1231 .and_then(|eval| eval.with_context(ctx).eval_slice(&Row::new()).ok())
1232 .and_then(|v| {
1233 if let Value::Integer(o) = v {
1234 Some(o.max(0) as usize)
1235 } else {
1236 None
1237 }
1238 })
1239 })
1240 .unwrap_or(0);
1241
1242 let limit = stmt
1243 .limit
1244 .as_ref()
1245 .and_then(|e| {
1246 ExpressionEval::compile(e, &[])
1247 .ok()
1248 .and_then(|eval| eval.with_context(ctx).eval_slice(&Row::new()).ok())
1249 .and_then(|v| {
1250 if let Value::Integer(l) = v {
1251 Some(l.max(0) as usize)
1252 } else {
1253 None
1254 }
1255 })
1256 })
1257 .unwrap_or(usize::MAX);
1258
1259 let target = if limit == usize::MAX {
1260 usize::MAX
1261 } else {
1262 offset.saturating_add(limit)
1263 };
1264
1265 let row_count = table.row_count();
1268 for row_id in 1..=(row_count as i64) {
1269 if !exclusion_set.contains(row_id) {
1270 all_row_ids.push(row_id);
1271 if all_row_ids.len() >= target {
1272 break;
1273 }
1274 }
1275 }
1276
1277 if offset > 0 && offset < all_row_ids.len() {
1279 all_row_ids = all_row_ids.split_off(offset);
1280 } else if offset >= all_row_ids.len() {
1281 all_row_ids.clear();
1282 }
1283
1284 if limit < all_row_ids.len() {
1286 all_row_ids.truncate(limit);
1287 }
1288 } else {
1289 for value in &values {
1291 if let Some(id) = exact_integer_pk_value(value) {
1292 all_row_ids.push(id);
1293 }
1294 }
1295 }
1296 } else if is_negated {
1297 return Ok(None);
1299 } else if let Some(row_ids) = indexed_row_ids {
1300 all_row_ids.extend(row_ids);
1301 }
1302
1303 if !is_negated {
1307 all_row_ids.sort_unstable();
1308 all_row_ids.dedup();
1309 }
1310
1311 let (early_limit_applied, early_limit, early_offset) =
1314 if stmt.order_by.is_empty() && remaining_predicate.is_none() {
1315 let offset = if let Some(ref offset_expr) = stmt.offset {
1316 match ExpressionEval::compile(offset_expr, &[])
1317 .ok()
1318 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1319 {
1320 Some(Value::Integer(o)) if o >= 0 => o as usize,
1321 _ => 0,
1322 }
1323 } else {
1324 0
1325 };
1326
1327 let limit = if let Some(ref limit_expr) = stmt.limit {
1328 match ExpressionEval::compile(limit_expr, &[])
1329 .ok()
1330 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1331 {
1332 Some(Value::Integer(l)) if l >= 0 => l as usize,
1333 _ => usize::MAX,
1334 }
1335 } else {
1336 usize::MAX
1337 };
1338
1339 if limit < usize::MAX {
1341 let take_count = (offset + limit).min(all_row_ids.len());
1342 all_row_ids.truncate(take_count);
1343 }
1344 (true, limit, offset)
1345 } else {
1346 (false, usize::MAX, 0)
1347 };
1348
1349 use radixdb_storage::expression::logical::ConstBoolExpr;
1351 let filter: Box<dyn radixdb_storage::expression::Expression> =
1352 Box::new(ConstBoolExpr::true_expr());
1353
1354 let mut rows = table.fetch_rows_by_ids(&all_row_ids, filter.as_ref())?;
1356
1357 if let Some(ref remaining) = remaining_predicate {
1359 let processed_remaining =
1361 if <Self as IndexOptimizerHost>::index_has_subqueries(remaining) {
1362 self.index_process_where_subqueries(remaining, ctx)?
1363 } else {
1364 remaining.clone()
1365 };
1366
1367 let columns_slice: Vec<String> = all_columns.to_vec();
1369 let row_filter =
1370 RowFilter::new(&processed_remaining, &columns_slice)?.with_context(ctx);
1371
1372 row_filter.retain_checked(&mut rows)?;
1374 }
1375
1376 if early_limit_applied {
1379 if early_offset > 0 {
1381 rows = rows
1382 .into_iter()
1383 .skip(early_offset)
1384 .take(early_limit)
1385 .collect();
1386 } else if early_limit < rows.len() {
1387 rows.truncate(early_limit);
1388 }
1389 } else if stmt.order_by.is_empty() {
1390 let offset = if let Some(ref offset_expr) = stmt.offset {
1391 match ExpressionEval::compile(offset_expr, &[])
1392 .ok()
1393 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1394 {
1395 Some(Value::Integer(o)) if o >= 0 => o as usize,
1396 _ => 0,
1397 }
1398 } else {
1399 0
1400 };
1401
1402 let limit = if let Some(ref limit_expr) = stmt.limit {
1403 match ExpressionEval::compile(limit_expr, &[])
1404 .ok()
1405 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1406 {
1407 Some(Value::Integer(l)) if l >= 0 => l as usize,
1408 _ => usize::MAX,
1409 }
1410 } else {
1411 usize::MAX
1412 };
1413
1414 if offset > 0 || limit < usize::MAX {
1415 rows = rows.into_iter().skip(offset).take(limit).collect();
1416 }
1417 }
1418
1419 let all_columns_lower = schema.column_names_lower_arc();
1422 let projected_rows = self.index_project_rows_with_alias(
1423 &stmt.columns,
1424 rows,
1425 all_columns,
1426 Some(&all_columns_lower),
1427 ctx,
1428 table_alias,
1429 )?;
1430 let output_columns = CompactArc::new(self.index_output_column_names(
1431 &stmt.columns,
1432 all_columns,
1433 table_alias,
1434 ));
1435 let result =
1436 ExecutorResult::with_arc_columns(CompactArc::clone(&output_columns), projected_rows);
1437 Ok(Some((Box::new(result), output_columns)))
1438 }
1439
1440 #[allow(clippy::type_complexity)]
1446 #[allow(clippy::too_many_arguments)]
1447 fn try_in_list_index_optimization(
1448 &self,
1449 stmt: &SelectStatement,
1450 where_expr: &Expression,
1451 table: &dyn Table,
1452 all_columns: &[String],
1453 table_alias: Option<&str>,
1454 ctx: &ExecutionContext,
1455 classification: &Arc<QueryClassification>,
1456 ) -> Result<Option<(Box<dyn QueryResult>, CompactArc<Vec<String>>)>> {
1457 let (column_name, values, is_negated, remaining_predicate) =
1459 match Self::extract_in_list_info(where_expr, ctx) {
1460 Some(info) => info,
1461 None => return Ok(None),
1462 };
1463
1464 if classification.select_has_correlated_subqueries {
1467 return Ok(None);
1468 }
1469
1470 let schema = table.schema();
1472 let pk_indices = schema.primary_key_indices();
1473 let is_pk_column = pk_indices.len() == 1 && {
1474 let pk_col_idx = pk_indices[0];
1475 schema.columns[pk_col_idx].data_type == radixdb_core::DataType::Integer
1476 && schema.columns[pk_col_idx].name_lower == column_name
1477 };
1478
1479 let indexed_row_ids = if !is_pk_column {
1483 match table.collect_row_ids_by_index_values(&column_name, &values) {
1484 Some(row_ids) => Some(row_ids?),
1485 None => return Ok(None),
1486 }
1487 } else {
1488 None
1489 };
1490
1491 if values.is_empty() {
1492 if is_negated {
1494 return Ok(None);
1496 } else {
1497 let output_columns = CompactArc::new(self.index_output_column_names(
1499 &stmt.columns,
1500 all_columns,
1501 table_alias,
1502 ));
1503 let result = ExecutorResult::with_arc_columns(
1504 CompactArc::clone(&output_columns),
1505 RowVec::new(),
1506 );
1507 return Ok(Some((Box::new(result), output_columns)));
1508 }
1509 }
1510
1511 if is_negated {
1514 return Ok(None);
1515 }
1516
1517 let mut all_row_ids = Vec::with_capacity(values.len());
1520 if is_pk_column {
1521 for value in &values {
1523 if let Some(id) = exact_integer_pk_value(value) {
1524 all_row_ids.push(id);
1525 }
1526 }
1527 } else if let Some(row_ids) = indexed_row_ids {
1528 all_row_ids = row_ids;
1529 }
1530
1531 all_row_ids.sort_unstable();
1534 all_row_ids.dedup();
1535
1536 let (early_limit_applied, early_limit, early_offset) = if stmt.order_by.is_empty()
1539 && remaining_predicate.is_none()
1540 && !table.has_cold_segments()
1541 {
1542 let offset = if let Some(ref offset_expr) = stmt.offset {
1543 match ExpressionEval::compile(offset_expr, &[])
1544 .ok()
1545 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1546 {
1547 Some(Value::Integer(o)) if o >= 0 => o as usize,
1548 _ => 0,
1549 }
1550 } else {
1551 0
1552 };
1553
1554 let limit = if let Some(ref limit_expr) = stmt.limit {
1555 match ExpressionEval::compile(limit_expr, &[])
1556 .ok()
1557 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1558 {
1559 Some(Value::Integer(l)) if l >= 0 => l as usize,
1560 _ => usize::MAX,
1561 }
1562 } else {
1563 usize::MAX
1564 };
1565
1566 if limit < usize::MAX {
1568 let take_count = (offset + limit).min(all_row_ids.len());
1569 all_row_ids.truncate(take_count);
1570 }
1571 (true, limit, offset)
1572 } else {
1573 (false, usize::MAX, 0)
1574 };
1575
1576 use radixdb_storage::expression::logical::ConstBoolExpr;
1578 let filter: Box<dyn radixdb_storage::expression::Expression> =
1579 Box::new(ConstBoolExpr::true_expr());
1580
1581 let mut rows = table.fetch_rows_by_ids(&all_row_ids, filter.as_ref())?;
1583
1584 let (indexed_column_idx, indexed_column) = schema
1588 .find_column(&column_name)
1589 .expect("indexed IN column must belong to the table schema");
1590 let typed_values: Vec<Value> = values
1591 .iter()
1592 .map(|value| value.coerce_to_type(indexed_column.data_type))
1593 .filter(|value| !value.is_null())
1594 .collect();
1595 rows.retain(|(_, row)| {
1596 row.get(indexed_column_idx).is_some_and(|candidate| {
1597 typed_values
1598 .iter()
1599 .any(|value| matches!(candidate.compare(value), Ok(std::cmp::Ordering::Equal)))
1600 })
1601 });
1602
1603 if let Some(ref remaining) = remaining_predicate {
1605 let processed_remaining =
1607 if <Self as IndexOptimizerHost>::index_has_subqueries(remaining) {
1608 self.index_process_where_subqueries(remaining, ctx)?
1609 } else {
1610 remaining.clone()
1611 };
1612
1613 let columns_slice: Vec<String> = all_columns.to_vec();
1615 let row_filter =
1616 RowFilter::new(&processed_remaining, &columns_slice)?.with_context(ctx);
1617
1618 row_filter.retain_checked(&mut rows)?;
1620 }
1621
1622 if early_limit_applied {
1625 if early_offset > 0 {
1627 rows = rows
1628 .into_iter()
1629 .skip(early_offset)
1630 .take(early_limit)
1631 .collect();
1632 } else if early_limit < rows.len() {
1633 rows.truncate(early_limit);
1634 }
1635 } else if stmt.order_by.is_empty() {
1636 let offset = if let Some(ref offset_expr) = stmt.offset {
1637 match ExpressionEval::compile(offset_expr, &[])
1638 .ok()
1639 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1640 {
1641 Some(Value::Integer(o)) if o >= 0 => o as usize,
1642 _ => 0,
1643 }
1644 } else {
1645 0
1646 };
1647
1648 let limit = if let Some(ref limit_expr) = stmt.limit {
1649 match ExpressionEval::compile(limit_expr, &[])
1650 .ok()
1651 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1652 {
1653 Some(Value::Integer(l)) if l >= 0 => l as usize,
1654 _ => usize::MAX,
1655 }
1656 } else {
1657 usize::MAX
1658 };
1659
1660 if offset > 0 || limit < usize::MAX {
1661 rows = rows.into_iter().skip(offset).take(limit).collect();
1662 }
1663 }
1664
1665 let all_columns_lower = schema.column_names_lower_arc();
1668 let projected_rows = self.index_project_rows_with_alias(
1669 &stmt.columns,
1670 rows,
1671 all_columns,
1672 Some(&all_columns_lower),
1673 ctx,
1674 table_alias,
1675 )?;
1676 let output_columns = CompactArc::new(self.index_output_column_names(
1677 &stmt.columns,
1678 all_columns,
1679 table_alias,
1680 ));
1681 let result =
1682 ExecutorResult::with_arc_columns(CompactArc::clone(&output_columns), projected_rows);
1683 Ok(Some((Box::new(result), output_columns)))
1684 }
1685
1686 fn extract_in_list_info(
1689 expr: &Expression,
1690 ctx: &ExecutionContext,
1691 ) -> Option<(String, Vec<Value>, bool, Option<Expression>)> {
1692 match expr {
1693 Expression::In(in_expr) => {
1695 let column_name = match in_expr.left.as_ref() {
1697 Expression::Identifier(id) => id.value_lower.to_string(),
1698 Expression::QualifiedIdentifier(qid) => qid.name.value_lower.to_string(),
1699 _ => return None, };
1701
1702 let values = match in_expr.right.as_ref() {
1704 Expression::List(list) => {
1705 Self::extract_literal_values(&list.elements, ctx)
1707 }
1708 Expression::ExpressionList(list) => {
1709 Self::extract_literal_values(&list.expressions, ctx)
1711 }
1712 _ => return None, };
1714
1715 values.map(|v| (column_name, v, in_expr.not, None))
1716 }
1717
1718 Expression::Infix(infix) if infix.op_type == InfixOperator::And => {
1720 if let Some((col, vals, neg, _)) = Self::extract_in_list_info(&infix.left, ctx) {
1722 return Some((col, vals, neg, Some((*infix.right).clone())));
1723 }
1724 if let Some((col, vals, neg, _)) = Self::extract_in_list_info(&infix.right, ctx) {
1726 return Some((col, vals, neg, Some((*infix.left).clone())));
1727 }
1728 None
1729 }
1730
1731 _ => None,
1732 }
1733 }
1734
1735 fn extract_literal_values(exprs: &[Expression], ctx: &ExecutionContext) -> Option<Vec<Value>> {
1738 let mut values = Vec::with_capacity(exprs.len());
1739 for expr in exprs {
1740 match ExpressionEval::compile(expr, &[]) {
1742 Ok(compiled) => match compiled.with_context(ctx).eval_slice(&Row::new()) {
1743 Ok(val) => values.push(val),
1744 Err(_) => return None, },
1746 Err(_) => return None, }
1748 }
1749 Some(values)
1750 }
1751
1752 fn extract_in_subquery_info(
1755 expr: &Expression,
1756 ) -> Option<(String, &ScalarSubquery, bool, Option<Expression>)> {
1757 match expr {
1758 Expression::In(in_expr) => {
1760 let column_name = match in_expr.left.as_ref() {
1762 Expression::Identifier(id) => id.value_lower.to_string(),
1763 Expression::QualifiedIdentifier(qid) => qid.name.value_lower.to_string(),
1764 _ => return None, };
1766
1767 if let Expression::ScalarSubquery(subquery) = in_expr.right.as_ref() {
1769 return Some((column_name, subquery, in_expr.not, None));
1770 }
1771 None
1772 }
1773
1774 Expression::Infix(infix) if infix.op_type == InfixOperator::And => {
1776 if let Some((col, sq, neg, _)) = Self::extract_in_subquery_info(&infix.left) {
1778 return Some((col, sq, neg, Some((*infix.right).clone())));
1779 }
1780 if let Some((col, sq, neg, _)) = Self::extract_in_subquery_info(&infix.right) {
1782 return Some((col, sq, neg, Some((*infix.left).clone())));
1783 }
1784 None
1785 }
1786
1787 _ => None,
1788 }
1789 }
1790
1791 #[allow(clippy::type_complexity)]
1799 fn try_in_hashset_index_optimization(
1800 &self,
1801 stmt: &SelectStatement,
1802 where_expr: &Expression,
1803 table: &dyn Table,
1804 all_columns: &[String],
1805 table_alias: Option<&str>,
1806 ctx: &ExecutionContext,
1807 ) -> Result<Option<(Box<dyn QueryResult>, CompactArc<Vec<String>>)>> {
1808 let (column_name, values, is_negated, remaining_predicate) =
1810 match Self::extract_in_hashset_info(where_expr) {
1811 Some(info) => info,
1812 None => return Ok(None),
1813 };
1814
1815 if is_negated {
1818 return Ok(None);
1819 }
1820
1821 let schema = table.schema();
1823 let pk_indices = schema.primary_key_indices();
1824 let is_pk_column = pk_indices.len() == 1 && {
1825 let pk_col_idx = pk_indices[0];
1826 schema.columns[pk_col_idx].data_type == radixdb_core::DataType::Integer
1827 && schema.columns[pk_col_idx].name_lower == column_name
1828 };
1829
1830 if is_negated && !is_pk_column {
1833 return Ok(None);
1834 }
1835
1836 let indexed_row_ids = if !is_pk_column {
1839 let values_vec: Vec<Value> = values.iter().cloned().collect();
1840 match table.collect_row_ids_by_index_values(&column_name, &values_vec) {
1841 Some(row_ids) => Some(row_ids?),
1842 None => return Ok(None),
1843 }
1844 } else {
1845 None
1846 };
1847
1848 let early_termination_target = if stmt.order_by.is_empty() && remaining_predicate.is_none()
1851 {
1852 let offset = stmt
1853 .offset
1854 .as_ref()
1855 .and_then(|offset_expr| {
1856 ExpressionEval::compile(offset_expr, &[])
1857 .ok()
1858 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1859 .and_then(|v| {
1860 if let Value::Integer(o) = v {
1861 Some(o.max(0) as usize)
1862 } else {
1863 None
1864 }
1865 })
1866 })
1867 .unwrap_or(0);
1868
1869 let limit = stmt.limit.as_ref().and_then(|limit_expr| {
1870 ExpressionEval::compile(limit_expr, &[])
1871 .ok()
1872 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1873 .and_then(|v| {
1874 if let Value::Integer(l) = v {
1875 Some(l.max(0) as usize)
1876 } else {
1877 None
1878 }
1879 })
1880 });
1881
1882 limit.map(|l| offset + l)
1883 } else {
1884 None };
1886
1887 let estimated_capacity = if let Some(target) = early_termination_target {
1891 target.min(values.len())
1892 } else {
1893 values.len()
1894 };
1895 let mut all_row_ids = Vec::with_capacity(estimated_capacity);
1896 if is_pk_column {
1897 if is_negated {
1898 let exclusion_set: I64Set =
1901 values.iter().filter_map(exact_integer_pk_value).collect();
1902
1903 let target = early_termination_target.unwrap_or(usize::MAX);
1904 let row_count = table.row_count();
1905
1906 for row_id in 1..=(row_count as i64) {
1907 if !exclusion_set.contains(row_id) {
1908 all_row_ids.push(row_id);
1909 if all_row_ids.len() >= target {
1910 break;
1911 }
1912 }
1913 }
1914 } else {
1915 for value in values.iter() {
1917 if let Some(target) = early_termination_target {
1919 if all_row_ids.len() >= target {
1920 break;
1921 }
1922 }
1923 if let Some(id) = exact_integer_pk_value(value) {
1924 all_row_ids.push(id);
1925 }
1926 }
1927 }
1928 } else if let Some(row_ids) = indexed_row_ids {
1929 all_row_ids.extend(row_ids);
1930 }
1931
1932 if all_row_ids.is_empty() {
1934 let output_columns = CompactArc::new(self.index_output_column_names(
1935 &stmt.columns,
1936 all_columns,
1937 table_alias,
1938 ));
1939 let result =
1940 ExecutorResult::with_arc_columns(CompactArc::clone(&output_columns), RowVec::new());
1941 return Ok(Some((Box::new(result), output_columns)));
1942 }
1943
1944 all_row_ids.sort_unstable();
1947 all_row_ids.dedup();
1948
1949 let (early_limit_applied, early_limit, early_offset) =
1952 if stmt.order_by.is_empty() && remaining_predicate.is_none() {
1953 let offset = if let Some(ref offset_expr) = stmt.offset {
1954 match ExpressionEval::compile(offset_expr, &[])
1955 .ok()
1956 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1957 {
1958 Some(Value::Integer(o)) if o >= 0 => o as usize,
1959 _ => 0,
1960 }
1961 } else {
1962 0
1963 };
1964
1965 let limit = if let Some(ref limit_expr) = stmt.limit {
1966 match ExpressionEval::compile(limit_expr, &[])
1967 .ok()
1968 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
1969 {
1970 Some(Value::Integer(l)) if l >= 0 => l as usize,
1971 _ => usize::MAX,
1972 }
1973 } else {
1974 usize::MAX
1975 };
1976
1977 if limit < usize::MAX {
1979 let take_count = (offset + limit).min(all_row_ids.len());
1980 all_row_ids.truncate(take_count);
1981 }
1982 (true, limit, offset)
1983 } else {
1984 (false, usize::MAX, 0)
1985 };
1986
1987 use radixdb_storage::expression::logical::ConstBoolExpr;
1989 let filter: Box<dyn radixdb_storage::expression::Expression> =
1990 Box::new(ConstBoolExpr::true_expr());
1991
1992 let mut rows = table.fetch_rows_by_ids(&all_row_ids, filter.as_ref())?;
1994
1995 if let Some(ref remaining) = remaining_predicate {
1997 let columns_slice: Vec<String> = all_columns.to_vec();
1999 let row_filter = RowFilter::new(remaining, &columns_slice)?.with_context(ctx);
2000
2001 row_filter.retain_checked(&mut rows)?;
2003 }
2004
2005 if early_limit_applied {
2008 if early_offset > 0 {
2010 rows = rows
2011 .into_iter()
2012 .skip(early_offset)
2013 .take(early_limit)
2014 .collect();
2015 } else if early_limit < rows.len() {
2016 rows.truncate(early_limit);
2017 }
2018 } else if stmt.order_by.is_empty() {
2019 let offset = if let Some(ref offset_expr) = stmt.offset {
2020 match ExpressionEval::compile(offset_expr, &[])
2021 .ok()
2022 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
2023 {
2024 Some(Value::Integer(o)) if o >= 0 => o as usize,
2025 _ => 0,
2026 }
2027 } else {
2028 0
2029 };
2030
2031 let limit = if let Some(ref limit_expr) = stmt.limit {
2032 match ExpressionEval::compile(limit_expr, &[])
2033 .ok()
2034 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
2035 {
2036 Some(Value::Integer(l)) if l >= 0 => l as usize,
2037 _ => usize::MAX,
2038 }
2039 } else {
2040 usize::MAX
2041 };
2042
2043 if offset > 0 || limit < usize::MAX {
2044 rows = rows.into_iter().skip(offset).take(limit).collect();
2045 }
2046 }
2047
2048 let all_columns_lower = schema.column_names_lower_arc();
2051 let projected_rows = self.index_project_rows_with_alias(
2052 &stmt.columns,
2053 rows,
2054 all_columns,
2055 Some(&all_columns_lower),
2056 ctx,
2057 table_alias,
2058 )?;
2059 let output_columns = CompactArc::new(self.index_output_column_names(
2060 &stmt.columns,
2061 all_columns,
2062 table_alias,
2063 ));
2064 let result =
2065 ExecutorResult::with_arc_columns(CompactArc::clone(&output_columns), projected_rows);
2066 Ok(Some((Box::new(result), output_columns)))
2067 }
2068
2069 #[allow(clippy::type_complexity)]
2072 fn extract_in_hashset_info(
2073 expr: &Expression,
2074 ) -> Option<(String, CompactArc<ValueSet>, bool, Option<Expression>)> {
2075 match expr {
2076 Expression::InHashSet(in_hash) => {
2078 let column_name = match in_hash.column.as_ref() {
2080 Expression::Identifier(id) => id.value_lower.to_string(),
2081 Expression::QualifiedIdentifier(qid) => qid.name.value_lower.to_string(),
2082 _ => return None, };
2084
2085 Some((column_name, in_hash.values.clone(), in_hash.not, None))
2086 }
2087
2088 Expression::Infix(infix) if infix.op_type == InfixOperator::And => {
2090 if let Some((col, vals, neg, _)) = Self::extract_in_hashset_info(&infix.left) {
2092 return Some((col, vals, neg, Some((*infix.right).clone())));
2093 }
2094 if let Some((col, vals, neg, _)) = Self::extract_in_hashset_info(&infix.right) {
2096 return Some((col, vals, neg, Some((*infix.left).clone())));
2097 }
2098 None
2099 }
2100
2101 _ => None,
2102 }
2103 }
2104
2105 #[allow(clippy::type_complexity)]
2115 fn try_vector_search_optimization(
2116 &self,
2117 stmt: &SelectStatement,
2118 table: &dyn Table,
2119 all_columns: &[String],
2120 ctx: &ExecutionContext,
2121 ) -> Result<Option<(Box<dyn QueryResult>, CompactArc<Vec<String>>)>> {
2122 if stmt.order_by.len() != 1 || stmt.limit.is_none() {
2124 return Ok(None);
2125 }
2126
2127 let order_by = &stmt.order_by[0];
2128 if !order_by.ascending {
2129 return Ok(None); }
2131
2132 let synthesized_fc;
2136 let func_call = match &order_by.expression {
2137 Expression::FunctionCall(fc) => Some(fc.as_ref()),
2138 Expression::Identifier(id) => {
2139 if let Some(fc) = Self::find_vec_distance_alias(&id.value_lower, &stmt.columns) {
2141 Some(fc)
2142 } else if let Some(infix) =
2143 Self::find_vec_distance_infix_alias(&id.value_lower, &stmt.columns)
2144 {
2145 synthesized_fc = FunctionCall {
2146 token: infix.token.clone(),
2147 function: "VEC_DISTANCE_L2".into(),
2148 arguments: vec![(*infix.left).clone(), (*infix.right).clone()],
2149 is_distinct: false,
2150 order_by: Vec::new(),
2151 filter: None,
2152 };
2153 Some(&synthesized_fc)
2154 } else {
2155 None
2156 }
2157 }
2158 Expression::Infix(infix) if infix.op_type == InfixOperator::VectorDistance => {
2159 synthesized_fc = FunctionCall {
2161 token: infix.token.clone(),
2162 function: "VEC_DISTANCE_L2".into(),
2163 arguments: vec![(*infix.left).clone(), (*infix.right).clone()],
2164 is_distinct: false,
2165 order_by: Vec::new(),
2166 filter: None,
2167 };
2168 Some(&synthesized_fc)
2169 }
2170 _ => None,
2171 };
2172
2173 let func_call = match func_call {
2174 Some(fc) => fc,
2175 None => return Ok(None),
2176 };
2177
2178 let fn_name_upper = func_call.function.to_uppercase();
2180 let is_vec_distance = matches!(
2181 fn_name_upper.as_str(),
2182 "VEC_DISTANCE_L2" | "VEC_DISTANCE_COSINE" | "VEC_DISTANCE_IP"
2183 );
2184 if !is_vec_distance || func_call.arguments.len() != 2 {
2185 return Ok(None);
2186 }
2187
2188 let vec_col_name = match &func_call.arguments[0] {
2190 Expression::Identifier(id) => id.value.clone(),
2191 Expression::QualifiedIdentifier(qid) => qid.name.value.clone(),
2192 _ => return Ok(None),
2193 };
2194
2195 let hnsw_index: Option<Arc<dyn Index>> = None;
2200
2201 let query_vec_value = match &func_call.arguments[1] {
2203 Expression::StringLiteral(s) => {
2204 match radixdb_core::value::parse_vector_str(&s.value) {
2206 Some(floats) => Value::vector(floats),
2207 None => return Ok(None),
2208 }
2209 }
2210 Expression::Parameter(_) => {
2211 match ExpressionEval::compile(&func_call.arguments[1], &[])
2213 .ok()
2214 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
2215 {
2216 Some(v @ Value::Extension(_)) => v,
2217 Some(Value::Text(s)) => {
2218 match radixdb_core::value::parse_vector_str(s.as_ref()) {
2219 Some(floats) => Value::vector(floats),
2220 None => return Ok(None),
2221 }
2222 }
2223 _ => return Ok(None),
2224 }
2225 }
2226 _ => return Ok(None),
2227 };
2228
2229 let limit = match &stmt.limit {
2231 Some(limit_expr) => {
2232 match ExpressionEval::compile(limit_expr, &[])
2233 .ok()
2234 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
2235 {
2236 Some(Value::Integer(l)) if l >= 0 => l as usize,
2237 Some(Value::Float(f)) if f >= 0.0 => f as usize,
2238 _ => return Ok(None),
2239 }
2240 }
2241 None => return Ok(None),
2242 };
2243
2244 let offset = if let Some(ref offset_expr) = stmt.offset {
2245 match ExpressionEval::compile(offset_expr, &[])
2246 .ok()
2247 .and_then(|e| e.with_context(ctx).eval_slice(&Row::new()).ok())
2248 {
2249 Some(Value::Integer(o)) if o >= 0 => o as usize,
2250 Some(Value::Float(f)) if f >= 0.0 => f as usize,
2251 _ => 0,
2252 }
2253 } else {
2254 0
2255 };
2256
2257 let k = limit.saturating_add(offset);
2258
2259 let where_filter = if let Some(ref where_expr) = stmt.where_clause {
2261 match RowFilter::new(where_expr, all_columns) {
2262 Ok(filter) => Some(filter.with_context(ctx)),
2263 Err(_) => return Ok(None), }
2265 } else {
2266 None
2267 };
2268
2269 let (nn_results, fetched_rows) = if let Some(ref idx) = hnsw_index {
2272 let oversample = if where_filter.is_some() { 4 } else { 1 };
2275 let hnsw_k = k.saturating_mul(oversample);
2276 let default_ef = idx.default_ef_search().unwrap_or(64);
2277 let ef_search = std::cmp::max(hnsw_k.saturating_mul(2), default_ef);
2278 let results = match idx.search_nearest(&query_vec_value, hnsw_k, ef_search) {
2279 Some(r) => r,
2280 None => return Ok(None),
2281 };
2282 if results.is_empty() {
2283 let output_columns = CompactArc::new(self.index_output_column_names(
2284 &stmt.columns,
2285 all_columns,
2286 None,
2287 ));
2288 let result = ExecutorResult::with_arc_columns(
2289 CompactArc::clone(&output_columns),
2290 RowVec::new(),
2291 );
2292 return Ok(Some((Box::new(result), output_columns)));
2293 }
2294 let row_ids: Vec<i64> = results.iter().map(|(rid, _)| *rid).collect();
2295 let rows = table.collect_rows_by_ids(&row_ids)?;
2296
2297 if let Some(ref filter) = where_filter {
2299 let row_map: rustc_hash::FxHashMap<i64, &radixdb_core::Row> =
2301 rows.iter().map(|(rid, row)| (*rid, row)).collect();
2302 let mut filtered_nn = Vec::with_capacity(k);
2303 let mut filtered_rows = RowVec::with_capacity(k);
2304 for (rid, dist) in &results {
2305 if let Some(row) = row_map.get(rid) {
2306 if filter.matches_checked(row)? {
2307 filtered_nn.push((*rid, *dist));
2308 filtered_rows.push((*rid, (*row).clone()));
2309 if filtered_nn.len() >= k {
2310 break; }
2312 }
2313 }
2314 }
2315 if filtered_nn.len() < k {
2320 return Ok(None);
2321 }
2322 (filtered_nn, filtered_rows)
2323 } else {
2324 (results, rows)
2325 }
2326 } else {
2327 let query_bytes = match &query_vec_value {
2329 Value::Extension(data)
2330 if data.first() == Some(&(radixdb_core::DataType::Vector as u8)) =>
2331 {
2332 &data[1..]
2333 }
2334 _ => return Ok(None),
2335 };
2336
2337 let schema = table.schema();
2339 let vec_col_idx = match schema
2340 .columns
2341 .iter()
2342 .position(|c| c.name.eq_ignore_ascii_case(&vec_col_name))
2343 {
2344 Some(idx) => idx,
2345 None => return Ok(None),
2346 };
2347
2348 let metric = match fn_name_upper.as_str() {
2349 "VEC_DISTANCE_L2" => super::parallel::DistanceMetric::L2,
2350 "VEC_DISTANCE_COSINE" => super::parallel::DistanceMetric::Cosine,
2351 "VEC_DISTANCE_IP" => super::parallel::DistanceMetric::InnerProduct,
2352 _ => return Ok(None),
2353 };
2354
2355 let scan_rows = if let Some(ref where_expr) = stmt.where_clause {
2357 let storage_expr = super::expr_converter::convert_ast_to_storage_expr(where_expr);
2359 let rows = table.collect_all_rows(storage_expr.as_deref())?;
2360 if let Some(ref filter) = where_filter {
2362 let mut filtered = RowVec::with_capacity(rows.len());
2363 for (id, row) in rows {
2364 if filter.matches_checked(&row)? {
2365 filtered.push((id, row));
2366 }
2367 }
2368 filtered
2369 } else {
2370 rows
2371 }
2372 } else {
2373 table.collect_all_rows_unsorted()?
2374 };
2375 if scan_rows.is_empty() {
2376 let output_columns = CompactArc::new(self.index_output_column_names(
2377 &stmt.columns,
2378 all_columns,
2379 None,
2380 ));
2381 let result = ExecutorResult::with_arc_columns(
2382 CompactArc::clone(&output_columns),
2383 RowVec::new(),
2384 );
2385 return Ok(Some((Box::new(result), output_columns)));
2386 }
2387
2388 let config = super::parallel::ParallelConfig::default();
2389 let topn = super::parallel::parallel_topn_vector_search(
2390 scan_rows,
2391 vec_col_idx,
2392 query_bytes,
2393 k,
2394 metric,
2395 &config,
2396 )?;
2397
2398 let nn: Vec<(i64, f64)> = topn.iter().map(|(rid, _, dist)| (*rid, *dist)).collect();
2400 let rows: RowVec = topn.into_iter().map(|(rid, row, _)| (rid, row)).collect();
2401 (nn, rows)
2402 };
2403
2404 let mut row_map: radixdb_core::I64Map<Row> =
2406 radixdb_core::I64Map::with_capacity(fetched_rows.len());
2407 for (rid, row) in fetched_rows.into_iter() {
2408 row_map.insert(rid, row);
2409 }
2410
2411 let mut projection_plan =
2413 self.compile_vector_projection_plan(&stmt.columns, all_columns, ctx, func_call)?;
2414
2415 let mut result_rows = RowVec::with_capacity(k);
2417 for (i, (row_id, dist)) in nn_results.iter().enumerate() {
2418 if i < offset {
2419 continue;
2420 }
2421 if result_rows.len() >= limit {
2422 break;
2423 }
2424 if let Some(base_row) = row_map.get(*row_id) {
2425 let projected =
2426 Self::apply_vector_projection(&mut projection_plan, base_row, *dist)?;
2427 result_rows.push((*row_id, projected));
2428 }
2429 }
2430
2431 let output_columns =
2432 CompactArc::new(self.index_output_column_names(&stmt.columns, all_columns, None));
2433
2434 let result =
2435 ExecutorResult::with_arc_columns(CompactArc::clone(&output_columns), result_rows);
2436 Ok(Some((Box::new(result), output_columns)))
2437 }
2438
2439 fn find_vec_distance_alias<'a>(
2442 alias_lower: &str,
2443 columns: &'a [Expression],
2444 ) -> Option<&'a FunctionCall> {
2445 for col_expr in columns {
2446 if let Expression::Aliased(aliased) = col_expr {
2447 if aliased.alias.value_lower == alias_lower {
2448 if let Expression::FunctionCall(fc) = &*aliased.expression {
2449 let fn_upper = fc.function.to_uppercase();
2450 if matches!(
2451 fn_upper.as_str(),
2452 "VEC_DISTANCE_L2" | "VEC_DISTANCE_COSINE" | "VEC_DISTANCE_IP"
2453 ) {
2454 return Some(fc.as_ref());
2455 }
2456 }
2457 }
2458 }
2459 }
2460 None
2461 }
2462
2463 fn find_vec_distance_infix_alias<'a>(
2466 alias_lower: &str,
2467 columns: &'a [Expression],
2468 ) -> Option<&'a InfixExpression> {
2469 for col_expr in columns {
2470 if let Expression::Aliased(aliased) = col_expr {
2471 if aliased.alias.value_lower == alias_lower {
2472 if let Expression::Infix(infix) = &*aliased.expression {
2473 if infix.op_type == InfixOperator::VectorDistance {
2474 return Some(infix);
2475 }
2476 }
2477 }
2478 }
2479 }
2480 None
2481 }
2482
2483 fn func_calls_match(a: &FunctionCall, b: &FunctionCall) -> bool {
2486 if !a.function.eq_ignore_ascii_case(&b.function) {
2487 return false;
2488 }
2489 if a.arguments.len() != b.arguments.len() {
2490 return false;
2491 }
2492 a.arguments.iter().zip(b.arguments.iter()).all(|(x, y)| {
2494 match (x, y) {
2495 (Expression::Identifier(a_id), Expression::Identifier(b_id)) => {
2496 a_id.value.eq_ignore_ascii_case(&b_id.value)
2497 }
2498 (
2499 Expression::QualifiedIdentifier(a_qid),
2500 Expression::QualifiedIdentifier(b_qid),
2501 ) => a_qid.name.value.eq_ignore_ascii_case(&b_qid.name.value),
2502 (Expression::Identifier(a_id), Expression::QualifiedIdentifier(b_qid))
2503 | (Expression::QualifiedIdentifier(b_qid), Expression::Identifier(a_id)) => {
2504 a_id.value.eq_ignore_ascii_case(&b_qid.name.value)
2505 }
2506 (Expression::StringLiteral(a_s), Expression::StringLiteral(b_s)) => {
2507 a_s.value == b_s.value
2508 }
2509 (Expression::IntegerLiteral(a_i), Expression::IntegerLiteral(b_i)) => {
2510 a_i.value == b_i.value
2511 }
2512 (Expression::FloatLiteral(a_f), Expression::FloatLiteral(b_f)) => {
2513 a_f.value == b_f.value
2514 }
2515 (Expression::FunctionCall(a_fc), Expression::FunctionCall(b_fc)) => {
2516 Self::func_calls_match(a_fc, b_fc)
2517 }
2518 _ => std::ptr::eq(x as *const Expression, y as *const Expression),
2521 }
2522 })
2523 }
2524
2525 fn compile_vector_projection_plan(
2529 &self,
2530 select_columns: &[Expression],
2531 all_columns: &[String],
2532 ctx: &ExecutionContext,
2533 order_by_fc: &FunctionCall,
2534 ) -> Result<Vec<VectorProjectionSlot>> {
2535 let mut plan = Vec::with_capacity(select_columns.len());
2536
2537 for col_expr in select_columns {
2538 let expr = match col_expr {
2539 Expression::Aliased(aliased) => &*aliased.expression,
2540 other => other,
2541 };
2542
2543 match expr {
2544 Expression::Star(_) => {
2545 plan.push(VectorProjectionSlot::Star);
2546 }
2547 Expression::Identifier(id) => {
2548 if let Some(idx) = all_columns
2549 .iter()
2550 .position(|c| c.eq_ignore_ascii_case(&id.value))
2551 {
2552 plan.push(VectorProjectionSlot::Column(idx));
2553 } else {
2554 return Err(radixdb_core::Error::ColumnNotFound(id.value.to_string()));
2555 }
2556 }
2557 Expression::QualifiedIdentifier(qid) => {
2558 if let Some(idx) = all_columns
2559 .iter()
2560 .position(|c| c.eq_ignore_ascii_case(&qid.name.value))
2561 {
2562 plan.push(VectorProjectionSlot::Column(idx));
2563 } else {
2564 return Err(radixdb_core::Error::ColumnNotFound(
2565 qid.name.value.to_string(),
2566 ));
2567 }
2568 }
2569 Expression::FunctionCall(fc) => {
2570 let fn_upper = fc.function.to_uppercase();
2571 if matches!(
2572 fn_upper.as_str(),
2573 "VEC_DISTANCE_L2" | "VEC_DISTANCE_COSINE" | "VEC_DISTANCE_IP"
2574 ) && Self::func_calls_match(fc, order_by_fc)
2575 {
2576 plan.push(VectorProjectionSlot::Distance);
2577 } else {
2578 let eval = ExpressionEval::compile(expr, all_columns)?.with_context(ctx);
2579 plan.push(VectorProjectionSlot::Compiled(Box::new(eval)));
2580 }
2581 }
2582 Expression::Infix(infix) if infix.op_type == InfixOperator::VectorDistance => {
2583 let equiv_fc = FunctionCall {
2586 token: infix.token.clone(),
2587 function: "VEC_DISTANCE_L2".into(),
2588 arguments: vec![(*infix.left).clone(), (*infix.right).clone()],
2589 is_distinct: false,
2590 order_by: Vec::new(),
2591 filter: None,
2592 };
2593 if Self::func_calls_match(&equiv_fc, order_by_fc) {
2594 plan.push(VectorProjectionSlot::Distance);
2595 } else {
2596 let eval = ExpressionEval::compile(expr, all_columns)?.with_context(ctx);
2597 plan.push(VectorProjectionSlot::Compiled(Box::new(eval)));
2598 }
2599 }
2600 _ => {
2601 let eval = ExpressionEval::compile(expr, all_columns)?.with_context(ctx);
2602 plan.push(VectorProjectionSlot::Compiled(Box::new(eval)));
2603 }
2604 }
2605 }
2606
2607 Ok(plan)
2608 }
2609
2610 fn apply_vector_projection(
2612 plan: &mut [VectorProjectionSlot],
2613 base_row: &Row,
2614 distance: f64,
2615 ) -> Result<Row> {
2616 let mut values = Vec::with_capacity(plan.len());
2617
2618 for slot in plan.iter_mut() {
2619 match slot {
2620 VectorProjectionSlot::Star => {
2621 for i in 0..base_row.len() {
2622 values.push(
2623 base_row
2624 .get(i)
2625 .cloned()
2626 .unwrap_or(Value::Null(radixdb_core::DataType::Null)),
2627 );
2628 }
2629 }
2630 VectorProjectionSlot::Column(idx) => {
2631 values.push(
2632 base_row
2633 .get(*idx)
2634 .cloned()
2635 .unwrap_or(Value::Null(radixdb_core::DataType::Null)),
2636 );
2637 }
2638 VectorProjectionSlot::Distance => {
2639 if distance.is_infinite() {
2640 values.push(Value::Null(radixdb_core::DataType::Float));
2642 } else {
2643 values.push(Value::Float(distance));
2644 }
2645 }
2646 VectorProjectionSlot::Compiled(eval) => {
2647 let val = eval.eval_slice(base_row)?;
2648 values.push(val);
2649 }
2650 }
2651 }
2652
2653 Ok(Row::from_values(values))
2654 }
2655}
2656
2657impl<T: IndexOptimizerHost + ?Sized> IndexOptimizerExt for T {}
2658
2659#[cfg(test)]
2660mod keyset_bound_tests {
2661 use super::IntegerPkKeysetBound::{After, Empty, From};
2662
2663 #[test]
2664 fn strongest_lower_bound_is_order_independent_and_preserves_exclusivity() {
2665 assert_eq!(After(1).strongest_lower(After(5)), After(5));
2666 assert_eq!(After(5).strongest_lower(After(1)), After(5));
2667 assert_eq!(From(6).strongest_lower(After(5)), From(6));
2668 assert_eq!(After(5).strongest_lower(From(6)), From(6));
2669 assert_eq!(From(5).strongest_lower(After(5)), After(5));
2670 assert_eq!(After(5).strongest_lower(From(5)), After(5));
2671 assert_eq!(
2672 After(i64::MAX).strongest_lower(From(i64::MAX)),
2673 After(i64::MAX)
2674 );
2675 assert_eq!(Empty.strongest_lower(After(1)), Empty);
2676 }
2677}