use crate::error::EvaluationError;
use crate::eval::expr::{BindError, EvalExpr};
use crate::eval::EvalContext;
use itertools::Itertools;
use partiql_types::{PartiqlShape, Static, StaticCategory, TYPE_DYNAMIC};
use partiql_value::Value;
use partiql_value::Value::{Missing, Null};
use std::borrow::{Borrow, Cow};
use std::fmt::{Debug, Formatter};
use std::hash::Hash;
use std::marker::PhantomData;
use partiql_value::datum::{
DatumCategory, DatumCategoryRef, DatumLower, DatumValueRef, RefTupleView,
};
use std::ops::ControlFlow;
trait TypeSatisfier {
fn satisfies(&self, value: &Value) -> bool;
}
impl TypeSatisfier for Static {
fn satisfies(&self, value: &Value) -> bool {
use partiql_value::datum::RefSequenceView;
match (self.category(), value.category()) {
(_, DatumCategoryRef::Null) => true,
(_, DatumCategoryRef::Missing) => true,
(StaticCategory::Scalar(ty), DatumCategoryRef::Scalar(scalar)) => match scalar {
DatumValueRef::Value(scalar) => {
matches!(
(ty, scalar),
(
Static::Int
| Static::Int8
| Static::Int16
| Static::Int32
| Static::Int64,
Value::Integer(_),
) | (Static::Bool, Value::Boolean(_))
| (Static::Decimal | Static::DecimalP(_, _), Value::Decimal(_))
| (Static::Float32 | Static::Float64, Value::Real(_))
| (
Static::String | Static::StringFixed(_) | Static::StringVarying(_),
Value::String(_),
)
| (Static::DateTime, Value::DateTime(_))
)
}
DatumValueRef::Dynamic(_) => {
unreachable!("Value must be 'lower'ed before trying to satisfy")
}
},
(StaticCategory::Sequence(shape), DatumCategoryRef::Sequence(seq)) => match shape {
PartiqlShape::Dynamic | PartiqlShape::Undefined => true,
shape => seq.into_iter().all(|v| shape.satisfies(&v)),
},
(StaticCategory::Tuple(), DatumCategoryRef::Tuple(_)) => {
true }
(StaticCategory::Graph(), DatumCategoryRef::Graph(_)) => {
true }
_ => false,
}
}
}
impl TypeSatisfier for PartiqlShape {
fn satisfies(&self, value: &Value) -> bool {
match (self, value) {
(_, Value::Null) => true,
(_, Value::Missing) => true,
(PartiqlShape::Dynamic, _) => true,
(PartiqlShape::AnyOf(anyof), val) => anyof.types().any(|typ| typ.satisfies(val)),
(PartiqlShape::Static(s), val) => s.ty().satisfies(val),
_ => false,
}
}
}
pub(crate) fn unwrap_args<const N: usize>(
args: Vec<Box<dyn EvalExpr>>,
) -> Result<[Box<dyn EvalExpr>; N], BindError> {
args.try_into()
.map_err(|args: Vec<_>| BindError::ArgNumMismatch {
expected: vec![N],
found: args.len(),
})
}
pub(crate) trait ExecuteEvalExpr<const N: usize>: Debug {
fn evaluate<'a, 'c, 'o>(
&'a self,
args: [Cow<'a, Value>; N],
ctx: &'c dyn EvalContext,
) -> Cow<'a, Value>
where
'c: 'a;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum ArgCheckControlFlow<B, C, R = B> {
Continue(C),
ShortCircuit(B),
ErrorOrShortCircuit(B),
Propagate(R),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct ArgValidateError {
pub(crate) message: String,
pub(crate) propagate: Value,
}
pub(crate) trait ArgChecker: Debug {
fn arg_check<'a>(
typ: &PartiqlShape,
arg: Cow<'a, Value>,
) -> ArgCheckControlFlow<Value, Cow<'a, Value>>;
fn validate_args(args: Vec<Cow<'_, Value>>) -> Result<Vec<Cow<'_, Value>>, ArgValidateError> {
Ok(args)
}
}
pub(crate) trait ArgShortCircuit: Debug {
fn is_strict_error() -> bool;
fn propagate() -> Value;
}
#[derive(Debug)]
pub(crate) struct PropagateMissing<const IS_ERR: bool> {}
impl<const IS_ERR: bool> ArgShortCircuit for PropagateMissing<IS_ERR> {
fn is_strict_error() -> bool {
IS_ERR
}
#[inline]
fn propagate() -> Value {
Missing
}
}
#[derive(Debug)]
pub(crate) struct PropagateNull<const IS_ERR: bool> {}
impl<const IS_ERR: bool> ArgShortCircuit for PropagateNull<IS_ERR> {
fn is_strict_error() -> bool {
IS_ERR
}
#[inline]
fn propagate() -> Value {
Null
}
}
#[derive(Debug)]
pub(crate) struct DefaultArgChecker<const STRICT: bool, OnMissing: ArgShortCircuit> {
marker: PhantomData<OnMissing>,
}
impl<const STRICT: bool, OnMissing: ArgShortCircuit> ArgChecker
for DefaultArgChecker<STRICT, OnMissing>
{
fn arg_check<'a>(
typ: &PartiqlShape,
arg: Cow<'a, Value>,
) -> ArgCheckControlFlow<Value, Cow<'a, Value>> {
let err = || {
if OnMissing::is_strict_error() {
ArgCheckControlFlow::ErrorOrShortCircuit(OnMissing::propagate())
} else {
ArgCheckControlFlow::ShortCircuit(OnMissing::propagate())
}
};
match arg.borrow() {
Missing => ArgCheckControlFlow::Propagate(OnMissing::propagate()),
Null => ArgCheckControlFlow::Propagate(Null),
val => {
if typ.satisfies(val) {
ArgCheckControlFlow::Continue(arg)
} else {
err()
}
}
}
}
}
#[derive(Debug)]
pub(crate) struct NullArgChecker {}
impl ArgChecker for NullArgChecker {
fn arg_check<'a>(
_typ: &PartiqlShape,
arg: Cow<'a, Value>,
) -> ArgCheckControlFlow<Value, Cow<'a, Value>> {
ArgCheckControlFlow::Continue(arg)
}
}
pub(crate) struct ArgCheckEvalExpr<
const STRICT: bool,
const N: usize,
E: ExecuteEvalExpr<N>,
ArgC: ArgChecker,
> {
pub(crate) types: [PartiqlShape; N],
pub(crate) args: [Box<dyn EvalExpr>; N],
pub(crate) expr: E,
pub(crate) arg_check: PhantomData<ArgC>,
}
impl<const STRICT: bool, const N: usize, E: ExecuteEvalExpr<N>, ArgC: ArgChecker> Debug
for ArgCheckEvalExpr<STRICT, N, E, ArgC>
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.expr.fmt(f)?;
write!(f, "(")?;
let mut sep = "";
for arg in &self.args {
write!(f, "{sep}")?;
arg.fmt(f)?;
sep = ", ";
}
write!(f, ")")?;
Ok(())
}
}
impl<const STRICT: bool, const N: usize, E: ExecuteEvalExpr<N>, ArgC: ArgChecker>
ArgCheckEvalExpr<STRICT, N, E, ArgC>
{
pub fn new(types: [PartiqlShape; N], args: [Box<dyn EvalExpr>; N], expr: E) -> Self {
Self {
types,
args,
expr,
arg_check: PhantomData {},
}
}
pub fn evaluate_args<'a, 'c>(
&'a self,
bindings: &'a dyn RefTupleView<'a, Value>,
ctx: &'c dyn EvalContext,
) -> ControlFlow<Value, [Cow<'a, Value>; N]>
where
'c: 'a,
{
let err_arg_count_mismatch = |args: Vec<_>| {
if STRICT {
ctx.add_error(EvaluationError::IllegalState(format!(
"# of evaluated arguments ({}) does not match expectation {}",
args.len(),
N
)));
}
ControlFlow::Break(Missing)
};
match evaluate_and_validate_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),
}
}
}
pub(crate) fn evaluate_and_validate_args<
'a,
'c,
't,
const STRICT: bool,
ArgC: ArgChecker,
F: Fn(usize) -> &'t PartiqlShape,
>(
args: &'a [Box<dyn EvalExpr>],
types: F,
bindings: &'a dyn RefTupleView<'a, Value>,
ctx: &'c dyn EvalContext,
) -> 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);
let arg = match arg {
Cow::Borrowed(arg) => arg.lower(),
Cow::Owned(arg) => arg.into_lower().map(Cow::Owned),
}
.expect("lowering failed"); 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"; 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 {
ControlFlow::Break(v)
} else {
match ArgC::validate_args(result) {
Ok(result) => ControlFlow::Continue(result),
Err(err) => {
if STRICT {
ctx.add_error(EvaluationError::IllegalState(format!(
"Arguments failed validation: {}",
err.message
)))
}
ControlFlow::Break(err.propagate)
}
}
}
}
impl<const STRICT: bool, const N: usize, E: ExecuteEvalExpr<N>, ArgC: ArgChecker> EvalExpr
for ArgCheckEvalExpr<STRICT, N, E, ArgC>
{
fn evaluate<'a, 'c, 'o>(
&'a self,
bindings: &'a dyn RefTupleView<'a, Value>,
ctx: &'c dyn EvalContext,
) -> Cow<'o, Value>
where
'c: 'a,
'a: 'o,
{
if STRICT && (ctx.has_errors()) {
return Cow::Owned(Missing);
}
match self.evaluate_args(bindings, ctx) {
ControlFlow::Continue(args) => self.expr.evaluate(args, ctx),
ControlFlow::Break(short_circuit) => Cow::Owned(short_circuit),
}
}
}
pub(crate) struct EvalExprWrapper<E, F> {
pub ident: E,
pub f: F,
}
impl<E, F> Debug for EvalExprWrapper<E, F>
where
E: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.ident.fmt(f)
}
}
impl<E: 'static, F: 'static> EvalExprWrapper<E, F> {
#[inline]
pub(crate) fn create_checked<const STRICT: bool, const N: usize, ArgC: 'static + ArgChecker>(
ident: E,
types: [PartiqlShape; N],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
EvalExprWrapper<E, F>: ExecuteEvalExpr<N>,
{
let args = unwrap_args(args)?;
let expr = Self { ident, f };
let expr = ArgCheckEvalExpr::<STRICT, N, _, ArgC>::new(types, args, expr);
Ok(Box::new(expr))
}
#[inline]
pub(crate) fn create_checked_with_ctx<
const STRICT: bool,
const N: usize,
ArgC: 'static + ArgChecker,
>(
ident: E,
types: [PartiqlShape; N],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
EvalExprWrapper<E, F>: ExecuteEvalExpr<N>,
{
let args = unwrap_args(args)?;
let expr = Self { ident, f };
let expr = ArgCheckEvalExpr::<STRICT, N, _, ArgC>::new(types, args, expr);
Ok(Box::new(expr))
}
}
#[derive(Debug, Default, Copy, Clone)]
pub(crate) struct UnaryValueExpr {}
impl<F> ExecuteEvalExpr<1> for EvalExprWrapper<UnaryValueExpr, F>
where
F: Fn(&Value, &dyn EvalContext) -> Value,
{
#[inline]
fn evaluate<'a, 'c, 'o>(
&'a self,
args: [Cow<'a, Value>; 1],
ctx: &'c dyn EvalContext,
) -> Cow<'a, Value>
where
'c: 'a,
{
let [arg] = args;
Cow::Owned((self.f)(arg.borrow(), ctx))
}
}
impl UnaryValueExpr {
#[allow(dead_code)]
#[inline]
pub(crate) fn create_with_any<const STRICT: bool, F>(
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value) -> Value,
{
Self::create_typed::<STRICT, F>([PartiqlShape::Dynamic; 1], args, f)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_typed<const STRICT: bool, F>(
types: [PartiqlShape; 1],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value) -> Value,
{
Self::create_typed_with_ctx::<{ STRICT }, _>(types, args, move |val, _| f(val))
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_typed_with_ctx<const STRICT: bool, F>(
types: [PartiqlShape; 1],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &dyn EvalContext) -> Value,
{
type Check<const STRICT: bool> = DefaultArgChecker<STRICT, PropagateMissing<true>>;
Self::create_checked_with_ctx::<{ STRICT }, Check<STRICT>, F>(types, args, f)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_checked<const STRICT: bool, ArgC, F>(
types: [PartiqlShape; 1],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value) -> Value,
ArgC: 'static + ArgChecker,
{
Self::create_checked_with_ctx::<{ STRICT }, ArgC, _>(types, args, move |val, _| f(val))
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_checked_with_ctx<const STRICT: bool, ArgC, F>(
types: [PartiqlShape; 1],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &dyn EvalContext) -> Value,
ArgC: 'static + ArgChecker,
{
EvalExprWrapper::create_checked_with_ctx::<{ STRICT }, 1, ArgC>(
Self::default(),
types,
args,
f,
)
}
}
#[derive(Debug, Default, Copy, Clone)]
pub(crate) struct BinaryValueExpr {}
impl<F> ExecuteEvalExpr<2> for EvalExprWrapper<BinaryValueExpr, F>
where
F: Fn(&Value, &Value, &dyn EvalContext) -> Value,
{
#[inline]
fn evaluate<'a, 'c, 'o>(
&'a self,
args: [Cow<'a, Value>; 2],
ctx: &'c dyn EvalContext,
) -> Cow<'a, Value>
where
'c: 'a,
{
let [arg1, arg2] = args;
Cow::Owned((self.f)(arg1.borrow(), arg2.borrow(), ctx))
}
}
impl BinaryValueExpr {
#[allow(dead_code)]
#[inline]
pub(crate) fn create_with_any<const STRICT: bool, F>(
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &Value) -> Value,
{
Self::create_typed::<STRICT, F>([TYPE_DYNAMIC; 2], args, f)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_typed<const STRICT: bool, F>(
types: [PartiqlShape; 2],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &Value) -> Value,
{
type Check<const STRICT: bool> = DefaultArgChecker<STRICT, PropagateMissing<true>>;
Self::create_checked::<{ STRICT }, Check<STRICT>, F>(types, args, f)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_checked<const STRICT: bool, ArgC, F>(
types: [PartiqlShape; 2],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &Value) -> Value,
ArgC: 'static + ArgChecker,
{
EvalExprWrapper::create_checked::<{ STRICT }, 2, ArgC>(
Self::default(),
types,
args,
move |v1, v2, _| f(v1, v2),
)
}
}
#[derive(Debug, Default, Copy, Clone)]
pub(crate) struct TernaryValueExpr {}
impl<F> ExecuteEvalExpr<3> for EvalExprWrapper<TernaryValueExpr, F>
where
F: Fn(&Value, &Value, &Value, &dyn EvalContext) -> Value,
{
#[inline]
fn evaluate<'a, 'c, 'o>(
&'a self,
args: [Cow<'a, Value>; 3],
ctx: &'c dyn EvalContext,
) -> Cow<'a, Value>
where
'c: 'a,
{
let [arg1, arg2, arg3] = args;
Cow::Owned((self.f)(arg1.borrow(), arg2.borrow(), arg3.borrow(), ctx))
}
}
impl TernaryValueExpr {
#[allow(dead_code)]
#[inline]
pub(crate) fn create_with_any<const STRICT: bool, F>(
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &Value, &Value) -> Value,
{
Self::create_typed::<STRICT, F>([TYPE_DYNAMIC; 3], args, f)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_typed<const STRICT: bool, F>(
types: [PartiqlShape; 3],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &Value, &Value) -> Value,
{
type Check<const STRICT: bool> = DefaultArgChecker<STRICT, PropagateMissing<true>>;
Self::create_checked::<{ STRICT }, Check<STRICT>, F>(types, args, f)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_checked<const STRICT: bool, ArgC, F>(
types: [PartiqlShape; 3],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &Value, &Value) -> Value,
ArgC: 'static + ArgChecker,
{
EvalExprWrapper::create_checked::<{ STRICT }, 3, ArgC>(
Self::default(),
types,
args,
move |v1, v2, v3, _| f(v1, v2, v3),
)
}
}
#[derive(Debug, Default, Copy, Clone)]
pub(crate) struct QuaternaryValueExpr {}
impl<F> ExecuteEvalExpr<4> for EvalExprWrapper<QuaternaryValueExpr, F>
where
F: Fn(&Value, &Value, &Value, &Value, &dyn EvalContext) -> Value,
{
#[inline]
fn evaluate<'a, 'c, 'o>(
&'a self,
args: [Cow<'a, Value>; 4],
ctx: &'c dyn EvalContext,
) -> Cow<'a, Value>
where
'c: 'a,
{
let [arg1, arg2, arg3, arg4] = args;
Cow::Owned((self.f)(
arg1.borrow(),
arg2.borrow(),
arg3.borrow(),
arg4.borrow(),
ctx,
))
}
}
impl QuaternaryValueExpr {
#[allow(dead_code)]
#[inline]
pub(crate) fn create_with_any<const STRICT: bool, F>(
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &Value, &Value, &Value) -> Value,
{
Self::create_typed::<STRICT, F>([TYPE_DYNAMIC; 4], args, f)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_typed<const STRICT: bool, F>(
types: [PartiqlShape; 4],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &Value, &Value, &Value) -> Value,
{
type Check<const STRICT: bool> = DefaultArgChecker<STRICT, PropagateMissing<true>>;
Self::create_checked::<{ STRICT }, Check<STRICT>, F>(types, args, f)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn create_checked<const STRICT: bool, ArgC, F>(
types: [PartiqlShape; 4],
args: Vec<Box<dyn EvalExpr>>,
f: F,
) -> Result<Box<dyn EvalExpr>, BindError>
where
F: 'static + Fn(&Value, &Value, &Value, &Value) -> Value,
ArgC: 'static + ArgChecker,
{
EvalExprWrapper::create_checked::<{ STRICT }, 4, ArgC>(
Self::default(),
types,
args,
move |v1, v2, v3, v4, _| f(v1, v2, v3, v4),
)
}
}