Skip to content

Commit

Permalink
Add EXTRACT builtin function; update partiql-tests (#340)
Browse files Browse the repository at this point in the history
  • Loading branch information
alancai98 authored Apr 17, 2023
1 parent 2cb413a commit e63267a
Show file tree
Hide file tree
Showing 11 changed files with 459 additions and 34 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
### Added
- Implements built-in function `EXTRACT`
### Fixes
- Fix parsing of `EXTRACT` datetime parts `YEAR`, `TIMEZONE_HOUR`, and `TIMEZONE_MINUTE`

## [0.3.0] - 2023-04-11
### Changed
Expand Down
13 changes: 8 additions & 5 deletions partiql-conformance-tests/tests/test_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,25 +194,28 @@ fn parse_test_value_time(reader: &mut Reader) -> DateTime {
}
reader.step_out().expect("step out of struct");

DateTime::from_hmfs_tz(
DateTime::from_hms_nano_tz(
time.hour.expect("hour"),
time.minute.expect("minute"),
time.second.expect("second"),
time.second.expect("second").trunc() as u8,
time.second.expect("second").fract() as u32,
time.tz_hour,
time.tz_minute,
)
}

fn parse_test_value_datetime(reader: &mut Reader) -> DateTime {
let ts = reader.read_timestamp().unwrap();
// TODO: fractional seconds Cf. https://github.com/amazon-ion/ion-rust/pull/482#issuecomment-1470615286
DateTime::from_ymdhms(
let offset = ts.offset();
DateTime::from_ymdhms_nano_offset_minutes(
ts.year(),
NonZeroU8::new(ts.month() as u8).unwrap(),
ts.day() as u8,
ts.hour() as u8,
ts.minute() as u8,
ts.second() as f64,
ts.second() as u8,
ts.nanoseconds(),
offset,
)
}

Expand Down
214 changes: 213 additions & 1 deletion partiql-eval/src/eval/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use itertools::Itertools;
use partiql_logical::Type;
use partiql_value::Value::{Boolean, Missing, Null};
use partiql_value::{
Bag, BinaryAnd, BinaryOr, BindingsName, List, NullableEq, NullableOrd, Tuple, UnaryPlus, Value,
Bag, BinaryAnd, BinaryOr, BindingsName, DateTime, List, NullableEq, NullableOrd, Tuple,
UnaryPlus, Value,
};
use regex::{Regex, RegexBuilder};
use rust_decimal::prelude::FromPrimitive;
use std::borrow::{Borrow, Cow};
use std::fmt::Debug;

Expand Down Expand Up @@ -935,3 +937,213 @@ impl EvalExpr for EvalFnCardinality {
Cow::Owned(result)
}
}

/// Represents a year `EXTRACT` function, e.g. `extract(YEAR FROM t)`.
#[derive(Debug)]
pub struct EvalFnExtractYear {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnExtractYear {
#[inline]
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let value = self.value.evaluate(bindings, ctx);
let result = match value.borrow() {
Null => Null,
Value::DateTime(dt) => match dt.as_ref() {
DateTime::Date(d) => Value::from(d.year()),
DateTime::Timestamp(tstamp) => Value::from(tstamp.year()),
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.year()),
DateTime::Time(_) => Missing,
DateTime::TimeWithTz(_, _) => Missing,
},
_ => Missing,
};
Cow::Owned(result)
}
}

/// Represents a month `EXTRACT` function, e.g. `extract(MONTH FROM t)`.
#[derive(Debug)]
pub struct EvalFnExtractMonth {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnExtractMonth {
#[inline]
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let value = self.value.evaluate(bindings, ctx);
let result = match value.borrow() {
Null => Null,
Value::DateTime(dt) => match dt.as_ref() {
DateTime::Date(d) => Value::from(d.month() as u8),
DateTime::Timestamp(tstamp) => Value::from(tstamp.month() as u8),
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.month() as u8),
DateTime::Time(_) => Missing,
DateTime::TimeWithTz(_, _) => Missing,
},
_ => Missing,
};
Cow::Owned(result)
}
}

/// Represents a day `EXTRACT` function, e.g. `extract(DAY FROM t)`.
#[derive(Debug)]
pub struct EvalFnExtractDay {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnExtractDay {
#[inline]
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let value = self.value.evaluate(bindings, ctx);
let result = match value.borrow() {
Null => Null,
Value::DateTime(dt) => match dt.as_ref() {
DateTime::Date(d) => Value::from(d.day()),
DateTime::Timestamp(tstamp) => Value::from(tstamp.day()),
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.day()),
DateTime::Time(_) => Missing,
DateTime::TimeWithTz(_, _) => Missing,
},
_ => Missing,
};
Cow::Owned(result)
}
}

