Skip to content

Commit e63267a

Browse files
authored
Add EXTRACT builtin function; update partiql-tests (#340)
1 parent 2cb413a commit e63267a

File tree

11 files changed

+459
-34
lines changed

11 files changed

+459
-34
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99
### Changed
1010
### Added
11+
- Implements built-in function `EXTRACT`
1112
### Fixes
13+
- Fix parsing of `EXTRACT` datetime parts `YEAR`, `TIMEZONE_HOUR`, and `TIMEZONE_MINUTE`
1214

1315
## [0.3.0] - 2023-04-11
1416
### Changed

partiql-conformance-tests/tests/test_value.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,25 +194,28 @@ fn parse_test_value_time(reader: &mut Reader) -> DateTime {
194194
}
195195
reader.step_out().expect("step out of struct");
196196

197-
DateTime::from_hmfs_tz(
197+
DateTime::from_hms_nano_tz(
198198
time.hour.expect("hour"),
199199
time.minute.expect("minute"),
200-
time.second.expect("second"),
200+
time.second.expect("second").trunc() as u8,
201+
time.second.expect("second").fract() as u32,
201202
time.tz_hour,
202203
time.tz_minute,
203204
)
204205
}
205206

206207
fn parse_test_value_datetime(reader: &mut Reader) -> DateTime {
207208
let ts = reader.read_timestamp().unwrap();
208-
// TODO: fractional seconds Cf. https://github.com/amazon-ion/ion-rust/pull/482#issuecomment-1470615286
209-
DateTime::from_ymdhms(
209+
let offset = ts.offset();
210+
DateTime::from_ymdhms_nano_offset_minutes(
210211
ts.year(),
211212
NonZeroU8::new(ts.month() as u8).unwrap(),
212213
ts.day() as u8,
213214
ts.hour() as u8,
214215
ts.minute() as u8,
215-
ts.second() as f64,
216+
ts.second() as u8,
217+
ts.nanoseconds(),
218+
offset,
216219
)
217220
}
218221

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

Lines changed: 213 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ use itertools::Itertools;
55
use partiql_logical::Type;
66
use partiql_value::Value::{Boolean, Missing, Null};
77
use partiql_value::{
8-
Bag, BinaryAnd, BinaryOr, BindingsName, List, NullableEq, NullableOrd, Tuple, UnaryPlus, Value,
8+
Bag, BinaryAnd, BinaryOr, BindingsName, DateTime, List, NullableEq, NullableOrd, Tuple,
9+
UnaryPlus, Value,
910
};
1011
use regex::{Regex, RegexBuilder};
12+
use rust_decimal::prelude::FromPrimitive;
1113
use std::borrow::{Borrow, Cow};
1214
use std::fmt::Debug;
1315

@@ -935,3 +937,213 @@ impl EvalExpr for EvalFnCardinality {
935937
Cow::Owned(result)
936938
}
937939
}
940+
941+
/// Represents a year `EXTRACT` function, e.g. `extract(YEAR FROM t)`.
942+
#[derive(Debug)]
943+
pub struct EvalFnExtractYear {
944+
pub value: Box<dyn EvalExpr>,
945+
}
946+
947+
impl EvalExpr for EvalFnExtractYear {
948+
#[inline]
949+
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
950+
let value = self.value.evaluate(bindings, ctx);
951+
let result = match value.borrow() {
952+
Null => Null,
953+
Value::DateTime(dt) => match dt.as_ref() {
954+
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,
959+
},
960+
_ => Missing,
961+
};
962+
Cow::Owned(result)
963+
}
964+
}
965+
966+
/// Represents a month `EXTRACT` function, e.g. `extract(MONTH FROM t)`.
967+
#[derive(Debug)]
968+
pub struct EvalFnExtractMonth {
969+
pub value: Box<dyn EvalExpr>,
970+
}
971+
972+
impl EvalExpr for EvalFnExtractMonth {
973+
#[inline]
974+
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
975+
let value = self.value.evaluate(bindings, ctx);
976+
let result = match value.borrow() {
977+
Null => Null,
978+
Value::DateTime(dt) => match dt.as_ref() {
979+
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,
984+
},
985+
_ => Missing,
986+
};
987+
Cow::Owned(result)
988+
}
989+
}
990+
991+
/// Represents a day `EXTRACT` function, e.g. `extract(DAY FROM t)`.
992+
#[derive(Debug)]
993+
pub struct EvalFnExtractDay {
994+
pub value: Box<dyn EvalExpr>,
995+
}
996+
997+
impl EvalExpr for EvalFnExtractDay {
998+
#[inline]
999+
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
1000+
let value = self.value.evaluate(bindings, ctx);
1001+
let result = match value.borrow() {
1002+
Null => Null,
1003+
Value::DateTime(dt) => match dt.as_ref() {
1004+
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,
1009+
},
1010+
_ => Missing,
1011+
};
1012+
Cow::Owned(result)
1013+
}
1014+
}
1015+
1016+
/// Represents an hour `EXTRACT` function, e.g. `extract(HOUR FROM t)`.
1017+
#[derive(Debug)]
1018+
pub struct EvalFnExtractHour {
1019+
pub value: Box<dyn EvalExpr>,
1020+
}
1021+
1022+
impl EvalExpr for EvalFnExtractHour {
1023+
#[inline]
1024+
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
1025+
let value = self.value.evaluate(bindings, ctx);
1026+
let result = match value.borrow() {
1027+
Null => Null,
1028+
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()),
1033+
DateTime::Date(_) => Missing,
1034+
},
1035+
_ => Missing,
1036+
};
1037+
Cow::Owned(result)
1038+
}
1039+
}
1040+
1041+
/// Represents a minute `EXTRACT` function, e.g. `extract(MINUTE FROM t)`.
1042+
#[derive(Debug)]
1043+
pub struct EvalFnExtractMinute {
1044+
pub value: Box<dyn EvalExpr>,
1045+
}
1046+
1047+
impl EvalExpr for EvalFnExtractMinute {
1048+
#[inline]
1049+
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
1050+
let value = self.value.evaluate(bindings, ctx);
1051+
let result = match value.borrow() {
1052+
Null => Null,
1053+
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()),
1058+
DateTime::Date(_) => Missing,
1059+
},
1060+
_ => Missing,
1061+
};
1062+
Cow::Owned(result)
1063+
}
1064+
}
1065+
1066+
/// Represents a second `EXTRACT` function, e.g. `extract(SECOND FROM t)`.
1067+
#[derive(Debug)]
1068+
pub struct EvalFnExtractSecond {
1069+
pub value: Box<dyn EvalExpr>,
1070+
}
1071+
1072+
fn total_seconds(second: u8, nanosecond: u32) -> Value {
1073+
let result = rust_decimal::Decimal::from_f64(((second as f64 * 1e9) + nanosecond as f64) / 1e9)
1074+
.expect("time as decimal");
1075+
Value::from(result)
1076+
}
1077+
1078+
impl EvalExpr for EvalFnExtractSecond {
1079+
#[inline]
1080+
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
1081+
let value = self.value.evaluate(bindings, ctx);
1082+
let result = match value.borrow() {
1083+
Null => Null,
1084+
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())
1090+
}
1091+
DateTime::Date(_) => Missing,
1092+
},
1093+
_ => Missing,
1094+
};
1095+
Cow::Owned(result)
1096+
}
1097+
}
1098+
1099+
/// Represents a timezone hour `EXTRACT` function, e.g. `extract(TIMEZONE_HOUR FROM t)`.
1100+
#[derive(Debug)]
1101+
pub struct EvalFnExtractTimezoneHour {
1102+
pub value: Box<dyn EvalExpr>,
1103+
}
1104+
1105+
impl EvalExpr for EvalFnExtractTimezoneHour {
1106+
#[inline]
1107+
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
1108+
let value = self.value.evaluate(bindings, ctx);
1109+
let result = match value.borrow() {
1110+
Null => Null,
1111+
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()),
1114+
DateTime::Date(_) => Missing,
1115+
DateTime::Time(_) => Missing,
1116+
DateTime::Timestamp(_) => Missing,
1117+
},
1118+
_ => Missing,
1119+
};
1120+
Cow::Owned(result)
1121+
}
1122+
}
1123+
1124+
/// Represents a timezone minute `EXTRACT` function, e.g. `extract(TIMEZONE_MINUTE FROM t)`.
1125+
#[derive(Debug)]
1126+
pub struct EvalFnExtractTimezoneMinute {
1127+
pub value: Box<dyn EvalExpr>,
1128+
}
1129+
1130+
impl EvalExpr for EvalFnExtractTimezoneMinute {
1131+
#[inline]
1132+
fn evaluate<'a>(&'a self, bindings: &'a Tuple, ctx: &'a dyn EvalContext) -> Cow<'a, Value> {
1133+
let value = self.value.evaluate(bindings, ctx);
1134+
let result = match value.borrow() {
1135+
Null => Null,
1136+
Value::DateTime(dt) => match dt.as_ref() {
1137+
DateTime::TimeWithTz(_, tz) => Value::from(tz.minutes_past_hour()),
1138+
DateTime::TimestampWithTz(tstamp) => {
1139+
Value::from(tstamp.offset().minutes_past_hour())
1140+
}
1141+
DateTime::Date(_) => Missing,
1142+
DateTime::Time(_) => Missing,
1143+
DateTime::Timestamp(_) => Missing,
1144+
},
1145+
_ => Missing,
1146+
};
1147+
Cow::Owned(result)
1148+
}
1149+
}

