Skip to content

Commit 6624e0e

Browse files
committed
Adds lowering of DATE, TIME, and TIMESTAMP literals to logical plan
1 parent 380f526 commit 6624e0e

File tree

8 files changed

+209
-82
lines changed

8 files changed

+209
-82
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- *BREAKING:* partiql-eval: modifies visibility of types implementing `EvalExpr` and `Evaluable`
1111
### Added
1212
- Implements built-in function `EXTRACT`
13+
- Adds lowering `DATE`/`TIME`/`TIMESTAMP` literals to logical plan
1314
### Fixes
1415
- Fix parsing of `EXTRACT` datetime parts `YEAR`, `TIMEZONE_HOUR`, and `TIMEZONE_MINUTE`
1516
- Fix logical plan to eval plan conversion for `EvalOrderBySortSpec` with arguments `DESC` and `NULLS LAST`

partiql-ast/src/ast.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ pub enum Type {
859859
NumericType,
860860
RealType,
861861
DoublePrecisionType,
862-
TimestampType,
862+
TimestampType(Option<u32>),
863863
CharacterType,
864864
CharacterVaryingType,
865865
MissingType,
@@ -868,8 +868,9 @@ pub enum Type {
868868
BlobType,
869869
ClobType,
870870
DateType,
871-
TimeType,
872-
ZonedTimestampType,
871+
TimeType(Option<u32>),
872+
TimeTypeWithTimeZone(Option<u32>),
873+
ZonedTimestampType(Option<u32>),
873874
StructType,
874875
TupleType,
875876
ListType,

partiql-eval/src/eval/expr/mod.rs

+45-35
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use partiql_value::{
1010
};
1111
use regex::{Regex, RegexBuilder};
1212
use rust_decimal::prelude::FromPrimitive;
13+
use rust_decimal::RoundingStrategy;
1314
use std::borrow::{Borrow, Cow};
1415
use std::fmt::Debug;
1516

@@ -952,10 +953,10 @@ impl EvalExpr for EvalFnExtractYear {
952953
Null => Null,
953954
Value::DateTime(dt) => match dt.as_ref() {
954955
DateTime::Date(d) => Value::from(d.year()),
955-
DateTime::Timestamp(tstamp) => Value::from(tstamp.year()),
956-
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.year()),
957-
DateTime::Time(_) => Missing,
958-
DateTime::TimeWithTz(_, _) => Missing,
956+
DateTime::Timestamp(tstamp, _) => Value::from(tstamp.year()),
957+
DateTime::TimestampWithTz(tstamp, _) => Value::from(tstamp.year()),
958+
DateTime::Time(_, _) => Missing,
959+
DateTime::TimeWithTz(_, _, _) => Missing,
959960
},
960961
_ => Missing,
961962
};
@@ -977,10 +978,10 @@ impl EvalExpr for EvalFnExtractMonth {
977978
Null => Null,
978979
Value::DateTime(dt) => match dt.as_ref() {
979980
DateTime::Date(d) => Value::from(d.month() as u8),
980-
DateTime::Timestamp(tstamp) => Value::from(tstamp.month() as u8),
981-
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.month() as u8),
982-
DateTime::Time(_) => Missing,
983-
DateTime::TimeWithTz(_, _) => Missing,
981+
DateTime::Timestamp(tstamp, _) => Value::from(tstamp.month() as u8),
982+
DateTime::TimestampWithTz(tstamp, _) => Value::from(tstamp.month() as u8),
983+
DateTime::Time(_, _) => Missing,
984+
DateTime::TimeWithTz(_, _, _) => Missing,
984985
},
985986
_ => Missing,
986987
};
@@ -1002,10 +1003,10 @@ impl EvalExpr for EvalFnExtractDay {
10021003
Null => Null,
10031004
Value::DateTime(dt) => match dt.as_ref() {
10041005
DateTime::Date(d) => Value::from(d.day()),
1005-
DateTime::Timestamp(tstamp) => Value::from(tstamp.day()),
1006-
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.day()),
1007-
DateTime::Time(_) => Missing,
1008-
DateTime::TimeWithTz(_, _) => Missing,
1006+
DateTime::Timestamp(tstamp, _) => Value::from(tstamp.day()),
1007+
DateTime::TimestampWithTz(tstamp, _) => Value::from(tstamp.day()),
1008+
DateTime::Time(_, _) => Missing,
1009+
DateTime::TimeWithTz(_, _, _) => Missing,
10091010
},
10101011
_ => Missing,
10111012
};
@@ -1026,10 +1027,10 @@ impl EvalExpr for EvalFnExtractHour {
10261027
let result = match value.borrow() {
10271028
Null => Null,
10281029
Value::DateTime(dt) => match dt.as_ref() {
1029-
DateTime::Time(t) => Value::from(t.hour()),
1030-
DateTime::TimeWithTz(t, _) => Value::from(t.hour()),
1031-
DateTime::Timestamp(tstamp) => Value::from(tstamp.hour()),
1032-
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.hour()),
1030+
DateTime::Time(t, _) => Value::from(t.hour()),
1031+
DateTime::TimeWithTz(t, _, _) => Value::from(t.hour()),
1032+
DateTime::Timestamp(tstamp, _) => Value::from(tstamp.hour()),
1033+
DateTime::TimestampWithTz(tstamp, _) => Value::from(tstamp.hour()),
10331034
DateTime::Date(_) => Missing,
10341035
},
10351036
_ => Missing,
@@ -1051,10 +1052,10 @@ impl EvalExpr for EvalFnExtractMinute {
10511052
let result = match value.borrow() {
10521053
Null => Null,
10531054
Value::DateTime(dt) => match dt.as_ref() {
1054-
DateTime::Time(t) => Value::from(t.minute()),
1055-
DateTime::TimeWithTz(t, _) => Value::from(t.minute()),
1056-
DateTime::Timestamp(tstamp) => Value::from(tstamp.minute()),
1057-
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.minute()),
1055+
DateTime::Time(t, _) => Value::from(t.minute()),
1056+
DateTime::TimeWithTz(t, _, _) => Value::from(t.minute()),
1057+
DateTime::Timestamp(tstamp, _) => Value::from(tstamp.minute()),
1058+
DateTime::TimestampWithTz(tstamp, _) => Value::from(tstamp.minute()),
10581059
DateTime::Date(_) => Missing,
10591060
},
10601061
_ => Missing,
@@ -1069,10 +1070,17 @@ pub(crate) struct EvalFnExtractSecond {
10691070
pub(crate) value: Box<dyn EvalExpr>,
10701071
}
10711072

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

10781086
impl EvalExpr for EvalFnExtractSecond {
@@ -1082,11 +1090,13 @@ impl EvalExpr for EvalFnExtractSecond {
10821090
let result = match value.borrow() {
10831091
Null => Null,
10841092
Value::DateTime(dt) => match dt.as_ref() {
1085-
DateTime::Time(t) => total_seconds(t.second(), t.nanosecond()),
1086-
DateTime::TimeWithTz(t, _) => total_seconds(t.second(), t.nanosecond()),
1087-
DateTime::Timestamp(tstamp) => total_seconds(tstamp.second(), tstamp.nanosecond()),
1088-
DateTime::TimestampWithTz(tstamp) => {
1089-
total_seconds(tstamp.second(), tstamp.nanosecond())
1093+
DateTime::Time(t, p) => total_seconds(t.second(), t.nanosecond(), *p),
1094+
DateTime::TimeWithTz(t, p, _) => total_seconds(t.second(), t.nanosecond(), *p),
1095+
DateTime::Timestamp(tstamp, p) => {
1096+
total_seconds(tstamp.second(), tstamp.nanosecond(), *p)
1097+
}
1098+
DateTime::TimestampWithTz(tstamp, p) => {
1099+
total_seconds(tstamp.second(), tstamp.nanosecond(), *p)
10901100
}
10911101
DateTime::Date(_) => Missing,
10921102
},
@@ -1109,11 +1119,11 @@ impl EvalExpr for EvalFnExtractTimezoneHour {
11091119
let result = match value.borrow() {
11101120
Null => Null,
11111121
Value::DateTime(dt) => match dt.as_ref() {
1112-
DateTime::TimeWithTz(_, tz) => Value::from(tz.whole_hours()),
1113-
DateTime::TimestampWithTz(tstamp) => Value::from(tstamp.offset().whole_hours()),
1122+
DateTime::TimeWithTz(_, _, tz) => Value::from(tz.whole_hours()),
1123+
DateTime::TimestampWithTz(tstamp, _) => Value::from(tstamp.offset().whole_hours()),
11141124
DateTime::Date(_) => Missing,
1115-
DateTime::Time(_) => Missing,
1116-
DateTime::Timestamp(_) => Missing,
1125+
DateTime::Time(_, _) => Missing,
1126+
DateTime::Timestamp(_, _) => Missing,
11171127
},
11181128
_ => Missing,
11191129
};
@@ -1134,13 +1144,13 @@ impl EvalExpr for EvalFnExtractTimezoneMinute {
11341144
let result = match value.borrow() {
11351145
Null => Null,
11361146
Value::DateTime(dt) => match dt.as_ref() {
1137-
DateTime::TimeWithTz(_, tz) => Value::from(tz.minutes_past_hour()),
1138-
DateTime::TimestampWithTz(tstamp) => {
1147+
DateTime::TimeWithTz(_, _, tz) => Value::from(tz.minutes_past_hour()),
1148+
DateTime::TimestampWithTz(tstamp, _) => {
11391149
Value::from(tstamp.offset().minutes_past_hour())
11401150
}
11411151
DateTime::Date(_) => Missing,
1142-
DateTime::Time(_) => Missing,
1143-
DateTime::Timestamp(_) => Missing,
1152+
DateTime::Time(_, _) => Missing,
1153+
DateTime::Timestamp(_, _) => Missing,
11441154
},
11451155
_ => Missing,
11461156
};

partiql-logical-planner/src/lower.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use partiql_ast::ast::{
1010
InsertValue, Item, Join, JoinKind, JoinSpec, Like, List, Lit, NodeId, NullOrderingSpec,
1111
OnConflict, OrderByExpr, OrderingSpec, Path, PathStep, ProjectExpr, Projection, ProjectionKind,
1212
Query, QuerySet, Remove, SearchedCase, Select, Set, SetExpr, SetQuantifier, Sexp, SimpleCase,
13-
SortSpec, Struct, SymbolPrimitive, UniOp, UniOpKind, VarRef,
13+
SortSpec, Struct, SymbolPrimitive, Type, UniOp, UniOpKind, VarRef,
1414
};
1515
use partiql_ast::visit::{Visit, Visitor};
1616
use partiql_logical as logical;
@@ -20,7 +20,7 @@ use partiql_logical::{
2020
PatternMatchExpr, SortSpecOrder, TupleExpr, ValueExpr,
2121
};
2222

23-
use partiql_value::{BindingsName, Value};
23+
use partiql_value::{BindingsName, DateTime, Value};
2424

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

@@ -843,7 +843,18 @@ impl<'ast> Visitor<'ast> for AstToLogical {
843843
Lit::BitStringLit(_) => todo!("BitStringLit"),
844844
Lit::HexStringLit(_) => todo!("HexStringLit"),
845845
Lit::CollectionLit(_) => todo!("CollectionLit"),
846-
Lit::TypedLit(_, _) => todo!("TypedLit"),
846+
Lit::TypedLit(s, t) => match t {
847+
Type::DateType => Value::DateTime(Box::new(DateTime::from_yyyy_mm_dd(s))),
848+
Type::TimeType(p) => Value::DateTime(Box::new(DateTime::from_hh_mm_ss(s, p))),
849+
Type::TimeTypeWithTimeZone(p) => {
850+
Value::DateTime(Box::new(DateTime::from_hh_mm_ss_time_zone(s, p)))
851+
}
852+
Type::TimestampType(p) => Value::DateTime(Box::new(DateTime::from_hh_mm_ss(s, p))),
853+
Type::ZonedTimestampType(p) => {
854+
Value::DateTime(Box::new(DateTime::from_hh_mm_ss_time_zone(s, p)))
855+
}
856+
_ => todo!("Other types"),
857+
},
847858
};
848859
self.push_value(val);
849860
}

partiql-parser/src/parse/partiql.lalrpop

+61-8
Original file line numberDiff line numberDiff line change
@@ -1184,14 +1184,7 @@ LiteralIon: ast::Lit = {
11841184
}
11851185

11861186
#[inline]
1187-
TypeKeywordStr: &'static str = {
1188-
"DATE" => "DATE",
1189-
"TIME" => "TIME",
1190-
"TIMESTAMP" => "TIMESTAMP",
1191-
"WITH" => "WITH",
1192-
"WITHOUT" => "WITHOUT",
1193-
"ZONE" => "ZONE",
1194-
}
1187+
TypeKeywordStr: &'static str = {}
11951188

11961189
#[inline]
11971190
TypeKeyword: ast::SymbolPrimitive = {
@@ -1214,8 +1207,68 @@ TypeName: ast::Type = {
12141207
<parts:TypeNamePart+> => ast::Type::CustomType( ast::CustomType{ parts } ),
12151208
}
12161209

1210+
#[inline]
1211+
TimePrecision: &'input str = {
1212+
"(" <p:"Int"> ")" => p
1213+
}
1214+
12171215
#[inline]
12181216
TypedLiteral: ast::Lit = {
1217+
"DATE" <s:"String"> => ast::Lit::TypedLit(s.to_owned(), ast::Type::DateType),
1218+
"TIME" <p:TimePrecision?> <s:"String"> => {
1219+
match p {
1220+
None => ast::Lit::TypedLit(s.to_owned(), ast::Type::TimeType(None)),
1221+
Some(p) => {
1222+
let precision = p.parse::<u32>().unwrap();
1223+
ast::Lit::TypedLit(s.to_owned(), ast::Type::TimeType(Some(precision)))
1224+
}
1225+
}
1226+
},
1227+
"TIME" <p:TimePrecision?> "WITH" "TIME" "ZONE" <s:"String"> => {
1228+
match p {
1229+
None => ast::Lit::TypedLit(s.to_owned(), ast::Type::TimeTypeWithTimeZone(None)),
1230+
Some(p) => {
1231+
let precision = p.parse::<u32>().unwrap();
1232+
ast::Lit::TypedLit(s.to_owned(), ast::Type::TimeTypeWithTimeZone(Some(precision)))
1233+
}
1234+
}
1235+
},
1236+
"TIME" <p:TimePrecision?> "WITHOUT" "TIME" "ZONE" <s:"String"> => {
1237+
match p {
1238+
None => ast::Lit::TypedLit(s.to_owned(), ast::Type::TimeType(None)),
1239+
Some(p) => {
1240+
let precision = p.parse::<u32>().unwrap();
1241+
ast::Lit::TypedLit(s.to_owned(), ast::Type::TimeType(Some(precision)))
1242+
}
1243+
}
1244+
},
1245+
"TIMESTAMP" <p:TimePrecision?> <s:"String"> => {
1246+
match p {
1247+
None => ast::Lit::TypedLit(s.to_owned(), ast::Type::TimestampType(None)),
1248+
Some(p) => {
1249+
let precision = p.parse::<u32>().unwrap();
1250+
ast::Lit::TypedLit(s.to_owned(), ast::Type::TimestampType(Some(precision)))
1251+
}
1252+
}
1253+
},
1254+
"TIMESTAMP" <p:TimePrecision?> "WITH" "TIME" "ZONE" <s:"String"> => {
1255+
match p {
1256+
None => ast::Lit::TypedLit(s.to_owned(), ast::Type::ZonedTimestampType(None)),
1257+
Some(p) => {
1258+
let precision = p.parse::<u32>().unwrap();
1259+
ast::Lit::TypedLit(s.to_owned(), ast::Type::ZonedTimestampType(Some(precision)))
1260+
}
1261+
}
1262+
},
1263+
"TIMESTAMP" <p:TimePrecision?> "WITHOUT" "TIME" "ZONE" <s:"String"> => {
1264+
match p {
1265+
None => ast::Lit::TypedLit(s.to_owned(), ast::Type::TimestampType(None)),
1266+
Some(p) => {
1267+
let precision = p.parse::<u32>().unwrap();
1268+
ast::Lit::TypedLit(s.to_owned(), ast::Type::TimestampType(Some(precision)))
1269+
}
1270+
}
1271+
},
12191272
<ty:TypeName> <s:"String"> => ast::Lit::TypedLit(s.to_owned(), ty),
12201273
// TODO we could support postgres-style literals with the following:
12211274
//<s:"String"> "::" <ty:TypeName> => ast::Lit::TypedLit(s.to_owned(), ty),

partiql-value/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ rust_decimal = { version = "1.25.0", default-features = false, features = ["std"
2828
rust_decimal_macros = "1.26"
2929
serde = { version = "1.*", features = ["derive"], optional = true }
3030
ion-rs = "0.16"
31-
time = { version = "0.3", features = ["macros", "serde"] }
31+
time = { version = "0.3", features = ["macros", "serde", "parsing"] }
3232
once_cell = "1"
3333
regex = "1.7"
3434

0 commit comments

Comments
 (0)