Skip to content

Commit

Permalink
Added support for AT TIME ZONE
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Allen committed Jul 18, 2022
1 parent ff82113 commit 0a897d2
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,11 @@ pub enum Expr {
expr: Box<Expr>,
data_type: DataType,
},
/// AT a timestamp to a different timezone e.g. `FROM_UNIXTIME(0) AT TIME ZONE 'UTC-06:00'`
AtTimeZone {
timestamp: Box<Expr>,
time_zone: String,
},
/// EXTRACT(DateTimeField FROM <expr>)
Extract {
field: DateTimeField,
Expand Down Expand Up @@ -562,6 +567,12 @@ impl fmt::Display for Expr {
Expr::CompositeAccess { expr, key } => {
write!(f, "{}.{}", expr, key)
}
Expr::AtTimeZone {
timestamp,
time_zone,
} => {
write!(f, "{} AT TIME ZONE '{}'", timestamp, time_zone)
}
}
}
}
Expand Down
39 changes: 39 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,28 @@ impl<'a> Parser<'a> {
)
}
}
Keyword::AT => {
// if self.parse_keyword(Keyword::TIME) {
// self.expect_keyword(Keyword::ZONE)?;
if self.parse_keywords(&[Keyword::TIME, Keyword::ZONE]) {
let time_zone = self.next_token();
match time_zone {
Token::SingleQuotedString(time_zone) => {
log::trace!("Peek token: {:?}", self.peek_token());
Ok(Expr::AtTimeZone {
timestamp: Box::new(expr),
time_zone,
})
}
tok => self.expected(
"Expected Token::SingleQuotedString after AT TIME ZONE",
tok,
),
}
} else {
self.expected("Expected Token::Word after AT", tok)
}
}
Keyword::NOT | Keyword::IN | Keyword::BETWEEN => {
self.prev_token();
let negated = self.parse_keyword(Keyword::NOT);
Expand Down Expand Up @@ -1358,15 +1380,32 @@ impl<'a> Parser<'a> {
const UNARY_NOT_PREC: u8 = 15;
const BETWEEN_PREC: u8 = 20;
const PLUS_MINUS_PREC: u8 = 30;
const TIME_ZONE_PREC: u8 = 20;

/// Get the precedence of the next token
pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
let token = self.peek_token();
debug!("get_next_precedence() {:?}", token);
let token_0 = self.peek_nth_token(0);
let token_1 = self.peek_nth_token(1);
let token_2 = self.peek_nth_token(2);
debug!("0: {token_0} 1: {token_1} 2: {token_2}");
match token {
Token::Word(w) if w.keyword == Keyword::OR => Ok(5),
Token::Word(w) if w.keyword == Keyword::AND => Ok(10),
Token::Word(w) if w.keyword == Keyword::XOR => Ok(24),

Token::Word(w) if w.keyword == Keyword::AT => {
match (self.peek_nth_token(1), self.peek_nth_token(2)) {
(Token::Word(w), Token::Word(w2))
if w.keyword == Keyword::TIME && w2.keyword == Keyword::ZONE =>
{
Ok(Self::TIME_ZONE_PREC)
}
_ => Ok(0),
}
}

Token::Word(w) if w.keyword == Keyword::NOT => match self.peek_nth_token(1) {
// The precedence of NOT varies depending on keyword that
// follows it. If it is followed by IN, BETWEEN, or LIKE,
Expand Down
59 changes: 59 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2790,6 +2790,65 @@ fn parse_literal_interval() {
);
}

#[test]
fn parse_at_timezone() {
let zero = Expr::Value(number("0"));
let sql = "SELECT FROM_UNIXTIME(0) AT TIME ZONE 'UTC-06:00' FROM t";
let select = verified_only_select(sql);
assert_eq!(
&Expr::AtTimeZone {
timestamp: Box::new(Expr::Function(Function {
name: ObjectName(vec![Ident {
value: "FROM_UNIXTIME".to_string(),
quote_style: None
}]),
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero.clone()))],
over: None,
distinct: false
})),
time_zone: "UTC-06:00".to_string()
},
expr_from_projection(only(&select.projection)),
);

let sql = r#"SELECT DATE_FORMAT(FROM_UNIXTIME(0) AT TIME ZONE 'UTC-06:00', '%Y-%m-%dT%H') AS "hour" FROM t"#;
let select = verified_only_select(sql);
assert_eq!(
&SelectItem::ExprWithAlias {
expr: Expr::Function(Function {
name: ObjectName(vec![Ident {
value: "DATE_FORMAT".to_string(),
quote_style: None,
},],),
args: vec![
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::AtTimeZone {
timestamp: Box::new(Expr::Function(Function {
name: ObjectName(vec![Ident {
value: "FROM_UNIXTIME".to_string(),
quote_style: None,
},],),
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero,),),],
over: None,
distinct: false,
},)),
time_zone: "UTC-06:00".to_string(),
},),),
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
Value::SingleQuotedString("%Y-%m-%dT%H".to_string(),),
),),),
],
over: None,
distinct: false,
},),
alias: Ident {
value: "hour".to_string(),
quote_style: Some('"',),
},
},
only(&select.projection),
);
}

#[test]
fn parse_simple_math_expr_plus() {
let sql = "SELECT a + b, 2 + a, 2.5 + a, a_f + b_f, 2 + a_f, 2.5 + a_f FROM c";
Expand Down

0 comments on commit 0a897d2

Please sign in to comment.