1use crate::utils::{extract_column_name, extract_literal_value, flip_operator, infix_to_operator};
44use radixdb_core::Value;
45use radixdb_sql::ast::{self as ast, InfixOperator, PrefixOperator};
46use radixdb_storage::expression::{
47 AndExpr, BetweenExpr, ComparisonExpr, Expression, InListExpr, LikeExpr, NotExpr, NullCheckExpr,
48 OrExpr,
49};
50
51pub fn convert_ast_to_storage_expr(expr: &ast::Expression) -> Option<Box<dyn Expression>> {
56 match expr {
57 ast::Expression::Infix(infix) => convert_infix(infix),
59
60 ast::Expression::Prefix(prefix) => convert_prefix(prefix),
62
63 ast::Expression::In(in_expr) => convert_in(in_expr),
65
66 ast::Expression::Between(between) => convert_between(between),
68
69 ast::Expression::Like(like) => convert_like(like),
71
72 ast::Expression::BooleanLiteral(b) => {
74 Some(Box::new(radixdb_storage::expression::ConstBoolExpr::new(
76 b.value,
77 )))
78 }
79
80 ast::Expression::FunctionCall(_)
82 | ast::Expression::ScalarSubquery(_)
83 | ast::Expression::Exists(_)
84 | ast::Expression::AllAny(_)
85 | ast::Expression::Case(_)
86 | ast::Expression::Cast(_)
87 | ast::Expression::Window(_)
88 | ast::Expression::Parameter(_)
89 | ast::Expression::IntervalLiteral(_)
90 | ast::Expression::BoundValue(_) => None,
91
92 ast::Expression::Identifier(_)
94 | ast::Expression::QualifiedIdentifier(_)
95 | ast::Expression::IntegerLiteral(_)
96 | ast::Expression::FloatLiteral(_)
97 | ast::Expression::StringLiteral(_)
98 | ast::Expression::NullLiteral(_)
99 | ast::Expression::List(_)
100 | ast::Expression::Distinct(_)
101 | ast::Expression::ExpressionList(_)
102 | ast::Expression::Aliased(_) => None,
103
104 ast::Expression::TableSource(_)
106 | ast::Expression::JoinSource(_)
107 | ast::Expression::SubquerySource(_)
108 | ast::Expression::ValuesSource(_)
109 | ast::Expression::CteReference(_)
110 | ast::Expression::FunctionTableSource(_)
111 | ast::Expression::Star(_)
112 | ast::Expression::QualifiedStar(_)
113 | ast::Expression::Default(_)
114 | ast::Expression::InHashSet(_) => None,
115 }
116}
117
118fn convert_infix(infix: &ast::InfixExpression) -> Option<Box<dyn Expression>> {
120 match infix.op_type {
121 InfixOperator::Equal
123 | InfixOperator::NotEqual
124 | InfixOperator::LessThan
125 | InfixOperator::LessEqual
126 | InfixOperator::GreaterThan
127 | InfixOperator::GreaterEqual => convert_comparison(infix),
128
129 InfixOperator::And => {
131 let left = convert_ast_to_storage_expr(&infix.left)?;
132 let right = convert_ast_to_storage_expr(&infix.right)?;
133 Some(Box::new(AndExpr::and(left, right)))
134 }
135
136 InfixOperator::Or => {
137 let left = convert_ast_to_storage_expr(&infix.left)?;
138 let right = convert_ast_to_storage_expr(&infix.right)?;
139 Some(Box::new(OrExpr::or(left, right)))
140 }
141
142 InfixOperator::Is | InfixOperator::IsNot => convert_is_null(infix),
144
145 _ => None,
147 }
148}
149
150fn convert_comparison(infix: &ast::InfixExpression) -> Option<Box<dyn Expression>> {
152 if let Some((column, value)) = try_extract_column_value(&infix.left, &infix.right) {
154 let operator = infix_to_operator(infix.op_type)?;
155 return Some(Box::new(ComparisonExpr::new(column, operator, value)));
156 }
157
158 if let Some((column, value)) = try_extract_column_value(&infix.right, &infix.left) {
160 let operator = flip_operator(infix_to_operator(infix.op_type)?);
161 return Some(Box::new(ComparisonExpr::new(column, operator, value)));
162 }
163
164 None
166}
167
168fn try_extract_column_value(
170 maybe_column: &ast::Expression,
171 maybe_value: &ast::Expression,
172) -> Option<(String, Value)> {
173 let column = extract_column_name(maybe_column)?;
174 let value = extract_literal_value(maybe_value)?;
175 Some((column, value))
176}
177
178fn convert_is_null(infix: &ast::InfixExpression) -> Option<Box<dyn Expression>> {
180 let column = extract_column_name(&infix.left)?;
181
182 let is_not = infix.op_type == InfixOperator::IsNot;
184
185 match &*infix.right {
187 ast::Expression::NullLiteral(_) => {
188 if is_not {
189 Some(Box::new(NullCheckExpr::is_not_null(column)))
190 } else {
191 Some(Box::new(NullCheckExpr::is_null(column)))
192 }
193 }
194 _ => None,
195 }
196}
197
198fn convert_prefix(prefix: &ast::PrefixExpression) -> Option<Box<dyn Expression>> {
200 match prefix.op_type {
201 PrefixOperator::Not => {
202 let inner = convert_ast_to_storage_expr(&prefix.right)?;
203 Some(Box::new(NotExpr::new(inner)))
204 }
205 _ => None,
207 }
208}
209
210fn convert_in(in_expr: &ast::InExpression) -> Option<Box<dyn Expression>> {
212 let column = extract_column_name(&in_expr.left)?;
213
214 let values: Option<Vec<Value>> = match &*in_expr.right {
216 ast::Expression::ExpressionList(list) => {
217 list.expressions.iter().map(extract_literal_value).collect()
218 }
219 ast::Expression::List(list) => list.elements.iter().map(extract_literal_value).collect(),
220 _ => return None, };
222
223 let values = values?;
224 if values.is_empty() {
225 return None;
226 }
227
228 let expr = if in_expr.not {
229 InListExpr::not_in(column, values)
230 } else {
231 InListExpr::new(column, values)
232 };
233 Some(Box::new(expr))
234}
235
236fn convert_between(between: &ast::BetweenExpression) -> Option<Box<dyn Expression>> {
238 let column = extract_column_name(&between.expr)?;
239 let low = extract_literal_value(&between.lower)?;
240 let high = extract_literal_value(&between.upper)?;
241
242 let expr = if between.not {
243 BetweenExpr::not_between(column, low, high)
244 } else {
245 BetweenExpr::new(column, low, high)
246 };
247 Some(Box::new(expr))
248}
249
250fn convert_like(like: &ast::LikeExpression) -> Option<Box<dyn Expression>> {
252 let column = extract_column_name(&like.left)?;
253
254 let pattern = match &*like.pattern {
256 ast::Expression::StringLiteral(s) => s.value.clone(),
257 _ => return None, };
259
260 let op_upper = like.operator.to_uppercase();
262 let is_not = op_upper.contains("NOT");
263 let is_ilike = op_upper.contains("ILIKE");
264
265 let expr = match (is_not, is_ilike) {
269 (true, true) => LikeExpr::not_ilike(column, pattern),
270 (true, false) => LikeExpr::not_like(column, pattern),
271 (false, true) => LikeExpr::new_ilike(column, pattern),
272 (false, false) => LikeExpr::new(column, pattern),
273 };
274 Some(Box::new(expr))
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::utils::dummy_token;
281 use radixdb_core::{DataType, Row, Schema, SchemaBuilder};
282 use radixdb_sql::token::TokenType;
283
284 fn test_schema() -> Schema {
285 SchemaBuilder::new("test")
286 .add_primary_key("id", DataType::Integer)
287 .add("name", DataType::Text)
288 .add("age", DataType::Integer)
289 .add_nullable("email", DataType::Text)
290 .build()
291 }
292
293 fn test_row() -> Row {
294 Row::from_values(vec![
295 Value::integer(1),
296 Value::text("Alice"),
297 Value::integer(30),
298 Value::text("alice@example.com"),
299 ])
300 }
301
302 fn make_ident(name: &str) -> ast::Expression {
304 ast::Expression::Identifier(ast::Identifier::new(
305 dummy_token(name, TokenType::Identifier),
306 name.to_string(),
307 ))
308 }
309
310 fn make_int(value: i64) -> ast::Expression {
312 ast::Expression::IntegerLiteral(ast::IntegerLiteral {
313 token: dummy_token(&value.to_string(), TokenType::Integer),
314 value,
315 })
316 }
317
318 fn make_float(value: f64) -> ast::Expression {
319 ast::Expression::FloatLiteral(ast::FloatLiteral {
320 token: dummy_token(&value.to_string(), TokenType::Float),
321 value,
322 })
323 }
324
325 fn make_str(value: &str) -> ast::Expression {
327 ast::Expression::StringLiteral(ast::StringLiteral {
328 token: dummy_token(value, TokenType::String),
329 value: value.into(),
330 type_hint: None,
331 })
332 }
333
334 fn make_infix(left: ast::Expression, op: &str, right: ast::Expression) -> ast::InfixExpression {
336 ast::InfixExpression::new(
337 dummy_token(op, TokenType::Operator),
338 Box::new(left),
339 op,
340 Box::new(right),
341 )
342 }
343
344 #[test]
345 fn test_convert_simple_equality() {
346 let ast_expr = ast::Expression::Infix(make_infix(make_ident("id"), "=", make_int(1)));
347
348 let storage_expr = convert_ast_to_storage_expr(&ast_expr);
349 assert!(storage_expr.is_some());
350
351 let mut expr = storage_expr.unwrap();
352 let schema = test_schema();
353 expr.prepare_for_schema(&schema);
354
355 let row = test_row();
356 assert!(expr.evaluate(&row).unwrap());
357 }
358
359 #[test]
360 fn test_convert_and_expression() {
361 let left_cond = ast::Expression::Infix(make_infix(make_ident("id"), "=", make_int(1)));
363 let right_cond = ast::Expression::Infix(make_infix(make_ident("age"), ">", make_int(20)));
364 let ast_expr = ast::Expression::Infix(make_infix(left_cond, "AND", right_cond));
365
366 let storage_expr = convert_ast_to_storage_expr(&ast_expr);
367 assert!(storage_expr.is_some());
368
369 let mut expr = storage_expr.unwrap();
370 let schema = test_schema();
371 expr.prepare_for_schema(&schema);
372
373 let row = test_row(); assert!(expr.evaluate(&row).unwrap());
375 }
376
377 #[test]
378 fn test_convert_value_on_left() {
379 let ast_expr = ast::Expression::Infix(make_infix(make_int(0), "<", make_ident("id")));
381
382 let storage_expr = convert_ast_to_storage_expr(&ast_expr);
383 assert!(storage_expr.is_some());
384
385 let mut expr = storage_expr.unwrap();
386 let schema = test_schema();
387 expr.prepare_for_schema(&schema);
388
389 let row = test_row(); assert!(expr.evaluate(&row).unwrap()); }
392
393 #[test]
394 fn test_converted_mixed_numeric_filter_keeps_compiled_dynamic_parity() {
395 const EXACT: i64 = 1_i64 << 53;
396 let schema = SchemaBuilder::new("numeric_boundary")
397 .add("value", DataType::Integer)
398 .build();
399 let row = Row::from_values(vec![Value::Integer(EXACT + 1)]);
400 let ast_expr = ast::Expression::Infix(make_infix(
401 make_ident("value"),
402 "=",
403 make_float(EXACT as f64),
404 ));
405
406 let mut expression = convert_ast_to_storage_expr(&ast_expr).unwrap();
407 expression.prepare_for_schema(&schema);
408 assert!(!expression.evaluate(&row).unwrap());
409 assert!(!expression.evaluate_fast(&row));
410
411 let compiled =
412 radixdb_storage::expression::CompiledFilter::compile(expression.as_ref(), &schema);
413 assert!(compiled.is_fully_compiled());
414 assert!(!compiled.matches(&row));
415 assert!(!compiled.matches_arc_slice(row.as_slice()));
416 }
417
418 #[test]
419 fn test_cannot_convert_function_call() {
420 let ast_expr = ast::Expression::FunctionCall(Box::new(ast::FunctionCall {
422 token: dummy_token("UPPER", TokenType::Identifier),
423 function: "UPPER".into(),
424 arguments: vec![make_ident("name")],
425 is_distinct: false,
426 order_by: vec![],
427 filter: None,
428 }));
429
430 let storage_expr = convert_ast_to_storage_expr(&ast_expr);
431 assert!(storage_expr.is_none());
432 }
433
434 #[test]
435 fn test_convert_string_comparison() {
436 let ast_expr =
438 ast::Expression::Infix(make_infix(make_ident("name"), "=", make_str("Alice")));
439
440 let storage_expr = convert_ast_to_storage_expr(&ast_expr);
441 assert!(storage_expr.is_some());
442
443 let mut expr = storage_expr.unwrap();
444 let schema = test_schema();
445 expr.prepare_for_schema(&schema);
446
447 let row = test_row();
448 assert!(expr.evaluate(&row).unwrap());
449 }
450}