Skip to content

Commit

Permalink
Add TUPLE_UNION and TUPLE_MERGE; Add 'vararg' functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jpschorr committed Sep 17, 2024
1 parent deff7a8 commit f4af8dd
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 33 deletions.
2 changes: 2 additions & 0 deletions partiql-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![deny(rust_2018_idioms)]
#![deny(clippy::all)]

pub static FN_VAR_ARG_MAX: usize = 10;
pub mod metadata;
pub mod node;
pub mod syntax;
83 changes: 83 additions & 0 deletions partiql-eval/src/eval/eval_expr_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,15 @@ impl<const STRICT: bool, const N: usize, E: ExecuteEvalExpr<N>, ArgC: ArgChecker
ControlFlow::Break(Missing)
};

match evaluate_args::<{ STRICT }, ArgC, _>(&self.args, |n| &self.types[n], bindings, ctx) {
ControlFlow::Continue(result) => match result.try_into() {
Ok(a) => ControlFlow::Continue(a),
Err(args) => err_arg_count_mismatch(args),
},
ControlFlow::Break(v) => ControlFlow::Break(v),
}

/*
let mut result = Vec::with_capacity(N);
let mut propagate = None;
Expand Down Expand Up @@ -324,6 +333,80 @@ impl<const STRICT: bool, const N: usize, E: ExecuteEvalExpr<N>, ArgC: ArgChecker
Err(args) => err_arg_count_mismatch(args),
}
}
*/
}
}

pub(crate) fn evaluate_args<
'a,
'c,
't,
const STRICT: bool,
ArgC: ArgChecker,
F: Fn(usize) -> &'t PartiqlShape,
>(
args: &'a [Box<dyn EvalExpr>],
types: F,
bindings: &'a Tuple,
ctx: &'c dyn EvalContext<'c>,
) -> ControlFlow<Value, Vec<Cow<'a, Value>>>
where
'c: 'a,
{
let mut result = Vec::with_capacity(args.len());

let mut propagate = None;

for (idx, arg) in args.iter().enumerate() {
let typ = types(idx);
let arg = arg.evaluate(bindings, ctx);

match ArgC::arg_check(typ, arg) {
ArgCheckControlFlow::Continue(v) => {
if propagate.is_none() {
result.push(v);
}
}
ArgCheckControlFlow::Propagate(v) => {
propagate = match propagate {
None => Some(v),
Some(prev) => match (prev, v) {
(Null, Missing) => Missing,
(Missing, _) => Missing,
(Null, _) => Null,
(_, new) => new,
}
.into(),
};
}
ArgCheckControlFlow::ShortCircuit(v) => return ControlFlow::Break(v),
ArgCheckControlFlow::ErrorOrShortCircuit(v) => {
if STRICT {
let arg_end = args.len();
let arg_count = 0..arg_end;

let signature = (arg_count)
.map(types)
.map(|typ| format!("{}", typ))
.join(",");
let before = (0..idx).map(|_| "_");
let arg = "MISSING"; // TODO display actual argument?
let after = (idx + 1..arg_end).map(|_| "_");
let arg_pattern = before.chain(std::iter::once(arg)).chain(after).join(",");
let msg = format!("expected `({signature})`, found `({arg_pattern})`");
ctx.add_error(EvaluationError::IllegalState(msg));
}
return ControlFlow::Break(v);
}
}
}

if let Some(v) = propagate {
// If `propagate` is a `Some`, then argument type checking failed, propagate the value
ControlFlow::Break(v)
} else {
// If `propagate` is `None`, then return result
ControlFlow::Continue(result)
}
}

Expand Down
91 changes: 88 additions & 3 deletions partiql-eval/src/eval/expr/operators.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::eval::eval_expr_wrapper::{
ArgCheckControlFlow, ArgChecker, ArgShortCircuit, BinaryValueExpr, DefaultArgChecker,
ExecuteEvalExpr, NullArgChecker, PropagateMissing, PropagateNull, TernaryValueExpr,
UnaryValueExpr,
evaluate_args, ArgCheckControlFlow, ArgChecker, ArgShortCircuit, BinaryValueExpr,
DefaultArgChecker, ExecuteEvalExpr, NullArgChecker, PropagateMissing, PropagateNull,
TernaryValueExpr, UnaryValueExpr,
};

use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr};
Expand Down Expand Up @@ -358,3 +358,88 @@ impl BindEvalExpr for EvalFnCardinality {
})
}
}

/// Represents a built-in tupleunion function, e.g. `tuple_union({ 'bob': 1 }, { 'sally': 2 })`.
#[derive(Debug, Default, Clone)]
pub(crate) struct EvalFnTupleUnion {}

