Skip to content

Commit

Permalink
Add SQL aggregates ANY, SOME, EVERY and their COLL_ versions
Browse files Browse the repository at this point in the history
  • Loading branch information
alancai98 committed May 31, 2023
1 parent 37bce5e commit 9d99994
Show file tree
Hide file tree
Showing 9 changed files with 355 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
### Added
- Implements the aggregation functions `ANY`, `SOME`, `EVERY` and their `COLL_` versions
### Fixes

## [0.4.1] - 2023-05-25
Expand Down
129 changes: 111 additions & 18 deletions partiql-eval/src/eval/evaluable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ pub(crate) enum AggFunc {
Max(Max),
Min(Min),
Sum(Sum),
Any(Any),
Every(Every),

Check warning on line 321 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L320-L321

Added lines #L320 - L321 were not covered by tests
}

impl AggregateFunction for AggFunc {
Expand All @@ -327,6 +329,8 @@ impl AggregateFunction for AggFunc {
AggFunc::Max(v) => v.next_value(input_value, group),
AggFunc::Min(v) => v.next_value(input_value, group),
AggFunc::Sum(v) => v.next_value(input_value, group),
AggFunc::Any(v) => v.next_value(input_value, group),
AggFunc::Every(v) => v.next_value(input_value, group),
}
}

Expand All @@ -337,6 +341,8 @@ impl AggregateFunction for AggFunc {
AggFunc::Max(v) => v.compute(group),
AggFunc::Min(v) => v.compute(group),
AggFunc::Sum(v) => v.compute(group),
AggFunc::Any(v) => v.compute(group),
AggFunc::Every(v) => v.compute(group),
}
}
}
Expand Down Expand Up @@ -436,7 +442,7 @@ impl AggregateFunction for Avg {
}

fn compute(&self, group: &Tuple) -> Value {
match self.avgs.get(group).expect("Expect group to exist in avgs") {
match self.avgs.get(group).unwrap_or(&(0, Null)) {
(0, _) => Null,
(c, s) => s / &Value::Decimal(rust_decimal::Decimal::from(*c)),
}
Expand Down Expand Up @@ -483,11 +489,7 @@ impl AggregateFunction for Count {
}

fn compute(&self, group: &Tuple) -> Value {
Value::from(
self.counts
.get(group)
.expect("Expect group to exist in counts"),
)
Value::from(self.counts.get(group).unwrap_or(&0))
}
}

Expand Down Expand Up @@ -531,10 +533,7 @@ impl AggregateFunction for Max {
}

fn compute(&self, group: &Tuple) -> Value {
self.maxes
.get(group)
.expect("Expect group to exist in sums")
.clone()
self.maxes.get(group).unwrap_or(&Null).clone()
}
}

Expand Down Expand Up @@ -578,10 +577,7 @@ impl AggregateFunction for Min {
}

fn compute(&self, group: &Tuple) -> Value {
self.mins
.get(group)
.expect("Expect group to exist in mins")
.clone()
self.mins.get(group).unwrap_or(&Null).clone()
}
}

Expand Down Expand Up @@ -625,10 +621,107 @@ impl AggregateFunction for Sum {
}

fn compute(&self, group: &Tuple) -> Value {
self.sums
.get(group)
.expect("Expect group to exist in sums")
.clone()
self.sums.get(group).unwrap_or(&Null).clone()
}
}

/// Represents SQL's `ANY`/`SOME` aggregation function
#[derive(Debug)]

Check warning on line 629 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L629

Added line #L629 was not covered by tests
pub(crate) struct Any {
anys: HashMap<Tuple, Value>,
aggregator: AggFilterFn,

Check warning on line 632 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L631-L632

Added lines #L631 - L632 were not covered by tests
}

impl Any {
pub(crate) fn new_distinct() -> Self {
Any {
anys: HashMap::new(),
aggregator: AggFilterFn::Distinct(AggFilterDistinct::new()),
}

Check warning on line 640 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L640

Added line #L640 was not covered by tests
}

pub(crate) fn new_all() -> Self {
Any {
anys: HashMap::new(),
aggregator: AggFilterFn::default(),
}

Check warning on line 647 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L647

Added line #L647 was not covered by tests
}
}

