Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function call lowering and evaluation for selected built-ins #264

Merged
merged 8 commits into from
Jan 9, 2023
214 changes: 214 additions & 0 deletions partiql-eval/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1099,3 +1099,217 @@ impl EvalContext for BasicContext {
&self.bindings
}
}

#[inline]
#[track_caller]
fn string_transform<FnTransform>(value: Value, transform_fn: FnTransform) -> Value
where
FnTransform: Fn(&str) -> Value,
{
match value {
Null => Value::Null,
Value::String(s) => transform_fn(s.as_ref()),
_ => Value::Missing,
}
}

#[derive(Debug)]
pub struct EvalFnLower {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnLower {
#[inline]
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
string_transform(self.value.evaluate(bindings, ctx), |s| {
s.to_lowercase().into()
})
}
}

#[derive(Debug)]
pub struct EvalFnUpper {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnUpper {
#[inline]
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
string_transform(self.value.evaluate(bindings, ctx), |s| {
s.to_uppercase().into()
})
}
}

#[derive(Debug)]
pub struct EvalFnCharLength {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnCharLength {
#[inline]
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
string_transform(self.value.evaluate(bindings, ctx), |s| {
s.chars().count().into()
})
}
}

#[derive(Debug)]
pub struct EvalFnSubstring {
pub value: Box<dyn EvalExpr>,
pub offset: Box<dyn EvalExpr>,
pub length: Option<Box<dyn EvalExpr>>,
}

impl EvalExpr for EvalFnSubstring {
#[inline]
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
let value = match self.value.evaluate(bindings, ctx) {
Null => None,
Value::String(s) => Some(s),
_ => return Value::Missing,
};
let offset = match self.offset.evaluate(bindings, ctx) {
Null => None,
Value::Integer(i) => Some(i),
_ => return Value::Missing,
};

if let Some(length) = &self.length {
let length = match length.evaluate(bindings, ctx) {
Value::Integer(i) => i as usize,
Value::Null => return Value::Null,
_ => return Value::Missing,
};
if let (Some(value), Some(offset)) = (value, offset) {
let (offset, length) = if length < 1 {
(0, 0)
} else if offset < 1 {
let length = std::cmp::max(offset + (length - 1) as i64, 0) as usize;
let offset = std::cmp::max(offset, 0) as usize;
(offset, length)
} else {
((offset - 1) as usize, length)
};
value
.chars()
.skip(offset)
.take(length)
.collect::<String>()
.into()
} else {
// either value or offset was NULL; return NULL
Value::Null
}
} else if let (Some(value), Some(offset)) = (value, offset) {
let offset = (std::cmp::max(offset, 1) - 1) as usize;
value.chars().skip(offset).collect::<String>().into()
} else {
// either value or offset was NULL; return NULL
Value::Null
}
}
}

#[inline]
#[track_caller]
fn trim<FnTrim>(value: Value, to_trim: Value, trim_fn: FnTrim) -> Value
where
FnTrim: Fn(&str, &str) -> Value,
{
let value = match value {
Value::String(s) => Some(s),
Null => None,
_ => return Value::Missing,
};
let to_trim = match to_trim {
Value::String(s) => s,
Null => return Value::Null,
_ => return Value::Missing,
};
if let Some(s) = value {
trim_fn(&s, &to_trim)
} else {
Value::Null
jpschorr marked this conversation as resolved.
Show resolved Hide resolved
}
}

#[derive(Debug)]
pub struct EvalFnBtrim {
pub value: Box<dyn EvalExpr>,
pub to_trim: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnBtrim {
#[inline]
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
trim(
self.value.evaluate(bindings, ctx),
self.to_trim.evaluate(bindings, ctx),
|s, to_trim| {
let to_trim = to_trim.chars().collect_vec();
s.trim_matches(&to_trim[..]).into()
},
)
}
}

#[derive(Debug)]
pub struct EvalFnRtrim {
pub value: Box<dyn EvalExpr>,
pub to_trim: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnRtrim {
#[inline]
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
trim(
self.value.evaluate(bindings, ctx),
self.to_trim.evaluate(bindings, ctx),
|s, to_trim| {
let to_trim = to_trim.chars().collect_vec();
s.trim_end_matches(&to_trim[..]).into()
},
)
}
}

