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 Some(match alias {
84 Some(alias) => attach_projection_alias(projection, Some(alias.clone())),
85 None => projection,
86 })
87 }
88 }
89}
90
91pub fn effective_table_projections(query: &TableQuery) -> Vec<Projection> {
92 if !query.select_items.is_empty() {
93 return query
94 .select_items
95 .iter()
96 .filter_map(select_item_to_projection)
97 .collect();
98 }
99 if query.columns.is_empty() {
100 vec![Projection::All]
101 } else {
102 query.columns.clone()
103 }
104}
105
106pub fn effective_table_filter(query: &TableQuery) -> Option<Filter> {
107 query
108 .filter
109 .clone()
110 .or_else(|| query.where_expr.as_ref().map(expr_to_filter))
111 .map(|f| f.optimize()) }
113
114pub fn effective_table_group_by_exprs(query: &TableQuery) -> Vec<Expr> {
115 if !query.group_by_exprs.is_empty() {
116 query.group_by_exprs.clone()
117 } else {
118 query
119 .group_by
120 .iter()
121 .map(|column| Expr::Column {
122 field: FieldRef::TableColumn {
123 table: String::new(),
124 column: column.clone(),
125 },
126 span: Span::synthetic(),
127 })
128 .collect()
129 }
130}
131
132pub fn effective_table_having_filter(query: &TableQuery) -> Option<Filter> {
133 query
134 .having
135 .clone()
136 .or_else(|| query.having_expr.as_ref().map(expr_to_filter))
137}
138
139pub fn effective_update_filter(query: &UpdateQuery) -> Option<Filter> {
140 query
141 .filter
142 .clone()
143 .or_else(|| query.where_expr.as_ref().map(expr_to_filter))
144}
145
146pub fn effective_insert_rows(query: &InsertQuery) -> Result<Vec<Vec<Value>>, String> {
147 if !query.value_exprs.is_empty() {
148 return query
149 .value_exprs
150 .iter()
151 .cloned()
152 .map(|row| row.into_iter().map(fold_expr_to_value).collect())
153 .collect();
154 }
155 Ok(query.values.clone())
156}
157
158pub fn effective_delete_filter(query: &DeleteQuery) -> Option<Filter> {
159 query
160 .filter
161 .clone()
162 .or_else(|| query.where_expr.as_ref().map(expr_to_filter))
163}
164
165pub fn effective_join_filter(query: &JoinQuery) -> Option<Filter> {
166 query.filter.clone()
167}
168
169pub fn effective_graph_filter(query: &GraphQuery) -> Option<Filter> {
170 query.filter.clone()
171}
172
173pub fn effective_graph_projections(query: &GraphQuery) -> Vec<Projection> {
174 query.return_.clone()
175}
176
177pub fn effective_path_filter(query: &PathQuery) -> Option<Filter> {
178 query.filter.clone()
179}
180
181pub fn effective_path_projections(query: &PathQuery) -> Vec<Projection> {
182 query.return_.clone()
183}
184
185pub fn effective_vector_filter(query: &VectorQuery) -> Option<MetadataFilter> {
186 query.filter.clone()
187}
188
189pub fn projection_to_expr(projection: &Projection) -> Option<(Expr, Option<String>)> {
190 match projection {
191 Projection::All => Some((
192 Expr::Column {
193 field: FieldRef::TableColumn {
194 table: String::new(),
195 column: "*".to_string(),
196 },
197 span: Span::synthetic(),
198 },
199 None,
200 )),
201 Projection::Column(column) => Some((
202 Expr::Column {
203 field: FieldRef::TableColumn {
204 table: String::new(),
205 column: column.clone(),
206 },
207 span: Span::synthetic(),
208 },
209 None,
210 )),
211 Projection::Alias(column, alias) => Some((
212 Expr::Column {
213 field: FieldRef::TableColumn {
214 table: String::new(),
215 column: column.clone(),
216 },
217 span: Span::synthetic(),
218 },
219 Some(alias.clone()),
220 )),
221 Projection::Function(name, args) => {
222 let (name, alias) = split_projection_function_alias(name);
223 let args = args
224 .iter()
225 .map(projection_to_expr)
226 .collect::<Option<Vec<_>>>()?
227 .into_iter()
228 .map(|(expr, _)| expr)
229 .collect();
230 Some((
231 Expr::FunctionCall {
232 name,
233 args,
234 span: Span::synthetic(),
235 },
236 alias,
237 ))
238 }
239 Projection::Expression(filter, alias) => Some((filter_to_expr(filter), alias.clone())),
240 Projection::Field(field, alias) => Some((
241 Expr::Column {
242 field: field.clone(),
243 span: Span::synthetic(),
244 },
245 alias.clone(),
246 )),
247 }
248}
249
250pub fn projection_to_select_item(projection: &Projection) -> Option<SelectItem> {
251 match projection {
252 Projection::All => Some(SelectItem::Wildcard),
253 other => {
254 let (expr, alias) = projection_to_expr(other)?;
255 Some(SelectItem::Expr { expr, alias })
256 }
257 }
258}
259
260pub fn effective_join_projections(query: &JoinQuery) -> Vec<Projection> {
261 if !query.return_items.is_empty() {
262 return query
263 .return_items
264 .iter()
265 .filter_map(select_item_to_projection)
266 .collect();
267 }
268 query.return_.clone()
269}
270
271pub fn expr_to_filter(expr: &Expr) -> Filter {
272 match expr {
273 Expr::BinaryOp { op, lhs, rhs, .. } => match op {
274 BinOp::And => Filter::And(Box::new(expr_to_filter(lhs)), Box::new(expr_to_filter(rhs))),
275 BinOp::Or => Filter::Or(Box::new(expr_to_filter(lhs)), Box::new(expr_to_filter(rhs))),
276 BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => {
277 try_specialized_compare_filter(lhs, *op, rhs).unwrap_or_else(|| {
278 Filter::CompareExpr {
279 lhs: lhs.as_ref().clone(),
280 op: binop_to_compare_op(*op),
281 rhs: rhs.as_ref().clone(),
282 }
283 })
284 }
285 BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::Concat => {
286 Filter::CompareExpr {
287 lhs: expr.clone(),
288 op: CompareOp::Eq,
289 rhs: Expr::lit(Value::Boolean(true)),
290 }
291 }
292 },
293 Expr::UnaryOp {
294 op: UnaryOp::Not,
295 operand,
296 ..
297 } => Filter::Not(Box::new(expr_to_filter(operand))),
298 Expr::IsNull {
299 operand, negated, ..
300 } => match operand.as_ref() {
301 Expr::Column { field, .. } => {
302 if *negated {
303 Filter::IsNotNull(field.clone())
304 } else {
305 Filter::IsNull(field.clone())
306 }
307 }
308 _ => Filter::CompareExpr {
309 lhs: expr.clone(),
310 op: CompareOp::Eq,
311 rhs: Expr::lit(Value::Boolean(true)),
312 },
313 },
314 Expr::InList {
315 target,
316 values,
317 negated,
318 ..
319 } => match (target.as_ref(), all_literal_values(values)) {
320 (Expr::Column { field, .. }, Some(values)) if !negated => Filter::In {
321 field: field.clone(),
322 values,
323 },
324 _ => Filter::CompareExpr {
325 lhs: expr.clone(),
326 op: CompareOp::Eq,
327 rhs: Expr::lit(Value::Boolean(true)),
328 },
329 },
330 Expr::Between {
331 target,
332 low,
333 high,
334 negated,
335 ..
336 } => match (
337 target.as_ref(),
338 literal_expr_value(low),
339 literal_expr_value(high),
340 ) {
341 (Expr::Column { field, .. }, Some(low), Some(high)) if !negated => Filter::Between {
342 field: field.clone(),
343 low,
344 high,
345 },
346 _ => Filter::CompareExpr {
347 lhs: expr.clone(),
348 op: CompareOp::Eq,
349 rhs: Expr::lit(Value::Boolean(true)),
350 },
351 },
352 Expr::Subquery { .. } => Filter::CompareExpr {
353 lhs: expr.clone(),
354 op: CompareOp::Eq,
355 rhs: Expr::lit(Value::Boolean(true)),
356 },
357 _ => Filter::CompareExpr {
358 lhs: expr.clone(),
359 op: CompareOp::Eq,
360 rhs: Expr::lit(Value::Boolean(true)),
361 },
362 }
363}
364
365pub fn boolean_expr_projection(expr: Expr) -> Projection {
366 Projection::Expression(
367 Box::new(Filter::CompareExpr {
368 lhs: expr,
369 op: CompareOp::Eq,
370 rhs: Expr::Literal {
371 value: Value::Boolean(true),
372 span: Span::synthetic(),
373 },
374 }),
375 None,
376 )
377}
378
379pub fn filter_to_expr(filter: &Filter) -> Expr {
380 match filter {
381 Filter::Compare { field, op, value } => Expr::BinaryOp {
382 op: compare_op_to_binop(*op),
383 lhs: Box::new(Expr::Column {
384 field: field.clone(),
385 span: Span::synthetic(),
386 }),
387 rhs: Box::new(Expr::Literal {
388 value: value.clone(),
389 span: Span::synthetic(),
390 }),
391 span: Span::synthetic(),
392 },
393 Filter::CompareFields { left, op, right } => Expr::BinaryOp {
394 op: compare_op_to_binop(*op),
395 lhs: Box::new(Expr::Column {
396 field: left.clone(),
397 span: Span::synthetic(),
398 }),
399 rhs: Box::new(Expr::Column {
400 field: right.clone(),
401 span: Span::synthetic(),
402 }),
403 span: Span::synthetic(),
404 },
405 Filter::CompareExpr { lhs, op, rhs } => Expr::BinaryOp {
406 op: compare_op_to_binop(*op),
407 lhs: Box::new(lhs.clone()),
408 rhs: Box::new(rhs.clone()),
409 span: Span::synthetic(),
410 },
411 Filter::And(left, right) => Expr::BinaryOp {
412 op: BinOp::And,
413 lhs: Box::new(filter_to_expr(left)),
414 rhs: Box::new(filter_to_expr(right)),
415 span: Span::synthetic(),
416 },
417 Filter::Or(left, right) => Expr::BinaryOp {
418 op: BinOp::Or,
419 lhs: Box::new(filter_to_expr(left)),
420 rhs: Box::new(filter_to_expr(right)),
421 span: Span::synthetic(),
422 },
423 Filter::Not(inner) => Expr::UnaryOp {
424 op: UnaryOp::Not,
425 operand: Box::new(filter_to_expr(inner)),
426 span: Span::synthetic(),
427 },
428 Filter::IsNull(field) => Expr::IsNull {
429 operand: Box::new(Expr::Column {
430 field: field.clone(),
431 span: Span::synthetic(),
432 }),
433 negated: false,
434 span: Span::synthetic(),
435 },
436 Filter::IsNotNull(field) => Expr::IsNull {
437 operand: Box::new(Expr::Column {
438 field: field.clone(),
439 span: Span::synthetic(),
440 }),
441 negated: true,
442 span: Span::synthetic(),
443 },
444 Filter::In { field, values } => Expr::InList {
445 target: Box::new(Expr::Column {
446 field: field.clone(),
447 span: Span::synthetic(),
448 }),
449 values: values
450 .iter()
451 .cloned()
452 .map(|value| Expr::Literal {
453 value,
454 span: Span::synthetic(),
455 })
456 .collect(),
457 negated: false,
458 span: Span::synthetic(),
459 },
460 Filter::Between { field, low, high } => Expr::Between {
461 target: Box::new(Expr::Column {
462 field: field.clone(),
463 span: Span::synthetic(),
464 }),
465 low: Box::new(Expr::Literal {
466 value: low.clone(),
467 span: Span::synthetic(),
468 }),
469 high: Box::new(Expr::Literal {
470 value: high.clone(),
471 span: Span::synthetic(),
472 }),
473 negated: false,
474 span: Span::synthetic(),
475 },
476 Filter::Like { field, pattern } => Expr::FunctionCall {
477 name: "LIKE".to_string(),
478 args: vec![
479 Expr::Column {
480 field: field.clone(),
481 span: Span::synthetic(),
482 },
483 Expr::Literal {
484 value: Value::text(pattern.clone()),
485 span: Span::synthetic(),
486 },
487 ],
488 span: Span::synthetic(),
489 },
490 Filter::StartsWith { field, prefix } => Expr::FunctionCall {
491 name: "STARTS_WITH".to_string(),
492 args: vec![
493 Expr::Column {
494 field: field.clone(),
495 span: Span::synthetic(),
496 },
497 Expr::Literal {
498 value: Value::text(prefix.clone()),
499 span: Span::synthetic(),
500 },
501 ],
502 span: Span::synthetic(),
503 },
504 Filter::EndsWith { field, suffix } => Expr::FunctionCall {
505 name: "ENDS_WITH".to_string(),
506 args: vec![
507 Expr::Column {
508 field: field.clone(),
509 span: Span::synthetic(),
510 },
511 Expr::Literal {
512 value: Value::text(suffix.clone()),
513 span: Span::synthetic(),
514 },
515 ],
516 span: Span::synthetic(),
517 },
518 Filter::Contains { field, substring } => Expr::FunctionCall {
519 name: "CONTAINS".to_string(),
520 args: vec![
521 Expr::Column {
522 field: field.clone(),
523 span: Span::synthetic(),
524 },
525 Expr::Literal {
526 value: Value::text(substring.clone()),
527 span: Span::synthetic(),
528 },
529 ],
530 span: Span::synthetic(),
531 },
532 }
533}
534
535pub fn projection_from_literal(value: &Value) -> Option<Projection> {
536 match value {
537 Value::Boolean(_) => Some(boolean_expr_projection(Expr::Literal {
538 value: value.clone(),
539 span: Span::synthetic(),
540 })),
541 _ => Some(Projection::Column(format!(
542 "LIT:{}",
543 render_projection_literal(value)
544 ))),
545 }
546}
547
548pub fn case_condition_projection(condition: Expr) -> Projection {
549 Projection::Expression(
550 Box::new(Filter::CompareExpr {
551 lhs: condition,
552 op: CompareOp::Eq,
553 rhs: Expr::Literal {
554 value: Value::Boolean(true),
555 span: Span::synthetic(),
556 },
557 }),
558 None,
559 )
560}
561
562pub fn fold_expr_to_value(expr: Expr) -> Result<Value, String> {
563 match expr {
564 Expr::Literal { value, .. } => Ok(value),
565 Expr::FunctionCall { name, args, .. } => {
566 if (name.eq_ignore_ascii_case("PASSWORD") || name.eq_ignore_ascii_case("SECRET"))
567 && args.len() == 1
568 {
569 let plaintext = match fold_expr_to_value(args.into_iter().next().unwrap())? {
570 Value::Text(text) => text,
571 other => {
572 return Err(format!(
573 "{name}() expects a string literal argument, got {other:?}"
574 ))
575 }
576 };
577 return Ok(if name.eq_ignore_ascii_case("PASSWORD") {
578 Value::Password(format!("@@plain@@{plaintext}"))
579 } else {
580 Value::Secret(format!("@@plain@@{plaintext}").into_bytes())
581 });
582 }
583 Err(format!(
584 "expression is not a foldable literal: FunctionCall({name})"
585 ))
586 }
587 Expr::UnaryOp { op, operand, .. } => {
588 let inner = fold_expr_to_value(*operand)?;
589 match (op, inner) {
590 (UnaryOp::Neg, Value::Integer(n)) => Ok(Value::Integer(-n)),
591 (UnaryOp::Neg, Value::UnsignedInteger(n)) => Ok(Value::Integer(-(n as i64))),
592 (UnaryOp::Neg, Value::Float(f)) => Ok(Value::Float(-f)),
593 (UnaryOp::Not, Value::Boolean(b)) => Ok(Value::Boolean(!b)),
594 (other_op, other) => Err(format!(
595 "unary `{other_op:?}` cannot fold to literal Value (operand: {other:?})"
596 )),
597 }
598 }
599 Expr::Cast { inner, .. } => fold_expr_to_value(*inner),
600 other => Err(format!("expression is not a foldable literal: {other:?}")),
601 }
602}
603
604fn projection_binop_name(op: BinOp) -> &'static str {
605 match op {
606 BinOp::Add => "ADD",
607 BinOp::Sub => "SUB",
608 BinOp::Mul => "MUL",
609 BinOp::Div => "DIV",
610 BinOp::Mod => "MOD",
611 BinOp::Concat => "CONCAT",
612 BinOp::Eq
613 | BinOp::Ne
614 | BinOp::Lt
615 | BinOp::Le
616 | BinOp::Gt
617 | BinOp::Ge
618 | BinOp::And
619 | BinOp::Or => {
620 unreachable!("boolean operators are lowered through Projection::Expression")
621 }
622 }
623}
624
625fn binop_to_compare_op(op: BinOp) -> CompareOp {
626 match op {
627 BinOp::Eq => CompareOp::Eq,
628 BinOp::Ne => CompareOp::Ne,
629 BinOp::Lt => CompareOp::Lt,
630 BinOp::Le => CompareOp::Le,
631 BinOp::Gt => CompareOp::Gt,
632 BinOp::Ge => CompareOp::Ge,
633 other => unreachable!("non-compare binop cannot lower to CompareOp: {other:?}"),
634 }
635}
636
637fn compare_op_to_binop(op: CompareOp) -> BinOp {
638 match op {
639 CompareOp::Eq => BinOp::Eq,
640 CompareOp::Ne => BinOp::Ne,
641 CompareOp::Lt => BinOp::Lt,
642 CompareOp::Le => BinOp::Le,
643 CompareOp::Gt => BinOp::Gt,
644 CompareOp::Ge => BinOp::Ge,
645 }
646}
647
648fn attach_projection_alias(proj: Projection, alias: Option<String>) -> Projection {
649 let Some(alias) = alias else { return proj };
650 match proj {
651 Projection::Field(f, _) => Projection::Field(f, Some(alias)),
652 Projection::Expression(filter, _) => Projection::Expression(filter, Some(alias)),
653 Projection::Function(name, args) => {
654 if name.contains(':') {
655 Projection::Function(name, args)
656 } else {
657 Projection::Function(format!("{name}:{alias}"), args)
658 }
659 }
660 Projection::Column(c) => Projection::Alias(c, alias),
661 other => other,
662 }
663}
664
665fn split_projection_function_alias(name: &str) -> (String, Option<String>) {
666 match name.split_once(':') {
667 Some((function, alias)) if !function.is_empty() && !alias.is_empty() => {
668 (function.to_string(), Some(alias.to_string()))
669 }
670 _ => (name.to_string(), None),
671 }
672}
673
674fn render_projection_literal(value: &Value) -> String {
675 match value {
676 Value::Null => String::new(),
677 Value::Integer(v) => v.to_string(),
678 Value::UnsignedInteger(v) => v.to_string(),
679 Value::Float(v) => {
680 if v.fract().abs() < f64::EPSILON {
681 (*v as i64).to_string()
682 } else {
683 v.to_string()
684 }
685 }
686 Value::Text(v) => v.to_string(),
687 Value::Boolean(true) => "true".to_string(),
688 Value::Boolean(false) => "false".to_string(),
689 Value::Array(_) | Value::Vector(_) | Value::Json(_) | Value::Blob(_) => {
694 format!("@RL:{}", serialize_value_json(value))
695 }
696 other => other.to_string(),
697 }
698}
699
700fn serialize_value_json(value: &Value) -> String {
701 match value {
703 Value::Array(items) => {
704 let mut out = String::from("[");
705 for (i, item) in items.iter().enumerate() {
706 if i > 0 {
707 out.push(',');
708 }
709 out.push_str(&serialize_value_json(item));
710 }
711 out.push(']');
712 out
713 }
714 Value::Vector(items) => {
715 let mut out = String::from("V[");
716 for (i, f) in items.iter().enumerate() {
717 if i > 0 {
718 out.push(',');
719 }
720 out.push_str(&f.to_string());
721 }
722 out.push(']');
723 out
724 }
725 Value::Integer(n) | Value::BigInt(n) => n.to_string(),
726 Value::UnsignedInteger(n) => n.to_string(),
727 Value::Float(f) => f.to_string(),
728 Value::Text(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
729 Value::Boolean(b) => b.to_string(),
730 Value::Null => "null".to_string(),
731 other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
732 }
733}
734
735fn try_specialized_compare_filter(lhs: &Expr, op: BinOp, rhs: &Expr) -> Option<Filter> {
736 let op = binop_to_compare_op(op);
737 match (lhs, rhs) {
738 (Expr::Column { field, .. }, Expr::Literal { value, .. }) => Some(Filter::Compare {
739 field: field.clone(),
740 op,
741 value: value.clone(),
742 }),
743 (Expr::Literal { value, .. }, Expr::Column { field, .. }) => Some(Filter::Compare {
744 field: field.clone(),
745 op: flipped_compare_op(op),
746 value: value.clone(),
747 }),
748 (Expr::Column { field: left, .. }, Expr::Column { field: right, .. }) => {
749 Some(Filter::CompareFields {
750 left: left.clone(),
751 op,
752 right: right.clone(),
753 })
754 }
755 _ => None,
756 }
757}
758
759fn flipped_compare_op(op: CompareOp) -> CompareOp {
760 match op {
761 CompareOp::Eq => CompareOp::Eq,
762 CompareOp::Ne => CompareOp::Ne,
763 CompareOp::Lt => CompareOp::Gt,
764 CompareOp::Le => CompareOp::Ge,
765 CompareOp::Gt => CompareOp::Lt,
766 CompareOp::Ge => CompareOp::Le,
767 }
768}
769
770fn literal_expr_value(expr: &Expr) -> Option<Value> {
771 match expr {
772 Expr::Literal { value, .. } => Some(value.clone()),
773 _ => None,
774 }
775}
776
777fn all_literal_values(values: &[Expr]) -> Option<Vec<Value>> {
778 values.iter().map(literal_expr_value).collect()
779}