use std::cell::RefCell;
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use std::num::NonZeroUsize;
use std::sync::{Arc, LazyLock};
use radixdb_core::CompactArc;
use lru::LruCache;
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use radixdb_core::StringMap;
use crate::operator::ColumnSource;
use radixdb_core::value::NULL_VALUE;
use radixdb_core::{DataType, Operator, Row, Value};
use radixdb_sql::ast::{
BetweenExpression, BooleanLiteral, Expression, FloatLiteral, FunctionCall, Identifier,
InExpression, InfixExpression, InfixOperator, IntegerLiteral, LikeExpression, ListExpression,
NullLiteral, PrefixExpression, QualifiedIdentifier, StringLiteral, WindowFrameBound,
};
use radixdb_sql::token::{Position, Token, TokenType};
pub use crate::expression::{expression_to_string, string_to_datatype};
pub(crate) use crate::memory::RetainedRowsBudget;
static DUMMY_TOKEN: LazyLock<Token> =
LazyLock::new(|| Token::new(TokenType::Identifier, String::new(), Position::default()));
#[inline]
pub fn dummy_token_ref() -> &'static Token {
&DUMMY_TOKEN
}
#[inline]
pub fn dummy_token_clone() -> Token {
DUMMY_TOKEN.clone()
}
#[inline]
pub fn dummy_token(literal: &str, token_type: TokenType) -> Token {
Token::new(token_type, literal, Position::default())
}
pub fn value_to_expression(v: &Value) -> Expression {
match v {
Value::Integer(i) => Expression::IntegerLiteral(IntegerLiteral {
token: dummy_token(&i.to_string(), TokenType::Integer),
value: *i,
}),
Value::Float(f) => Expression::FloatLiteral(FloatLiteral {
token: dummy_token(&f.to_string(), TokenType::Float),
value: *f,
}),
Value::Text(s) => Expression::StringLiteral(StringLiteral {
token: dummy_token(&format!("'{}'", s), TokenType::String),
value: s.as_str().into(),
type_hint: None,
}),
Value::Boolean(b) => Expression::BooleanLiteral(BooleanLiteral {
token: dummy_token(if *b { "TRUE" } else { "FALSE" }, TokenType::Keyword),
value: *b,
}),
Value::Null(_) => Expression::NullLiteral(NullLiteral {
token: dummy_token("NULL", TokenType::Keyword),
}),
_ => Expression::BoundValue(Box::new(v.clone())),
}
}
pub fn substitute_outer_references(
expr: &Expression,
outer_row: &FxHashMap<CompactArc<str>, Value>,
) -> Expression {
substitute_outer_references_inner(expr, outer_row).unwrap_or_else(|| expr.clone())
}
fn substitute_outer_references_inner(
expr: &Expression,
outer_row: &FxHashMap<CompactArc<str>, Value>,
) -> Option<Expression> {
match expr {
Expression::QualifiedIdentifier(qid) => {
let qualified_name = format!("{}.{}", qid.qualifier.value_lower, qid.name.value_lower);
if let Some(value) = outer_row.get(qualified_name.as_str()) {
return Some(value_to_expression(value));
}
None
}
Expression::Identifier(id) => {
if let Some(value) = outer_row.get(id.value_lower.as_str()) {
return Some(value_to_expression(value));
}
None
}
Expression::Infix(infix) => {
let new_left = substitute_outer_references_inner(&infix.left, outer_row);
let new_right = substitute_outer_references_inner(&infix.right, outer_row);
if new_left.is_some() || new_right.is_some() {
Some(Expression::Infix(InfixExpression {
token: infix.token.clone(),
left: Box::new(new_left.unwrap_or_else(|| (*infix.left).clone())),
operator: infix.operator.clone(),
op_type: infix.op_type,
right: Box::new(new_right.unwrap_or_else(|| (*infix.right).clone())),
}))
} else {
None
}
}
Expression::Prefix(prefix) => substitute_outer_references_inner(&prefix.right, outer_row)
.map(|new_right| {
Expression::Prefix(PrefixExpression {
token: prefix.token.clone(),
operator: prefix.operator.clone(),
op_type: prefix.op_type,
right: Box::new(new_right),
})
}),
Expression::In(in_expr) => {
let new_left = substitute_outer_references_inner(&in_expr.left, outer_row);
let new_right = match &*in_expr.right {
Expression::List(list) => {
let mut any_changed = false;
let new_elements: Vec<Option<Expression>> = list
.elements
.iter()
.map(|e| {
let result = substitute_outer_references_inner(e, outer_row);
if result.is_some() {
any_changed = true;
}
result
})
.collect();
if any_changed {
Some(Expression::List(Box::new(ListExpression {
token: list.token.clone(),
elements: new_elements
.into_iter()
.zip(list.elements.iter())
.map(|(new, old)| new.unwrap_or_else(|| old.clone()))
.collect(),
})))
} else {
None
}
}
other => substitute_outer_references_inner(other, outer_row),
};
if new_left.is_some() || new_right.is_some() {
Some(Expression::In(InExpression {
token: in_expr.token.clone(),
left: Box::new(new_left.unwrap_or_else(|| (*in_expr.left).clone())),
not: in_expr.not,
right: Box::new(new_right.unwrap_or_else(|| (*in_expr.right).clone())),
}))
} else {
None
}
}
Expression::Between(between) => {
let new_expr = substitute_outer_references_inner(&between.expr, outer_row);
let new_lower = substitute_outer_references_inner(&between.lower, outer_row);
let new_upper = substitute_outer_references_inner(&between.upper, outer_row);
if new_expr.is_some() || new_lower.is_some() || new_upper.is_some() {
Some(Expression::Between(BetweenExpression {
token: between.token.clone(),
expr: Box::new(new_expr.unwrap_or_else(|| (*between.expr).clone())),
not: between.not,
lower: Box::new(new_lower.unwrap_or_else(|| (*between.lower).clone())),
upper: Box::new(new_upper.unwrap_or_else(|| (*between.upper).clone())),
}))
} else {
None
}
}
Expression::Like(like) => {
let new_left = substitute_outer_references_inner(&like.left, outer_row);
let new_pattern = substitute_outer_references_inner(&like.pattern, outer_row);
let new_escape = like
.escape
.as_ref()
.and_then(|e| substitute_outer_references_inner(e, outer_row));
if new_left.is_some() || new_pattern.is_some() || new_escape.is_some() {
Some(Expression::Like(LikeExpression {
token: like.token.clone(),
left: Box::new(new_left.unwrap_or_else(|| (*like.left).clone())),
operator: like.operator.clone(),
pattern: Box::new(new_pattern.unwrap_or_else(|| (*like.pattern).clone())),
escape: if new_escape.is_some() {
new_escape.map(Box::new)
} else {
like.escape.clone()
},
}))
} else {
None
}
}
Expression::FunctionCall(func) => {
let mut any_changed = false;
let new_args: Vec<Option<Expression>> = func
.arguments
.iter()
.map(|arg| {
let result = substitute_outer_references_inner(arg, outer_row);
if result.is_some() {
any_changed = true;
}
result
})
.collect();
let new_filter = func
.filter
.as_ref()
.and_then(|f| substitute_outer_references_inner(f, outer_row));
if new_filter.is_some() {
any_changed = true;
}
if any_changed {
Some(Expression::FunctionCall(Box::new(FunctionCall {
token: func.token.clone(),
function: func.function.clone(),
arguments: new_args
.into_iter()
.zip(func.arguments.iter())
.map(|(new, old)| new.unwrap_or_else(|| old.clone()))
.collect(),
is_distinct: func.is_distinct,
order_by: func.order_by.clone(),
filter: if new_filter.is_some() {
new_filter.map(Box::new)
} else {
func.filter.clone()
},
})))
} else {
None
}
}
_ => None,
}
}
pub fn build_column_index_map(columns: &[String]) -> StringMap<usize> {
let mut map: StringMap<usize> = StringMap::with_capacity(columns.len());
for (i, c) in columns.iter().enumerate() {
map.insert(c.to_lowercase(), i);
}
let mut base_count: StringMap<u8> = StringMap::default();
for c in columns {
let lower = c.to_lowercase();
if let Some(dot_pos) = lower.rfind('.') {
let base = &lower[dot_pos + 1..];
let entry = base_count.entry(base.to_string()).or_insert(0);
*entry = entry.saturating_add(1);
}
}
for (i, c) in columns.iter().enumerate() {
let lower = c.to_lowercase();
if let Some(dot_pos) = lower.rfind('.') {
let base = &lower[dot_pos + 1..];
if base_count.get(base).copied().unwrap_or(0) == 1 && !map.contains_key(base) {
map.insert(base.to_string(), i);
}
}
}
map
}
#[inline]
pub fn combine_rows(left: &Row, right: &Row, left_count: usize, right_count: usize) -> Vec<Value> {
let mut combined = Vec::with_capacity(left_count + right_count);
combined.extend(left.iter().cloned());
combined.extend(right.iter().cloned());
combined
}
#[inline]
pub fn combine_rows_with_nulls(
row: &Row,
row_count: usize,
null_count: usize,
row_is_left: bool,
) -> Vec<Value> {
let mut values = Vec::with_capacity(row_count + null_count);
if row_is_left {
values.extend(row.iter().cloned());
values.resize(row_count + null_count, NULL_VALUE);
} else {
values.resize(null_count, NULL_VALUE);
values.extend(row.iter().cloned());
}
values
}
#[inline]
pub fn hash_composite_key(row: &Row, key_indices: &[usize]) -> u64 {
let mut hasher = FxHasher::default();
for &idx in key_indices {
if let Some(value) = row.get(idx) {
hash_value_into(value, &mut hasher);
} else {
0xDEADBEEFu64.hash(&mut hasher);
}
}
hasher.finish()
}
#[inline]
pub fn hash_value_into<H: Hasher>(value: &Value, hasher: &mut H) {
value.hash(hasher);
}
#[inline]
pub fn values_equal(a: &Value, b: &Value) -> bool {
!a.is_null() && !b.is_null() && a == b
}
pub fn compare_values(a: &Value, b: &Value) -> Ordering {
match (a.is_null(), b.is_null()) {
(true, true) => Ordering::Equal,
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
(false, false) => a.cmp(b),
}
}
#[inline]
pub fn verify_composite_key_equality(
row1: &Row,
row2: &Row,
indices1: &[usize],
indices2: &[usize],
) -> bool {
debug_assert_eq!(indices1.len(), indices2.len());
for (&idx1, &idx2) in indices1.iter().zip(indices2.iter()) {
match (row1.get(idx1), row2.get(idx2)) {
(Some(v1), Some(v2)) => {
if !values_equal(v1, v2) {
return false;
}
}
(None, None) => {
return false;
}
_ => {
return false;
}
}
}
true
}
#[inline]
pub fn hash_row(row: &Row) -> u64 {
let mut hasher = FxHasher::default();
for value in row.iter() {
value.hash(&mut hasher);
}
hasher.finish()
}
#[inline]
pub fn rows_equal(a: &Row, b: &Row) -> bool {
if a.len() != b.len() {
return false;
}
for i in 0..a.len() {
match (a.get(i), b.get(i)) {
(Some(va), Some(vb)) if va == vb => continue,
(None, None) => continue,
_ => return false,
}
}
true
}
#[inline]
pub fn extract_column_name(expr: &Expression) -> Option<String> {
match expr {
Expression::Identifier(Identifier { value, .. }) => Some(value.to_string()),
Expression::QualifiedIdentifier(QualifiedIdentifier { name, .. }) => {
Some(name.value.to_string())
}
_ => None,
}
}
#[inline]
pub fn extract_literal_value(expr: &Expression) -> Option<Value> {
match expr {
Expression::IntegerLiteral(i) => Some(Value::Integer(i.value)),
Expression::FloatLiteral(f) => Some(Value::Float(f.value)),
Expression::StringLiteral(s) => Some(if let Some(type_hint) = &s.type_hint {
match type_hint.to_uppercase().as_str() {
"TIMESTAMP" | "DATETIME" => radixdb_core::value::parse_timestamp(&s.value)
.map(Value::Timestamp)
.unwrap_or_else(|_| Value::Text(s.value.clone())),
"DATE" => radixdb_core::value::parse_date_days_since_unix_epoch(&s.value)
.map(Value::date)
.unwrap_or_else(|| Value::Text(s.value.clone())),
_ => Value::Text(s.value.clone()),
}
} else {
Value::Text(s.value.clone())
}),
Expression::BooleanLiteral(b) => Some(Value::Boolean(b.value)),
Expression::NullLiteral(_) => Some(Value::Null(DataType::Text)),
_ => None,
}
}
#[inline]
pub fn flip_operator(op: Operator) -> Operator {
match op {
Operator::Lt => Operator::Gt,
Operator::Lte => Operator::Gte,
Operator::Gt => Operator::Lt,
Operator::Gte => Operator::Lte,
other => other, }
}
#[inline]
pub fn infix_to_operator(op: InfixOperator) -> Option<Operator> {
match op {
InfixOperator::Equal => Some(Operator::Eq),
InfixOperator::NotEqual => Some(Operator::Ne),
InfixOperator::LessThan => Some(Operator::Lt),
InfixOperator::LessEqual => Some(Operator::Lte),
InfixOperator::GreaterThan => Some(Operator::Gt),
InfixOperator::GreaterEqual => Some(Operator::Gte),
_ => None,
}
}
#[inline]
pub fn extract_base_column_name(col_name: &str) -> String {
if let Some(dot_idx) = col_name.rfind('.') {
col_name[dot_idx + 1..].to_lowercase()
} else {
col_name.to_lowercase()
}
}
pub fn expression_has_parameters(expr: &Expression) -> bool {
match expr {
Expression::Parameter(_) => true,
Expression::Prefix(prefix) => expression_has_parameters(&prefix.right),
Expression::Infix(infix) => {
expression_has_parameters(&infix.left) || expression_has_parameters(&infix.right)
}
Expression::In(in_expr) => {
expression_has_parameters(&in_expr.left)
|| match in_expr.right.as_ref() {
Expression::List(list) => list.elements.iter().any(expression_has_parameters),
Expression::ExpressionList(list) => {
list.expressions.iter().any(expression_has_parameters)
}
other => expression_has_parameters(other),
}
}
Expression::Between(between) => {
expression_has_parameters(&between.expr)
|| expression_has_parameters(&between.lower)
|| expression_has_parameters(&between.upper)
}
Expression::Like(like) => {
expression_has_parameters(&like.left) || expression_has_parameters(&like.pattern)
}
Expression::Case(case) => {
case.value
.as_ref()
.is_some_and(|e| expression_has_parameters(e))
|| case.when_clauses.iter().any(|wc| {
expression_has_parameters(&wc.condition)
|| expression_has_parameters(&wc.then_result)
})
|| case
.else_value
.as_ref()
.is_some_and(|e| expression_has_parameters(e))
}
Expression::FunctionCall(func) => func.arguments.iter().any(expression_has_parameters),
Expression::Aliased(aliased) => expression_has_parameters(&aliased.expression),
Expression::Cast(cast) => expression_has_parameters(&cast.expr),
_ => false,
}
}
pub fn expressions_equivalent(a: &Expression, b: &Expression) -> bool {
match (a, b) {
(Expression::Identifier(ia), Expression::Identifier(ib)) => {
ia.value_lower == ib.value_lower
}
(Expression::QualifiedIdentifier(qa), Expression::QualifiedIdentifier(qb)) => {
qa.qualifier.value_lower == qb.qualifier.value_lower
&& qa.name.value_lower == qb.name.value_lower
}
(Expression::IntegerLiteral(la), Expression::IntegerLiteral(lb)) => la.value == lb.value,
(Expression::FloatLiteral(la), Expression::FloatLiteral(lb)) => {
Value::Float(la.value) == Value::Float(lb.value)
}
(Expression::StringLiteral(la), Expression::StringLiteral(lb)) => la.value == lb.value,
(Expression::BooleanLiteral(la), Expression::BooleanLiteral(lb)) => la.value == lb.value,
(Expression::NullLiteral(_), Expression::NullLiteral(_)) => true,
(Expression::Infix(ia), Expression::Infix(ib)) => {
ia.op_type == ib.op_type
&& expressions_equivalent(&ia.left, &ib.left)
&& expressions_equivalent(&ia.right, &ib.right)
}
(Expression::Prefix(pa), Expression::Prefix(pb)) => {
pa.operator == pb.operator && expressions_equivalent(&pa.right, &pb.right)
}
(Expression::Between(ba), Expression::Between(bb)) => {
ba.not == bb.not
&& expressions_equivalent(&ba.expr, &bb.expr)
&& expressions_equivalent(&ba.lower, &bb.lower)
&& expressions_equivalent(&ba.upper, &bb.upper)
}
(Expression::In(ia), Expression::In(ib)) => {
ia.not == ib.not
&& expressions_equivalent(&ia.left, &ib.left)
&& expressions_equivalent(&ia.right, &ib.right)
}
(Expression::ExpressionList(la), Expression::ExpressionList(lb)) => {
la.expressions.len() == lb.expressions.len()
&& la
.expressions
.iter()
.zip(lb.expressions.iter())
.all(|(ae, be)| expressions_equivalent(ae, be))
}
(Expression::Like(la), Expression::Like(lb)) => {
la.operator == lb.operator
&& expressions_equivalent(&la.left, &lb.left)
&& expressions_equivalent(&la.pattern, &lb.pattern)
&& match (&la.escape, &lb.escape) {
(None, None) => true,
(Some(ea), Some(eb)) => expressions_equivalent(ea, eb),
_ => false,
}
}
(Expression::FunctionCall(fa), Expression::FunctionCall(fb)) => {
function_calls_equivalent(fa, fb)
}
(Expression::Window(wa), Expression::Window(wb)) => {
function_calls_equivalent(&wa.function, &wb.function)
&& wa.window_ref == wb.window_ref
&& wa.partition_by.len() == wb.partition_by.len()
&& wa
.partition_by
.iter()
.zip(wb.partition_by.iter())
.all(|(ae, be)| expressions_equivalent(ae, be))
&& wa.order_by.len() == wb.order_by.len()
&& wa.order_by.iter().zip(wb.order_by.iter()).all(|(oa, ob)| {
oa.ascending == ob.ascending
&& oa.nulls_first == ob.nulls_first
&& expressions_equivalent(&oa.expression, &ob.expression)
})
&& match (&wa.frame, &wb.frame) {
(None, None) => true,
(Some(fa), Some(fb)) => {
fa.unit == fb.unit
&& window_bounds_equivalent(&fa.start, &fb.start)
&& match (&fa.end, &fb.end) {
(None, None) => true,
(Some(ea), Some(eb)) => window_bounds_equivalent(ea, eb),
_ => false,
}
}
_ => false,
}
}
_ => false,
}
}
fn function_calls_equivalent(fa: &FunctionCall, fb: &FunctionCall) -> bool {
fa.function.eq_ignore_ascii_case(&fb.function)
&& fa.is_distinct == fb.is_distinct
&& fa.arguments.len() == fb.arguments.len()
&& fa
.arguments
.iter()
.zip(fb.arguments.iter())
.all(|(ae, be)| expressions_equivalent(ae, be))
&& fa.order_by.len() == fb.order_by.len()
&& fa.order_by.iter().zip(fb.order_by.iter()).all(|(oa, ob)| {
oa.ascending == ob.ascending
&& oa.nulls_first == ob.nulls_first
&& expressions_equivalent(&oa.expression, &ob.expression)
})
&& match (&fa.filter, &fb.filter) {
(None, None) => true,
(Some(ea), Some(eb)) => expressions_equivalent(ea, eb),
_ => false,
}
}
fn window_bounds_equivalent(a: &WindowFrameBound, b: &WindowFrameBound) -> bool {
match (a, b) {
(WindowFrameBound::CurrentRow, WindowFrameBound::CurrentRow)
| (WindowFrameBound::UnboundedPreceding, WindowFrameBound::UnboundedPreceding)
| (WindowFrameBound::UnboundedFollowing, WindowFrameBound::UnboundedFollowing) => true,
(WindowFrameBound::Preceding(ea), WindowFrameBound::Preceding(eb))
| (WindowFrameBound::Following(ea), WindowFrameBound::Following(eb)) => {
expressions_equivalent(ea, eb)
}
_ => false,
}
}
pub fn flatten_and_predicates(expr: &Expression) -> Vec<Expression> {
match expr {
Expression::Infix(infix) if infix.operator.to_uppercase() == "AND" => {
let mut result = flatten_and_predicates(&infix.left);
result.extend(flatten_and_predicates(&infix.right));
result
}
_ => vec![expr.clone()],
}
}
pub fn combine_predicates_with_and(preds: Vec<Expression>) -> Option<Expression> {
if preds.is_empty() {
return None;
}
let mut result = preds.into_iter();
let first = result.next().unwrap();
Some(result.fold(first, |acc, pred| {
Expression::Infix(InfixExpression::new(
Token::new(TokenType::Keyword, "AND", Position::default()),
Box::new(acc),
"AND".to_string(),
Box::new(pred),
))
}))
}
pub fn extract_and_conditions(expr: &Expression) -> Vec<&Expression> {
let mut conditions = Vec::new();
fn collect<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
if let Expression::Infix(infix) = expr {
if matches!(infix.op_type, InfixOperator::And) {
collect(&infix.left, out);
collect(&infix.right, out);
return;
}
}
out.push(expr);
}
collect(expr, &mut conditions);
conditions
}
pub fn collect_table_qualifiers(expr: &Expression) -> FxHashSet<String> {
let mut qualifiers = FxHashSet::default();
collect_table_qualifiers_impl(expr, &mut qualifiers);
qualifiers
}
fn collect_table_qualifiers_impl(expr: &Expression, qualifiers: &mut FxHashSet<String>) {
match expr {
Expression::QualifiedIdentifier(qi) => {
qualifiers.insert(qi.qualifier.value_lower.to_string());
}
Expression::Infix(infix) => {
collect_table_qualifiers_impl(&infix.left, qualifiers);
collect_table_qualifiers_impl(&infix.right, qualifiers);
}
Expression::Prefix(prefix) => {
collect_table_qualifiers_impl(&prefix.right, qualifiers);
}
Expression::In(in_expr) => {
collect_table_qualifiers_impl(&in_expr.left, qualifiers);
match in_expr.right.as_ref() {
Expression::ExpressionList(el) => {
for elem in &el.expressions {
collect_table_qualifiers_impl(elem, qualifiers);
}
}
Expression::List(list) => {
for elem in &list.elements {
collect_table_qualifiers_impl(elem, qualifiers);
}
}
other => {
collect_table_qualifiers_impl(other, qualifiers);
}
}
}
Expression::Between(between) => {
collect_table_qualifiers_impl(&between.expr, qualifiers);
collect_table_qualifiers_impl(&between.lower, qualifiers);
collect_table_qualifiers_impl(&between.upper, qualifiers);
}
Expression::Like(like) => {
collect_table_qualifiers_impl(&like.left, qualifiers);
collect_table_qualifiers_impl(&like.pattern, qualifiers);
}
Expression::FunctionCall(func) => {
for arg in &func.arguments {
collect_table_qualifiers_impl(arg, qualifiers);
}
}
Expression::Aliased(aliased) => {
collect_table_qualifiers_impl(&aliased.expression, qualifiers);
}
Expression::Cast(cast) => {
collect_table_qualifiers_impl(&cast.expr, qualifiers);
}
Expression::Case(case) => {
if let Some(ref val) = case.value {
collect_table_qualifiers_impl(val, qualifiers);
}
for when in &case.when_clauses {
collect_table_qualifiers_impl(&when.condition, qualifiers);
collect_table_qualifiers_impl(&when.then_result, qualifiers);
}
if let Some(ref else_val) = case.else_value {
collect_table_qualifiers_impl(else_val, qualifiers);
}
}
_ => {}
}
}
pub fn get_table_alias_from_expr(expr: &Expression) -> Option<String> {
match expr {
Expression::TableSource(ts) => Some(
ts.alias
.as_ref()
.map(|a| a.value.to_string())
.unwrap_or_else(|| ts.name.value.to_string()),
),
Expression::SubquerySource(ss) => ss.alias.as_ref().map(|a| a.value.to_string()),
Expression::FunctionTableSource(fs) => Some(
fs.alias
.as_ref()
.map(|a| a.value.to_string())
.unwrap_or_else(|| fs.function.value.to_string()),
),
Expression::ValuesSource(vs) => vs.alias.as_ref().map(|a| a.value.to_string()),
Expression::CteReference(cr) => Some(
cr.alias
.as_ref()
.map(|a| a.value.to_string())
.unwrap_or_else(|| cr.name.value.to_string()),
),
_ => None,
}
}
pub fn strip_table_qualifier(expr: &Expression, table_alias: &str) -> Expression {
let alias_lower = table_alias.to_lowercase();
match expr {
Expression::QualifiedIdentifier(qi) if qi.qualifier.value_lower.as_str() == alias_lower => {
Expression::Identifier(Identifier::new(
qi.name.token.clone(),
qi.name.value.clone(),
))
}
Expression::Infix(infix) => Expression::Infix(InfixExpression::new(
infix.token.clone(),
Box::new(strip_table_qualifier(&infix.left, table_alias)),
infix.operator.clone(),
Box::new(strip_table_qualifier(&infix.right, table_alias)),
)),
Expression::Prefix(prefix) => Expression::Prefix(PrefixExpression::new(
prefix.token.clone(),
prefix.operator.clone(),
Box::new(strip_table_qualifier(&prefix.right, table_alias)),
)),
Expression::In(in_expr) => {
let new_left = strip_table_qualifier(&in_expr.left, table_alias);
let new_right = match in_expr.right.as_ref() {
Expression::List(list) => Expression::List(Box::new(ListExpression {
token: list.token.clone(),
elements: list
.elements
.iter()
.map(|e| strip_table_qualifier(e, table_alias))
.collect(),
})),
other => strip_table_qualifier(other, table_alias),
};
Expression::In(InExpression {
token: in_expr.token.clone(),
left: Box::new(new_left),
right: Box::new(new_right),
not: in_expr.not,
})
}
Expression::Between(between) => Expression::Between(BetweenExpression {
token: between.token.clone(),
expr: Box::new(strip_table_qualifier(&between.expr, table_alias)),
lower: Box::new(strip_table_qualifier(&between.lower, table_alias)),
upper: Box::new(strip_table_qualifier(&between.upper, table_alias)),
not: between.not,
}),
Expression::Like(like) => Expression::Like(LikeExpression {
token: like.token.clone(),
left: Box::new(strip_table_qualifier(&like.left, table_alias)),
pattern: Box::new(strip_table_qualifier(&like.pattern, table_alias)),
operator: like.operator.clone(),
escape: like
.escape
.as_ref()
.map(|e| Box::new(strip_table_qualifier(e, table_alias))),
}),
Expression::FunctionCall(func) => Expression::FunctionCall(Box::new(FunctionCall {
token: func.token.clone(),
function: func.function.clone(),
arguments: func
.arguments
.iter()
.map(|a| strip_table_qualifier(a, table_alias))
.collect(),
is_distinct: func.is_distinct,
order_by: func.order_by.clone(),
filter: func
.filter
.as_ref()
.map(|f| Box::new(strip_table_qualifier(f, table_alias))),
})),
other => other.clone(),
}
}
pub fn add_table_qualifier(expr: &Expression, table_alias: &str) -> Expression {
match expr {
Expression::Identifier(id) => {
Expression::QualifiedIdentifier(QualifiedIdentifier {
token: Token::new(TokenType::Identifier, table_alias, Position::default()),
qualifier: Box::new(Identifier::new(
Token::new(TokenType::Identifier, table_alias, Position::default()),
table_alias.to_string(),
)),
intermediate: None,
name: Box::new(id.clone()),
})
}
Expression::Infix(infix) => Expression::Infix(InfixExpression::new(
infix.token.clone(),
Box::new(add_table_qualifier(&infix.left, table_alias)),
infix.operator.clone(),
Box::new(add_table_qualifier(&infix.right, table_alias)),
)),
Expression::Prefix(prefix) => Expression::Prefix(PrefixExpression::new(
prefix.token.clone(),
prefix.operator.clone(),
Box::new(add_table_qualifier(&prefix.right, table_alias)),
)),
Expression::In(in_expr) => {
let new_left = add_table_qualifier(&in_expr.left, table_alias);
let new_right = match in_expr.right.as_ref() {
Expression::List(list) => Expression::List(Box::new(ListExpression {
token: list.token.clone(),
elements: list
.elements
.iter()
.map(|e| add_table_qualifier(e, table_alias))
.collect(),
})),
other => add_table_qualifier(other, table_alias),
};
Expression::In(InExpression {
token: in_expr.token.clone(),
left: Box::new(new_left),
right: Box::new(new_right),
not: in_expr.not,
})
}
Expression::Between(between) => Expression::Between(BetweenExpression {
token: between.token.clone(),
expr: Box::new(add_table_qualifier(&between.expr, table_alias)),
lower: Box::new(add_table_qualifier(&between.lower, table_alias)),
upper: Box::new(add_table_qualifier(&between.upper, table_alias)),
not: between.not,
}),
Expression::Like(like) => Expression::Like(LikeExpression {
token: like.token.clone(),
left: Box::new(add_table_qualifier(&like.left, table_alias)),
pattern: Box::new(add_table_qualifier(&like.pattern, table_alias)),
operator: like.operator.clone(),
escape: like
.escape
.as_ref()
.map(|e| Box::new(add_table_qualifier(e, table_alias))),
}),
Expression::FunctionCall(func) => Expression::FunctionCall(Box::new(FunctionCall {
token: func.token.clone(),
function: func.function.clone(),
arguments: func
.arguments
.iter()
.map(|a| add_table_qualifier(a, table_alias))
.collect(),
is_distinct: func.is_distinct,
order_by: func.order_by.clone(),
filter: func
.filter
.as_ref()
.map(|f| Box::new(add_table_qualifier(f, table_alias))),
})),
other => other.clone(),
}
}
#[inline]
pub fn is_aggregate_function(name: &str) -> bool {
radixdb_functions::registry::global_registry().is_aggregate(name)
}
pub fn expression_contains_aggregate(expr: &Expression) -> bool {
match expr {
Expression::FunctionCall(func) => {
if is_aggregate_function(&func.function) {
return true;
}
func.arguments.iter().any(expression_contains_aggregate)
}
Expression::Aliased(aliased) => expression_contains_aggregate(&aliased.expression),
Expression::Infix(infix) => {
expression_contains_aggregate(&infix.left)
|| expression_contains_aggregate(&infix.right)
}
Expression::Prefix(prefix) => expression_contains_aggregate(&prefix.right),
Expression::Cast(cast) => expression_contains_aggregate(&cast.expr),
Expression::Case(case) => {
for when_clause in &case.when_clauses {
if expression_contains_aggregate(&when_clause.condition)
|| expression_contains_aggregate(&when_clause.then_result)
{
return true;
}
}
if let Some(ref else_val) = case.else_value {
if expression_contains_aggregate(else_val) {
return true;
}
}
false
}
_ => false,
}
}
#[inline]
pub fn extract_column_name_with_qualifier(expr: &Expression) -> Option<(Option<String>, String)> {
match expr {
Expression::Identifier(id) => Some((None, id.value_lower.to_string())),
Expression::QualifiedIdentifier(qid) => Some((
Some(qid.qualifier.value_lower.to_string()),
qid.name.value_lower.to_string(),
)),
_ => None,
}
}
pub fn find_column_index(col_info: &(Option<String>, String), columns: &[String]) -> Option<usize> {
let (qualifier, col_name) = col_info;
let qualified = qualifier.as_ref().map(|q| format!("{}.{}", q, col_name));
for (idx, column) in columns.iter().enumerate() {
let col_lower = column.to_lowercase();
if col_lower == *col_name {
return Some(idx);
}
if let Some(ref q) = qualified {
if col_lower == *q {
return Some(idx);
}
}
}
if qualifier.is_none() {
let suffix_pattern = format!(".{}", col_name);
for (idx, column) in columns.iter().enumerate() {
let col_lower = column.to_lowercase();
if col_lower.ends_with(&suffix_pattern) {
return Some(idx);
}
}
}
None
}
pub fn parse_vector_dimension(type_str: &str) -> u16 {
let upper = type_str.to_uppercase();
if let Some(inner) = upper
.strip_prefix("VECTOR(")
.and_then(|s| s.strip_suffix(')'))
{
inner.trim().parse::<u16>().unwrap_or(0)
} else {
0
}
}
pub fn extract_join_keys_and_residual(
condition: &Expression,
left_columns: &[String],
right_columns: &[String],
) -> (Vec<usize>, Vec<usize>, Vec<Expression>) {
let mut left_indices = Vec::new();
let mut right_indices = Vec::new();
let mut residual = Vec::new();
extract_join_keys_recursive(
condition,
left_columns,
right_columns,
&mut left_indices,
&mut right_indices,
&mut residual,
);
(left_indices, right_indices, residual)
}
fn extract_join_keys_recursive(
condition: &Expression,
left_columns: &[String],
right_columns: &[String],
left_indices: &mut Vec<usize>,
right_indices: &mut Vec<usize>,
residual: &mut Vec<Expression>,
) {
match condition {
Expression::Infix(infix) if infix.op_type == InfixOperator::And => {
extract_join_keys_recursive(
&infix.left,
left_columns,
right_columns,
left_indices,
right_indices,
residual,
);
extract_join_keys_recursive(
&infix.right,
left_columns,
right_columns,
left_indices,
right_indices,
residual,
);
}
Expression::Infix(infix) if infix.op_type == InfixOperator::Equal => {
if let (Some(left_col), Some(right_col)) = (
extract_column_name_with_qualifier(&infix.left),
extract_column_name_with_qualifier(&infix.right),
) {
if let (Some(left_idx), Some(right_idx)) = (
find_column_index(&left_col, left_columns),
find_column_index(&right_col, right_columns),
) {
left_indices.push(left_idx);
right_indices.push(right_idx);
return;
}
if let (Some(left_idx), Some(right_idx)) = (
find_column_index(&right_col, left_columns),
find_column_index(&left_col, right_columns),
) {
left_indices.push(left_idx);
right_indices.push(right_idx);
return;
}
}
residual.push(condition.clone());
}
_ => {
residual.push(condition.clone());
}
}
}
fn expression_contains_column(expr: &Expression, target_lower: &str) -> bool {
match expr {
Expression::Identifier(ident) => {
ident.value_lower.as_str() == target_lower
|| extract_base_column_name(&ident.value) == target_lower
}
Expression::QualifiedIdentifier(qi) => {
qi.name.value_lower.as_str() == target_lower
|| extract_base_column_name(&qi.name.value) == target_lower
}
Expression::FunctionCall(fc) => fc
.arguments
.iter()
.any(|arg| expression_contains_column(arg, target_lower)),
Expression::Infix(infix) => {
expression_contains_column(&infix.left, target_lower)
|| expression_contains_column(&infix.right, target_lower)
}
Expression::Prefix(prefix) => expression_contains_column(&prefix.right, target_lower),
Expression::In(in_expr) => expression_contains_column(&in_expr.left, target_lower),
Expression::Between(between) => expression_contains_column(&between.expr, target_lower),
Expression::Like(like) => expression_contains_column(&like.left, target_lower),
Expression::Case(case) => {
let in_value = case
.value
.as_ref()
.map(|e| expression_contains_column(e, target_lower))
.unwrap_or(false);
let in_branches = case.when_clauses.iter().any(|clause| {
expression_contains_column(&clause.condition, target_lower)
|| expression_contains_column(&clause.then_result, target_lower)
});
let in_else = case
.else_value
.as_ref()
.map(|e| expression_contains_column(e, target_lower))
.unwrap_or(false);
in_value || in_branches || in_else
}
Expression::Cast(cast) => expression_contains_column(&cast.expr, target_lower),
Expression::ScalarSubquery(_) | Expression::SubquerySource(_) => false,
_ => false,
}
}
pub fn filter_references_column(expr: &Expression, target_col: &str) -> bool {
let target_lower = target_col.to_lowercase();
match expr {
Expression::In(in_expr) => {
expression_contains_column(&in_expr.left, &target_lower)
}
Expression::Infix(infix) => {
if infix.operator == "AND" || infix.operator == "OR" {
return filter_references_column(&infix.left, target_col)
|| filter_references_column(&infix.right, target_col);
}
expression_contains_column(&infix.left, &target_lower)
|| expression_contains_column(&infix.right, &target_lower)
}
Expression::Between(between) => {
expression_contains_column(&between.expr, &target_lower)
}
Expression::Like(like) => {
expression_contains_column(&like.left, &target_lower)
}
Expression::Prefix(prefix) => {
filter_references_column(&prefix.right, target_col)
}
Expression::FunctionCall(fc) => {
fc.arguments
.iter()
.any(|arg| expression_contains_column(arg, &target_lower))
}
_ => false,
}
}
pub fn substitute_filter_column(
expr: &Expression,
from_col: &str,
to_col: &str,
) -> Option<Expression> {
let from_lower = from_col.to_lowercase();
let from_base = extract_base_column_name(from_col);
match expr {
Expression::In(in_expr) => {
if let Some(col_name) = extract_column_name(&in_expr.left) {
let col_lower = col_name.to_lowercase();
let col_base = extract_base_column_name(&col_name);
if col_lower == from_lower || col_base == from_base {
let new_left = create_column_identifier(to_col);
return Some(Expression::In(InExpression {
token: in_expr.token.clone(),
left: Box::new(new_left),
right: in_expr.right.clone(),
not: in_expr.not,
}));
}
}
}
Expression::Infix(infix) => {
let left_col = extract_column_name(&infix.left);
let right_col = extract_column_name(&infix.right);
if let Some(col_name) = &left_col {
let col_lower = col_name.to_lowercase();
let col_base = extract_base_column_name(col_name);
if col_lower == from_lower || col_base == from_base {
let new_left = create_column_identifier(to_col);
return Some(Expression::Infix(InfixExpression::new(
infix.token.clone(),
Box::new(new_left),
infix.operator.clone(),
infix.right.clone(),
)));
}
}
if let Some(col_name) = &right_col {
let col_lower = col_name.to_lowercase();
let col_base = extract_base_column_name(col_name);
if col_lower == from_lower || col_base == from_base {
let new_right = create_column_identifier(to_col);
return Some(Expression::Infix(InfixExpression::new(
infix.token.clone(),
infix.left.clone(),
infix.operator.clone(),
Box::new(new_right),
)));
}
}
}
Expression::Between(between) => {
if let Some(col_name) = extract_column_name(&between.expr) {
let col_lower = col_name.to_lowercase();
let col_base = extract_base_column_name(&col_name);
if col_lower == from_lower || col_base == from_base {
let new_expr = create_column_identifier(to_col);
return Some(Expression::Between(BetweenExpression {
token: between.token.clone(),
expr: Box::new(new_expr),
lower: between.lower.clone(),
upper: between.upper.clone(),
not: between.not,
}));
}
}
}
Expression::Like(like) => {
if let Some(col_name) = extract_column_name(&like.left) {
let col_lower = col_name.to_lowercase();
let col_base = extract_base_column_name(&col_name);
if col_lower == from_lower || col_base == from_base {
let new_left = create_column_identifier(to_col);
return Some(Expression::Like(LikeExpression {
token: like.token.clone(),
left: Box::new(new_left),
pattern: like.pattern.clone(),
operator: like.operator.clone(),
escape: like.escape.clone(),
}));
}
}
}
_ => {}
}
None
}
fn create_column_identifier(col_name: &str) -> Expression {
if let Some(dot_idx) = col_name.find('.') {
let qualifier = &col_name[..dot_idx];
let name = &col_name[dot_idx + 1..];
Expression::QualifiedIdentifier(QualifiedIdentifier {
token: dummy_token(col_name, TokenType::Identifier),
qualifier: Box::new(Identifier::new(
dummy_token(qualifier, TokenType::Identifier),
qualifier.to_string(),
)),
intermediate: None,
name: Box::new(Identifier::new(
dummy_token(name, TokenType::Identifier),
name.to_string(),
)),
})
} else {
Expression::Identifier(Identifier::new(
dummy_token(col_name, TokenType::Identifier),
col_name.to_string(),
))
}
}
#[derive(Clone)]
pub struct JoinProjectionIndices {
pub columns: Vec<ColumnSource>,
pub output_columns: Vec<String>,
}
fn join_projection_source(combined_index: usize, outer_width: usize) -> ColumnSource {
if combined_index < outer_width {
ColumnSource::Outer(combined_index)
} else {
ColumnSource::Inner(combined_index - outer_width)
}
}
type JoinProjectionLookupBucket = Vec<(Vec<String>, Arc<StringMap<usize>>)>;
thread_local! {
static JOIN_PROJECTION_LOOKUP_CACHE: RefCell<LruCache<u64, JoinProjectionLookupBucket>> =
RefCell::new(LruCache::new(NonZeroUsize::new(512).unwrap()));
}
pub fn clear_join_projection_lookup_cache() {
JOIN_PROJECTION_LOOKUP_CACHE.with(|cache| cache.borrow_mut().clear());
}
fn build_join_projection_lookup(
outer_columns: &[String],
inner_columns: &[String],
) -> Arc<StringMap<usize>> {
let mut hasher = FxHasher::default();
outer_columns.len().hash(&mut hasher);
inner_columns.len().hash(&mut hasher);
for column in outer_columns.iter().chain(inner_columns) {
column.hash(&mut hasher);
}
let key = hasher.finish();
JOIN_PROJECTION_LOOKUP_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
if let Some(bucket) = cache.get(&key) {
if let Some((_, lookup)) = bucket.iter().find(|(columns, _)| {
columns.len() == outer_columns.len() + inner_columns.len()
&& columns
.iter()
.zip(outer_columns.iter().chain(inner_columns))
.all(|(cached, actual)| cached == actual)
}) {
return Arc::clone(lookup);
}
}
let columns = outer_columns
.iter()
.chain(inner_columns)
.cloned()
.collect::<Vec<_>>();
let lookup = Arc::new(build_column_index_map(&columns));
if let Some(bucket) = cache.get_mut(&key) {
bucket.push((columns, Arc::clone(&lookup)));
} else {
cache.put(key, vec![(columns, Arc::clone(&lookup))]);
}
lookup
})
}
pub fn compute_join_projection(
select_exprs: &[Expression],
outer_columns: &[String],
inner_columns: &[String],
) -> Option<JoinProjectionIndices> {
let outer_width = outer_columns.len();
let lookup = build_join_projection_lookup(outer_columns, inner_columns);
let mut columns = Vec::new();
let mut output_columns = Vec::new();
for expr in select_exprs {
match expr {
Expression::Star(_) | Expression::QualifiedStar(_) => return None,
Expression::Identifier(id) => {
let col_lower = id.value_lower.as_str();
let index = lookup.get(col_lower).copied()?;
columns.push(join_projection_source(index, outer_width));
output_columns.push(id.value.to_string());
}
Expression::QualifiedIdentifier(qid) => {
let full_name = format!("{}.{}", qid.qualifier.value_lower, qid.name.value_lower);
let index = lookup
.get(&full_name)
.copied()
.or_else(|| lookup.get(qid.name.value_lower.as_str()).copied())?;
columns.push(join_projection_source(index, outer_width));
output_columns.push(qid.name.value.to_string());
}
Expression::Aliased(aliased) => {
let alias_name = aliased.alias.value.to_string();
match &*aliased.expression {
Expression::Identifier(id) => {
let col_lower = id.value_lower.as_str();
let index = lookup.get(col_lower).copied()?;
columns.push(join_projection_source(index, outer_width));
output_columns.push(alias_name);
}
Expression::QualifiedIdentifier(qid) => {
let full_name =
format!("{}.{}", qid.qualifier.value_lower, qid.name.value_lower);
let index = lookup
.get(&full_name)
.copied()
.or_else(|| lookup.get(qid.name.value_lower.as_str()).copied())?;
columns.push(join_projection_source(index, outer_width));
output_columns.push(alias_name);
}
_ => return None, }
}
_ => return None,
}
}
Some(JoinProjectionIndices {
columns,
output_columns,
})
}
include!("utils/tests.rs");