partiql-eval/src/plan.rs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ use crate::eval::evaluable::{
1818
use crate::eval::expr::pattern_match::like_to_re_pattern;
1919
use crate::eval::expr::{
2020
EvalBagExpr, EvalBetweenExpr, EvalBinOp, EvalBinOpExpr, EvalDynamicLookup, EvalExpr, EvalFnAbs,
21-
EvalFnBitLength, EvalFnBtrim, EvalFnCardinality, EvalFnCharLength, EvalFnExists, EvalFnLower,
22-
EvalFnLtrim, EvalFnModulus, EvalFnOctetLength, EvalFnOverlay, EvalFnPosition, EvalFnRtrim,
23-
EvalFnSubstring, EvalFnUpper, EvalIsTypeExpr, EvalLikeMatch, EvalLikeNonStringNonLiteralMatch,
24-
EvalListExpr, EvalLitExpr, EvalPath, EvalSearchedCaseExpr, EvalTupleExpr, EvalUnaryOp,
25-
EvalUnaryOpExpr, EvalVarRef,
21+
EvalFnBitLength, EvalFnBtrim, EvalFnCardinality, EvalFnCharLength, EvalFnExists,
22+
EvalFnExtractDay, EvalFnExtractHour, EvalFnExtractMinute, EvalFnExtractMonth,
23+
EvalFnExtractSecond, EvalFnExtractTimezoneHour, EvalFnExtractTimezoneMinute, EvalFnExtractYear,
24+
EvalFnLower, EvalFnLtrim, EvalFnModulus, EvalFnOctetLength, EvalFnOverlay, EvalFnPosition,
25+
EvalFnRtrim, EvalFnSubstring, EvalFnUpper, EvalIsTypeExpr, EvalLikeMatch,
26+
EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr, EvalPath, EvalSearchedCaseExpr,
27+
EvalTupleExpr, EvalUnaryOp, EvalUnaryOpExpr, EvalVarRef,
2628
};
2729
use crate::eval::EvalPlan;
2830
use partiql_value::Value::Null;
@@ -585,6 +587,54 @@ impl EvaluatorPlanner {
585587
value: args.pop().unwrap(),
586588
})
587589
}
590+
CallName::ExtractYear => {
591+
assert_eq!(args.len(), 1);
592+
Box::new(EvalFnExtractYear {
593+
value: args.pop().unwrap(),
594+
})
595+
}
596+
CallName::ExtractMonth => {
597+
assert_eq!(args.len(), 1);
598+
Box::new(EvalFnExtractMonth {
599+
value: args.pop().unwrap(),
600+
})
601+
}
602+
CallName::ExtractDay => {
603+
assert_eq!(args.len(), 1);
604+
Box::new(EvalFnExtractDay {
605+
value: args.pop().unwrap(),
606+
})
607+
}
608+
CallName::ExtractHour => {
609+
assert_eq!(args.len(), 1);
610+
Box::new(EvalFnExtractHour {
611+
value: args.pop().unwrap(),
612+
})
613+
}
614+
CallName::ExtractMinute => {
615+
assert_eq!(args.len(), 1);
616+
Box::new(EvalFnExtractMinute {
617+
value: args.pop().unwrap(),
618+
})
619+
}
620+
CallName::ExtractSecond => {
621+
assert_eq!(args.len(), 1);
622+
Box::new(EvalFnExtractSecond {
623+
value: args.pop().unwrap(),
624+
})
625+
}
626+
CallName::ExtractTimezoneHour => {
627+
assert_eq!(args.len(), 1);
628+
Box::new(EvalFnExtractTimezoneHour {
629+
value: args.pop().unwrap(),
630+
})
631+
}
632+
CallName::ExtractTimezoneMinute => {
633+
assert_eq!(args.len(), 1);
634+
Box::new(EvalFnExtractTimezoneMinute {
635+
value: args.pop().unwrap(),
636+
})
637+
}
588638
}
589639
}
590640
}

0 commit comments

Comments
 (0)