Skip to content

Commit

Permalink
Adds lowering of DATE, TIME, and TIMESTAMP literals to logical plan
Browse files Browse the repository at this point in the history
  • Loading branch information
alancai98 committed Apr 20, 2023
1 parent 380f526 commit 11ab169
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 82 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- *BREAKING:* partiql-eval: modifies visibility of types implementing `EvalExpr` and `Evaluable`
### Added
- Implements built-in function `EXTRACT`
- Adds lowering `DATE`/`TIME`/`TIMESTAMP` literals to logical plan
### Fixes
- Fix parsing of `EXTRACT` datetime parts `YEAR`, `TIMEZONE_HOUR`, and `TIMEZONE_MINUTE`
- Fix logical plan to eval plan conversion for `EvalOrderBySortSpec` with arguments `DESC` and `NULLS LAST`
Expand Down
7 changes: 4 additions & 3 deletions partiql-ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ pub enum Type {
NumericType,
RealType,
DoublePrecisionType,
TimestampType,
TimestampType(Option<u32>),
CharacterType,
CharacterVaryingType,
MissingType,
Expand All @@ -868,8 +868,9 @@ pub enum Type {
BlobType,
ClobType,
DateType,
TimeType,
ZonedTimestampType,
TimeType(Option<u32>),
TimeTypeWithTimeZone(Option<u32>),
ZonedTimestampType(Option<u32>),
StructType,
TupleType,
ListType,
Expand Down
80 changes: 45 additions & 35 deletions partiql-eval/src/eval/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use partiql_value::{
};
use regex::{Regex, RegexBuilder};
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::RoundingStrategy;
use std::borrow::{Borrow, Cow};
use std::fmt::Debug;

Expand Down Expand Up @@ -952,10 +953,10 @@ impl EvalExpr for EvalFnExtractYear {
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,
DateTime::Timestamp(tstamp, _) => Value::from(tstamp.year()),
DateTime::TimestampWithTz(tstamp, _) => Value::from(tstamp.year()),
DateTime::Time(_, _) => Missing,
DateTime::TimeWithTz(_, _, _) => Missing,
},
_ => Missing,
};
Expand All @@ -977,10 +978,10 @@ impl EvalExpr for EvalFnExtractMonth {
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,
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,
};
Expand All @@ -1002,10 +1003,10 @@ impl EvalExpr for EvalFnExtractDay {
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,
DateTime::Timestamp(tstamp, _) => Value::from(tstamp.day()),
DateTime::TimestampWithTz(tstamp, _) => Value::from(tstamp.day()),
DateTime::Time(_, _) => Missing,
DateTime::TimeWithTz(_, _, _) => Missing,
},
_ => Missing,
};
Expand All @@ -1026,10 +1027,10 @@ impl EvalExpr for EvalFnExtractHour {
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::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,
Expand All @@ -1051,10 +1052,10 @@ impl EvalExpr for EvalFnExtractMinute {
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::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,
Expand All @@ -1069,10 +1070,17 @@ pub(crate) struct EvalFnExtractSecond {
pub(crate) value: Box<dyn EvalExpr>,
}

fn total_seconds(second: u8, nanosecond: u32) -> Value {
fn total_seconds(second: u8, nanosecond: u32, precision: Option<u32>) -> Value {
let result = rust_decimal::Decimal::from_f64(((second as f64 * 1e9) + nanosecond as f64) / 1e9)
.expect("time as decimal");
Value::from(result)
match precision {
None => Value::from(result),
Some(p) => {
// TODO: currently using `RoundingStrategy::MidpointAwayFromZero`, which follows what
// Kotlin does. Need to determine if this strategy is what we want or some configurability
Value::from(result.round_dp_with_strategy(p, RoundingStrategy::MidpointAwayFromZero))
}
}
}

impl EvalExpr for EvalFnExtractSecond {
Expand All @@ -1082,11 +1090,13 @@ impl EvalExpr for EvalFnExtractSecond {
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::Time(t, p) => total_seconds(t.second(), t.nanosecond(), *p),
DateTime::TimeWithTz(t, p, _) => total_seconds(t.second(), t.nanosecond(), *p),
DateTime::Timestamp(tstamp, p) => {
total_seconds(tstamp.second(), tstamp.nanosecond(), *p)
}
DateTime::TimestampWithTz(tstamp, p) => {
total_seconds(tstamp.second(), tstamp.nanosecond(), *p)
}
DateTime::Date(_) => Missing,
},
Expand All @@ -1109,11 +1119,11 @@ impl EvalExpr for EvalFnExtractTimezoneHour {
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::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,
DateTime::Time(_, _) => Missing,
DateTime::Timestamp(_, _) => Missing,
},
_ => Missing,
};
Expand All @@ -1134,13 +1144,13 @@ impl EvalExpr for EvalFnExtractTimezoneMinute {
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) => {
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,
DateTime::Time(_, _) => Missing,
DateTime::Timestamp(_, _) => Missing,
},
_ => Missing,
};
Expand Down
17 changes: 14 additions & 3 deletions partiql-logical-planner/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use partiql_ast::ast::{
InsertValue, Item, Join, JoinKind, JoinSpec, Like, List, Lit, NodeId, NullOrderingSpec,
OnConflict, OrderByExpr, OrderingSpec, Path, PathStep, ProjectExpr, Projection, ProjectionKind,
Query, QuerySet, Remove, SearchedCase, Select, Set, SetExpr, SetQuantifier, Sexp, SimpleCase,
SortSpec, Struct, SymbolPrimitive, UniOp, UniOpKind, VarRef,
SortSpec, Struct, SymbolPrimitive, Type, UniOp, UniOpKind, VarRef,
};
use partiql_ast::visit::{Visit, Visitor};
use partiql_logical as logical;
Expand All @@ -20,7 +20,7 @@ use partiql_logical::{
PatternMatchExpr, SortSpecOrder, TupleExpr, ValueExpr,
};

use partiql_value::{BindingsName, Value};
use partiql_value::{BindingsName, DateTime, Value};

use std::collections::{HashMap, HashSet};

Expand Down Expand Up @@ -843,7 +843,18 @@ impl<'ast> Visitor<'ast> for AstToLogical {
Lit::BitStringLit(_) => todo!("BitStringLit"),
Lit::HexStringLit(_) => todo!("HexStringLit"),
Lit::CollectionLit(_) => todo!("CollectionLit"),
Lit::TypedLit(_, _) => todo!("TypedLit"),
Lit::TypedLit(s, t) => match t {
Type::DateType => Value::DateTime(Box::new(DateTime::from_yyyy_mm_dd(s))),
Type::TimeType(p) => Value::DateTime(Box::new(DateTime::from_hh_mm_ss(s, p))),
Type::TimeTypeWithTimeZone(p) => {
Value::DateTime(Box::new(DateTime::from_hh_mm_ss_time_zone(s, p)))
}
Type::TimestampType(p) => Value::DateTime(Box::new(DateTime::from_hh_mm_ss(s, p))),
Type::ZonedTimestampType(p) => {
Value::DateTime(Box::new(DateTime::from_hh_mm_ss_time_zone(s, p)))
}
_ => todo!("Other types"),
},
};
self.push_value(val);
}
Expand Down
69 changes: 61 additions & 8 deletions partiql-parser/src/parse/partiql.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -1184,14 +1184,7 @@ LiteralIon: ast::Lit = {
}

#[inline]
TypeKeywordStr: &'static str = {
"DATE" => "DATE",
"TIME" => "TIME",
"TIMESTAMP" => "TIMESTAMP",
"WITH" => "WITH",
"WITHOUT" => "WITHOUT",
"ZONE" => "ZONE",
}
TypeKeywordStr: &'static str = {}

