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 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 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 (
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 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 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 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 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}