Skip to content

Commit

Permalink
Fix subquery global binding lookup and error propagation (#454)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpschorr authored Mar 16, 2024
1 parent f9ea25c commit b6f73b7
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

### Fixed
- partiql-eval: Fixed propagation of errors in subqueries to outer query
- partiql-eval: Fixed handling of nested binding environments in subqueries

## [0.7.0] - 2024-03-12
### Changed
Expand Down
5 changes: 4 additions & 1 deletion partiql-catalog/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use partiql_value::{BindingsName, DateTime, Tuple, Value};
use std::any::Any;
use std::fmt::Debug;

pub trait Bindings<T>: Debug {
pub trait Bindings<T>: Debug
where
T: Debug,
{
fn get(&self, name: &BindingsName<'_>) -> Option<&T>;
}

Expand Down
27 changes: 27 additions & 0 deletions partiql-eval/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,33 @@ pub mod basic {
}
}
}

#[derive(Debug)]
pub struct NestedBindings<'a, T>
where
T: Debug,
{
bindings: MapBindings<T>,
parent: &'a dyn Bindings<T>,
}

impl<'a, T> NestedBindings<'a, T>
where
T: Debug,
{
pub fn new(bindings: MapBindings<T>, parent: &'a dyn Bindings<T>) -> Self {
Self { bindings, parent }
}
}

impl<'a, T> Bindings<T> for NestedBindings<'a, T>
where
T: Debug,
{
fn get(&self, name: &BindingsName<'_>) -> Option<&T> {
self.bindings.get(name).or_else(|| self.parent.get(name))
}
}
}

#[cfg(test)]
Expand Down
13 changes: 9 additions & 4 deletions partiql-eval/src/eval/evaluable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1321,10 +1321,15 @@ impl EvalExpr for EvalSubQueryExpr {
let nested_ctx: NestedContext<'_, '_> = NestedContext::new(bindings, ctx);

let mut plan = self.plan.borrow_mut();
if let Ok(evaluated) = plan.execute_mut(&nested_ctx) {
evaluated.result
} else {
Missing

match plan.execute_mut(&nested_ctx) {
Ok(evaluated) => evaluated.result,
Err(err) => {
for e in err.errors {
ctx.add_error(e);
}
Missing
}
}
};
Cow::Owned(value)
Expand Down
7 changes: 5 additions & 2 deletions partiql-eval/src/eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use petgraph::{Directed, Outgoing};

use partiql_value::Value;

use crate::env::basic::MapBindings;
use crate::env::basic::{MapBindings, NestedBindings};

use petgraph::graph::NodeIndex;

Expand Down Expand Up @@ -181,6 +181,7 @@ impl<'a> BasicContext<'a> {
}
}
}

impl<'a> SessionContext<'a> for BasicContext<'a> {
fn bindings(&self) -> &dyn Bindings<Value> {
&self.bindings
Expand All @@ -195,6 +196,7 @@ impl<'a> SessionContext<'a> for BasicContext<'a> {
self.user.get(&key).copied()
}
}