/// Represents an hour `EXTRACT` function, e.g. `extract(HOUR FROM t)`.
#[derive(Debug)]
pub struct EvalFnExtractHour {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnExtractHour {
#[inline]
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let value = self.value.evaluate(bindings, ctx);
let result = match value.borrow() {
Null => Null,
Value::DateTime(dt) => match dt.as_ref() {
DateTime::Time(t) => Value::from(t.hour()),
DateTime::TimeWithTz(t, _) => Value::from(t.hour()),
DateTime::Timestamp(tstamp) => Value::from(tstamp.hour()),
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.hour()),
DateTime::Date(_) => Missing,
},
_ => Missing,
};
Cow::Owned(result)
}
}

/// Represents a minute `EXTRACT` function, e.g. `extract(MINUTE FROM t)`.
#[derive(Debug)]
pub struct EvalFnExtractMinute {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnExtractMinute {
#[inline]
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let value = self.value.evaluate(bindings, ctx);
let result = match value.borrow() {
Null => Null,
Value::DateTime(dt) => match dt.as_ref() {
DateTime::Time(t) => Value::from(t.minute()),
DateTime::TimeWithTz(t, _) => Value::from(t.minute()),
DateTime::Timestamp(tstamp) => Value::from(tstamp.minute()),
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.minute()),
DateTime::Date(_) => Missing,
},
_ => Missing,
};
Cow::Owned(result)
}
}

/// Represents a second `EXTRACT` function, e.g. `extract(SECOND FROM t)`.
#[derive(Debug)]
pub struct EvalFnExtractSecond {
pub value: Box<dyn EvalExpr>,
}

fn total_seconds(second: u8, nanosecond: u32) -> Value {
let result = rust_decimal::Decimal::from_f64(((second as f64 * 1e9) + nanosecond as f64) / 1e9)
.expect("time as decimal");
Value::from(result)
}

impl EvalExpr for EvalFnExtractSecond {
#[inline]
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let value = self.value.evaluate(bindings, ctx);
let result = match value.borrow() {
Null => Null,
Value::DateTime(dt) => match dt.as_ref() {
DateTime::Time(t) => total_seconds(t.second(), t.nanosecond()),
DateTime::TimeWithTz(t, _) => total_seconds(t.second(), t.nanosecond()),
DateTime::Timestamp(tstamp) => total_seconds(tstamp.second(), tstamp.nanosecond()),
DateTime::TimestampWithTz(tstamp) => {
total_seconds(tstamp.second(), tstamp.nanosecond())
}
DateTime::Date(_) => Missing,
},
_ => Missing,
};
Cow::Owned(result)
}
}

/// Represents a timezone hour `EXTRACT` function, e.g. `extract(TIMEZONE_HOUR FROM t)`.
#[derive(Debug)]
pub struct EvalFnExtractTimezoneHour {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnExtractTimezoneHour {
#[inline]
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let value = self.value.evaluate(bindings, ctx);
let result = match value.borrow() {
Null => Null,
Value::DateTime(dt) => match dt.as_ref() {
DateTime::TimeWithTz(_, tz) => Value::from(tz.whole_hours()),
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.offset().whole_hours()),
DateTime::Date(_) => Missing,
DateTime::Time(_) => Missing,
DateTime::Timestamp(_) => Missing,
},
_ => Missing,
};
Cow::Owned(result)
}
}

/// Represents a timezone minute `EXTRACT` function, e.g. `extract(TIMEZONE_MINUTE FROM t)`.
#[derive(Debug)]
pub struct EvalFnExtractTimezoneMinute {
pub value: Box<dyn EvalExpr>,
}