impl AggregateFunction for Any {
fn next_value(&mut self, input_value: &Value, group: &Tuple) {
if !input_value.is_null_or_missing()
&& self.aggregator.filter_value(input_value.clone(), group)
{
match self.anys.get_mut(group) {
None => {
match input_value {
Boolean(_) => self.anys.insert(group.clone(), input_value.clone()),
_ => self.anys.insert(group.clone(), Missing),

Check warning on line 660 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L660

Added line #L660 was not covered by tests
};
}
Some(acc) => {
*acc = match (acc.clone(), input_value) {
(Boolean(l), Value::Boolean(r)) => Value::Boolean(l || *r),
(_, _) => Missing,

Check warning on line 666 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L666

Added line #L666 was not covered by tests
};
}
}
}
}

fn compute(&self, group: &Tuple) -> Value {
self.anys.get(group).unwrap_or(&Null).clone()
}
}

/// Represents SQL's `EVERY` aggregation function
#[derive(Debug)]

Check warning on line 679 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L679

Added line #L679 was not covered by tests
pub(crate) struct Every {
everys: HashMap<Tuple, Value>,
aggregator: AggFilterFn,

Check warning on line 682 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L681-L682

Added lines #L681 - L682 were not covered by tests
}

impl Every {
pub(crate) fn new_distinct() -> Self {
Every {
everys: HashMap::new(),
aggregator: AggFilterFn::Distinct(AggFilterDistinct::new()),
}

Check warning on line 690 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L690

Added line #L690 was not covered by tests
}

pub(crate) fn new_all() -> Self {
Every {
everys: HashMap::new(),
aggregator: AggFilterFn::default(),
}

Check warning on line 697 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L697

Added line #L697 was not covered by tests
}
}

impl AggregateFunction for Every {
fn next_value(&mut self, input_value: &Value, group: &Tuple) {
if !input_value.is_null_or_missing()
&& self.aggregator.filter_value(input_value.clone(), group)
{
match self.everys.get_mut(group) {
None => {
match input_value {
Boolean(_) => self.everys.insert(group.clone(), input_value.clone()),
_ => self.everys.insert(group.clone(), Missing),

Check warning on line 710 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L710

Added line #L710 was not covered by tests
};
}
Some(acc) => {
*acc = match (acc.clone(), input_value) {
(Boolean(l), Value::Boolean(r)) => Value::Boolean(l && *r),
(_, _) => Missing,

Check warning on line 716 in partiql-eval/src/eval/evaluable.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/evaluable.rs#L716

Added line #L716 was not covered by tests
};
}
}
}
}

fn compute(&self, group: &Tuple) -> Value {
self.everys.get(group).unwrap_or(&Null).clone()
}
}

Expand Down
114 changes: 114 additions & 0 deletions partiql-eval/src/eval/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1434,3 +1434,117 @@ impl EvalExpr for EvalFnBaseTableExpr {
Cow::Owned(result)
}
}

/// Represents the `COLL_ANY`/`COLL_SOME` function, e.g. `COLL_ANY(DISTINCT [true, true, false])`.
#[derive(Debug)]

Check warning on line 1439 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1439

Added line #L1439 was not covered by tests
pub(crate) struct EvalFnCollAny {
pub(crate) setq: SetQuantifier,
pub(crate) elems: Box<dyn EvalExpr>,

Check warning on line 1442 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1441-L1442

Added lines #L1441 - L1442 were not covered by tests
}

#[inline]
#[track_caller]
fn coll_any(elems: Vec<&Value>) -> Value {
if elems.is_empty() {
Null
} else {
let mut any = false;
for e in elems {
match e {
Value::Boolean(b) => any = any || *b,
_ => return Missing,
}
}
Value::from(any)
}
}

