1use crate::storage::engine::vector_metadata::MetadataFilter;
2use crate::storage::query::ast::{
3 BinOp, CompareOp, DeleteQuery, Expr, FieldRef, Filter, GraphQuery, InsertQuery, JoinQuery,
4 PathQuery, Projection, SelectItem, Span, TableQuery, UnaryOp, UpdateQuery, VectorQuery,
5};
6use crate::storage::schema::Value;
7
8pub const PARAMETER_PROJECTION_PREFIX: &str = "__user_param_projection__:";
9
10pub fn expr_to_projection(expr: &Expr) -> Option<Projection> {
11 match expr {
12 Expr::Literal { value, .. } => projection_from_literal(value),
13 Expr::Column { field, .. } => {
14 if matches!(
15 field,
16 FieldRef::TableColumn { table, column } if table.is_empty() && column == "*"
17 ) {
18 Some(Projection::All)
19 } else {
20 Some(Projection::Field(field.clone(), None))
21 }
22 }
23 Expr::Parameter { index, .. } => Some(Projection::Column(format!(
24 "{PARAMETER_PROJECTION_PREFIX}{index}"
25 ))),
26 Expr::BinaryOp { op, lhs, rhs, .. } => match op {
27 BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::Concat => {
28 Some(Projection::Function(
29 projection_binop_name(*op).to_string(),
30 vec![expr_to_projection(lhs)?, expr_to_projection(rhs)?],
31 ))
32 }
33 _ => Some(boolean_expr_projection(expr.clone())),
34 },
35 Expr::UnaryOp { op, operand, .. } => match op {
36 UnaryOp::Neg => Some(Projection::Function(
37 "SUB".to_string(),
38 vec![
39 Projection::Column("LIT:0".to_string()),
40 expr_to_projection(operand)?,
41 ],
42 )),
43 UnaryOp::Not => Some(boolean_expr_projection(expr.clone())),
44 },
45 Expr::Cast { inner, target, .. } => Some(Projection::Function(
46 "CAST".to_string(),
47 vec![
48 expr_to_projection(inner)?,
49 Projection::Column(format!("TYPE:{target}")),
50 ],
51 )),
52 Expr::FunctionCall { name, args, .. } => Some(Projection::Function(
53 name.to_uppercase(),
54 args.iter()
55 .map(expr_to_projection)
56 .collect::<Option<Vec<_>>>()?,
57 )),
58 Expr::Case {
59 branches, else_, ..
60 } => {
61 let mut args = Vec::with_capacity(branches.len() * 2 + usize::from(else_.is_some()));
62 for (cond, value) in branches {
63 args.push(case_condition_projection(cond.clone()));
64 args.push(expr_to_projection(value)?);
65 }
66 if let Some(else_expr) = else_ {
67 args.push(expr_to_projection(else_expr)?);
68 }
69 Some(Projection::Function("CASE".to_string(), args))
70 }
71 Expr::IsNull { .. }
72 | Expr::InList { .. }
73 | Expr::Between { .. }
74 | Expr::Subquery { .. } => Some(boolean_expr_projection(expr.clone())),
75 }
76}
77
78pub fn select_item_to_projection(item: &SelectItem) -> Option<Projection> {
79 match item {
80 SelectItem::Wildcard => Some(Projection::All),
81 SelectItem::Expr { expr, alias } => {
82 let projection = expr_to_projection(expr)?;
83 let output_name = alias.clone().or_else(|| Some(render_expr_label(expr)));
84 Some(attach_projection_alias(projection, output_name))
85 }
86 }
87}
88
89pub fn effective_table_projections(query: &TableQuery) -> Vec<Projection> {
90 if !query.select_items.is_empty() {
91 return query
92 .select_items
93 .iter()
94 .filter_map(select_item_to_projection)
95 .collect();
96 }
97 if query.columns.is_empty() {
98 vec![Projection::All]
99 } else {
100 query.columns.clone()
101 }
102}
103
104pub fn effective_table_filter(query: &TableQuery) -> Option<Filter> {
105 query
106 .filter
107 .clone()
108 .or_else(|| query.where_expr.as_ref().map(expr_to_filter))
109 .map(|f| f.optimize()) }
111
112pub fn effective_table_group_by_exprs(query: &TableQuery) -> Vec<Expr> {
113 if !query.group_by_exprs.is_empty() {
114 query.group_by_exprs.clone()
115 } else {
116 query
117 .group_by
118 .iter()
119 .map(|column| Expr::Column {
120 field: FieldRef::TableColumn {
121 table: String::new(),
122 column: column.clone(),
123 },
124 span: Span::synthetic(),
125 })
126 .collect()
127 }
128}
129
130pub fn effective_table_having_filter(query: &TableQuery) -> Option<Filter> {
131 query
132 .having
133 .clone()
134 .or_else(|| query.having_expr.as_ref().map(expr_to_filter))
135}
136
137pub fn effective_update_filter(query: &UpdateQuery) -> Option<Filter> {
138 query
139 .filter
140 .clone()
141 .or_else(|| query.where_expr.as_ref().map(expr_to_filter))
142}
143
144pub fn effective_insert_rows(query: &InsertQuery) -> Result<Vec<Vec<Value>>, String> {
145 if !query.value_exprs.is_empty() {
146 return query
147 .value_exprs
148 .iter()
149 .cloned()
150 .map(|row| row.into_iter().map(fold_expr_to_value).collect())
151 .collect();
152 }
153 Ok(query.values.clone())
154}
155
156pub fn effective_delete_filter(query: &DeleteQuery) -> Option<Filter> {
157 query
158 .filter
159 .clone()
160 .or_else(|| query.where_expr.as_ref().map(expr_to_filter))
161}
162
163pub fn effective_join_filter(query: &JoinQuery) -> Option<Filter> {
164 query.filter.clone()
165}
166
167pub fn effective_graph_filter(query: &GraphQuery) -> Option<Filter> {
168 query.filter.clone()
169}
170
171pub fn effective_graph_projections(query: &GraphQuery) -> Vec<Projection> {
172 query.return_.clone()
173}
174
175pub fn effective_path_filter(query: &PathQuery) -> Option<Filter> {
176 query.filter.clone()
177}
178
179pub fn effective_path_projections(query: &PathQuery) -> Vec<Projection> {
180 query.return_.clone()
181}
182
183pub fn effective_vector_filter(query: &VectorQuery) -> Option<MetadataFilter> {
184 query.filter.clone()
185}
186
187pub fn projection_to_expr(projection: &Projection) -> Option<(Expr, Option<String>)> {
188 match projection {
189 Projection::All => Some((
190 Expr::Column {
191 field: FieldRef::TableColumn {
192 table: String::new(),
193 column: "*".to_string(),
194 },
195 span: Span::synthetic(),
196 },
197 None,
198 )),
199 Projection::Column(column) => Some((projection_column_to_expr(column), None)),
200 Projection::Alias(column, alias) => {
201 Some((projection_column_to_expr(column), Some(alias.clone())))
202 }
203 Projection::Function(name, args) => {
204 let (name, alias) = split_projection_function_alias(name);
205 let args = args
206 .iter()
207 .map(projection_to_expr)
208 .collect::<Option<Vec<_>>>()?
209 .into_iter()
210 .map(|(expr, _)| expr)
211 .collect();
212 Some((
213 Expr::FunctionCall {
214 name,
215 args,
216 span: Span::synthetic(),
217 },
218 alias,
219 ))
220 }
221 Projection::Expression(filter, alias) => Some((filter_to_expr(filter), alias.clone())),
222 Projection::Field(field, alias) => Some((
223 Expr::Column {
224 field: field.clone(),
225 span: Span::synthetic(),
226 },
227 alias.clone(),
228 )),
229 }
230}
231
232fn projection_column_to_expr(column: &str) -> Expr {
233 if let Some(value) = projection_literal_value(column) {
234 return Expr::Literal {
235 value,
236 span: Span::synthetic(),
237 };
238 }
239
240 Expr::Column {
241 field: FieldRef::TableColumn {
242 table: String::new(),
243 column: column.to_string(),
244 },
245 span: Span::synthetic(),
246 }
247}
248
249fn projection_literal_value(column: &str) -> Option<Value> {
250 let literal = column.strip_prefix("LIT:")?;
251 if literal.is_empty() {
252 return Some(Value::Null);
253 }
254 if let Ok(value) = literal.parse::<i64>() {
255 return Some(Value::Integer(value));
256 }
257 if let Ok(value) = literal.parse::<f64>() {
258 return Some(Value::Float(value));
259 }
260 Some(Value::text(literal.to_string()))
261}
262
263pub fn projection_to_select_item(projection: &Projection) -> Option<SelectItem> {
264 match projection {
265 Projection::All => Some(SelectItem::Wildcard),
266 other => {
267 let (expr, alias) = projection_to_expr(other)?;
268 Some(SelectItem::Expr { expr, alias })
269 }
270 }
271}
272
273pub fn effective_join_projections(query: &JoinQuery) -> Vec<Projection> {
274 if !query.return_items.is_empty() {
275 return query
276 .return_items
277 .iter()
278 .filter_map(select_item_to_projection)
279 .collect();
280 }
281 query.return_.clone()
282}
283
284pub fn expr_to_filter(expr: &Expr) -> Filter {
285 match expr {
286 Expr::BinaryOp { op, lhs, rhs, .. } => match op {
287 BinOp::And => Filter::And(Box::new(expr_to_filter(lhs)), Box::new(expr_to_filter(rhs))),
288 BinOp::Or => Filter::Or(Box::new(expr_to_filter(lhs)), Box::new(expr_to_filter(rhs))),
289 BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => {
290 try_specialized_compare_filter(lhs, *op, rhs).unwrap_or_else(|| {
291 Filter::CompareExpr {
292 lhs: lhs.as_ref().clone(),
293 op: binop_to_compare_op(*op),
294 rhs: rhs.as_ref().clone(),
295 }
296 })
297 }
298 BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::Concat => {
299 Filter::CompareExpr {
300 lhs: expr.clone(),
301 op: CompareOp::Eq,
302 rhs: Expr::lit(Value::Boolean(true)),
303 }
304 }
305 },
306 Expr::UnaryOp {
307 op: UnaryOp::Not,
308 operand,
309 ..
310 } => Filter::Not(Box::new(expr_to_filter(operand))),
311 Expr::IsNull {
312 operand, negated, ..
313 } => match operand.as_ref() {
314 Expr::Column { field, .. } => {
315 if *negated {
316 Filter::IsNotNull(field.clone())
317 } else {
318 Filter::IsNull(field.clone())
319 }
320 }
321 _ => Filter::CompareExpr {
322 lhs: expr.clone(),
323 op: CompareOp::Eq,
324 rhs: Expr::lit(Value::Boolean(true)),
325 },
326 },
327 Expr::InList {
328 target,
329 values,
330 negated,
331 ..
332 } => match (target.as_ref(), all_literal_values(values)) {
333 (Expr::Column { field, .. }, Some(values)) if !negated => Filter::In {
334 field: field.clone(),
335 values,
336 },
337 _ => Filter::CompareExpr {
338 lhs: expr.clone(),
339 op: CompareOp::Eq,
340 rhs: Expr::lit(Value::Boolean(true)),
341 },
342 },
343 Expr::Between {
344 target,
345 low,
346 high,
347 negated,
348 ..
349 } => match (
350 target.as_ref(),
351 literal_expr_value(low),
352 literal_expr_value(high),
353 ) {
354 (Expr::Column { field, .. }, Some(low), Some(high)) if !negated => Filter::Between {
355 field: field.clone(),
356 low,
357 high,
358 },
359 _ => Filter::CompareExpr {
360 lhs: expr.clone(),
361 op: CompareOp::Eq,
362 rhs: Expr::lit(Value::Boolean(true)),
363 },
364 },
365 Expr::Subquery { .. } => Filter::CompareExpr {
366 lhs: expr.clone(),
367 op: CompareOp::Eq,
368 rhs: Expr::lit(Value::Boolean(true)),
369 },
370 _ => Filter::CompareExpr {
371 lhs: expr.clone(),
372 op: CompareOp::Eq,
373 rhs: Expr::lit(Value::Boolean(true)),
374 },
375 }
376}
377
378pub fn boolean_expr_projection(expr: Expr) -> Projection {
379 Projection::Expression(
380 Box::new(Filter::CompareExpr {
381 lhs: expr,
382 op: CompareOp::Eq,
383 rhs: Expr::Literal {
384 value: Value::Boolean(true),
385 span: Span::synthetic(),
386 },
387 }),
388 None,
389 )
390}
391
392pub fn filter_to_expr(filter: &Filter) -> Expr {
393 match filter {
394 Filter::Compare { field, op, value } => Expr::BinaryOp {
395 op: compare_op_to_binop(*op),
396 lhs: Box::new(Expr::Column {
397 field: field.clone(),
398 span: Span::synthetic(),
399 }),
400 rhs: Box::new(Expr::Literal {
401 value: value.clone(),
402 span: Span::synthetic(),
403 }),
404 span: Span::synthetic(),
405 },
406 Filter::CompareFields { left, op, right } => Expr::BinaryOp {
407 op: compare_op_to_binop(*op),
408 lhs: Box::new(Expr::Column {
409 field: left.clone(),
410 span: Span::synthetic(),
411 }),
412 rhs: Box::new(Expr::Column {
413 field: right.clone(),
414 span: Span::synthetic(),
415 }),
416 span: Span::synthetic(),
417 },
418 Filter::CompareExpr { lhs, op, rhs } => Expr::BinaryOp {
419 op: compare_op_to_binop(*op),
420 lhs: Box::new(lhs.clone()),
421 rhs: Box::new(rhs.clone()),
422 span: Span::synthetic(),
423 },
424 Filter::And(left, right) => Expr::BinaryOp {
425 op: BinOp::And,
426 lhs: Box::new(filter_to_expr(left)),
427 rhs: Box::new(filter_to_expr(right)),
428 span: Span::synthetic(),
429 },
430 Filter::Or(left, right) => Expr::BinaryOp {
431 op: BinOp::Or,
432 lhs: Box::new(filter_to_expr(left)),
433 rhs: Box::new(filter_to_expr(right)),
434 span: Span::synthetic(),
435 },
436 Filter::Not(inner) => Expr::UnaryOp {
437 op: UnaryOp::Not,
438 operand: Box::new(filter_to_expr(inner)),
439 span: Span::synthetic(),
440 },
441 Filter::IsNull(field) => Expr::IsNull {
442 operand: Box::new(Expr::Column {
443 field: field.clone(),
444 span: Span::synthetic(),
445 }),
446 negated: false,
447 span: Span::synthetic(),
448 },
449 Filter::IsNotNull(field) => Expr::IsNull {
450 operand: Box::new(Expr::Column {
451 field: field.clone(),
452 span: Span::synthetic(),
453 }),
454 negated: true,
455 span: Span::synthetic(),
456 },
457 Filter::In { field, values } => Expr::InList {
458 target: Box::new(Expr::Column {
459 field: field.clone(),
460 span: Span::synthetic(),
461 }),
462 values: values
463 .iter()
464 .cloned()
465 .map(|value| Expr::Literal {
466 value,
467 span: Span::synthetic(),
468 })
469 .collect(),
470 negated: false,
471 span: Span::synthetic(),
472 },
473 Filter::Between { field, low, high } => Expr::Between {
474 target: Box::new(Expr::Column {
475 field: field.clone(),
476 span: Span::synthetic(),
477 }),
478 low: Box::new(Expr::Literal {
479 value: low.clone(),
480 span: Span::synthetic(),
481 }),
482 high: Box::new(Expr::Literal {
483 value: high.clone(),
484 span: Span::synthetic(),
485 }),
486 negated: false,
487 span: Span::synthetic(),
488 },
489 Filter::Like { field, pattern } => Expr::FunctionCall {
490 name: "LIKE".to_string(),
491 args: vec![
492 Expr::Column {
493 field: field.clone(),
494 span: Span::synthetic(),
495 },
496 Expr::Literal {
497 value: Value::text(pattern.clone()),
498 span: Span::synthetic(),
499 },
500 ],
501 span: Span::synthetic(),
502 },
503 Filter::StartsWith { field, prefix } => Expr::FunctionCall {
504 name: "STARTS_WITH".to_string(),
505 args: vec![
506 Expr::Column {
507 field: field.clone(),
508 span: Span::synthetic(),
509 },
510 Expr::Literal {
511 value: Value::text(prefix.clone()),
512 span: Span::synthetic(),
513 },
514 ],
515 span: Span::synthetic(),
516 },
517 Filter::EndsWith { field, suffix } => Expr::FunctionCall {
518 name: "ENDS_WITH".to_string(),
519 args: vec![
520 Expr::Column {
521 field: field.clone(),
522 span: Span::synthetic(),
523 },
524 Expr::Literal {
525 value: Value::text(suffix.clone()),
526 span: Span::synthetic(),
527 },
528 ],
529 span: Span::synthetic(),
530 },
531 Filter::Contains { field, substring } => Expr::FunctionCall {
532 name: "CONTAINS".to_string(),
533 args: vec![
534 Expr::Column {
535 field: field.clone(),
536 span: Span::synthetic(),
537 },
538 Expr::Literal {
539 value: Value::text(substring.clone()),
540 span: Span::synthetic(),
541 },
542 ],
543 span: Span::synthetic(),
544 },
545 }
546}
547
548pub fn projection_from_literal(value: &Value) -> Option<Projection> {
549 match value {
550 Value::Boolean(_) => Some(boolean_expr_projection(Expr::Literal {
551 value: value.clone(),
552 span: Span::synthetic(),
553 })),
554 _ => Some(Projection::Column(format!(
555 "LIT:{}",
556 render_projection_literal(value)
557 ))),
558 }
559}
560
561pub fn case_condition_projection(condition: Expr) -> Projection {
562 Projection::Expression(
563 Box::new(Filter::CompareExpr {
564 lhs: condition,
565 op: CompareOp::Eq,
566 rhs: Expr::Literal {
567 value: Value::Boolean(true),
568 span: Span::synthetic(),
569 },
570 }),
571 None,
572 )
573}
574
575pub fn fold_expr_to_value(expr: Expr) -> Result<Value, String> {
576 match expr {
577 Expr::Literal { value, .. } => Ok(value),
578 Expr::FunctionCall { name, args, .. } => {
579 if (name.eq_ignore_ascii_case("PASSWORD") || name.eq_ignore_ascii_case("SECRET"))
580 && args.len() == 1
581 {
582 let plaintext = match fold_expr_to_value(args.into_iter().next().unwrap())? {
583 Value::Text(text) => text,
584 other => {
585 return Err(format!(
586 "{name}() expects a string literal argument, got {other:?}"
587 ))
588 }
589 };
590 return Ok(if name.eq_ignore_ascii_case("PASSWORD") {
591 Value::Password(format!("@@plain@@{plaintext}"))
592 } else {
593 Value::Secret(format!("@@plain@@{plaintext}").into_bytes())
594 });
595 }
596 Err(format!(
597 "expression is not a foldable literal: FunctionCall({name})"
598 ))
599 }
600 Expr::UnaryOp { op, operand, .. } => {
601 let inner = fold_expr_to_value(*operand)?;
602 match (op, inner) {
603 (UnaryOp::Neg, Value::Integer(n)) => Ok(Value::Integer(-n)),
604 (UnaryOp::Neg, Value::UnsignedInteger(n)) => Ok(Value::Integer(-(n as i64))),
605 (UnaryOp::Neg, Value::Float(f)) => Ok(Value::Float(-f)),
606 (UnaryOp::Not, Value::Boolean(b)) => Ok(Value::Boolean(!b)),
607 (other_op, other) => Err(format!(
608 "unary `{other_op:?}` cannot fold to literal Value (operand: {other:?})"
609 )),
610 }
611 }
612 Expr::Cast { inner, .. } => fold_expr_to_value(*inner),
613 other => Err(format!("expression is not a foldable literal: {other:?}")),
614 }
615}
616
617fn projection_binop_name(op: BinOp) -> &'static str {
618 match op {
619 BinOp::Add => "ADD",
620 BinOp::Sub => "SUB",
621 BinOp::Mul => "MUL",
622 BinOp::Div => "DIV",
623 BinOp::Mod => "MOD",
624 BinOp::Concat => "CONCAT",
625 BinOp::Eq
626 | BinOp::Ne
627 | BinOp::Lt
628 | BinOp::Le
629 | BinOp::Gt
630 | BinOp::Ge
631 | BinOp::And
632 | BinOp::Or => {
633 unreachable!("boolean operators are lowered through Projection::Expression")
634 }
635 }
636}
637
638fn render_expr_label(expr: &Expr) -> String {
639 render_expr_label_prec(expr, 0)
640}
641
642fn render_expr_label_prec(expr: &Expr, parent_prec: u8) -> String {
643 match expr {
644 Expr::Literal { value, .. } => render_sql_literal_label(value),
645 Expr::Column { field, .. } => render_field_label(field),
646 Expr::Parameter { index, .. } => format!("${index}"),
647 Expr::BinaryOp { op, lhs, rhs, .. } => {
648 let prec = op.precedence();
649 let rendered = format!(
650 "{} {} {}",
651 render_expr_label_prec(lhs, prec),
652 render_binop_label(*op),
653 render_expr_label_prec(rhs, prec + 1)
654 );
655 if prec < parent_prec {
656 format!("({rendered})")
657 } else {
658 rendered
659 }
660 }
661 Expr::UnaryOp { op, operand, .. } => match op {
662 UnaryOp::Neg => format!("-{}", render_expr_label_prec(operand, u8::MAX)),
663 UnaryOp::Not => format!("NOT {}", render_expr_label_prec(operand, u8::MAX)),
664 },
665 Expr::Cast { inner, target, .. } => {
666 format!("CAST({} AS {target})", render_expr_label(inner))
667 }
668 Expr::FunctionCall { name, args, .. } => {
669 let args = args
670 .iter()
671 .map(render_expr_label)
672 .collect::<Vec<_>>()
673 .join(", ");
674 format!("{name}({args})")
675 }
676 Expr::Case {
677 branches, else_, ..
678 } => {
679 let mut out = String::from("CASE");
680 for (condition, value) in branches {
681 out.push_str(" WHEN ");
682 out.push_str(&render_expr_label(condition));
683 out.push_str(" THEN ");
684 out.push_str(&render_expr_label(value));
685 }
686 if let Some(else_expr) = else_ {
687 out.push_str(" ELSE ");
688 out.push_str(&render_expr_label(else_expr));
689 }
690 out.push_str(" END");
691 out
692 }
693 Expr::IsNull {
694 operand, negated, ..
695 } => {
696 let op = if *negated { "IS NOT NULL" } else { "IS NULL" };
697 format!("{} {op}", render_expr_label_prec(operand, u8::MAX))
698 }
699 Expr::InList {
700 target,
701 values,
702 negated,
703 ..
704 } => {
705 let op = if *negated { "NOT IN" } else { "IN" };
706 let values = values
707 .iter()
708 .map(render_expr_label)
709 .collect::<Vec<_>>()
710 .join(", ");
711 format!("{} {op} ({values})", render_expr_label(target))
712 }
713 Expr::Between {
714 target,
715 low,
716 high,
717 negated,
718 ..
719 } => {
720 let op = if *negated { "NOT BETWEEN" } else { "BETWEEN" };
721 format!(
722 "{} {op} {} AND {}",
723 render_expr_label(target),
724 render_expr_label(low),
725 render_expr_label(high)
726 )
727 }
728 Expr::Subquery { .. } => "subquery".to_string(),
729 }
730}
731
732fn render_binop_label(op: BinOp) -> &'static str {
733 match op {
734 BinOp::Add => "+",
735 BinOp::Sub => "-",
736 BinOp::Mul => "*",
737 BinOp::Div => "/",
738 BinOp::Mod => "%",
739 BinOp::Concat => "||",
740 BinOp::Eq => "=",
741 BinOp::Ne => "!=",
742 BinOp::Lt => "<",
743 BinOp::Le => "<=",
744 BinOp::Gt => ">",
745 BinOp::Ge => ">=",
746 BinOp::And => "AND",
747 BinOp::Or => "OR",
748 }
749}
750
751fn render_field_label(field: &FieldRef) -> String {
752 match field {
753 FieldRef::TableColumn { table, column } => {
754 if table.is_empty() {
755 column.clone()
756 } else {
757 format!("{table}.{column}")
758 }
759 }
760 FieldRef::NodeProperty { alias, property } => format!("{alias}.{property}"),
761 FieldRef::EdgeProperty { alias, property } => format!("{alias}.{property}"),
762 FieldRef::NodeId { alias } => format!("{alias}.id"),
763 }
764}
765
766fn render_sql_literal_label(value: &Value) -> String {
767 match value {
768 Value::Null => "NULL".to_string(),
769 Value::Text(value) => format!("'{}'", value.replace('\'', "''")),
770 Value::Boolean(value) => value.to_string(),
771 Value::Integer(value) => value.to_string(),
772 Value::UnsignedInteger(value) => value.to_string(),
773 Value::Float(value) => {
774 if value.fract().abs() < f64::EPSILON {
775 (*value as i64).to_string()
776 } else {
777 value.to_string()
778 }
779 }
780 other => other.to_string(),
781 }
782}
783
784fn binop_to_compare_op(op: BinOp) -> CompareOp {
785 match op {
786 BinOp::Eq => CompareOp::Eq,
787 BinOp::Ne => CompareOp::Ne,
788 BinOp::Lt => CompareOp::Lt,
789 BinOp::Le => CompareOp::Le,
790 BinOp::Gt => CompareOp::Gt,
791 BinOp::Ge => CompareOp::Ge,
792 other => unreachable!("non-compare binop cannot lower to CompareOp: {other:?}"),
793 }
794}
795
796fn compare_op_to_binop(op: CompareOp) -> BinOp {
797 match op {
798 CompareOp::Eq => BinOp::Eq,
799 CompareOp::Ne => BinOp::Ne,
800 CompareOp::Lt => BinOp::Lt,
801 CompareOp::Le => BinOp::Le,
802 CompareOp::Gt => BinOp::Gt,
803 CompareOp::Ge => BinOp::Ge,
804 }
805}
806
807fn attach_projection_alias(proj: Projection, alias: Option<String>) -> Projection {
808 let Some(alias) = alias else { return proj };
809 match proj {
810 Projection::Field(f, _) => Projection::Field(f, Some(alias)),
811 Projection::Expression(filter, _) => Projection::Expression(filter, Some(alias)),
812 Projection::Function(name, args) => {
813 if name.contains(':') {
814 Projection::Function(name, args)
815 } else {
816 Projection::Function(format!("{name}:{alias}"), args)
817 }
818 }
819 Projection::Column(c) => Projection::Alias(c, alias),
820 other => other,
821 }
822}
823
824fn split_projection_function_alias(name: &str) -> (String, Option<String>) {
825 match name.split_once(':') {
826 Some((function, alias)) if !function.is_empty() && !alias.is_empty() => {
827 (function.to_string(), Some(alias.to_string()))
828 }
829 _ => (name.to_string(), None),
830 }
831}
832
833fn render_projection_literal(value: &Value) -> String {
834 match value {
835 Value::Null => String::new(),
836 Value::Integer(v) => v.to_string(),
837 Value::UnsignedInteger(v) => v.to_string(),
838 Value::Float(v) => {
839 if v.fract().abs() < f64::EPSILON {
840 (*v as i64).to_string()
841 } else {
842 v.to_string()
843 }
844 }
845 Value::Text(v) => v.to_string(),
846 Value::Boolean(true) => "true".to_string(),
847 Value::Boolean(false) => "false".to_string(),
848 Value::Array(_) | Value::Vector(_) | Value::Json(_) | Value::Blob(_) => {
853 format!("@RL:{}", serialize_value_json(value))
854 }
855 other => other.to_string(),
856 }
857}
858
859fn serialize_value_json(value: &Value) -> String {
860 match value {
862 Value::Array(items) => {
863 let mut out = String::from("[");
864 for (i, item) in items.iter().enumerate() {
865 if i > 0 {
866 out.push(',');
867 }
868 out.push_str(&serialize_value_json(item));
869 }
870 out.push(']');
871 out
872 }
873 Value::Vector(items) => {
874 let mut out = String::from("V[");
875 for (i, f) in items.iter().enumerate() {
876 if i > 0 {
877 out.push(',');
878 }
879 out.push_str(&f.to_string());
880 }
881 out.push(']');
882 out
883 }
884 Value::Integer(n) | Value::BigInt(n) => n.to_string(),
885 Value::UnsignedInteger(n) => n.to_string(),
886 Value::Float(f) => f.to_string(),
887 Value::Text(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
888 Value::Boolean(b) => b.to_string(),
889 Value::Null => "null".to_string(),
890 other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
891 }
892}
893
894fn try_specialized_compare_filter(lhs: &Expr, op: BinOp, rhs: &Expr) -> Option<Filter> {
895 let op = binop_to_compare_op(op);
896 match (lhs, rhs) {
897 (Expr::Column { field, .. }, Expr::Literal { value, .. }) => Some(Filter::Compare {
898 field: field.clone(),
899 op,
900 value: value.clone(),
901 }),
902 (Expr::Literal { value, .. }, Expr::Column { field, .. }) => Some(Filter::Compare {
903 field: field.clone(),
904 op: flipped_compare_op(op),
905 value: value.clone(),
906 }),
907 (Expr::Column { field: left, .. }, Expr::Column { field: right, .. }) => {
908 Some(Filter::CompareFields {
909 left: left.clone(),
910 op,
911 right: right.clone(),
912 })
913 }
914 _ => None,
915 }
916}
917
918fn flipped_compare_op(op: CompareOp) -> CompareOp {
919 match op {
920 CompareOp::Eq => CompareOp::Eq,
921 CompareOp::Ne => CompareOp::Ne,
922 CompareOp::Lt => CompareOp::Gt,
923 CompareOp::Le => CompareOp::Ge,
924 CompareOp::Gt => CompareOp::Lt,
925 CompareOp::Ge => CompareOp::Le,
926 }
927}
928
929fn literal_expr_value(expr: &Expr) -> Option<Value> {
930 match expr {
931 Expr::Literal { value, .. } => Some(value.clone()),
932 _ => None,
933 }
934}
935
936fn all_literal_values(values: &[Expr]) -> Option<Vec<Value>> {
937 values.iter().map(literal_expr_value).collect()
938}