Skip to content

Commit

Permalink
Merge 73a47be into 2cb413a
Browse files Browse the repository at this point in the history
  • Loading branch information
alancai98 authored Apr 17, 2023
2 parents 2cb413a + 73a47be commit cbe8cd7
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

0 comments on commit cbe8cd7

Please sign in to comment.