Skip to main content

partiql_eval/
plan.rs

1use crate::error::{ErrorNode, PlanErr, PlanningError};
2use crate::eval;
3use crate::eval::evaluable::{
4    AggregateFunction, Any, Avg, Count, EvalGroupingStrategy, EvalJoinKind, EvalOrderBy,
5    EvalOrderBySortCondition, EvalOrderBySortSpec, EvalOuterExcept, EvalOuterIntersect,
6    EvalOuterUnion, EvalSubQueryExpr, Evaluable, Every, Max, Min, Sum,
7};
8use crate::eval::expr::{
9    BindError, BindEvalExpr, EvalBagExpr, EvalBetweenExpr, EvalCollFn, EvalDynamicLookup, EvalExpr,
10    EvalExtractFn, EvalFnAbs, EvalFnBaseTableExpr, EvalFnCardinality, EvalFnExists, EvalFnOverlay,
11    EvalFnPosition, EvalFnSubstring, EvalGraphMatch, EvalIsTypeExpr, EvalLikeMatch,
12    EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr, EvalOpBinary, EvalOpUnary,
13    EvalPath, EvalSearchedCaseExpr, EvalStringFn, EvalTrimFn, EvalTupleExpr, EvalVarRef,
14};
15use crate::eval::graph::plan::ValueFilter;
16use crate::eval::graph::string_graph::StringGraphTypes;
17use crate::eval::EvalPlan;
18use eval::graph::plan as physical;
19use itertools::{Either, Itertools};
20use partiql_catalog::catalog::{FunctionEntryFunction, SharedCatalog};
21use partiql_extension_ion::boxed_ion::BoxedIonType;
22use partiql_logical as logical;
23use partiql_logical::{
24    AggFunc, BagOperator, BinaryOp, BindingsOp, CallName, GraphMatchExpr, GroupingStrategy,
25    IsTypeExpr, JoinKind, Lit, LogicalPlan, OpId, PathComponent, Pattern, PatternMatchExpr,
26    ProjectAllMode, SearchedCase, SetQuantifier, SortSpecNullOrder, SortSpecOrder, Type, UnaryOp,
27    ValueExpr, VarRefType,
28};
29use partiql_value::boxed_variant::DynBoxedVariantTypeFactory;
30use partiql_value::{Bag, List, Tuple, Value, Variant};
31use petgraph::prelude::StableGraph;
32use rustc_hash::FxHashMap;
33use std::rc::Rc;
34
35#[macro_export]
36macro_rules! correct_num_args_or_err {
37    ($self:expr, $args:expr, $exact_num:literal, $name:expr) => {
38        if $args.len() != $exact_num {
39            $self.errors.push(PlanningError::IllegalState(format!(
40                "Wrong number of arguments for {}",
41                $name.to_string()
42            )));
43            return Box::new(ErrorNode::new());
44        }
45    };
46    ($self:expr, $args:expr, $min_num:literal, $max_num:literal, $name:expr) => {
47        if !($min_num..=$max_num).contains(&$args.len()) {
48            $self
49                .errors
50                .push(PlanningError::IllegalState($name.to_string()));
51            return Box::new(ErrorNode::new());
52        }
53    };
54}
55
56#[derive(Debug, Eq, PartialEq)]
57pub enum EvaluationMode {
58    Strict,
59    Permissive,
60}
61
62pub struct EvaluatorPlanner<'c> {
63    mode: EvaluationMode,
64    catalog: &'c dyn SharedCatalog,
65    errors: Vec<PlanningError>,
66}
67
68impl From<(&str, BindError)> for PlanningError {
69    fn from((name, err): (&str, BindError)) -> Self {
70        match err {
71            BindError::ArgNumMismatch { .. } => {
72                PlanningError::IllegalState(format!("Wrong number of arguments for {name}"))
73            }
74            BindError::Unknown => {
75                PlanningError::IllegalState(format!("Unknown error binding {name}"))
76            }
77            BindError::NotYetImplemented(name) => PlanningError::NotYetImplemented(name),
78            BindError::ArgumentConstraint(msg) => PlanningError::IllegalState(msg),
79            BindError::LiteralValue(err) => {
80                PlanningError::IllegalState(format!("Literal error: {err}"))
81            }
82        }
83    }
84}
85
86impl From<&logical::SetQuantifier> for eval::evaluable::SetQuantifier {
87    fn from(setq: &SetQuantifier) -> Self {
88        match setq {
89            SetQuantifier::All => eval::evaluable::SetQuantifier::All,
90            SetQuantifier::Distinct => eval::evaluable::SetQuantifier::Distinct,
91        }
92    }
93}
94
95impl From<&UnaryOp> for EvalOpUnary {
96    fn from(op: &UnaryOp) -> Self {
97        match op {
98            UnaryOp::Pos => EvalOpUnary::Pos,
99            UnaryOp::Neg => EvalOpUnary::Neg,
100            UnaryOp::Not => EvalOpUnary::Not,
101        }
102    }
103}
104
105impl From<&BinaryOp> for EvalOpBinary {
106    fn from(op: &BinaryOp) -> Self {
107        match op {
108            BinaryOp::And => EvalOpBinary::And,
109            BinaryOp::Or => EvalOpBinary::Or,
110            BinaryOp::Concat => EvalOpBinary::Concat,
111            BinaryOp::Eq => EvalOpBinary::Eq,
112            BinaryOp::Neq => EvalOpBinary::Neq,
113            BinaryOp::Gt => EvalOpBinary::Gt,
114            BinaryOp::Gteq => EvalOpBinary::Gteq,
115            BinaryOp::Lt => EvalOpBinary::Lt,
116            BinaryOp::Lteq => EvalOpBinary::Lteq,
117            BinaryOp::Add => EvalOpBinary::Add,
118            BinaryOp::Sub => EvalOpBinary::Sub,
119            BinaryOp::Mul => EvalOpBinary::Mul,
120            BinaryOp::Div => EvalOpBinary::Div,
121            BinaryOp::Mod => EvalOpBinary::Mod,
122            BinaryOp::Exp => EvalOpBinary::Exp,
123            BinaryOp::In => EvalOpBinary::In,
124        }
125    }
126}
127
128impl<'c> EvaluatorPlanner<'c> {
129    pub fn new(mode: EvaluationMode, catalog: &'c dyn SharedCatalog) -> Self {
130        EvaluatorPlanner {
131            mode,
132            catalog,
133            errors: vec![],
134        }
135    }
136
137    #[inline]
138    pub fn compile(&mut self, plan: &LogicalPlan<BindingsOp>) -> Result<EvalPlan, PlanErr> {
139        let plan = match self.mode {
140            EvaluationMode::Strict => self.plan_eval::<true>(plan),
141            EvaluationMode::Permissive => self.plan_eval::<false>(plan),
142        };
143        let errors = std::mem::take(&mut self.errors);
144        if !errors.is_empty() {
145            Err(PlanErr { errors })
146        } else {
147            Ok(plan)
148        }
149    }
150
151    #[inline]
152    fn plan_eval<const STRICT: bool>(&mut self, lg: &LogicalPlan<BindingsOp>) -> EvalPlan {
153        let flows = lg.flows();
154
155        let mut plan_graph: StableGraph<_, _> = Default::default();
156        let mut seen = FxHashMap::default();
157
158        for (s, d, branch_num) in flows {
159            let mut add_node = |op_id: &OpId| {
160                let logical_op = lg.operator(*op_id).unwrap();
161                *seen.entry(*op_id).or_insert_with(|| {
162                    plan_graph.add_node(self.get_eval_node::<{ STRICT }>(logical_op))
163                })
164            };
165
166            let (s, d) = (add_node(s), add_node(d));
167            plan_graph.add_edge(s, d, *branch_num);
168        }
169        let mode = if STRICT {
170            EvaluationMode::Strict
171        } else {
172            EvaluationMode::Permissive
173        };
174        EvalPlan::new(mode, plan_graph)
175    }
176
177    fn get_eval_node<const STRICT: bool>(&mut self, be: &BindingsOp) -> Box<dyn Evaluable> {
178        match be {
179            BindingsOp::Scan(logical::Scan {
180                expr,
181                as_key,
182                at_key,
183            }) => {
184                if let Some(at_key) = at_key {
185                    Box::new(eval::evaluable::EvalScan::new_with_at_key(
186                        self.plan_value::<{ STRICT }>(expr),
187                        as_key,
188                        at_key,
189                    ))
190                } else {
191                    Box::new(eval::evaluable::EvalScan::new(
192                        self.plan_value::<{ STRICT }>(expr),
193                        as_key,
194                    ))
195                }
196            }
197            BindingsOp::Project(logical::Project { exprs }) => {
198                let exprs: Vec<(_, _)> = exprs
199                    .iter()
200                    .map(|(k, v)| (k.clone(), self.plan_value::<{ STRICT }>(v)))
201                    .collect();
202                Box::new(eval::evaluable::EvalSelect::new(exprs))
203            }
204            BindingsOp::ProjectAll(mode) => Box::new(eval::evaluable::EvalSelectAll::new(
205                mode == &ProjectAllMode::PassThrough,
206            )),
207            BindingsOp::ProjectValue(logical::ProjectValue { expr }) => {
208                let expr = self.plan_value::<{ STRICT }>(expr);
209                Box::new(eval::evaluable::EvalSelectValue::new(expr))
210            }
211            BindingsOp::Filter(logical::Filter { expr }) => Box::new(
212                eval::evaluable::EvalFilter::new(self.plan_value::<{ STRICT }>(expr)),
213            ),
214            BindingsOp::Having(logical::Having { expr }) => Box::new(
215                eval::evaluable::EvalHaving::new(self.plan_value::<{ STRICT }>(expr)),
216            ),
217            BindingsOp::Distinct => Box::new(eval::evaluable::EvalDistinct::new()),
218            BindingsOp::Sink => Box::new(eval::evaluable::EvalSink {}),
219            BindingsOp::Pivot(logical::Pivot { key, value }) => {
220                Box::new(eval::evaluable::EvalPivot::new(
221                    self.plan_value::<{ STRICT }>(key),
222                    self.plan_value::<{ STRICT }>(value),
223                ))
224            }
225            BindingsOp::Unpivot(logical::Unpivot {
226                expr,
227                as_key,
228                at_key,
229            }) => Box::new(eval::evaluable::EvalUnpivot::new(
230                self.plan_value::<{ STRICT }>(expr),
231                as_key,
232                at_key.clone(),
233            )),
234            BindingsOp::Join(logical::Join {
235                kind,
236                left,
237                right,
238                on,
239            }) => {
240                let kind = match kind {
241                    // Model CROSS JOINs as INNER JOINs as mentioned by equivalence mentioned in
242                    // section 5.3 of spec https://partiql.org/assets/PartiQL-Specification.pdf#subsection.5.3
243                    JoinKind::Cross | JoinKind::Inner => EvalJoinKind::Inner,
244                    JoinKind::Left => EvalJoinKind::Left,
245                    JoinKind::Right => EvalJoinKind::Right,
246                    JoinKind::Full => EvalJoinKind::Full,
247                };
248                let on = on
249                    .as_ref()
250                    .map(|on_condition| self.plan_value::<{ STRICT }>(on_condition));
251                Box::new(eval::evaluable::EvalJoin::new(
252                    kind,
253                    self.get_eval_node::<{ STRICT }>(left),
254                    self.get_eval_node::<{ STRICT }>(right),
255                    on,
256                ))
257            }
258            BindingsOp::GroupBy(logical::GroupBy {
259                strategy,
260                exprs,
261                aggregate_exprs,
262                group_as_alias,
263            }) => {
264                let strategy = match strategy {
265                    GroupingStrategy::GroupFull => EvalGroupingStrategy::GroupFull,
266                    GroupingStrategy::GroupPartial => EvalGroupingStrategy::GroupPartial,
267                };
268                let (aliases, exprs): (Vec<String>, Vec<Box<dyn EvalExpr>>) = exprs
269                    .iter()
270                    .map(|(k, v)| (k.clone(), self.plan_value::<{ STRICT }>(v)))
271                    .unzip();
272
273                let mut plan_agg = |a_e: &logical::AggregateExpression| {
274                    let func = match &a_e.func {
275                        AggFunc::AggAvg => Box::new(Avg {}) as Box<dyn AggregateFunction>,
276                        AggFunc::AggCount => Box::new(Count {}) as Box<dyn AggregateFunction>,
277                        AggFunc::AggMax => Box::new(Max {}) as Box<dyn AggregateFunction>,
278                        AggFunc::AggMin => Box::new(Min {}) as Box<dyn AggregateFunction>,
279                        AggFunc::AggSum => Box::new(Sum {}) as Box<dyn AggregateFunction>,
280                        AggFunc::AggAny => Box::new(Any {}) as Box<dyn AggregateFunction>,
281                        AggFunc::AggEvery => Box::new(Every {}) as Box<dyn AggregateFunction>,
282                    };
283                    eval::evaluable::AggregateExpression {
284                        name: a_e.name.to_string(),
285                        expr: self.plan_value::<{ STRICT }>(&a_e.expr),
286                        func,
287                    }
288                };
289
290                let (aggs, distinct_aggs) =
291                    aggregate_exprs.iter().partition_map(|ae| match ae.setq {
292                        SetQuantifier::All => Either::Left(plan_agg(ae)),
293                        SetQuantifier::Distinct => Either::Right(plan_agg(ae)),
294                    });
295
296                let group_as_alias = group_as_alias
297                    .as_ref()
298                    .map(std::string::ToString::to_string);
299                Box::new(eval::evaluable::EvalGroupBy::new(
300                    strategy,
301                    exprs,
302                    aliases,
303                    aggs,
304                    distinct_aggs,
305                    group_as_alias,
306                ))
307            }
308            BindingsOp::ExprQuery(logical::ExprQuery { expr }) => {
309                let expr = self.plan_value::<{ STRICT }>(expr);
310                Box::new(eval::evaluable::EvalExprQuery::new(expr))
311            }
312            BindingsOp::OrderBy(logical::OrderBy { specs }) => {
313                let cmp = specs
314                    .iter()
315                    .map(|spec| {
316                        let expr = self.plan_value::<{ STRICT }>(&spec.expr);
317                        let spec = match (&spec.order, &spec.null_order) {
318                            (SortSpecOrder::Asc, SortSpecNullOrder::First) => {
319                                EvalOrderBySortSpec::AscNullsFirst
320                            }
321                            (SortSpecOrder::Asc, SortSpecNullOrder::Last) => {
322                                EvalOrderBySortSpec::AscNullsLast
323                            }
324                            (SortSpecOrder::Desc, SortSpecNullOrder::First) => {
325                                EvalOrderBySortSpec::DescNullsFirst
326                            }
327                            (SortSpecOrder::Desc, SortSpecNullOrder::Last) => {
328                                EvalOrderBySortSpec::DescNullsLast
329                            }
330                        };
331                        EvalOrderBySortCondition { expr, spec }
332                    })
333                    .collect_vec();
334                Box::new(EvalOrderBy { cmp })
335            }
336            BindingsOp::LimitOffset(logical::LimitOffset { limit, offset }) => {
337                Box::new(eval::evaluable::EvalLimitOffset {
338                    limit: limit.as_ref().map(|e| self.plan_value::<{ STRICT }>(e)),
339                    offset: offset.as_ref().map(|e| self.plan_value::<{ STRICT }>(e)),
340                })
341            }
342            BindingsOp::BagOp(logical::BagOp {
343                bag_op: setop,
344                setq,
345            }) => {
346                let setq = setq.into();
347                match setop {
348                    BagOperator::Union => self.err_nyi("BagOperator::Union"),
349                    BagOperator::Intersect => self.err_nyi("BagOperator::Intersect"),
350                    BagOperator::Except => self.err_nyi("BagOperator::Except"),
351                    BagOperator::OuterUnion => Box::new(EvalOuterUnion::new(setq)),
352                    BagOperator::OuterIntersect => Box::new(EvalOuterIntersect::new(setq)),
353                    BagOperator::OuterExcept => Box::new(EvalOuterExcept::new(setq)),
354                }
355            }
356        }
357    }
358
359    #[inline]
360    fn err_nyi(&mut self, feature: &str) -> Box<ErrorNode> {
361        let msg = format!("{feature} not yet implemented in evaluator");
362        self.err(PlanningError::NotYetImplemented(msg))
363    }
364
365    #[inline]
366    fn err(&mut self, err: PlanningError) -> Box<ErrorNode> {
367        self.errors.push(err);
368        Box::new(ErrorNode::new())
369    }
370
371    fn unwrap_bind(
372        &mut self,
373        name: &str,
374        op: Result<Box<dyn EvalExpr>, BindError>,
375    ) -> Box<dyn EvalExpr> {
376        match op {
377            Ok(op) => op,
378            Err(err) => self.err((name, err).into()),
379        }
380    }
381
382    fn plan_values<'v, const STRICT: bool, I>(&mut self, vals: I) -> Vec<Box<dyn EvalExpr>>
383    where
384        I: Iterator<Item = &'v ValueExpr>,
385    {
386        vals.map(|arg| self.plan_value::<{ STRICT }>(arg))
387            .collect_vec()
388    }
389
390    fn plan_value<const STRICT: bool>(&mut self, ve: &ValueExpr) -> Box<dyn EvalExpr> {
391        let mut plan_args = |arguments: &[&ValueExpr]| {
392            self.plan_values::<{ STRICT }, _>(arguments.iter().map(std::ops::Deref::deref))
393        };
394
395        let (name, bind) = match ve {
396            ValueExpr::UnExpr(op, operand) => (
397                "unary operator",
398                EvalOpUnary::from(op).bind::<{ STRICT }>(plan_args(&[operand])),
399            ),
400            ValueExpr::BinaryExpr(op, lhs, rhs) => (
401                "binary operator",
402                EvalOpBinary::from(op).bind::<{ STRICT }>(plan_args(&[lhs, rhs])),
403            ),
404            ValueExpr::Lit(lit) => (
405                "literal",
406                match plan_lit(lit.as_ref()) {
407                    Ok(lit) => EvalLitExpr::new(lit).bind::<{ STRICT }>(vec![]),
408                    Err(e) => Ok(self.err(e) as Box<dyn EvalExpr>),
409                },
410            ),
411            ValueExpr::Path(expr, components) => (
412                "path",
413                Ok(Box::new(EvalPath {
414                    expr: self.plan_value::<{ STRICT }>(expr),
415                    components: components
416                        .iter()
417                        .map(|c| match c {
418                            PathComponent::Key(k) => eval::expr::EvalPathComponent::Key(k.clone()),
419                            PathComponent::Index(i) => eval::expr::EvalPathComponent::Index(*i),
420                            PathComponent::KeyExpr(k) => eval::expr::EvalPathComponent::KeyExpr(
421                                self.plan_value::<{ STRICT }>(k),
422                            ),
423                            PathComponent::IndexExpr(i) => {
424                                eval::expr::EvalPathComponent::IndexExpr(
425                                    self.plan_value::<{ STRICT }>(i),
426                                )
427                            }
428                        })
429                        .collect(),
430                }) as Box<dyn EvalExpr>),
431            ),
432            ValueExpr::VarRef(name, var_ref_type) => (
433                "var ref",
434                match var_ref_type {
435                    VarRefType::Global => EvalVarRef::Global(name.clone()),
436                    VarRefType::Local => EvalVarRef::Local(name.clone()),
437                }
438                .bind::<{ STRICT }>(vec![]),
439            ),
440            ValueExpr::TupleExpr(expr) => {
441                let attrs: Vec<Box<dyn EvalExpr>> = expr
442                    .attrs
443                    .iter()
444                    .map(|attr| self.plan_value::<{ STRICT }>(attr))
445                    .collect();
446                let vals: Vec<Box<dyn EvalExpr>> = expr
447                    .values
448                    .iter()
449                    .map(|attr| self.plan_value::<{ STRICT }>(attr))
450                    .collect();
451                (
452                    "tuple expr",
453                    Ok(Box::new(EvalTupleExpr { attrs, vals }) as Box<dyn EvalExpr>),
454                )
455            }
456            ValueExpr::ListExpr(expr) => {
457                let elements: Vec<Box<dyn EvalExpr>> = expr
458                    .elements
459                    .iter()
460                    .map(|elem| self.plan_value::<{ STRICT }>(elem))
461                    .collect();
462                (
463                    "list expr",
464                    Ok(Box::new(EvalListExpr { elements }) as Box<dyn EvalExpr>),
465                )
466            }
467            ValueExpr::BagExpr(expr) => {
468                let elements: Vec<Box<dyn EvalExpr>> = expr
469                    .elements
470                    .iter()
471                    .map(|elem| self.plan_value::<{ STRICT }>(elem))
472                    .collect();
473                (
474                    "bag expr",
475                    Ok(Box::new(EvalBagExpr { elements }) as Box<dyn EvalExpr>),
476                )
477            }
478            ValueExpr::BetweenExpr(logical::BetweenExpr { value, from, to }) => {
479                let args = plan_args(&[value, from, to]);
480                ("between", EvalBetweenExpr {}.bind::<{ STRICT }>(args))
481            }
482            ValueExpr::PatternMatchExpr(PatternMatchExpr { value, pattern }) => {
483                let expr = match pattern {
484                    Pattern::Like(logical::LikeMatch { pattern, escape }) => {
485                        match EvalLikeMatch::create(pattern, escape) {
486                            Ok(like) => like.bind::<{ STRICT }>(plan_args(&[value])),
487                            Err(err) => Ok(self.err(err) as Box<dyn EvalExpr>),
488                        }
489                    }
490                    Pattern::LikeNonStringNonLiteral(logical::LikeNonStringNonLiteralMatch {
491                        pattern,
492                        escape,
493                    }) => {
494                        let args = plan_args(&[value, pattern, escape]);
495                        EvalLikeNonStringNonLiteralMatch {}.bind::<{ STRICT }>(args)
496                    }
497                };
498
499                ("pattern expr", expr)
500            }
501            ValueExpr::GraphMatch(graph_match) => {
502                let GraphMatchExpr { value, pattern } = graph_match.as_ref();
503                let args = plan_args(&[value]);
504                let expr = match self.plan_graph_plan::<{ STRICT }>(pattern) {
505                    Ok(pattern) => EvalGraphMatch::new(pattern).bind::<{ STRICT }>(args),
506                    Err(e) => Ok(self.err(e) as Box<dyn EvalExpr>),
507                };
508                ("graphmatch expr", expr)
509            }
510            ValueExpr::SubQueryExpr(expr) => (
511                "subquery",
512                Ok(Box::new(EvalSubQueryExpr::new(
513                    self.plan_eval::<{ STRICT }>(&expr.plan),
514                )) as Box<dyn EvalExpr>),
515            ),
516            ValueExpr::SimpleCase(e) => {
517                let cases = e
518                    .cases
519                    .iter()
520                    .map(|case| {
521                        (
522                            self.plan_value::<{ STRICT }>(&ValueExpr::BinaryExpr(
523                                BinaryOp::Eq,
524                                e.expr.clone(),
525                                case.0.clone(),
526                            )),
527                            self.plan_value::<{ STRICT }>(case.1.as_ref()),
528                        )
529                    })
530                    .collect();
531                let default = match &e.default {
532                    // If no `ELSE` clause is specified, use implicit `ELSE NULL` (see section 6.9, pg 142 of SQL-92 spec)
533                    None => self.unwrap_bind(
534                        "simple case default",
535                        EvalLitExpr::new(Value::Null).bind::<{ STRICT }>(vec![]),
536                    ),
537                    Some(def) => self.plan_value::<{ STRICT }>(def),
538                };
539                // Here, rewrite `SimpleCaseExpr`s as `SearchedCaseExpr`s
540                (
541                    "simple case",
542                    Ok(Box::new(EvalSearchedCaseExpr { cases, default }) as Box<dyn EvalExpr>),
543                )
544            }
545            ValueExpr::SearchedCase(e) => {
546                let cases = e
547                    .cases
548                    .iter()
549                    .map(|case| {
550                        (
551                            self.plan_value::<{ STRICT }>(case.0.as_ref()),
552                            self.plan_value::<{ STRICT }>(case.1.as_ref()),
553                        )
554                    })
555                    .collect();
556                let default = match &e.default {
557                    // If no `ELSE` clause is specified, use implicit `ELSE NULL` (see section 6.9, pg 142 of SQL-92 spec)
558                    None => self.unwrap_bind(
559                        "searched case default",
560                        EvalLitExpr::new(Value::Null).bind::<{ STRICT }>(vec![]),
561                    ),
562                    Some(def) => self.plan_value::<{ STRICT }>(def.as_ref()),
563                };
564                (
565                    "searched case",
566                    Ok(Box::new(EvalSearchedCaseExpr { cases, default }) as Box<dyn EvalExpr>),
567                )
568            }
569            ValueExpr::IsTypeExpr(i) => (
570                "is type",
571                Ok(Box::new(EvalIsTypeExpr {
572                    expr: self.plan_value::<{ STRICT }>(i.expr.as_ref()),
573                    is_type: i.is_type.clone(),
574                    invert: i.not,
575                }) as Box<dyn EvalExpr>),
576            ),
577            ValueExpr::NullIfExpr(n) => {
578                // NULLIF can be rewritten using CASE WHEN expressions as per section 6.9 pg 142 of SQL-92 spec:
579                //     1) NULLIF (V1, V2) is equivalent to the following <case specification>:
580                //         CASE WHEN V1=V2 THEN NULL ELSE V1 END
581                let rewritten_as_case = ValueExpr::SearchedCase(SearchedCase {
582                    cases: vec![(
583                        Box::new(ValueExpr::BinaryExpr(
584                            BinaryOp::Eq,
585                            n.lhs.clone(),
586                            n.rhs.clone(),
587                        )),
588                        Box::new(ValueExpr::Lit(Box::new(logical::Lit::Null))),
589                    )],
590                    default: Some(n.lhs.clone()),
591                });
592                (
593                    "null if",
594                    Ok(self.plan_value::<{ STRICT }>(&rewritten_as_case)),
595                )
596            }
597            ValueExpr::CoalesceExpr(c) => {
598                // COALESCE can be rewritten using CASE WHEN expressions as per section 6.9 pg 142 of SQL-92 spec:
599                //     2) COALESCE (V1, V2) is equivalent to the following <case specification>:
600                //         CASE WHEN V1 IS NOT NULL THEN V1 ELSE V2 END
601                //
602                //     3) COALESCE (V1, V2, . . . ,n ), for n >= 3, is equivalent to the following <case specification>:
603                //         CASE WHEN V1 IS NOT NULL THEN V1 ELSE COALESCE (V2, . . . ,n )
604                //         END
605                if c.elements.is_empty() {
606                    self.errors.push(PlanningError::IllegalState(
607                        "Wrong number of arguments to coalesce".to_string(),
608                    ));
609                    return Box::new(ErrorNode::new());
610                }
611                fn as_case(v: &ValueExpr, elems: &[ValueExpr]) -> ValueExpr {
612                    let sc = SearchedCase {
613                        cases: vec![(
614                            Box::new(ValueExpr::IsTypeExpr(IsTypeExpr {
615                                not: true,
616                                expr: Box::new(v.clone()),
617                                is_type: Type::NullType,
618                            })),
619                            Box::new(v.clone()),
620                        )],
621                        default: elems.first().map(|v2| Box::new(as_case(v2, &elems[1..]))),
622                    };
623                    ValueExpr::SearchedCase(sc)
624                }
625                (
626                    "coalesce",
627                    Ok(self.plan_value::<{ STRICT }>(&as_case(
628                        c.elements.first().unwrap(),
629                        &c.elements[1..],
630                    ))),
631                )
632            }
633            ValueExpr::DynamicLookup(lookups) => {
634                let lookups = lookups
635                    .iter()
636                    .map(|lookup| self.plan_value::<{ STRICT }>(lookup))
637                    .collect_vec();
638
639                (
640                    "dynamic lookup",
641                    Ok(Box::new(EvalDynamicLookup { lookups }) as Box<dyn EvalExpr>),
642                )
643            }
644            ValueExpr::Call(logical::CallExpr { name, arguments }) => {
645                let args = self.plan_values::<{ STRICT }, _>(arguments.iter());
646                match name {
647                    CallName::Lower => ("lower", EvalStringFn::Lower.bind::<{ STRICT }>(args)),
648                    CallName::Upper => ("upper", EvalStringFn::Upper.bind::<{ STRICT }>(args)),
649                    CallName::CharLength => (
650                        "char_length",
651                        EvalStringFn::CharLength.bind::<{ STRICT }>(args),
652                    ),
653                    CallName::OctetLength => (
654                        "octet_length",
655                        EvalStringFn::OctetLength.bind::<{ STRICT }>(args),
656                    ),
657                    CallName::BitLength => (
658                        "bit_length",
659                        EvalStringFn::BitLength.bind::<{ STRICT }>(args),
660                    ),
661                    CallName::LTrim => ("ltrim", EvalTrimFn::Start.bind::<{ STRICT }>(args)),
662                    CallName::BTrim => ("btrim", EvalTrimFn::Both.bind::<{ STRICT }>(args)),
663                    CallName::RTrim => ("rtrim", EvalTrimFn::End.bind::<{ STRICT }>(args)),
664                    CallName::Substring => {
665                        ("substring", EvalFnSubstring {}.bind::<{ STRICT }>(args))
666                    }
667                    CallName::Position => ("position", EvalFnPosition {}.bind::<{ STRICT }>(args)),
668                    CallName::Overlay => ("overlay", EvalFnOverlay {}.bind::<{ STRICT }>(args)),
669                    CallName::Exists => ("exists", EvalFnExists {}.bind::<{ STRICT }>(args)),
670                    CallName::Abs => ("abs", EvalFnAbs {}.bind::<{ STRICT }>(args)),
671                    CallName::Mod => ("mod", EvalOpBinary::Mod.bind::<{ STRICT }>(args)),
672                    CallName::Cardinality => {
673                        ("cardinality", EvalFnCardinality {}.bind::<{ STRICT }>(args))
674                    }
675                    CallName::ExtractYear => {
676                        ("extract year", EvalExtractFn::Year.bind::<{ STRICT }>(args))
677                    }
678                    CallName::ExtractMonth => (
679                        "extract month",
680                        EvalExtractFn::Month.bind::<{ STRICT }>(args),
681                    ),
682                    CallName::ExtractDay => {
683                        ("extract day", EvalExtractFn::Day.bind::<{ STRICT }>(args))
684                    }
685                    CallName::ExtractHour => {
686                        ("extract hour", EvalExtractFn::Hour.bind::<{ STRICT }>(args))
687                    }
688                    CallName::ExtractMinute => (
689                        "extract minute",
690                        EvalExtractFn::Minute.bind::<{ STRICT }>(args),
691                    ),
692                    CallName::ExtractSecond => (
693                        "extract second",
694                        EvalExtractFn::Second.bind::<{ STRICT }>(args),
695                    ),
696                    CallName::ExtractTimezoneHour => (
697                        "extract timezone_hour",
698                        EvalExtractFn::TzHour.bind::<{ STRICT }>(args),
699                    ),
700                    CallName::ExtractTimezoneMinute => (
701                        "extract timezone_minute",
702                        EvalExtractFn::TzMinute.bind::<{ STRICT }>(args),
703                    ),
704
705                    CallName::CollAvg(setq) => (
706                        "coll_avg",
707                        EvalCollFn::Avg(setq.into()).bind::<{ STRICT }>(args),
708                    ),
709                    CallName::CollCount(setq) => (
710                        "coll_count",
711                        EvalCollFn::Count(setq.into()).bind::<{ STRICT }>(args),
712                    ),
713                    CallName::CollMax(setq) => (
714                        "coll_max",
715                        EvalCollFn::Max(setq.into()).bind::<{ STRICT }>(args),
716                    ),
717                    CallName::CollMin(setq) => (
718                        "coll_min",
719                        EvalCollFn::Min(setq.into()).bind::<{ STRICT }>(args),
720                    ),
721                    CallName::CollSum(setq) => (
722                        "coll_sum",
723                        EvalCollFn::Sum(setq.into()).bind::<{ STRICT }>(args),
724                    ),
725                    CallName::CollAny(setq) => (
726                        "coll_any",
727                        EvalCollFn::Any(setq.into()).bind::<{ STRICT }>(args),
728                    ),
729                    CallName::CollEvery(setq) => (
730                        "coll_every",
731                        EvalCollFn::Every(setq.into()).bind::<{ STRICT }>(args),
732                    ),
733                    CallName::ByName(name) => {
734                        let plan = match self.catalog.get_function(name) {
735                            None => {
736                                self.errors.push(PlanningError::IllegalState(format!(
737                                    "Function call spec {name} does not exist in catalog",
738                                )));
739
740                                Ok(Box::new(ErrorNode::new()) as Box<dyn EvalExpr>)
741                            }
742                            Some(function) => match function.entry() {
743                                FunctionEntryFunction::Scalar(_) => {
744                                    todo!("Scalar functions in catalog by name")
745                                }
746                                FunctionEntryFunction::Table(tbl_fn) => {
747                                    Ok(Box::new(EvalFnBaseTableExpr {
748                                        args,
749                                        expr: tbl_fn.plan_eval(),
750                                    }) as Box<dyn EvalExpr>)
751                                }
752                                FunctionEntryFunction::Aggregate() => {
753                                    todo!("Aggregate functions in catalog by name")
754                                }
755                            },
756                        };
757                        (name.as_str(), plan)
758                    }
759                    CallName::ById(name, oid, overload_idx) => {
760                        let func = self.catalog.get_function_by_id(*oid);
761                        let plan = match func {
762                            Some(func) => match func.entry() {
763                                FunctionEntryFunction::Table(_) => {
764                                    todo!("table functions in catalog by id")
765                                }
766                                FunctionEntryFunction::Scalar(scfn) => {
767                                    match scfn.get(*overload_idx) {
768                                        None => {
769                                            self.errors.push(PlanningError::IllegalState(format!(
770                                                "Function call spec {name} overload #{overload_idx} does not exist in catalog",
771                                            )));
772
773                                            Ok(Box::new(ErrorNode::new()) as Box<dyn EvalExpr>)
774                                        }
775                                        Some(overload) => overload.clone().bind::<{ STRICT }>(args),
776                                    }
777                                }
778                                FunctionEntryFunction::Aggregate() => {
779                                    todo!("Aggregate functions in catalog by id")
780                                }
781                            },
782                            None => {
783                                self.errors.push(PlanningError::IllegalState(format!(
784                                    "Function call spec {name} does not exist in catalog",
785                                )));
786
787                                Ok(Box::new(ErrorNode::new()) as Box<dyn EvalExpr>)
788                            }
789                        };
790                        (name.as_str(), plan)
791                    }
792                }
793            }
794        };
795
796        self.unwrap_bind(name, bind)
797    }
798
799    fn plan_graph_plan<const STRICT: bool>(
800        &mut self,
801        pattern: &logical::graph::PathPatternMatch,
802    ) -> Result<eval::graph::plan::PathPatternMatch<StringGraphTypes>, PlanningError> {
803        self.plan_path_pattern_match::<{ STRICT }>(pattern)
804    }
805
806    fn plan_bind_spec<const STRICT: bool>(
807        &mut self,
808        pattern: &logical::graph::BindSpec,
809    ) -> Result<physical::BindSpec<StringGraphTypes>, PlanningError> {
810        Ok(physical::BindSpec(pattern.0.clone()))
811    }
812
813    #[allow(clippy::only_used_in_recursion)]
814    fn plan_label_filter<const STRICT: bool>(
815        &mut self,
816        pattern: &logical::graph::LabelFilter,
817    ) -> Result<physical::LabelFilter<StringGraphTypes>, PlanningError> {
818        Ok(match pattern {
819            logical::graph::LabelFilter::Always => physical::LabelFilter::Always,
820            logical::graph::LabelFilter::Never => physical::LabelFilter::Never,
821            logical::graph::LabelFilter::Named(n) => physical::LabelFilter::Named(n.clone()),
822            logical::graph::LabelFilter::Negated(inner) => physical::LabelFilter::Negated(
823                Box::new(self.plan_label_filter::<{ STRICT }>(inner)?),
824            ),
825            logical::graph::LabelFilter::Conjunction(inner) => {
826                let inner: Result<Vec<_>, _> = inner
827                    .iter()
828                    .map(|p| self.plan_label_filter::<{ STRICT }>(p))
829                    .collect();
830                physical::LabelFilter::Conjunction(inner?)
831            }
832            logical::graph::LabelFilter::Disjunction(inner) => {
833                let inner: Result<Vec<_>, _> = inner
834                    .iter()
835                    .map(|p| self.plan_label_filter::<{ STRICT }>(p))
836                    .collect();
837                physical::LabelFilter::Disjunction(inner?)
838            }
839        })
840    }
841
842    fn plan_value_filter<const STRICT: bool>(
843        &mut self,
844        pattern: &logical::graph::ValueFilter,
845    ) -> Result<physical::ValueFilter, PlanningError> {
846        Ok(match pattern {
847            logical::graph::ValueFilter::Always => physical::ValueFilter::Always,
848            logical::graph::ValueFilter::Filter(exprs) => {
849                let filters = self.plan_values::<{ STRICT }, _>(exprs.iter());
850                physical::ValueFilter::Filter(filters.into_iter().map(Rc::from).collect())
851            }
852        })
853    }
854
855    fn plan_node_filter<const STRICT: bool>(
856        &mut self,
857        pattern: &logical::graph::NodeFilter,
858    ) -> Result<physical::NodeFilter<StringGraphTypes>, PlanningError> {
859        Ok(physical::NodeFilter {
860            label: self.plan_label_filter::<{ STRICT }>(&pattern.label)?,
861            filter: self.plan_value_filter::<{ STRICT }>(&pattern.filter)?,
862        })
863    }
864
865    fn plan_edge_filter<const STRICT: bool>(
866        &mut self,
867        pattern: &logical::graph::EdgeFilter,
868    ) -> Result<physical::EdgeFilter<StringGraphTypes>, PlanningError> {
869        Ok(physical::EdgeFilter {
870            label: self.plan_label_filter::<{ STRICT }>(&pattern.label)?,
871            filter: self.plan_value_filter::<{ STRICT }>(&pattern.filter)?,
872        })
873    }
874
875    fn plan_step_filter<const STRICT: bool>(
876        &mut self,
877        pattern: &logical::graph::StepFilter,
878    ) -> Result<physical::TripleStepFilter<StringGraphTypes>, PlanningError> {
879        let dir = match pattern.dir {
880            logical::graph::DirectionFilter::L => physical::DirectionFilter::L,
881            logical::graph::DirectionFilter::R => physical::DirectionFilter::R,
882            logical::graph::DirectionFilter::U => physical::DirectionFilter::U,
883            logical::graph::DirectionFilter::LU => physical::DirectionFilter::LU,
884            logical::graph::DirectionFilter::UR => physical::DirectionFilter::UR,
885            logical::graph::DirectionFilter::LR => physical::DirectionFilter::LR,
886            logical::graph::DirectionFilter::LUR => physical::DirectionFilter::LUR,
887        };
888        Ok(physical::TripleStepFilter {
889            dir,
890            triple: self.plan_triple_filter::<{ STRICT }>(&pattern.triple)?,
891        })
892    }
893
894    fn plan_triple_filter<const STRICT: bool>(
895        &mut self,
896        pattern: &logical::graph::TripleFilter,
897    ) -> Result<physical::TripleFilter<StringGraphTypes>, PlanningError> {
898        Ok(physical::TripleFilter {
899            lhs: self.plan_node_filter::<{ STRICT }>(&pattern.lhs)?,
900            e: self.plan_edge_filter::<{ STRICT }>(&pattern.e)?,
901            rhs: self.plan_node_filter::<{ STRICT }>(&pattern.rhs)?,
902        })
903    }
904
905    fn plan_node_match<const STRICT: bool>(
906        &mut self,
907        pattern: &logical::graph::NodeMatch,
908    ) -> Result<physical::NodeMatch<StringGraphTypes>, PlanningError> {
909        Ok(physical::NodeMatch {
910            binder: self.plan_bind_spec::<{ STRICT }>(&pattern.binder)?,
911            spec: self.plan_node_filter::<{ STRICT }>(&pattern.spec)?,
912        })
913    }
914
915    fn plan_path_match<const STRICT: bool>(
916        &mut self,
917        pattern: &logical::graph::TripleMatch,
918    ) -> Result<physical::TripleStepMatch<StringGraphTypes>, PlanningError> {
919        let (l, m, r) = &pattern.binders;
920        let binders = (
921            self.plan_bind_spec::<{ STRICT }>(l)?,
922            self.plan_bind_spec::<{ STRICT }>(m)?,
923            self.plan_bind_spec::<{ STRICT }>(r)?,
924        );
925        let filter = self.plan_value_filter::<{ STRICT }>(&pattern.filter)?;
926        let path_mode = plan_path_mode(&pattern.path_mode)?;
927        Ok(physical::TripleStepMatch {
928            binders,
929            spec: self.plan_step_filter::<{ STRICT }>(&pattern.spec)?,
930            filter,
931            path_mode,
932        })
933    }
934
935    fn plan_path_pattern_match<const STRICT: bool>(
936        &mut self,
937        pattern: &logical::graph::PathPatternMatch,
938    ) -> Result<physical::PathPatternMatch<StringGraphTypes>, PlanningError> {
939        Ok(match pattern {
940            logical::graph::PathPatternMatch::Node(n) => {
941                physical::PathPatternMatch::Node(self.plan_node_match::<{ STRICT }>(n)?)
942            }
943            logical::graph::PathPatternMatch::Match(m) => {
944                physical::PathPatternMatch::Match(self.plan_path_match::<{ STRICT }>(m)?)
945            }
946            logical::graph::PathPatternMatch::Concat(ms, path_mode) => {
947                let path_mode = plan_path_mode(path_mode)?;
948                let matches: Result<Vec<_>, _> = ms
949                    .iter()
950                    .map(|triples_series| {
951                        let path_mode = plan_path_mode(&triples_series.path_mode)?;
952                        let triples: Result<Vec<_>, _> = triples_series
953                            .triples
954                            .iter()
955                            .map(|triple| {
956                                self.plan_path_match::<{ STRICT }>(triple)
957                                    .map(physical::PathPatternMatch::Match)
958                            })
959                            .collect();
960                        let filter = self.plan_value_filter::<{ STRICT }>(&triples_series.filter);
961                        match (triples, filter) {
962                            (Err(e), _) => Err(e),
963                            (_, Err(e)) => Err(e),
964                            (Ok(triples), Ok(filter)) => Ok(physical::PathPatternMatch::Concat(
965                                triples, filter, path_mode,
966                            )),
967                        }
968                    })
969                    .collect();
970                physical::PathPatternMatch::Concat(matches?, ValueFilter::Always, path_mode)
971            }
972        })
973    }
974}
975
976fn plan_path_mode(
977    path_mode: &logical::graph::PathMode,
978) -> Result<physical::PathMode, PlanningError> {
979    Ok(match path_mode {
980        logical::graph::PathMode::Walk => physical::PathMode::Walk,
981        logical::graph::PathMode::Trail => physical::PathMode::Trail,
982        logical::graph::PathMode::Acyclic => physical::PathMode::Acyclic,
983        logical::graph::PathMode::Simple => physical::PathMode::Simple,
984    })
985}
986
987fn plan_lit(lit: &Lit) -> Result<Value, PlanningError> {
988    let lit_to_val = |lit| plan_lit(lit);
989    Ok(match lit {
990        Lit::Null => Value::Null,
991        Lit::Missing => Value::Missing,
992        Lit::Int8(n) => Value::from(*n),
993        Lit::Int16(n) => Value::from(*n),
994        Lit::Int32(n) => Value::from(*n),
995        Lit::Int64(n) => Value::from(*n),
996        Lit::Decimal(d) => Value::from(*d),
997        Lit::Double(f) => Value::from(*f),
998        Lit::Bool(b) => Value::from(*b),
999        Lit::String(s) => Value::from(s.as_ref()),
1000        Lit::Variant(contents, _typ) => {
1001            let ion_typ = BoxedIonType::default().to_dyn_type_tag();
1002            let variant = Variant::new(contents.clone(), ion_typ)
1003                .map_err(|e| PlanningError::IllegalState(e.to_string()));
1004            Value::from(variant?)
1005        }
1006        Lit::Struct(strct) => strct
1007            .iter()
1008            .map(|(k, v)| lit_to_val(v).map(move |v| (k, v)))
1009            .collect::<Result<Tuple, _>>()?
1010            .into(),
1011        Lit::Bag(bag) => bag
1012            .iter()
1013            .map(lit_to_val)
1014            .collect::<Result<Bag, _>>()?
1015            .into(),
1016        Lit::List(list) => list
1017            .iter()
1018            .map(lit_to_val)
1019            .collect::<Result<List, _>>()?
1020            .into(),
1021    })
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026    use super::*;
1027    use partiql_catalog::catalog::PartiqlCatalog;
1028    use partiql_logical::CallExpr;
1029    use partiql_logical::ExprQuery;
1030
1031    #[test]
1032    fn test_logical_to_eval_plan_bad_num_arguments() {
1033        // Tests that the logical to eval plan can report multiple errors.
1034        // The following is a logical plan with two functions with the wrong number of arguments.
1035        // Equivalent query: ABS(1, 2) + MOD(3)
1036        // We define the logical plan manually because the AST to logical lowering will detect and
1037        // report the error.
1038        let mut logical = LogicalPlan::new();
1039        fn lit_int(i: usize) -> ValueExpr {
1040            ValueExpr::Lit(Box::new(logical::Lit::Int64(i as i64)))
1041        }
1042
1043        let expq = logical.add_operator(BindingsOp::ExprQuery(ExprQuery {
1044            expr: ValueExpr::BinaryExpr(
1045                BinaryOp::Add,
1046                Box::new(ValueExpr::Call(CallExpr {
1047                    name: CallName::Abs,
1048                    arguments: vec![lit_int(1), lit_int(2)],
1049                })),
1050                Box::new(ValueExpr::Call(CallExpr {
1051                    name: CallName::Mod,
1052                    arguments: vec![lit_int(3)],
1053                })),
1054            ),
1055        }));
1056        let sink = logical.add_operator(BindingsOp::Sink);
1057        logical.add_flow(expq, sink);
1058
1059        let catalog = PartiqlCatalog::default().to_shared_catalog();
1060        let mut planner = EvaluatorPlanner::new(EvaluationMode::Permissive, &catalog);
1061        let plan = planner.compile(&logical);
1062
1063        assert!(plan.is_err());
1064        let planning_errs = plan.expect_err("Expect errs").errors;
1065        assert_eq!(planning_errs.len(), 2);
1066    }
1067}