Skip to content

Commit 4623cf7

Browse files
authored
Fix comparison ops, add between op (#222)
1 parent 9fb8e0a commit 4623cf7

File tree

5 files changed

+708
-100
lines changed

5 files changed

+708
-100
lines changed

partiql-eval/src/eval.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -648,8 +648,8 @@ impl EvalExpr for EvalBinOpExpr {
648648

649649
let rhs = self.rhs.evaluate(bindings, ctx);
650650
match self.op {
651-
EvalBinOp::And => lhs.and(rhs),
652-
EvalBinOp::Or => lhs.or(rhs),
651+
EvalBinOp::And => lhs.and(&rhs),
652+
EvalBinOp::Or => lhs.or(&rhs),
653653
EvalBinOp::Concat => {
654654
// TODO non-naive concat. Also doesn't properly propagate MISSING and NULL
655655
let lhs = if let Value::String(s) = lhs {
@@ -664,12 +664,12 @@ impl EvalExpr for EvalBinOpExpr {
664664
};
665665
Value::String(Box::new(format!("{}{}", lhs, rhs)))
666666
}
667-
EvalBinOp::Eq => lhs.eq(rhs),
668-
EvalBinOp::Neq => lhs.neq(rhs),
669-
EvalBinOp::Gt => lhs.gt(rhs),
670-
EvalBinOp::Gteq => lhs.gteq(rhs),
671-
EvalBinOp::Lt => lhs.lt(rhs),
672-
EvalBinOp::Lteq => lhs.lteq(rhs),
667+
EvalBinOp::Eq => NullableEq::eq(&lhs, &rhs),
668+
EvalBinOp::Neq => lhs.neq(&rhs),
669+
EvalBinOp::Gt => NullableOrd::gt(&lhs, &rhs),
670+
EvalBinOp::Gteq => NullableOrd::gteq(&lhs, &rhs),
671+
EvalBinOp::Lt => NullableOrd::lt(&lhs, &rhs),
672+
EvalBinOp::Lteq => NullableOrd::lteq(&lhs, &rhs),
673673
EvalBinOp::Add => lhs + rhs,
674674
EvalBinOp::Sub => lhs - rhs,
675675
EvalBinOp::Mul => lhs * rhs,
@@ -680,6 +680,22 @@ impl EvalExpr for EvalBinOpExpr {
680680
}
681681
}
682682

683+
#[derive(Debug)]
684+
pub struct EvalBetweenExpr {
685+
pub value: Box<dyn EvalExpr>,
686+
pub from: Box<dyn EvalExpr>,
687+
pub to: Box<dyn EvalExpr>,
688+
}
689+
690+
impl EvalExpr for EvalBetweenExpr {
691+
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
692+
let value = self.value.evaluate(bindings, ctx);
693+
let from = self.from.evaluate(bindings, ctx);
694+
let to = self.to.evaluate(bindings, ctx);
695+
value.gteq(&from).and(&value.lteq(&to))
696+
}
697+
}
698+
683699
pub trait EvalContext {
684700
fn bindings(&self) -> &dyn Bindings<Value>;
685701
}

partiql-eval/src/lib.rs

Lines changed: 196 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ mod tests {
1717
use partiql_logical as logical;
1818
use partiql_logical::BindingsExpr::{Distinct, Project, ProjectValue};
1919
use partiql_logical::{
20-
BagExpr, BinaryOp, BindingsExpr, JoinKind, ListExpr, LogicalPlan, PathComponent, TupleExpr,
21-
ValueExpr,
20+
BagExpr, BetweenExpr, BinaryOp, BindingsExpr, JoinKind, ListExpr, LogicalPlan,
21+
PathComponent, TupleExpr, ValueExpr,
2222
};
2323
use partiql_value as value;
2424
use partiql_value::{
@@ -470,6 +470,200 @@ mod tests {
470470
}
471471

472472
#[test]
473+
fn comparison_ops() {
474+
// Lt
475+
// Plan for `select lhs < rhs as result from data`
476+
eval_bin_op(
477+
BinaryOp::Lt,
478+
Value::from(1),
479+
Value::from(2.),
480+
Value::from(true),
481+
);
482+
eval_bin_op(
483+
BinaryOp::Lt,
484+
Value::from("abc"),
485+
Value::from("def"),
486+
Value::from(true),
487+
);
488+
eval_bin_op(
489+
BinaryOp::Lt,
490+
Value::Missing,
491+
Value::from(2.),
492+
Value::Missing,
493+
);
494+
eval_bin_op(BinaryOp::Lt, Value::Null, Value::from(2.), Value::Null);
495+
eval_bin_op(
496+
BinaryOp::Lt,
497+
Value::from(1),
498+
Value::from("foo"),
499+
Value::Missing,
500+
);
501+
502+
// Gt
503+
// Plan for `select lhs > rhs as result from data`
504+
eval_bin_op(
505+
BinaryOp::Gt,
506+
Value::from(1),
507+
Value::from(2.),
508+
Value::from(false),
509+
);
510+
eval_bin_op(
511+
BinaryOp::Gt,
512+
Value::from("abc"),
513+
Value::from("def"),
514+
Value::from(false),
515+
);
516+
eval_bin_op(
517+
BinaryOp::Gt,
518+
Value::Missing,
519+
Value::from(2.),
520+
Value::Missing,
521+
);
522+
eval_bin_op(BinaryOp::Gt, Value::Null, Value::from(2.), Value::Null);
523+
eval_bin_op(
524+
BinaryOp::Gt,
525+
Value::from(1),
526+
Value::from("foo"),
527+
Value::Missing,
528+
);
529+
530+
// Lteq
531+
// Plan for `select lhs <= rhs as result from data`
532+
eval_bin_op(
533+
BinaryOp::Lteq,
534+
Value::from(1),
535+
Value::from(2.),
536+
Value::from(true),
537+
);
538+
eval_bin_op(
539+
BinaryOp::Lteq,
540+
Value::from("abc"),
541+
Value::from("def"),
542+
Value::from(true),
543+
);
544+
eval_bin_op(
545+
BinaryOp::Lteq,
546+
Value::Missing,
547+
Value::from(2.),
548+
Value::Missing,
549+
);
550+
eval_bin_op(BinaryOp::Lt, Value::Null, Value::from(2.), Value::Null);
551+
eval_bin_op(
552+
BinaryOp::Lteq,
553+
Value::from(1),
554+
Value::from("foo"),
555+
Value::Missing,
556+
);
557+
558+
// Gteq
559+
// Plan for `select lhs >= rhs as result from data`
560+
eval_bin_op(
561+
BinaryOp::Gteq,
562+
Value::from(1),
563+
Value::from(2.),
564+
Value::from(false),
565+
);
566+
eval_bin_op(
567+
BinaryOp::Gteq,
568+
Value::from("abc"),
569+
Value::from("def"),
570+
Value::from(false),
571+
);
572+
eval_bin_op(
573+
BinaryOp::Gteq,
574+
Value::Missing,
575+
Value::from(2.),
576+
Value::Missing,
577+
);
578+
eval_bin_op(BinaryOp::Gteq, Value::Null, Value::from(2.), Value::Null);
579+
eval_bin_op(
580+
BinaryOp::Gteq,
581+
Value::from(1),
582+
Value::from("foo"),
583+
Value::Missing,
584+
);
585+
}
586+
587+
#[test]
588+
fn between_op() {
589+
fn eval_between_op(value: Value, from: Value, to: Value, expected_first_elem: Value) {
590+
let mut plan = LogicalPlan::new();
591+
let scan = plan.add_operator(BindingsExpr::Scan(logical::Scan {
592+
expr: ValueExpr::VarRef(BindingsName::CaseInsensitive("data".into())),
593+
as_key: "data".to_string(),
594+
at_key: None,
595+
}));
596+
597+
let project = plan.add_operator(Project(logical::Project {
598+
exprs: HashMap::from([(
599+
"result".to_string(),
600+
ValueExpr::BetweenExpr(BetweenExpr {
601+
value: Box::new(ValueExpr::Path(
602+
Box::new(ValueExpr::VarRef(BindingsName::CaseInsensitive(
603+
"data".into(),
604+
))),
605+
vec![PathComponent::Key("value".to_string())],
606+
)),
607+
from: Box::new(ValueExpr::Lit(Box::new(from))),
608+
to: Box::new(ValueExpr::Lit(Box::new(to))),
609+
}),
610+
)]),
611+
}));
612+
613+
let sink = plan.add_operator(BindingsExpr::Sink);
614+
plan.extend_with_flows(&[(scan, project), (project, sink)]);
615+
616+
let mut bindings = MapBindings::default();
617+
bindings.insert(
618+
"data",
619+
partiql_list![Tuple::from([("value".into(), value)])].into(),
620+
);
621+
622+
let result = evaluate(plan, bindings).coerce_to_bag();
623+
assert!(!&result.is_empty());
624+
let expected_result =
625+
partiql_bag!(Tuple::from([("result".into(), expected_first_elem)]));
626+
assert_eq!(expected_result, result);
627+
}
628+
eval_between_op(
629+
Value::from(2),
630+
Value::from(1),
631+
Value::from(3),
632+
Value::from(true),
633+
);
634+
eval_between_op(
635+
Value::from(2),
636+
Value::from(1.),
637+
Value::from(dec!(3.)),
638+
Value::from(true),
639+
);
640+
eval_between_op(
641+
Value::from(1),
642+
Value::from(2),
643+
Value::from(3),
644+
Value::from(false),
645+
);
646+
eval_between_op(Value::Null, Value::from(1), Value::from(3), Value::Null);
647+
eval_between_op(Value::from(2), Value::Null, Value::from(3), Value::Null);
648+
eval_between_op(Value::from(2), Value::from(1), Value::Null, Value::Null);
649+
eval_between_op(Value::Missing, Value::from(1), Value::from(3), Value::Null);
650+
eval_between_op(Value::from(2), Value::Missing, Value::from(3), Value::Null);
651+
eval_between_op(Value::from(2), Value::from(1), Value::Missing, Value::Null);
652+
// left part of AND evaluates to false
653+
eval_between_op(
654+
Value::from(1),
655+
Value::from(2),
656+
Value::Null,
657+
Value::from(false),
658+
);
659+
eval_between_op(
660+
Value::from(1),
661+
Value::from(2),
662+
Value::Missing,
663+
Value::from(false),
664+
);
665+
}
666+
473667
fn select_with_cross_join() {
474668
let mut lg = LogicalPlan::new();
475669

partiql-eval/src/plan.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ use partiql_logical::{
77

88
use crate::eval;
99
use crate::eval::{
10-
EvalBagExpr, EvalBinOp, EvalBinOpExpr, EvalExpr, EvalJoinKind, EvalListExpr, EvalLitExpr,
11-
EvalPath, EvalPlan, EvalTupleExpr, EvalUnaryOp, EvalUnaryOpExpr, EvalVarRef, Evaluable,
10+
EvalBagExpr, EvalBetweenExpr, EvalBinOp, EvalBinOpExpr, EvalExpr, EvalJoinKind, EvalListExpr,
11+
EvalLitExpr, EvalPath, EvalPlan, EvalTupleExpr, EvalUnaryOp, EvalUnaryOpExpr, EvalVarRef,
12+
Evaluable,
1213
};
1314

1415
pub struct EvaluatorPlanner;
@@ -136,7 +137,7 @@ impl EvaluatorPlanner {
136137
BinaryOp::Gt => EvalBinOp::Gt,
137138
BinaryOp::Gteq => EvalBinOp::Gteq,
138139
BinaryOp::Lt => EvalBinOp::Lt,
139-
BinaryOp::Lteq => EvalBinOp::Gteq,
140+
BinaryOp::Lteq => EvalBinOp::Lteq,
140141
BinaryOp::Add => EvalBinOp::Add,
141142
BinaryOp::Sub => EvalBinOp::Sub,
142143
BinaryOp::Mul => EvalBinOp::Mul,
@@ -187,6 +188,12 @@ impl EvaluatorPlanner {
187188
.collect();
188189
Box::new(EvalBagExpr { elements })
189190
}
191+
ValueExpr::BetweenExpr(expr) => {
192+
let value = self.plan_values(*expr.value);
193+
let from = self.plan_values(*expr.from);
194+
let to = self.plan_values(*expr.to);
195+
Box::new(EvalBetweenExpr { value, from, to })
196+
}
190197
}
191198
}
192199
}

partiql-logical/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ pub enum ValueExpr {
117117
TupleExpr(TupleExpr),
118118
ListExpr(ListExpr),
119119
BagExpr(BagExpr),
120+
BetweenExpr(BetweenExpr),
120121
}
121122

122123
#[derive(Clone, Debug)]
@@ -156,6 +157,13 @@ impl BagExpr {
156157
}
157158
}
158159

160+
#[derive(Clone, Debug)]
161+
pub struct BetweenExpr {
162+
pub value: Box<ValueExpr>,
163+
pub from: Box<ValueExpr>,
164+
pub to: Box<ValueExpr>,
165+
}
166+
159167
// Bindings -> Bindings : Where, OrderBy, Offset, Limit, Join, SetOp, Select, Distinct, GroupBy, Unpivot, Let
160168
// Values -> Bindings : From
161169
// Bindings -> Values : Select Value

0 commit comments

Comments
 (0)