diff --git a/core/translate/optimizer/constraints.rs b/core/translate/optimizer/constraints.rs index d2dff657c..168a6a1c0 100644 --- a/core/translate/optimizer/constraints.rs +++ b/core/translate/optimizer/constraints.rs @@ -7,6 +7,7 @@ use std::{ use crate::{ schema::{Column, Index}, translate::{ + collate::get_collseq_from_expr, expr::as_binary_components, plan::{JoinOrderMember, TableReferences, WhereTerm}, planner::{table_mask_from_expr, TableMask}, @@ -54,6 +55,9 @@ pub struct Constraint { /// An estimated selectivity factor (0.0 to 1.0) indicating the fraction of rows /// expected to satisfy this constraint. Used for cost and cardinality estimation. pub selectivity: f64, + /// Whether the constraint is usable for an index seek. + /// This is explicitly set to false if the constraint has a different collation than the constrained column. + pub usable: bool, } #[derive(Debug, Clone, Copy, PartialEq)] @@ -76,6 +80,19 @@ impl Constraint { rhs.clone() } } + + pub fn get_constraining_expr_ref<'a>(&self, where_clause: &'a [WhereTerm]) -> &'a ast::Expr { + let (idx, side) = self.where_clause_pos; + let where_term = &where_clause[idx]; + let Ok(Some((lhs, _, rhs))) = as_binary_components(&where_term.expr) else { + panic!("Expected a valid binary expression"); + }; + if side == BinaryExprSide::Lhs { + &lhs + } else { + &rhs + } + } } #[derive(Debug, Clone)] @@ -235,6 +252,7 @@ pub fn constraints_from_where_clause( table_col_pos: *column, lhs_mask: table_mask_from_expr(rhs, table_references)?, selectivity: estimate_selectivity(table_column, operator), + usable: true, }); } } @@ -251,6 +269,7 @@ pub fn constraints_from_where_clause( table_col_pos: rowid_alias_column.unwrap(), lhs_mask: table_mask_from_expr(rhs, table_references)?, selectivity: estimate_selectivity(table_column, operator), + usable: true, }); } } @@ -266,6 +285,7 @@ pub fn constraints_from_where_clause( table_col_pos: *column, lhs_mask: table_mask_from_expr(lhs, table_references)?, selectivity: estimate_selectivity(table_column, operator), + usable: true, }); } } @@ -279,6 +299,7 @@ pub fn constraints_from_where_clause( table_col_pos: rowid_alias_column.unwrap(), lhs_mask: table_mask_from_expr(lhs, table_references)?, selectivity: estimate_selectivity(table_column, operator), + usable: true, }); } } @@ -298,7 +319,19 @@ pub fn constraints_from_where_clause( }); // For each constraint we found, add a reference to it for each index that may be able to use it. - for (i, constraint) in cs.constraints.iter().enumerate() { + for (i, constraint) in cs.constraints.iter_mut().enumerate() { + let constrained_column = &table_reference.table.columns()[constraint.table_col_pos]; + let column_collation = constrained_column.collation.unwrap_or_default(); + let constraining_expr = constraint.get_constraining_expr_ref(where_clause); + // Index seek keys must use the same collation as the constrained column. + match get_collseq_from_expr(constraining_expr, table_references)? { + Some(collation) if collation != column_collation => { + constraint.usable = false; + continue; + } + _ => {} + } + if rowid_alias_column == Some(constraint.table_col_pos) { let rowid_candidate = cs .candidates diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index 59cd2680f..a540d6a7d 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -337,15 +337,21 @@ fn optimize_table_access( }); continue; }; - let temp_constraint_refs = (0..table_constraints.constraints.len()) + let usable_constraints = table_constraints + .constraints + .iter() + .cloned() + .filter(|c| c.usable) + .collect::>(); + let temp_constraint_refs = (0..usable_constraints.len()) .map(|i| ConstraintRef { constraint_vec_pos: i, - index_col_pos: table_constraints.constraints[i].table_col_pos, + index_col_pos: usable_constraints[i].table_col_pos, sort_order: SortOrder::Asc, }) .collect::>(); let usable_constraint_refs = usable_constraints_for_join_order( - &table_constraints.constraints, + &usable_constraints, &temp_constraint_refs, &best_join_order[..=i], ); @@ -358,14 +364,14 @@ fn optimize_table_access( } let ephemeral_index = ephemeral_index_build( &joined_tables[table_idx], - &table_constraints.constraints, + &usable_constraints, usable_constraint_refs, ); let ephemeral_index = Arc::new(ephemeral_index); joined_tables[table_idx].op = Operation::Search(Search::Seek { index: Some(ephemeral_index), seek_def: build_seek_def_from_constraints( - &table_constraints.constraints, + &usable_constraints, usable_constraint_refs, *iter_dir, where_clause,