diff --git a/partiql-eval/src/eval/evaluable.rs b/partiql-eval/src/eval/evaluable.rs index 72495a45..8433871c 100644 --- a/partiql-eval/src/eval/evaluable.rs +++ b/partiql-eval/src/eval/evaluable.rs @@ -1374,36 +1374,6 @@ impl Evaluable for EvalSink { } } -/// Represents an evaluation operator for sub-queries, e.g. `SELECT a FROM b` in -/// `SELECT b.c, (SELECT a FROM b) FROM books AS b`. -#[derive(Debug)] -pub(crate) struct EvalSubQueryExpr { - pub(crate) plan: Rc>, -} - -impl EvalSubQueryExpr { - pub(crate) fn new(plan: EvalPlan) -> Self { - EvalSubQueryExpr { - plan: Rc::new(RefCell::new(plan)), - } - } -} - -impl EvalExpr for EvalSubQueryExpr { - fn evaluate<'a>(&'a self, bindings: &'a Tuple, _ctx: &'a dyn EvalContext) -> Cow<'a, Value> { - let value = if let Ok(evaluated) = self - .plan - .borrow_mut() - .execute_mut(MapBindings::from(bindings)) - { - evaluated.result - } else { - Missing - }; - Cow::Owned(value) - } -} - /// /// Coercion function F for bag operators described in RFC-0007 /// - F(absent_value) -> << >> diff --git a/partiql-eval/src/eval/expr/operators.rs b/partiql-eval/src/eval/expr/operators.rs index aeaa0398..f8589cac 100644 --- a/partiql-eval/src/eval/expr/operators.rs +++ b/partiql-eval/src/eval/expr/operators.rs @@ -11,7 +11,7 @@ use partiql_types::{ ArrayType, BagType, PartiqlType, StructType, TypeKind, TYPE_ANY, TYPE_BOOL, TYPE_NUMERIC_TYPES, }; use partiql_value::Value::{Boolean, Missing, Null}; -use partiql_value::{BinaryAnd, BinaryOr, EqualityValue, NullableEq, NullableOrd, Value}; +use partiql_value::{BinaryAnd, BinaryOr, EqualityValue, NullableEq, NullableOrd, Tuple, Value}; use std::borrow::{Borrow, Cow}; use std::fmt::Debug; @@ -51,6 +51,25 @@ impl ExecuteEvalExpr<0> for Value { } } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) struct EvalOpIdentity {} + +impl BindEvalExpr for EvalOpIdentity { + fn bind( + &self, + args: Vec>, + ) -> Result, BindError> { + return Ok(Box::new(self.clone())); + } +} + +impl EvalExpr for EvalOpIdentity { + #[inline] + fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> { + Cow::Owned(Value::from(bindings.clone())) + } +} + #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub(crate) enum EvalOpUnary { Pos, diff --git a/partiql-eval/src/plan.rs b/partiql-eval/src/plan.rs index 3f2a64fb..e3079a11 100644 --- a/partiql-eval/src/plan.rs +++ b/partiql-eval/src/plan.rs @@ -14,15 +14,16 @@ use crate::error::{ErrorNode, PlanErr, PlanningError}; use crate::eval; use crate::eval::evaluable::{ Any, Avg, Count, EvalGroupingStrategy, EvalJoinKind, EvalOrderBy, EvalOrderBySortCondition, - EvalOrderBySortSpec, EvalOuterExcept, EvalOuterIntersect, EvalOuterUnion, EvalSubQueryExpr, - Evaluable, Every, Max, Min, Sum, + EvalOrderBySortSpec, EvalOuterExcept, EvalOuterIntersect, EvalOuterUnion, Evaluable, Every, + Max, Min, Sum, }; use crate::eval::expr::{ BindError, BindEvalExpr, EvalBagExpr, EvalBetweenExpr, EvalCollFn, EvalDynamicLookup, EvalExpr, EvalExtractFn, EvalFnAbs, EvalFnBaseTableExpr, EvalFnCardinality, EvalFnExists, EvalFnOverlay, EvalFnPosition, EvalFnSubstring, EvalIsTypeExpr, EvalLikeMatch, - EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr, EvalOpBinary, EvalOpUnary, - EvalPath, EvalSearchedCaseExpr, EvalStringFn, EvalTrimFn, EvalTupleExpr, EvalVarRef, + EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr, EvalOpBinary, EvalOpIdentity, + EvalOpUnary, EvalPath, EvalSearchedCaseExpr, EvalStringFn, EvalTrimFn, EvalTupleExpr, + EvalVarRef, }; use crate::eval::EvalPlan; use partiql_catalog::Catalog; @@ -405,6 +406,10 @@ impl<'c> EvaluatorPlanner<'c> { }; let (name, bind) = match ve { + ValueExpr::Identity => ( + "identity operator", + EvalOpIdentity {}.bind::<{ STRICT }>(vec![]), + ), ValueExpr::UnExpr(op, operand) => ( "unary operator", EvalOpUnary::from(op).bind::<{ STRICT }>(plan_args(&[operand])), @@ -510,12 +515,6 @@ impl<'c> EvaluatorPlanner<'c> { ("pattern expr", expr) } - ValueExpr::SubQueryExpr(expr) => ( - "subquery", - Ok(Box::new(EvalSubQueryExpr::new( - self.plan_eval::<{ STRICT }>(&expr.plan), - )) as Box), - ), ValueExpr::SimpleCase(e) => { let cases = e .cases diff --git a/partiql-logical-planner/src/lower.rs b/partiql-logical-planner/src/lower.rs index e742465e..30005f28 100644 --- a/partiql-logical-planner/src/lower.rs +++ b/partiql-logical-planner/src/lower.rs @@ -40,14 +40,28 @@ use std::sync::atomic::{AtomicU32, Ordering}; type FnvIndexMap = IndexMap; +#[macro_export] +macro_rules! illegal_state_fault { + ($self:ident, $msg:expr) => { + illegal_state_err!($self, $msg); + return partiql_ast::visit::Traverse::Stop; + }; +} + +#[macro_export] +macro_rules! illegal_state_err { + ($self:ident, $msg:expr) => { + $self + .errors + .push(AstTransformError::IllegalState($msg.to_string())); + }; +} + #[macro_export] macro_rules! eq_or_fault { ($self:ident, $lhs:expr, $rhs:expr, $msg:expr) => { if $lhs != $rhs { - $self - .errors - .push(AstTransformError::IllegalState($msg.to_string())); - return partiql_ast::visit::Traverse::Stop; + illegal_state_fault!($self, $msg); } }; } @@ -56,10 +70,7 @@ macro_rules! eq_or_fault { macro_rules! true_or_fault { ($self:ident, $expr:expr, $msg:expr) => { if !$expr { - $self - .errors - .push(AstTransformError::IllegalState($msg.to_string())); - return partiql_ast::visit::Traverse::Stop; + illegal_state_fault!($self, $msg); } }; } @@ -424,26 +435,42 @@ impl<'a> AstToLogical<'a> { ValueExpr::VarRef(symprim_to_binding(&varref.name), VarRefType::Global) } + #[inline] + fn enter_ctx(&mut self, ctx: QueryContext) { + self.ctx_stack.push(ctx); + } + + #[inline] + fn exit_ctx(&mut self) -> QueryContext { + self.ctx_stack.pop().expect("ctx level") + } + + #[inline] + fn current_ctx(&self) -> Option<&QueryContext> { + self.ctx_stack.last() + } + #[inline] fn enter_q(&mut self) { self.q_stack.push(Default::default()); - self.ctx_stack.push(QueryContext::Query); + self.enter_ctx(QueryContext::Query); } #[inline] fn exit_q(&mut self) -> QueryClauses { - self.ctx_stack.pop(); + self.exit_ctx(); self.q_stack.pop().expect("q level") } #[inline] - fn current_ctx(&self) -> Option<&QueryContext> { - self.ctx_stack.last() + fn enter_from(&mut self, id: NodeId) { + self.from_lets.insert(id); + self.enter_ctx(QueryContext::FromLet); } #[inline] - fn current_ctx_mut(&mut self) -> &mut QueryContext { - self.ctx_stack.last_mut().unwrap() + fn exit_from(&mut self) { + self.exit_ctx(); } #[inline] @@ -504,15 +531,23 @@ impl<'a> AstToLogical<'a> { #[inline] fn enter_path(&mut self) { self.path_stack.push(vec![]); - self.ctx_stack.push(QueryContext::Query); } #[inline] fn exit_path(&mut self) -> Vec { - self.ctx_stack.pop(); self.path_stack.pop().expect("path level") } + #[inline] + fn enter_path_step(&mut self) { + self.enter_ctx(QueryContext::Path); + } + + #[inline] + fn exit_path_step(&mut self) { + self.exit_ctx(); + } + #[inline] fn push_path_step(&mut self, step: PathComponent) { self.path_stack.last_mut().unwrap().push(step); @@ -521,12 +556,12 @@ impl<'a> AstToLogical<'a> { #[inline] fn enter_sort(&mut self) { self.sort_stack.push(vec![]); - self.ctx_stack.push(QueryContext::Order); + self.enter_ctx(QueryContext::Order); } #[inline] fn exit_sort(&mut self) -> Vec { - self.ctx_stack.pop(); + self.exit_ctx(); self.sort_stack.pop().expect("sort specs") } @@ -1267,16 +1302,16 @@ impl<'a, 'ast> Visitor<'ast> for AstToLogical<'a> { Traverse::Continue } - fn enter_var_ref(&mut self, _var_ref: &'ast VarRef) -> Traverse { + fn enter_var_ref(&mut self, var_ref: &'ast VarRef) -> Traverse { let is_path = matches!(self.current_ctx(), Some(QueryContext::Path)); if !is_path { - let options = self.resolve_varref(_var_ref); + let options = self.resolve_varref(var_ref); self.push_vexpr(options); } else { let VarRef { name: SymbolPrimitive { value, case }, qualifier: _, - } = _var_ref; + } = var_ref; let name = match case { CaseSensitivity::CaseSensitive => { BindingsName::CaseSensitive(Cow::Owned(value.clone())) @@ -1311,8 +1346,9 @@ impl<'a, 'ast> Visitor<'ast> for AstToLogical<'a> { Traverse::Continue } - fn enter_path_step(&mut self, _path_step: &'ast PathStep) -> Traverse { - if let PathStep::PathExpr(expr) = _path_step { + fn enter_path_step(&mut self, path_step: &'ast PathStep) -> Traverse { + self.enter_path_step(); + if let PathStep::PathExpr(expr) = path_step { self.enter_env(); match *(expr.index) { Expr::VarRef(_) => { @@ -1363,6 +1399,7 @@ impl<'a, 'ast> Visitor<'ast> for AstToLogical<'a> { }; self.push_path_step(step); + self.exit_path_step(); Traverse::Continue } @@ -1386,9 +1423,9 @@ impl<'a, 'ast> Visitor<'ast> for AstToLogical<'a> { } fn enter_from_let(&mut self, from_let: &'ast FromLet) -> Traverse { - self.from_lets.insert(*self.current_node()); - *self.current_ctx_mut() = QueryContext::FromLet; + self.enter_from(*self.current_node()); self.enter_env(); + self.enter_benv(); let id = *self.current_node(); @@ -1402,37 +1439,70 @@ impl<'a, 'ast> Visitor<'ast> for AstToLogical<'a> { } fn exit_from_let(&mut self, from_let: &'ast FromLet) -> Traverse { - *self.current_ctx_mut() = QueryContext::Query; + self.exit_from(); let mut env = self.exit_env(); - eq_or_fault!(self, env.len(), 1, "env.len() != 1"); + let mut benv = self.exit_benv(); - let expr = env.pop().unwrap(); + let mut lower = |expr| { + let FromLet { + kind, + as_alias, + at_alias, + .. + } = from_let; + + let as_key = self.infer_id(&expr, as_alias).value; + let at_key = at_alias + .as_ref() + .map(|SymbolPrimitive { value, case: _ }| value.clone()); + + let bexpr = match kind { + FromLetKind::Scan => logical::BindingsOp::Scan(logical::Scan { + expr, + as_key, + at_key, + }), + FromLetKind::Unpivot => logical::BindingsOp::Unpivot(logical::Unpivot { + expr, + as_key, + at_key, + }), + }; + let id = self.plan.add_operator(bexpr); + self.push_bexpr(id); + id + }; - let FromLet { - kind, - as_alias, - at_alias, - .. - } = from_let; - let as_key = self.infer_id(&expr, as_alias).value; - let at_key = at_alias - .as_ref() - .map(|SymbolPrimitive { value, case: _ }| value.clone()); + // Each clause of a `FROM` (i.e., a 'from let') should have either an expression or a relation + // as input. + // + // An expression input corresponds to the object to the right of the `FROM` in clauses like: + // - `FROM <<{a:1},{a:2},{a:3}>>` + // - `FROM MyTable` + // + // A relation input corresponds to a subquery input like: + // - `FROM (SELECT ... FROM ...) + match (env.len(), benv.len()) { + (0, 1) => { + // A relation input; Lower to a Scan over relation's output + eq_or_fault!(self, benv.len(), 1, "benv.len() != 1"); + let src = benv.pop().unwrap(); + let expr = ValueExpr::Identity; + + let id = lower(expr); + self.plan.add_flow(src, id); + } + (1, 0) => { + // An expression input; Lower to a Scan/Unpivot over an expression evaluation + eq_or_fault!(self, env.len(), 1, "env.len() != 1"); + let expr = env.pop().unwrap(); - let bexpr = match kind { - FromLetKind::Scan => logical::BindingsOp::Scan(logical::Scan { - expr, - as_key, - at_key, - }), - FromLetKind::Unpivot => logical::BindingsOp::Unpivot(logical::Unpivot { - expr, - as_key, - at_key, - }), - }; - let id = self.plan.add_operator(bexpr); - self.push_bexpr(id); + let _ = lower(expr); + } + _ => { + illegal_state_fault!(self, "Input to From Let is malformed"); + } + } Traverse::Continue } diff --git a/partiql-logical/src/lib.rs b/partiql-logical/src/lib.rs index 9a485d76..328e5f13 100644 --- a/partiql-logical/src/lib.rs +++ b/partiql-logical/src/lib.rs @@ -410,6 +410,7 @@ pub struct ExprQuery { #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ValueExpr { + Identity, UnExpr(UnaryOp, Box), BinaryExpr(BinaryOp, Box, Box), Lit(Box), @@ -421,7 +422,6 @@ pub enum ValueExpr { BagExpr(BagExpr), BetweenExpr(BetweenExpr), PatternMatchExpr(PatternMatchExpr), - SubQueryExpr(SubQueryExpr), SimpleCase(SimpleCase), SearchedCase(SearchedCase), IsTypeExpr(IsTypeExpr), diff --git a/partiql-value/src/lib.rs b/partiql-value/src/lib.rs index b9b2f016..0d2e0f20 100644 --- a/partiql-value/src/lib.rs +++ b/partiql-value/src/lib.rs @@ -535,6 +535,11 @@ impl Value { self.is_bag() || self.is_list() } + #[inline] + pub fn is_string(&self) -> bool { + matches!(self, Value::String(_)) + } + #[inline] /// Returns true if and only if Value is an integer, real, or decimal pub fn is_number(&self) -> bool {