Collate: prevent using an index if collation sequences don't match

This commit is contained in:
Jussi Saurio
2025-10-02 21:25:33 +03:00
parent d42f3c7cbb
commit edfe0cb4fe
2 changed files with 45 additions and 6 deletions

View File

@@ -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

View File

@@ -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::<Vec<_>>();
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::<Vec<_>>();
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,