Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EXTRACT builtin function; update partiql-tests #340

Merged
merged 5 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
alancai98 marked this conversation as resolved.
Show resolved Hide resolved
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