Skip to content

Commit

Permalink
Function call lowering and evaluation for selected built-ins (#264)
Browse files Browse the repository at this point in the history
Function call lowering and evaluation for selected built-ins:
- char_len
- lower
- upper
- substring
- trim
- coalesce
- nullif
- exists
  • Loading branch information
jpschorr authored Jan 9, 2023
1 parent 6b9aec6 commit cbda845
Show file tree
Hide file tree
Showing 8 changed files with 774 additions and 30 deletions.
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
}
}

#[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

0 comments on commit cbda845

Please sign in to comment.