impl BindEvalExpr for EvalFnTupleUnion {
fn bind<const STRICT: bool>(
&self,
args: Vec<Box<dyn EvalExpr>>,
) -> Result<Box<dyn EvalExpr>, BindError> {
Ok(Box::new(EvalExprFnTupleUnion::<{ STRICT }> { args }))
}
}

#[derive(Debug, Default)]
pub(crate) struct EvalExprFnTupleUnion<const STRICT: bool> {
args: Vec<Box<dyn EvalExpr>>,
}

impl<const STRICT: bool> EvalExpr for EvalExprFnTupleUnion<STRICT> {
fn evaluate<'a, 'c>(
&'a self,
bindings: &'a Tuple,
ctx: &'c dyn EvalContext<'c>,
) -> Cow<'a, Value>
where
'c: 'a,
{
type Check<const STRICT: bool> = DefaultArgChecker<STRICT, PropagateMissing<true>>;
let typ = PartiqlShapeBuilder::init_or_get().new_struct(StructType::new_any());
match evaluate_args::<{ STRICT }, Check<STRICT>, _>(&self.args, |_| &typ, bindings, ctx) {
ControlFlow::Break(v) => Cow::Owned(v),
ControlFlow::Continue(args) => {
let mut t = Tuple::default();
for arg in args {
t.extend(
arg.as_tuple_ref()
.pairs()
.map(|(k, v)| (k.as_str(), v.clone())),
)
}
Cow::Owned(Value::from(t))
}
}
}
}

/// Represents a built-in tuplemerge function, e.g. `tuple_merge({ 'bob': 1 }, { 'sally': 2 })`.
#[derive(Debug, Default, Clone)]
pub(crate) struct EvalFnTupleMerge {}

impl BindEvalExpr for EvalFnTupleMerge {
fn bind<const STRICT: bool>(
&self,
args: Vec<Box<dyn EvalExpr>>,
) -> Result<Box<dyn EvalExpr>, BindError> {
Ok(Box::new(EvalExprFnTupleMerge::<{ STRICT }> { args }))
}
}

#[derive(Debug, Default)]
pub(crate) struct EvalExprFnTupleMerge<const STRICT: bool> {
args: Vec<Box<dyn EvalExpr>>,
}

impl<const STRICT: bool> EvalExpr for EvalExprFnTupleMerge<STRICT> {
fn evaluate<'a, 'c>(
&'a self,
bindings: &'a Tuple,
ctx: &'c dyn EvalContext<'c>,
) -> Cow<'a, Value>
where
'c: 'a,
{
type Check<const STRICT: bool> = DefaultArgChecker<STRICT, PropagateMissing<true>>;
let typ = PartiqlShapeBuilder::init_or_get().new_struct(StructType::new_any());
match evaluate_args::<{ STRICT }, Check<STRICT>, _>(&self.args, |_| &typ, bindings, ctx) {
ControlFlow::Break(v) => Cow::Owned(v),
ControlFlow::Continue(args) => args
.into_iter()
.reduce(|l, r| Cow::Owned(l.as_tuple_ref().tuple_concat(&r.as_tuple_ref()).into()))
.unwrap_or_else(|| Cow::Owned(Value::from(Tuple::default()))),
}
}
}
27 changes: 1 addition & 26 deletions partiql-eval/src/eval/expr/strings.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
use crate::eval::eval_expr_wrapper::{
BinaryValueExpr, EvalExprWrapper, ExecuteEvalExpr, QuaternaryValueExpr, TernaryValueExpr,
UnaryValueExpr,
BinaryValueExpr, QuaternaryValueExpr, TernaryValueExpr, UnaryValueExpr,
};

use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr};
use crate::eval::EvalContext;
use itertools::Itertools;

use partiql_types::{type_int, type_string};
use partiql_value::Value;
use partiql_value::Value::Missing;

use std::borrow::{Borrow, Cow};
use std::fmt::Debug;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
Expand Down Expand Up @@ -60,28 +57,6 @@ impl BindEvalExpr for EvalStringFn {
}
}

