diff --git a/CHANGELOG.md b/CHANGELOG.md index 0315d343..3c7c7996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 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` +- Fix parsing of `EXTRACT` to allow keywords after the `FROM` ## [0.3.0] - 2023-04-11 ### Changed diff --git a/partiql-parser/src/preprocessor.rs b/partiql-parser/src/preprocessor.rs index 77688083..a76257bb 100644 --- a/partiql-parser/src/preprocessor.rs +++ b/partiql-parser/src/preprocessor.rs @@ -96,7 +96,9 @@ mod built_ins { #[rustfmt::skip] patterns: vec![ // e.g., extract(day from x) => extract("day":true, "from": x) - vec![Id(re), Syn(Token::True), Kw(Token::From), AnyOne(true), AnyStar(false)] + // Note the `true` passed to Any* as we need to support type-related keywords after `FROM` + // such as `TIME WITH TIME ZONE` + vec![Id(re), Syn(Token::True), Kw(Token::From), AnyOne(true), AnyStar(true)] ], } } @@ -907,6 +909,60 @@ mod tests { preprocess(r#"extract(second from a)"#)?, lex(r#"extract(second:True, "from" : a)"#)? ); + assert_eq!( + preprocess(r#"extract(hour from TIME WITH TIME ZONE '01:23:45.678-06:30')"#)?, + lex(r#"extract(hour:True, "from" : TIME WITH TIME ZONE '01:23:45.678-06:30')"#)? + ); + assert_eq!( + preprocess(r#"extract(minute from TIME WITH TIME ZONE '01:23:45.678-06:30')"#)?, + lex(r#"extract(minute:True, "from" : TIME WITH TIME ZONE '01:23:45.678-06:30')"#)? + ); + assert_eq!( + preprocess(r#"extract(second from TIME WITH TIME ZONE '01:23:45.678-06:30')"#)?, + lex(r#"extract(second:True, "from" : TIME WITH TIME ZONE '01:23:45.678-06:30')"#)? + ); + assert_eq!( + preprocess(r#"extract(timezone_hour from TIME WITH TIME ZONE '01:23:45.678-06:30')"#)?, + lex( + r#"extract(timezone_hour:True, "from" : TIME WITH TIME ZONE '01:23:45.678-06:30')"# + )? + ); + assert_eq!( + preprocess( + r#"extract(timezone_minute from TIME WITH TIME ZONE '01:23:45.678-06:30')"# + )?, + lex( + r#"extract(timezone_minute:True, "from" : TIME WITH TIME ZONE '01:23:45.678-06:30')"# + )? + ); + assert_eq!( + preprocess(r#"extract(hour from TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"#)?, + lex(r#"extract(hour:True, "from" : TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"#)? + ); + assert_eq!( + preprocess(r#"extract(minute from TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"#)?, + lex(r#"extract(minute:True, "from" : TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"#)? + ); + assert_eq!( + preprocess(r#"extract(second from TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"#)?, + lex(r#"extract(second:True, "from" : TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"#)? + ); + assert_eq!( + preprocess( + r#"extract(timezone_hour from TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"# + )?, + lex( + r#"extract(timezone_hour:True, "from" : TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"# + )? + ); + assert_eq!( + preprocess( + r#"extract(timezone_minute from TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"# + )?, + lex( + r#"extract(timezone_minute:True, "from" : TIME (2) WITH TIME ZONE '01:23:45.678-06:30')"# + )? + ); assert_eq!(preprocess(r#"count(a)"#)?, lex(r#"count(a)"#)?); assert_eq!(