#[inline]
TypeKeyword: ast::SymbolPrimitive = {
Expand All @@ -1209,8 +1202,68 @@ TypeNamePart: ast::CustomTypePart = {
<id:TypeKeyword> "(" <args:CommaSepPlus<TypeParam>> ")" => ast::CustomTypePart::Parameterized( id, args ),
}

#[inline]
TimePrecision: &'input str = {
"(" <p:"Int"> ")" => p
}

#[inline]
TypeName: ast::Type = {
"DATE" => ast::Type::DateType,
"TIME" <p:TimePrecision?> => {
match p {
None => ast::Type::TimeType(None),
Some(p) => {
let precision = p.parse::<u32>().unwrap();
ast::Type::TimeType(Some(precision))
}
}
},
"TIME" <p:TimePrecision?> "WITH" "TIME" "ZONE" => {
match p {
None => ast::Type::TimeTypeWithTimeZone(None),
Some(p) => {
let precision = p.parse::<u32>().unwrap();
ast::Type::TimeTypeWithTimeZone(Some(precision))
}
}
},
"TIME" <p:TimePrecision?> "WITHOUT" "TIME" "ZONE" => {
match p {
None => ast::Type::TimeType(None),
Some(p) => {
let precision = p.parse::<u32>().unwrap();
ast::Type::TimeType(Some(precision))
}
}
},
"TIMESTAMP" <p:TimePrecision?> => {
match p {
None => ast::Type::TimestampType(None),
Some(p) => {
let precision = p.parse::<u32>().unwrap();
ast::Type::TimestampType(Some(precision))
}
}
},
"TIMESTAMP" <p:TimePrecision?> "WITH" "TIME" "ZONE" => {
match p {
None => ast::Type::ZonedTimestampType(None),
Some(p) => {
let precision = p.parse::<u32>().unwrap();
ast::Type::ZonedTimestampType(Some(precision))
}
}
},
"TIMESTAMP" <p:TimePrecision?> "WITHOUT" "TIME" "ZONE" => {
match p {
None => ast::Type::TimestampType(None),
Some(p) => {
let precision = p.parse::<u32>().unwrap();
ast::Type::TimestampType(Some(precision))
}
}
},
<parts:TypeNamePart+> => ast::Type::CustomType( ast::CustomType{ parts } ),
}

Expand Down
2 changes: 1 addition & 1 deletion partiql-value/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ rust_decimal = { version = "1.25.0", default-features = false, features = ["std"
rust_decimal_macros = "1.26"
serde = { version = "1.*", features = ["derive"], optional = true }
ion-rs = "0.16"
time = { version = "0.3", features = ["macros", "serde"] }
time = { version = "0.3", features = ["macros", "serde", "parsing"] }
once_cell = "1"
regex = "1.7"

Expand Down
Loading

0 comments on commit 11ab169

Please sign in to comment.