Skip to content

Adds lowering of DATE, TIME, and TIMESTAMP literals to logical plan #345

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- *BREAKING:* partiql-eval: modifies visibility of types implementing `EvalExpr` and `Evaluable`
- *BREAKING:* partiql-ast: inclusion of optional precision on the time and timestamp `Type`s
### 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
19 changes: 16 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,20 @@ 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_yyyy_mm_dd_hh_mm_ss(s, p)))
}
Type::ZonedTimestampType(p) => {
Value::DateTime(Box::new(DateTime::from_yyyy_mm_dd_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