impl EvalExpr for EvalFnCollAny {
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let elems = self.elems.evaluate(bindings, ctx);
let result = match elems.borrow() {
Null => Null,
Value::List(l) => {
let l_nums: Vec<&Value> = match self.setq {
SetQuantifier::All => l.iter().filter(|&e| !e.is_null_or_missing()).collect(),
SetQuantifier::Distinct => l

Check warning on line 1470 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1470

Added line #L1470 was not covered by tests
.iter()
.filter(|&e| !e.is_null_or_missing())

Check warning on line 1472 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1472

Added line #L1472 was not covered by tests
.unique()
.collect(),
};
coll_any(l_nums)
}
Value::Bag(b) => {
let b_nums: Vec<&Value> = match self.setq {
SetQuantifier::All => b.iter().filter(|&e| !e.is_null_or_missing()).collect(),
SetQuantifier::Distinct => b

Check warning on line 1481 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1481

Added line #L1481 was not covered by tests
.iter()
.filter(|&e| !e.is_null_or_missing())

Check warning on line 1483 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1483

Added line #L1483 was not covered by tests
.unique()
.collect(),
};
coll_any(b_nums)
}
_ => Missing,
};
Cow::Owned(result)
}
}

/// Represents the `COLL_EVERY` function, e.g. `COLL_EVERY(DISTINCT [true, true, false])`.
#[derive(Debug)]

Check warning on line 1496 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1496

Added line #L1496 was not covered by tests
pub(crate) struct EvalFnCollEvery {
pub(crate) setq: SetQuantifier,
pub(crate) elems: Box<dyn EvalExpr>,

Check warning on line 1499 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1498-L1499

Added lines #L1498 - L1499 were not covered by tests
}

#[inline]
#[track_caller]
fn coll_every(elems: Vec<&Value>) -> Value {
if elems.is_empty() {
Null
} else {
let mut every = true;
for e in elems {
match e {
Value::Boolean(b) => every = every && *b,
_ => return Missing,
}
}
Value::from(every)
}
}