impl EvalExpr for EvalFnExtractTimezoneMinute {
#[inline]
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
let value = self.value.evaluate(bindings, ctx);
let result = match value.borrow() {
Null => Null,
Value::DateTime(dt) => match dt.as_ref() {
DateTime::TimeWithTz(_, tz) => Value::from(tz.minutes_past_hour()),
DateTime::TimestampWithTz(tstamp) => {
Value::from(tstamp.offset().minutes_past_hour())
}
DateTime::Date(_) => Missing,
DateTime::Time(_) => Missing,
DateTime::Timestamp(_) => Missing,
},
_ => Missing,
};
Cow::Owned(result)
}
}
60 changes: 55 additions & 5 deletions partiql-eval/src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ use crate::eval::evaluable::{
use crate::eval::expr::pattern_match::like_to_re_pattern;
use crate::eval::expr::{
EvalBagExpr, EvalBetweenExpr, EvalBinOp, EvalBinOpExpr, EvalDynamicLookup, EvalExpr, EvalFnAbs,
EvalFnBitLength, EvalFnBtrim, EvalFnCardinality, EvalFnCharLength, EvalFnExists, EvalFnLower,
EvalFnLtrim, EvalFnModulus, EvalFnOctetLength, EvalFnOverlay, EvalFnPosition, EvalFnRtrim,
EvalFnSubstring, EvalFnUpper, EvalIsTypeExpr, EvalLikeMatch, EvalLikeNonStringNonLiteralMatch,
EvalListExpr, EvalLitExpr, EvalPath, EvalSearchedCaseExpr, EvalTupleExpr, EvalUnaryOp,
EvalUnaryOpExpr, EvalVarRef,
EvalFnBitLength, EvalFnBtrim, EvalFnCardinality, EvalFnCharLength, 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_value::Value::Null;
Expand Down Expand Up @@ -585,6 +587,54 @@ impl EvaluatorPlanner {
value: args.pop().unwrap(),
})
}
CallName::ExtractYear => {
assert_eq!(args.len(), 1);
Box::new(EvalFnExtractYear {
value: args.pop().unwrap(),
})
}
CallName::ExtractMonth => {
assert_eq!(args.len(), 1);
Box::new(EvalFnExtractMonth {
value: args.pop().unwrap(),
})
}
CallName::ExtractDay => {
assert_eq!(args.len(), 1);
Box::new(EvalFnExtractDay {
value: args.pop().unwrap(),
})
}
CallName::ExtractHour => {
assert_eq!(args.len(), 1);
Box::new(EvalFnExtractHour {
value: args.pop().unwrap(),
})
}
CallName::ExtractMinute => {
assert_eq!(args.len(), 1);
Box::new(EvalFnExtractMinute {
value: args.pop().unwrap(),
})
}
CallName::ExtractSecond => {
assert_eq!(args.len(), 1);
Box::new(EvalFnExtractSecond {
value: args.pop().unwrap(),
})
}
CallName::ExtractTimezoneHour => {
assert_eq!(args.len(), 1);
Box::new(EvalFnExtractTimezoneHour {
value: args.pop().unwrap(),
})
}
CallName::ExtractTimezoneMinute => {
assert_eq!(args.len(), 1);
Box::new(EvalFnExtractTimezoneMinute {
value: args.pop().unwrap(),
})
}
}
}
}
Expand Down
Loading

1 comment on commit e63267a

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PartiQL (rust) Benchmark

Benchmark suite Current: e63267a Previous: 2cb413a Ratio
parse-1 6009 ns/iter (± 32) 6295 ns/iter (± 52) 0.95
parse-15 59511 ns/iter (± 143) 60897 ns/iter (± 294) 0.98
parse-30 117124 ns/iter (± 248) 120959 ns/iter (± 440) 0.97
compile-1 4746 ns/iter (± 14) 5187 ns/iter (± 35) 0.91
compile-15 35249 ns/iter (± 22) 36956 ns/iter (± 53) 0.95
compile-30 72488 ns/iter (± 84) 76159 ns/iter (± 210) 0.95
plan-1 19141 ns/iter (± 23) 19834 ns/iter (± 17) 0.97
plan-15 356062 ns/iter (± 810) 363140 ns/iter (± 949) 0.98
plan-30 719534 ns/iter (± 1211) 737260 ns/iter (± 1587) 0.98
eval-1 23756020 ns/iter (± 602467) 22145134 ns/iter (± 224355) 1.07
eval-15 119588157 ns/iter (± 375164) 122278409 ns/iter (± 413341) 0.98
eval-30 226931928 ns/iter (± 379982) 239722063 ns/iter (± 377737) 0.95
join 14435 ns/iter (± 21) 14707 ns/iter (± 36) 0.98
simple 5136 ns/iter (± 7) 5451 ns/iter (± 8) 0.94
simple-no 2264 ns/iter (± 6) 2324 ns/iter (± 1) 0.97
numbers 144 ns/iter (± 0) 107 ns/iter (± 0) 1.35
parse-simple 705 ns/iter (± 0) 700 ns/iter (± 0) 1.01
parse-ion 2599 ns/iter (± 10) 2653 ns/iter (± 1) 0.98
parse-group 8693 ns/iter (± 12) 9051 ns/iter (± 21) 0.96
parse-complex 21972 ns/iter (± 151) 22462 ns/iter (± 62) 0.98
parse-complex-fexpr 35056 ns/iter (± 83) 33389 ns/iter (± 99) 1.05

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.