impl<F, R> ExecuteEvalExpr<1> for EvalExprWrapper<EvalStringFn, F>
where
F: Fn(&Box<String>) -> R,
R: Into<Value>,
{
#[inline]
fn evaluate<'a, 'c>(
&'a self,
args: [Cow<'a, Value>; 1],
_ctx: &'c dyn EvalContext<'c>,
) -> Cow<'a, Value>
where
'c: 'a,
{
let [value] = args;
Cow::Owned(match value.borrow() {
Value::String(s) => ((self.f)(s)).into(),
_ => Missing,
})
}
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum EvalTrimFn {
/// Represents a built-in trim string function, e.g. `trim(both from ' foobar ')`.
Expand Down
13 changes: 10 additions & 3 deletions partiql-eval/src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ use crate::eval::evaluable::{
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,
EvalFnPosition, EvalFnSubstring, EvalFnTupleMerge, EvalFnTupleUnion, EvalIsTypeExpr,
EvalLikeMatch, EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr, EvalOpBinary,
EvalOpUnary, EvalPath, EvalSearchedCaseExpr, EvalStringFn, EvalTrimFn, EvalTupleExpr,
EvalVarRef,
};
use crate::eval::EvalPlan;
use partiql_catalog::Catalog;
Expand Down Expand Up @@ -711,6 +712,12 @@ impl<'c> EvaluatorPlanner<'c> {
"coll_every",
EvalCollFn::Every(setq.into()).bind::<{ STRICT }>(args),
),
CallName::TupleUnion => {
("tupleunion", EvalFnTupleUnion {}.bind::<{ STRICT }>(args))
}
CallName::TupleMerge => {
("tuplemerge", EvalFnTupleMerge {}.bind::<{ STRICT }>(args))
}
CallName::ByName(name) => match self.catalog.get_function(name) {
None => {
self.errors.push(PlanningError::IllegalState(format!(
Expand Down
39 changes: 39 additions & 0 deletions partiql-logical-planner/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::collections::HashMap;
use std::fmt::Debug;

use partiql_catalog::call_defs::{CallDef, CallSpec, CallSpecArg};
use partiql_common::FN_VAR_ARG_MAX;
use unicase::UniCase;

fn function_call_def_char_len() -> CallDef {
Expand Down Expand Up @@ -725,6 +726,42 @@ fn function_call_def_coll_every() -> CallDef {
}
}

fn vararg_fn_overloads<F>(f: F) -> Vec<CallSpec>
where
F: Clone + 'static + Fn(Vec<ValueExpr>) -> logical::ValueExpr + Send + Sync,
{
(1..=FN_VAR_ARG_MAX)
.map(|n| CallSpec {
input: std::iter::repeat(CallSpecArg::Positional).take(n).collect(),
output: Box::new(f.clone()),
})
.collect()
}

fn function_call_def_tupleunion() -> CallDef {
CallDef {
names: vec!["tupleunion", "tuple_union"],
overloads: vararg_fn_overloads(|args| {
logical::ValueExpr::Call(logical::CallExpr {
name: logical::CallName::TupleUnion,
arguments: args,
})
}),
}
}

fn function_call_def_tuplemerge() -> CallDef {
CallDef {
names: vec!["tuplemerge", "tuple_merge"],
overloads: vararg_fn_overloads(|args| {
logical::ValueExpr::Call(logical::CallExpr {
name: logical::CallName::TupleMerge,
arguments: args,
})
}),
}
}

pub(crate) static FN_SYM_TAB: Lazy<FnSymTab> = Lazy::new(function_call_def);

/// Function symbol table
Expand Down Expand Up @@ -770,6 +807,8 @@ pub fn function_call_def() -> FnSymTab {
function_call_def_coll_sum(),
function_call_def_coll_any(),
function_call_def_coll_every(),
function_call_def_tupleunion(),
function_call_def_tuplemerge(),
] {
assert!(!def.names.is_empty());
let primary = def.names[0];
Expand Down
2 changes: 2 additions & 0 deletions partiql-logical/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,8 @@ pub enum CallName {
CollSum(SetQuantifier),
CollAny(SetQuantifier),
CollEvery(SetQuantifier),
TupleUnion,
TupleMerge,
ByName(String),
}

Expand Down
14 changes: 13 additions & 1 deletion partiql-value/src/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ impl Tuple {
other
.pairs()
.chain(self.pairs())
.map(|(a, v)| (a, v.clone()))
.unique_by(|(a, _)| *a)
.map(|(a, v)| (a, v.clone()))
.collect()
}

Expand Down Expand Up @@ -190,6 +190,18 @@ where
}
}

impl<S, T> Extend<(S, T)> for Tuple
where
S: AsRef<str>,
T: Into<Value>,
{
fn extend<I: IntoIterator<Item = (S, T)>>(&mut self, iter: I) {
for (k, v) in iter {
self.insert(k.as_ref(), v.into());
}
}
}

impl Iterator for Tuple {
type Item = (String, Value);

Expand Down
5 changes: 5 additions & 0 deletions partiql/tests/snapshots/tuple_ops__tuplemerge.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: partiql/tests/tuple_ops.rs
expression: tuple
---
{ 'sally': 4, 'bob': 1 }
5 changes: 5 additions & 0 deletions partiql/tests/snapshots/tuple_ops__tupleunion.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: partiql/tests/tuple_ops.rs
expression: tuple
---
{ 'bob': 1, 'sally': 'error', 'sally': 1, 'sally': 2, 'sally': 3, 'sally': 4 }
Loading

0 comments on commit f4af8dd

Please sign in to comment.