impl EvalExpr for EvalFnCollEvery {
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let elems = self.elems.evaluate(bindings, ctx);
let result = match elems.borrow() {
Null => Null,
Value::List(l) => {
let l_nums: Vec<&Value> = match self.setq {
SetQuantifier::All => l.iter().filter(|&e| !e.is_null_or_missing()).collect(),
SetQuantifier::Distinct => l

Check warning on line 1527 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1527

Added line #L1527 was not covered by tests
.iter()
.filter(|&e| !e.is_null_or_missing())

Check warning on line 1529 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1529

Added line #L1529 was not covered by tests
.unique()
.collect(),
};
coll_every(l_nums)
}
Value::Bag(b) => {
let b_nums: Vec<&Value> = match self.setq {
SetQuantifier::All => b.iter().filter(|&e| !e.is_null_or_missing()).collect(),
SetQuantifier::Distinct => b

Check warning on line 1538 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1538

Added line #L1538 was not covered by tests
.iter()
.filter(|&e| !e.is_null_or_missing())

Check warning on line 1540 in partiql-eval/src/eval/expr/mod.rs

View check run for this annotation

Codecov / codecov/patch

partiql-eval/src/eval/expr/mod.rs#L1540

Added line #L1540 was not covered by tests
.unique()
.collect(),
};
coll_every(b_nums)
}
_ => Missing,
};
Cow::Owned(result)
}
}
44 changes: 35 additions & 9 deletions partiql-eval/src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ use partiql_logical::{
use crate::error::{ErrorNode, PlanErr, PlanningError};
use crate::eval;
use crate::eval::evaluable::{
Avg, Count, EvalGroupingStrategy, EvalJoinKind, EvalOrderBy, EvalOrderBySortCondition,
EvalOrderBySortSpec, EvalSubQueryExpr, Evaluable, Max, Min, Sum,
Any, Avg, Count, EvalGroupingStrategy, EvalJoinKind, EvalOrderBy, EvalOrderBySortCondition,
EvalOrderBySortSpec, EvalSubQueryExpr, Evaluable, Every, Max, Min, Sum,
};
use crate::eval::expr::pattern_match::like_to_re_pattern;
use crate::eval::expr::{
EvalBagExpr, EvalBetweenExpr, EvalBinOp, EvalBinOpExpr, EvalDynamicLookup, EvalExpr, EvalFnAbs,
EvalFnBaseTableExpr, EvalFnBitLength, EvalFnBtrim, EvalFnCardinality, EvalFnCharLength,
EvalFnCollAvg, EvalFnCollCount, EvalFnCollMax, EvalFnCollMin, EvalFnCollSum, EvalFnExists,
EvalFnExtractDay, EvalFnExtractHour, EvalFnExtractMinute, EvalFnExtractMonth,
EvalFnExtractSecond, EvalFnExtractTimezoneHour, EvalFnExtractTimezoneMinute, EvalFnExtractYear,
EvalFnLower, EvalFnLtrim, EvalFnModulus, EvalFnOctetLength, EvalFnOverlay, EvalFnPosition,
EvalFnRtrim, EvalFnSubstring, EvalFnUpper, EvalIsTypeExpr, EvalLikeMatch,
EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr, EvalPath, EvalSearchedCaseExpr,
EvalTupleExpr, EvalUnaryOp, EvalUnaryOpExpr, EvalVarRef,
EvalFnCollAny, EvalFnCollAvg, EvalFnCollCount, EvalFnCollEvery, EvalFnCollMax, EvalFnCollMin,
EvalFnCollSum, EvalFnExists, EvalFnExtractDay, EvalFnExtractHour, EvalFnExtractMinute,
EvalFnExtractMonth, EvalFnExtractSecond, EvalFnExtractTimezoneHour,
EvalFnExtractTimezoneMinute, EvalFnExtractYear, EvalFnLower, EvalFnLtrim, EvalFnModulus,
EvalFnOctetLength, EvalFnOverlay, EvalFnPosition, EvalFnRtrim, EvalFnSubstring, EvalFnUpper,
EvalIsTypeExpr, EvalLikeMatch, EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr,
EvalPath, EvalSearchedCaseExpr, EvalTupleExpr, EvalUnaryOp, EvalUnaryOpExpr, EvalVarRef,
};
use crate::eval::EvalPlan;
use partiql_catalog::Catalog;
Expand Down Expand Up @@ -216,6 +216,12 @@ impl<'c> EvaluatorPlanner<'c> {
(AggFunc::AggSum, logical::SetQuantifier::All) => {
eval::evaluable::AggFunc::Sum(Sum::new_all())
}
(AggFunc::AggAny, logical::SetQuantifier::All) => {
eval::evaluable::AggFunc::Any(Any::new_all())
}
(AggFunc::AggEvery, logical::SetQuantifier::All) => {
eval::evaluable::AggFunc::Every(Every::new_all())
}
(AggFunc::AggAvg, logical::SetQuantifier::Distinct) => {
eval::evaluable::AggFunc::Avg(Avg::new_distinct())
}
Expand All @@ -231,6 +237,12 @@ impl<'c> EvaluatorPlanner<'c> {
(AggFunc::AggSum, logical::SetQuantifier::Distinct) => {
eval::evaluable::AggFunc::Sum(Sum::new_distinct())
}
(AggFunc::AggAny, logical::SetQuantifier::Distinct) => {
eval::evaluable::AggFunc::Any(Any::new_distinct())
}
(AggFunc::AggEvery, logical::SetQuantifier::Distinct) => {
eval::evaluable::AggFunc::Every(Every::new_distinct())
}
};
eval::evaluable::AggregateExpression {
name: a_e.name.to_string(),
Expand Down Expand Up @@ -725,6 +737,20 @@ impl<'c> EvaluatorPlanner<'c> {
elems: args.pop().unwrap(),
})
}
CallName::CollAny(setq) => {
correct_num_args_or_err!(self, args, 1, "coll_any/coll_some");
Box::new(EvalFnCollAny {
setq: plan_set_quantifier(setq),
elems: args.pop().unwrap(),
})
}
CallName::CollEvery(setq) => {
correct_num_args_or_err!(self, args, 1, "coll_every");
Box::new(EvalFnCollEvery {
setq: plan_set_quantifier(setq),
elems: args.pop().unwrap(),
})
}
CallName::ByName(name) => match self.catalog.get_function(name) {
None => {
self.errors.push(PlanningError::IllegalState(format!(
Expand Down
Loading

0 comments on commit 9d99994

Please sign in to comment.