impl<'a> EvalContext<'a> for BasicContext<'a> {
fn as_session(&'a self) -> &'a dyn SessionContext<'a> {
self
Expand All @@ -215,12 +217,13 @@ impl<'a> EvalContext<'a> for BasicContext<'a> {

#[derive(Debug)]
pub struct NestedContext<'a, 'c> {
pub bindings: MapBindings<Value>,
pub bindings: NestedBindings<'a, Value>,
pub parent: &'a dyn EvalContext<'c>,
}

impl<'a, 'c> NestedContext<'a, 'c> {
pub fn new(bindings: MapBindings<Value>, parent: &'a dyn EvalContext<'c>) -> Self {
let bindings = NestedBindings::new(bindings, parent.bindings());
NestedContext { bindings, parent }
}
}
Expand Down
2 changes: 2 additions & 0 deletions partiql/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![deny(rust_2018_idioms)]
#![deny(clippy::all)]

mod subquery_tests;

#[cfg(test)]
mod tests {
use partiql_ast_passes::error::AstTransformationError;
Expand Down
155 changes: 155 additions & 0 deletions partiql/src/subquery_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#![deny(rust_2018_idioms)]

Check warning on line 1 in partiql/src/subquery_tests.rs

View workflow job for this annotation

GitHub Actions / clippy

deny(rust_2018_idioms) is ignored unless specified at crate level

warning: deny(rust_2018_idioms) is ignored unless specified at crate level --> partiql/src/subquery_tests.rs:1:9 | 1 | #![deny(rust_2018_idioms)] | ^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_attributes)]` on by default
#![deny(clippy::all)]
#![warn(clippy::pedantic)]

#[cfg(test)]
mod tests {
use partiql_catalog::context::SystemContext;
use partiql_catalog::{Catalog, PartiqlCatalog};
use partiql_eval::env::basic::MapBindings;
use partiql_eval::error::EvalErr;
use partiql_eval::eval::BasicContext;
use partiql_eval::plan::EvaluationMode;
use partiql_logical::{LogicalPlan, ProjectValue, VarRefType};
use partiql_value::{bag, tuple, Bag, BindingsName, DateTime, Value};
use std::any::Any;

#[track_caller]
#[inline]
pub(crate) fn evaluate(
catalog: &dyn Catalog,
logical: partiql_logical::LogicalPlan<partiql_logical::BindingsOp>,
bindings: MapBindings<Value>,
ctx_vals: &[(String, &(dyn Any))],
) -> Result<Value, EvalErr> {
let mut planner =
partiql_eval::plan::EvaluatorPlanner::new(EvaluationMode::Strict, catalog);

let mut plan = planner.compile(&logical).expect("Expect no plan error");

let sys = SystemContext {
now: DateTime::from_system_now_utc(),
};
let mut ctx = BasicContext::new(bindings, sys);
for (k, v) in ctx_vals {
ctx.user.insert(k.as_str().into(), *v);
}
plan.execute_mut(&ctx).map(|out| out.result)
}

#[test]
fn locals_in_subqueries() {
// `SELECT VALUE _1 from (SELECT VALUE foo from <<{'a': 'b'}>> AS foo) AS _1;`
let mut sub_query = partiql_logical::LogicalPlan::new();
let scan_op_id =
sub_query.add_operator(partiql_logical::BindingsOp::Scan(partiql_logical::Scan {
expr: partiql_logical::ValueExpr::Lit(Box::new(Value::Bag(Box::new(Bag::from(
vec![tuple![("a", "b")].into()],
))))),
as_key: "foo".into(),
at_key: None,
}));
let project_value_op_id = sub_query.add_operator(
partiql_logical::BindingsOp::ProjectValue(partiql_logical::ProjectValue {
expr: partiql_logical::ValueExpr::VarRef(
BindingsName::CaseSensitive("foo".into()),
VarRefType::Local,
),
}),
);
sub_query.add_flow(scan_op_id, project_value_op_id);

let sink_op_id = sub_query.add_operator(partiql_logical::BindingsOp::Sink);
sub_query.add_flow(project_value_op_id, sink_op_id);

let mut plan = LogicalPlan::new();
let scan_op_id =
plan.add_operator(partiql_logical::BindingsOp::Scan(partiql_logical::Scan {
expr: partiql_logical::ValueExpr::SubQueryExpr(partiql_logical::SubQueryExpr {
plan: sub_query,
}),
as_key: "_1".into(),
at_key: None,
}));

let project_value_op_id =
plan.add_operator(partiql_logical::BindingsOp::ProjectValue(ProjectValue {
expr: partiql_logical::ValueExpr::VarRef(
BindingsName::CaseSensitive("_1".into()),
VarRefType::Local,
),
}));
plan.add_flow(scan_op_id, project_value_op_id);

let sink_op_id = plan.add_operator(partiql_logical::BindingsOp::Sink);
plan.add_flow(project_value_op_id, sink_op_id);

let catalog = PartiqlCatalog::default();
let bindings = MapBindings::default();
let res = evaluate(&catalog, plan, bindings, &[]).expect("should eval correctly");
dbg!(&res);
assert!(res != Value::Missing);
assert_eq!(res, Value::from(bag![tuple![("a", "b")]]))
}

#[test]
fn globals_in_subqueries() {
// `foo` is defined in global environment as `<<{'a': 'b'}>>`
// `SELECT VALUE _1 FROM (SELECT VALUE foo FROM foo AS foo) AS _1;`
let mut sub_query = partiql_logical::LogicalPlan::new();
let scan_op_id =
sub_query.add_operator(partiql_logical::BindingsOp::Scan(partiql_logical::Scan {
expr: partiql_logical::ValueExpr::VarRef(
BindingsName::CaseSensitive("foo".into()),
VarRefType::Global,
),
as_key: "foo".into(),
at_key: None,
}));
let project_value_op_id = sub_query.add_operator(
partiql_logical::BindingsOp::ProjectValue(partiql_logical::ProjectValue {
expr: partiql_logical::ValueExpr::VarRef(
BindingsName::CaseSensitive("foo".into()),
VarRefType::Local,
),
}),
);
sub_query.add_flow(scan_op_id, project_value_op_id);

let sink_op_id = sub_query.add_operator(partiql_logical::BindingsOp::Sink);
sub_query.add_flow(project_value_op_id, sink_op_id);

let mut plan = LogicalPlan::new();
let scan_op_id =
plan.add_operator(partiql_logical::BindingsOp::Scan(partiql_logical::Scan {
expr: partiql_logical::ValueExpr::SubQueryExpr(partiql_logical::SubQueryExpr {
plan: sub_query,
}),
as_key: "_1".into(),
at_key: None,
}));

let project_value_op_id =
plan.add_operator(partiql_logical::BindingsOp::ProjectValue(ProjectValue {
expr: partiql_logical::ValueExpr::VarRef(
BindingsName::CaseSensitive("_1".into()),
VarRefType::Local,
),
}));
plan.add_flow(scan_op_id, project_value_op_id);

let sink_op_id = plan.add_operator(partiql_logical::BindingsOp::Sink);
plan.add_flow(project_value_op_id, sink_op_id);

let catalog = PartiqlCatalog::default();
let mut bindings = MapBindings::default();
bindings.insert(
"foo",
Value::Bag(Box::new(Bag::from(vec![tuple![("a", "b")].into()]))),
);
let res = evaluate(&catalog, plan, bindings, &[]).expect("should eval correctly");
dbg!(&res);
assert!(res != Value::Missing);
assert_eq!(res, Value::from(bag![tuple![("a", "b")]]))
}
}

1 comment on commit b6f73b7

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PartiQL (rust) Benchmark

Benchmark suite Current: b6f73b7 Previous: f9ea25c Ratio
arith_agg-avg 756669 ns/iter (± 7483) 753295 ns/iter (± 10692) 1.00
arith_agg-avg_distinct 837761 ns/iter (± 2793) 840324 ns/iter (± 4163) 1.00
arith_agg-count 797658 ns/iter (± 15101) 793350 ns/iter (± 10629) 1.01
arith_agg-count_distinct 830209 ns/iter (± 5183) 839384 ns/iter (± 2146) 0.99
arith_agg-min 795669 ns/iter (± 2988) 798513 ns/iter (± 6679) 1.00
arith_agg-min_distinct 834058 ns/iter (± 2519) 833475 ns/iter (± 2626) 1.00
arith_agg-max 807020 ns/iter (± 1912) 804812 ns/iter (± 46953) 1.00
arith_agg-max_distinct 846442 ns/iter (± 2326) 840423 ns/iter (± 3375) 1.01
arith_agg-sum 803699 ns/iter (± 3407) 799199 ns/iter (± 31039) 1.01
arith_agg-sum_distinct 834747 ns/iter (± 2232) 837469 ns/iter (± 3462) 1.00
arith_agg-avg-count-min-max-sum 945625 ns/iter (± 2785) 940946 ns/iter (± 3800) 1.00
arith_agg-avg-count-min-max-sum-group_by 1198499 ns/iter (± 21056) 1191733 ns/iter (± 24045) 1.01
arith_agg-avg-count-min-max-sum-group_by-group_as 1745326 ns/iter (± 17859) 1765004 ns/iter (± 15301) 0.99
arith_agg-avg_distinct-count_distinct-min_distinct-max_distinct-sum_distinct 1200656 ns/iter (± 13514) 1266530 ns/iter (± 6843) 0.95
arith_agg-avg_distinct-count_distinct-min_distinct-max_distinct-sum_distinct-group_by 1514836 ns/iter (± 27290) 1531211 ns/iter (± 18789) 0.99
arith_agg-avg_distinct-count_distinct-min_distinct-max_distinct-sum_distinct-group_by-group_as 2070089 ns/iter (± 7165) 2097034 ns/iter (± 9635) 0.99
parse-1 4315 ns/iter (± 15) 4246 ns/iter (± 14) 1.02
parse-15 39841 ns/iter (± 112) 39512 ns/iter (± 138) 1.01
parse-30 77570 ns/iter (± 1255) 77971 ns/iter (± 220) 0.99
compile-1 4386 ns/iter (± 46) 4275 ns/iter (± 98) 1.03
compile-15 31381 ns/iter (± 968) 30620 ns/iter (± 105) 1.02
compile-30 63121 ns/iter (± 256) 62886 ns/iter (± 209) 1.00
plan-1 69295 ns/iter (± 195) 69165 ns/iter (± 229) 1.00
plan-15 1074600 ns/iter (± 12131) 1069606 ns/iter (± 19098) 1.00
plan-30 2156016 ns/iter (± 3093) 2148107 ns/iter (± 6347) 1.00
eval-1 12537278 ns/iter (± 261870) 13395677 ns/iter (± 284023) 0.94
eval-15 85439293 ns/iter (± 841699) 87240379 ns/iter (± 742836) 0.98
eval-30 164229235 ns/iter (± 1538778) 167428923 ns/iter (± 586993) 0.98
join 9799 ns/iter (± 284) 9691 ns/iter (± 38) 1.01
simple 2524 ns/iter (± 18) 2511 ns/iter (± 10) 1.01
simple-no 457 ns/iter (± 1) 431 ns/iter (± 2) 1.06
numbers 57 ns/iter (± 0) 57 ns/iter (± 0) 1
parse-simple 559 ns/iter (± 9) 539 ns/iter (± 1) 1.04
parse-ion 1753 ns/iter (± 4) 1762 ns/iter (± 16) 0.99
parse-group 5826 ns/iter (± 40) 5689 ns/iter (± 25) 1.02
parse-complex 14918 ns/iter (± 68) 14800 ns/iter (± 86) 1.01
parse-complex-fexpr 21549 ns/iter (± 913) 20886 ns/iter (± 91) 1.03

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.