#[derive(Debug)]
pub struct EvalFnLtrim {
pub value: Box<dyn EvalExpr>,
pub to_trim: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnLtrim {
#[inline]
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
trim(
self.value.evaluate(bindings, ctx),
self.to_trim.evaluate(bindings, ctx),
|s, to_trim| {
let to_trim = to_trim.chars().collect_vec();
s.trim_start_matches(&to_trim[..]).into()
},
)
}
}

#[derive(Debug)]
pub struct EvalFnExists {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnExists {
#[inline]
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
let value = self.value.evaluate(bindings, ctx);
let exists = match value {
Value::Bag(b) => !b.is_empty(),
Value::List(l) => !l.is_empty(),
Value::Tuple(t) => !t.is_empty(),
_ => false,
};
Value::Boolean(exists)
}
}
78 changes: 73 additions & 5 deletions partiql-eval/src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ use std::collections::HashMap;

use partiql_logical as logical;
use partiql_logical::{
BinaryOp, BindingsOp, IsTypeExpr, JoinKind, LogicalPlan, OpId, PathComponent, Pattern,
PatternMatchExpr, SearchedCase, Type, UnaryOp, ValueExpr,
BinaryOp, BindingsOp, CallName, IsTypeExpr, JoinKind, LogicalPlan, OpId, PathComponent,
Pattern, PatternMatchExpr, SearchedCase, Type, UnaryOp, ValueExpr,
};

use crate::eval;
use crate::eval::{
EvalBagExpr, EvalBetweenExpr, EvalBinOp, EvalBinOpExpr, EvalDynamicLookup, EvalExpr,
EvalIsTypeExpr, EvalJoinKind, EvalLikeMatch, EvalListExpr, EvalLitExpr, EvalPath, EvalPlan,
EvalSearchedCaseExpr, EvalSubQueryExpr, EvalTupleExpr, EvalUnaryOp, EvalUnaryOpExpr,
EvalVarRef, Evaluable,
EvalFnBtrim, EvalFnCharLength, EvalFnExists, EvalFnLower, EvalFnLtrim, EvalFnRtrim,
EvalFnSubstring, EvalFnUpper, EvalIsTypeExpr, EvalJoinKind, EvalLikeMatch, EvalListExpr,
EvalLitExpr, EvalPath, EvalPlan, EvalSearchedCaseExpr, EvalSubQueryExpr, EvalTupleExpr,
EvalUnaryOp, EvalUnaryOpExpr, EvalVarRef, Evaluable,
};
use crate::pattern_match::like_to_re_pattern;
use partiql_value::Value::Null;
Expand Down Expand Up @@ -337,6 +338,73 @@ impl EvaluatorPlanner {

Box::new(EvalDynamicLookup { lookups })
}
ValueExpr::Call(logical::CallExpr { name, arguments }) => {
let mut args = arguments
.into_iter()
.map(|arg| self.plan_values(arg))
.collect_vec();
match name {
CallName::Lower => {
assert_eq!(args.len(), 1);
Box::new(EvalFnLower {
value: args.pop().unwrap(),
})
}
CallName::Upper => {
assert_eq!(args.len(), 1);
Box::new(EvalFnUpper {
value: args.pop().unwrap(),
})
}
CallName::CharLength => {
assert_eq!(args.len(), 1);
Box::new(EvalFnCharLength {
value: args.pop().unwrap(),
})
}
CallName::LTrim => {
assert_eq!(args.len(), 2);
let value = args.pop().unwrap();
let to_trim = args.pop().unwrap();
Box::new(EvalFnLtrim { value, to_trim })
}
CallName::BTrim => {
assert_eq!(args.len(), 2);
let value = args.pop().unwrap();
let to_trim = args.pop().unwrap();
Box::new(EvalFnBtrim { value, to_trim })
}
CallName::RTrim => {
assert_eq!(args.len(), 2);
let value = args.pop().unwrap();
let to_trim = args.pop().unwrap();
Box::new(EvalFnRtrim { value, to_trim })
}
CallName::Substring => {
assert!((2usize..=3).contains(&args.len()));

let length = if args.len() == 3 {
Some(args.pop().unwrap())
} else {
None
};
let offset = args.pop().unwrap();
let value = args.pop().unwrap();

Box::new(EvalFnSubstring {
value,
offset,
length,
})
}
CallName::Exists => {
assert_eq!(args.len(), 1);
Box::new(EvalFnExists {
value: args.pop().unwrap(),
})
}
}
}
}
}
}
Loading