use crate::catalog::types::Value;
use crate::query::plan::Expr;
use std::collections::HashMap;
pub(super) fn extract_primary_key_values(
predicate: &Expr,
primary_key: &[String],
) -> Option<Vec<Value>> {
if primary_key.is_empty() {
return None;
}
let mut equalities: HashMap<String, Value> = HashMap::new();
if !collect_eq_constraints(predicate, &mut equalities) {
return None;
}
if equalities.len() != primary_key.len() {
return None;
}
let mut values = Vec::with_capacity(primary_key.len());
for key_col in primary_key {
let value = equalities.get(key_col)?;
values.push(value.clone());
}
Some(values)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PrimaryKeyPrefix {
pub values: Vec<Value>,
pub exact: bool,
}
pub(crate) fn extract_primary_key_prefix(
predicate: &Expr,
primary_key: &[String],
) -> Option<PrimaryKeyPrefix> {
if primary_key.is_empty() {
return None;
}
let mut equalities: HashMap<String, Value> = HashMap::new();
let pure_conjunction = collect_eq_constraints(predicate, &mut equalities);
let mut values = Vec::new();
for key_col in primary_key {
match equalities.get(key_col) {
Some(value) => values.push(value.clone()),
None => break,
}
}
if values.is_empty() {
return None;
}
let exact = pure_conjunction && equalities.len() == values.len();
Some(PrimaryKeyPrefix { values, exact })
}
pub(super) fn collect_eq_constraints(expr: &Expr, equalities: &mut HashMap<String, Value>) -> bool {
match expr {
Expr::Eq(column, value) => {
if let Some(existing) = equalities.get(column) {
existing == value
} else {
equalities.insert(column.clone(), value.clone());
true
}
}
Expr::And(lhs, rhs) => {
collect_eq_constraints(lhs, equalities) && collect_eq_constraints(rhs, equalities)
}
_ => false,
}
}
#[cfg(test)]
mod prefix_tests {
use super::*;
use crate::query::plan::Expr;
fn pk() -> Vec<String> {
vec!["a".into(), "b".into(), "c".into()]
}
fn eq(col: &str, v: &str) -> Expr {
Expr::Eq(col.into(), Value::Text(v.into()))
}
#[test]
fn single_leading_column_is_exact_prefix() {
let p = extract_primary_key_prefix(&eq("a", "x"), &pk()).unwrap();
assert_eq!(p.values, vec![Value::Text("x".into())]);
assert!(p.exact);
}
#[test]
fn two_leading_columns_is_exact_prefix() {
let pred = eq("a", "x").and(eq("b", "y"));
let p = extract_primary_key_prefix(&pred, &pk()).unwrap();
assert_eq!(
p.values,
vec![Value::Text("x".into()), Value::Text("y".into())]
);
assert!(p.exact);
}
#[test]
fn full_primary_key_is_exact_prefix() {
let pred = eq("a", "x").and(eq("b", "y")).and(eq("c", "z"));
let p = extract_primary_key_prefix(&pred, &pk()).unwrap();
assert_eq!(p.values.len(), 3);
assert!(p.exact);
}
#[test]
fn gap_in_key_columns_yields_inexact_prefix() {
let pred = eq("a", "x").and(eq("c", "z"));
let p = extract_primary_key_prefix(&pred, &pk()).unwrap();
assert_eq!(p.values, vec![Value::Text("x".into())]);
assert!(!p.exact);
}
#[test]
fn extra_non_key_equality_is_inexact() {
let pred = eq("a", "x").and(Expr::Eq("other".into(), Value::Integer(1)));
let p = extract_primary_key_prefix(&pred, &pk()).unwrap();
assert_eq!(p.values, vec![Value::Text("x".into())]);
assert!(!p.exact);
}
#[test]
fn non_leading_column_has_no_prefix() {
assert!(extract_primary_key_prefix(&eq("b", "y"), &pk()).is_none());
}
#[test]
fn top_level_or_has_no_prefix() {
let pred = eq("a", "x").or(eq("a", "y"));
assert!(extract_primary_key_prefix(&pred, &pk()).is_none());
}
#[test]
fn conjoined_non_eq_keeps_prefix_but_marks_residual() {
let pred = Expr::And(
Box::new(eq("a", "x")),
Box::new(Expr::Gt("b".into(), Value::Text("y".into()))),
);
let p = extract_primary_key_prefix(&pred, &pk()).unwrap();
assert_eq!(p.values, vec![Value::Text("x".into())]);
assert!(!p.exact);
}
#[test]
fn empty_primary_key_has_no_prefix() {
assert!(extract_primary_key_prefix(&eq("a", "x"), &[]).is_none());
}
}