Skip to content

Commit 19f730c

Browse files
bitemyappgandronchik
authored andcommitted
Add support for AT TIME ZONE (apache#539)
* Support for empty array literals * Added support for AT TIME ZONE Co-authored-by: Chris Allen <chrisa@indeed.com> qwe
1 parent 952bb1b commit 19f730c

File tree

4 files changed

+133
-3
lines changed

4 files changed

+133
-3
lines changed

src/ast/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,11 @@ pub enum Expr {
252252
expr: Box<Expr>,
253253
data_type: DataType,
254254
},
255+
/// AT a timestamp to a different timezone e.g. `FROM_UNIXTIME(0) AT TIME ZONE 'UTC-06:00'`
256+
AtTimeZone {
257+
timestamp: Box<Expr>,
258+
time_zone: String,
259+
},
255260
/// EXTRACT(DateTimeField FROM <expr>)
256261
Extract {
257262
field: DateTimeField,
@@ -525,6 +530,12 @@ impl fmt::Display for Expr {
525530
Expr::DotExpr { expr, field } => {
526531
write!(f, "{}.{}", expr, field)
527532
}
533+
Expr::AtTimeZone {
534+
timestamp,
535+
time_zone,
536+
} => {
537+
write!(f, "{} AT TIME ZONE '{}'", timestamp, time_zone)
538+
}
528539
}
529540
}
530541
}

src/parser.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -889,9 +889,17 @@ impl<'a> Parser<'a> {
889889
/// Parses an array expression `[ex1, ex2, ..]`
890890
/// if `named` is `true`, came from an expression like `ARRAY[ex1, ex2]`
891891
pub fn parse_array_expr(&mut self, named: bool) -> Result<Expr, ParserError> {
892-
let exprs = self.parse_comma_separated(Parser::parse_expr)?;
893-
self.expect_token(&Token::RBracket)?;
894-
Ok(Expr::Array(Array { elem: exprs, named }))
892+
if self.peek_token() == Token::RBracket {
893+
let _ = self.next_token();
894+
Ok(Expr::Array(Array {
895+
elem: vec![],
896+
named,
897+
}))
898+
} else {
899+
let exprs = self.parse_comma_separated(Parser::parse_expr)?;
900+
self.expect_token(&Token::RBracket)?;
901+
Ok(Expr::Array(Array { elem: exprs, named }))
902+
}
895903
}
896904

897905
/// Parses an array subquery `ARRAY(SELECT 1 UNION SELECT 2)`
@@ -1205,6 +1213,28 @@ impl<'a> Parser<'a> {
12051213
)
12061214
}
12071215
}
1216+
Keyword::AT => {
1217+
// if self.parse_keyword(Keyword::TIME) {
1218+
// self.expect_keyword(Keyword::ZONE)?;
1219+
if self.parse_keywords(&[Keyword::TIME, Keyword::ZONE]) {
1220+
let time_zone = self.next_token();
1221+
match time_zone {
1222+
Token::SingleQuotedString(time_zone) => {
1223+
log::trace!("Peek token: {:?}", self.peek_token());
1224+
Ok(Expr::AtTimeZone {
1225+
timestamp: Box::new(expr),
1226+
time_zone,
1227+
})
1228+
}
1229+
tok => self.expected(
1230+
"Expected Token::SingleQuotedString after AT TIME ZONE",
1231+
tok,
1232+
),
1233+
}
1234+
} else {
1235+
self.expected("Expected Token::Word after AT", tok)
1236+
}
1237+
}
12081238
Keyword::NOT | Keyword::IN | Keyword::BETWEEN => {
12091239
self.prev_token();
12101240
let negated = self.parse_keyword(Keyword::NOT);
@@ -1333,15 +1363,32 @@ impl<'a> Parser<'a> {
13331363
const UNARY_NOT_PREC: u8 = 15;
13341364
const BETWEEN_PREC: u8 = 20;
13351365
const PLUS_MINUS_PREC: u8 = 30;
1366+
const TIME_ZONE_PREC: u8 = 20;
13361367

13371368
/// Get the precedence of the next token
13381369
pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
13391370
let token = self.peek_token();
13401371
debug!("get_next_precedence() {:?}", token);
1372+
let token_0 = self.peek_nth_token(0);
1373+
let token_1 = self.peek_nth_token(1);
1374+
let token_2 = self.peek_nth_token(2);
1375+
debug!("0: {token_0} 1: {token_1} 2: {token_2}");
13411376
match token {
13421377
Token::Word(w) if w.keyword == Keyword::OR => Ok(5),
13431378
Token::Word(w) if w.keyword == Keyword::AND => Ok(10),
13441379
Token::Word(w) if w.keyword == Keyword::XOR => Ok(24),
1380+
1381+
Token::Word(w) if w.keyword == Keyword::AT => {
1382+
match (self.peek_nth_token(1), self.peek_nth_token(2)) {
1383+
(Token::Word(w), Token::Word(w2))
1384+
if w.keyword == Keyword::TIME && w2.keyword == Keyword::ZONE =>
1385+
{
1386+
Ok(Self::TIME_ZONE_PREC)
1387+
}
1388+
_ => Ok(0),
1389+
}
1390+
}
1391+
13451392
Token::Word(w) if w.keyword == Keyword::NOT => match self.peek_nth_token(1) {
13461393
// The precedence of NOT varies depending on keyword that
13471394
// follows it. If it is followed by IN, BETWEEN, or LIKE,

tests/sqlparser_common.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2708,6 +2708,68 @@ fn parse_literal_interval() {
27082708
);
27092709
}
27102710

2711+
#[test]
2712+
fn parse_at_timezone() {
2713+
let zero = Expr::Value(number("0"));
2714+
let sql = "SELECT FROM_UNIXTIME(0) AT TIME ZONE 'UTC-06:00' FROM t";
2715+
let select = verified_only_select(sql);
2716+
assert_eq!(
2717+
&Expr::AtTimeZone {
2718+
timestamp: Box::new(Expr::Function(Function {
2719+
name: ObjectName(vec![Ident {
2720+
value: "FROM_UNIXTIME".to_string(),
2721+
quote_style: None
2722+
}]),
2723+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero.clone()))],
2724+
over: None,
2725+
distinct: false,
2726+
special: false,
2727+
})),
2728+
time_zone: "UTC-06:00".to_string()
2729+
},
2730+
expr_from_projection(only(&select.projection)),
2731+
);
2732+
2733+
let sql = r#"SELECT DATE_FORMAT(FROM_UNIXTIME(0) AT TIME ZONE 'UTC-06:00', '%Y-%m-%dT%H') AS "hour" FROM t"#;
2734+
let select = verified_only_select(sql);
2735+
assert_eq!(
2736+
&SelectItem::ExprWithAlias {
2737+
expr: Expr::Function(Function {
2738+
name: ObjectName(vec![Ident {
2739+
value: "DATE_FORMAT".to_string(),
2740+
quote_style: None,
2741+
},],),
2742+
args: vec![
2743+
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::AtTimeZone {
2744+
timestamp: Box::new(Expr::Function(Function {
2745+
name: ObjectName(vec![Ident {
2746+
value: "FROM_UNIXTIME".to_string(),
2747+
quote_style: None,
2748+
},],),
2749+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero,),),],
2750+
over: None,
2751+
distinct: false,
2752+
special: false,
2753+
},)),
2754+
time_zone: "UTC-06:00".to_string(),
2755+
},),),
2756+
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
2757+
Value::SingleQuotedString("%Y-%m-%dT%H".to_string(),),
2758+
),),),
2759+
],
2760+
over: None,
2761+
distinct: false,
2762+
special: false,
2763+
},),
2764+
alias: Ident {
2765+
value: "hour".to_string(),
2766+
quote_style: Some('"',),
2767+
},
2768+
},
2769+
only(&select.projection),
2770+
);
2771+
}
2772+
27112773
#[test]
27122774
fn parse_simple_math_expr_plus() {
27132775
let sql = "SELECT a + b, 2 + a, 2.5 + a, a_f + b_f, 2 + a_f, 2.5 + a_f FROM c";

tests/sqlparser_postgres.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,16 @@ fn parse_array_index_expr() {
11851185
},
11861186
expr_from_projection(only(&select.projection)),
11871187
);
1188+
1189+
let sql = "SELECT ARRAY[]";
1190+
let select = pg_and_generic().verified_only_select(sql);
1191+
assert_eq!(
1192+
&Expr::Array(sqlparser::ast::Array {
1193+
elem: vec![],
1194+
named: true
1195+
}),
1196+
expr_from_projection(only(&select.projection)),
1197+
);
11881198
}
11891199

11901200
#[test]

0 commit comments

Comments
 (0)