Skip to content

Commit 6381138

Browse files
committed
bigquery: CAST AS x FORMAT support
1 parent 83cb734 commit 6381138

File tree

6 files changed

+130
-6
lines changed

6 files changed

+130
-6
lines changed

src/ast/mod.rs

+61-3
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,16 @@ impl fmt::Display for JsonOperator {
322322
}
323323
}
324324

325+
/// Options for `CAST` / `TRY_CAST`
326+
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax>
327+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
328+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
329+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
330+
pub enum CastFormat {
331+
Value(Value),
332+
ValueAtTimeZone(Value, Value),
333+
}
334+
325335
/// An SQL expression of any type.
326336
///
327337
/// The parser does not distinguish between expressions of different types
@@ -437,19 +447,28 @@ pub enum Expr {
437447
Cast {
438448
expr: Box<Expr>,
439449
data_type: DataType,
450+
// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by BigQuery
451+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
452+
format: Option<CastFormat>,
440453
},
441454
/// TRY_CAST an expression to a different data type e.g. `TRY_CAST(foo AS VARCHAR(123))`
442455
// this differs from CAST in the choice of how to implement invalid conversions
443456
TryCast {
444457
expr: Box<Expr>,
445458
data_type: DataType,
459+
// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by BigQuery
460+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
461+
format: Option<CastFormat>,
446462
},
447463
/// SAFE_CAST an expression to a different data type e.g. `SAFE_CAST(foo AS FLOAT64)`
448464
// only available for BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#safe_casting
449465
// this works the same as `TRY_CAST`
450466
SafeCast {
451467
expr: Box<Expr>,
452468
data_type: DataType,
469+
// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by BigQuery
470+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
471+
format: Option<CastFormat>,
453472
},
454473
/// AT a timestamp to a different timezone e.g. `FROM_UNIXTIME(0) AT TIME ZONE 'UTC-06:00'`
455474
AtTimeZone {
@@ -597,6 +616,15 @@ pub enum Expr {
597616
},
598617
}
599618

619+
impl fmt::Display for CastFormat {
620+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
621+
match self {
622+
CastFormat::Value(v) => write!(f, "{v}"),
623+
CastFormat::ValueAtTimeZone(v, tz) => write!(f, "{v} AT TIME ZONE {tz}"),
624+
}
625+
}
626+
}
627+
600628
impl fmt::Display for Expr {
601629
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
602630
match self {
@@ -753,9 +781,39 @@ impl fmt::Display for Expr {
753781
write!(f, "{op}{expr}")
754782
}
755783
}
756-
Expr::Cast { expr, data_type } => write!(f, "CAST({expr} AS {data_type})"),
757-
Expr::TryCast { expr, data_type } => write!(f, "TRY_CAST({expr} AS {data_type})"),
758-
Expr::SafeCast { expr, data_type } => write!(f, "SAFE_CAST({expr} AS {data_type})"),
784+
Expr::Cast {
785+
expr,
786+
data_type,
787+
format,
788+
} => {
789+
if let Some(format) = format {
790+
write!(f, "CAST({expr} AS {data_type} FORMAT {format})")
791+
} else {
792+
write!(f, "CAST({expr} AS {data_type})")
793+
}
794+
}
795+
Expr::TryCast {
796+
expr,
797+
data_type,
798+
format,
799+
} => {
800+
if let Some(format) = format {
801+
write!(f, "TRY_CAST({expr} AS {data_type} FORMAT {format})")
802+
} else {
803+
write!(f, "TRY_CAST({expr} AS {data_type})")
804+
}
805+
}
806+
Expr::SafeCast {
807+
expr,
808+
data_type,
809+
format,
810+
} => {
811+
if let Some(format) = format {
812+
write!(f, "SAFE_CAST({expr} AS {data_type} FORMAT {format})")
813+
} else {
814+
write!(f, "SAFE_CAST({expr} AS {data_type})")
815+
}
816+
}
759817
Expr::Extract { field, expr } => write!(f, "EXTRACT({field} FROM {expr})"),
760818
Expr::Ceil { expr, field } => {
761819
if field == &DateTimeField::NoDateTime {

src/parser/mod.rs

+23
Original file line numberDiff line numberDiff line change
@@ -1139,16 +1139,34 @@ impl<'a> Parser<'a> {
11391139
})
11401140
}
11411141

1142+
pub fn parse_optional_cast_format(&mut self) -> Result<Option<CastFormat>, ParserError> {
1143+
if self.parse_keyword(Keyword::FORMAT) {
1144+
let value = self.parse_value()?;
1145+
if self.parse_keywords(&[Keyword::AT, Keyword::TIME, Keyword::ZONE]) {
1146+
Ok(Some(CastFormat::ValueAtTimeZone(
1147+
value,
1148+
self.parse_value()?,
1149+
)))
1150+
} else {
1151+
Ok(Some(CastFormat::Value(value)))
1152+
}
1153+
} else {
1154+
Ok(None)
1155+
}
1156+
}
1157+
11421158
/// Parse a SQL CAST function e.g. `CAST(expr AS FLOAT)`
11431159
pub fn parse_cast_expr(&mut self) -> Result<Expr, ParserError> {
11441160
self.expect_token(&Token::LParen)?;
11451161
let expr = self.parse_expr()?;
11461162
self.expect_keyword(Keyword::AS)?;
11471163
let data_type = self.parse_data_type()?;
1164+
let format = self.parse_optional_cast_format()?;
11481165
self.expect_token(&Token::RParen)?;
11491166
Ok(Expr::Cast {
11501167
expr: Box::new(expr),
11511168
data_type,
1169+
format,
11521170
})
11531171
}
11541172

@@ -1158,10 +1176,12 @@ impl<'a> Parser<'a> {
11581176
let expr = self.parse_expr()?;
11591177
self.expect_keyword(Keyword::AS)?;
11601178
let data_type = self.parse_data_type()?;
1179+
let format = self.parse_optional_cast_format()?;
11611180
self.expect_token(&Token::RParen)?;
11621181
Ok(Expr::TryCast {
11631182
expr: Box::new(expr),
11641183
data_type,
1184+
format,
11651185
})
11661186
}
11671187

@@ -1171,10 +1191,12 @@ impl<'a> Parser<'a> {
11711191
let expr = self.parse_expr()?;
11721192
self.expect_keyword(Keyword::AS)?;
11731193
let data_type = self.parse_data_type()?;
1194+
let format = self.parse_optional_cast_format()?;
11741195
self.expect_token(&Token::RParen)?;
11751196
Ok(Expr::SafeCast {
11761197
expr: Box::new(expr),
11771198
data_type,
1199+
format,
11781200
})
11791201
}
11801202

@@ -2101,6 +2123,7 @@ impl<'a> Parser<'a> {
21012123
Ok(Expr::Cast {
21022124
expr: Box::new(expr),
21032125
data_type: self.parse_data_type()?,
2126+
format: None,
21042127
})
21052128
}
21062129

tests/sqlparser_bigquery.rs

+33-2
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,39 @@ fn parse_trailing_comma() {
304304

305305
#[test]
306306
fn parse_cast_type() {
307-
let sql = r#"SELECT SAFE_CAST(1 AS INT64)"#;
308-
bigquery().verified_only_select(sql);
307+
let sql = r"SELECT SAFE_CAST(1 AS INT64)";
308+
bigquery_and_generic().verified_only_select(sql);
309+
}
310+
311+
#[test]
312+
fn parse_cast_date_format() {
313+
let sql =
314+
r"SELECT CAST(date_valid_from AS DATE FORMAT 'YYYY-MM-DD') AS date_valid_from FROM foo";
315+
bigquery_and_generic().verified_only_select(sql);
316+
}
317+
318+
#[test]
319+
fn parse_cast_time_format() {
320+
let sql = r"SELECT CAST(TIME '21:30:00' AS STRING FORMAT 'PM') AS date_time_to_string";
321+
bigquery_and_generic().verified_only_select(sql);
322+
}
323+
324+
#[test]
325+
fn parse_cast_timestamp_format_tz() {
326+
let sql = r"SELECT CAST(TIMESTAMP '2008-12-25 00:00:00+00:00' AS STRING FORMAT 'TZH' AT TIME ZONE 'Asia/Kolkata') AS date_time_to_string";
327+
bigquery_and_generic().verified_only_select(sql);
328+
}
329+
330+
#[test]
331+
fn parse_cast_string_to_bytes_format() {
332+
let sql = r"SELECT CAST('Hello' AS BYTES FORMAT 'ASCII') AS string_to_bytes";
333+
bigquery_and_generic().verified_only_select(sql);
334+
}
335+
336+
#[test]
337+
fn parse_cast_bytes_to_string_format() {
338+
let sql = r"SELECT CAST(B'\x48\x65\x6c\x6c\x6f' AS STRING FORMAT 'ASCII') AS bytes_to_string";
339+
bigquery_and_generic().verified_only_select(sql);
309340
}
310341

311342
#[test]

tests/sqlparser_common.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,7 @@ fn parse_cast() {
19341934
&Expr::Cast {
19351935
expr: Box::new(Expr::Identifier(Ident::new("id"))),
19361936
data_type: DataType::BigInt(None),
1937+
format: None,
19371938
},
19381939
expr_from_projection(only(&select.projection))
19391940
);
@@ -1944,6 +1945,7 @@ fn parse_cast() {
19441945
&Expr::Cast {
19451946
expr: Box::new(Expr::Identifier(Ident::new("id"))),
19461947
data_type: DataType::TinyInt(None),
1948+
format: None,
19471949
},
19481950
expr_from_projection(only(&select.projection))
19491951
);
@@ -1970,6 +1972,7 @@ fn parse_cast() {
19701972
&Expr::Cast {
19711973
expr: Box::new(Expr::Identifier(Ident::new("id"))),
19721974
data_type: DataType::Nvarchar(Some(50)),
1975+
format: None,
19731976
},
19741977
expr_from_projection(only(&select.projection))
19751978
);
@@ -1980,6 +1983,7 @@ fn parse_cast() {
19801983
&Expr::Cast {
19811984
expr: Box::new(Expr::Identifier(Ident::new("id"))),
19821985
data_type: DataType::Clob(None),
1986+
format: None,
19831987
},
19841988
expr_from_projection(only(&select.projection))
19851989
);
@@ -1990,6 +1994,7 @@ fn parse_cast() {
19901994
&Expr::Cast {
19911995
expr: Box::new(Expr::Identifier(Ident::new("id"))),
19921996
data_type: DataType::Clob(Some(50)),
1997+
format: None,
19931998
},
19941999
expr_from_projection(only(&select.projection))
19952000
);
@@ -2000,6 +2005,7 @@ fn parse_cast() {
20002005
&Expr::Cast {
20012006
expr: Box::new(Expr::Identifier(Ident::new("id"))),
20022007
data_type: DataType::Binary(Some(50)),
2008+
format: None,
20032009
},
20042010
expr_from_projection(only(&select.projection))
20052011
);
@@ -2010,6 +2016,7 @@ fn parse_cast() {
20102016
&Expr::Cast {
20112017
expr: Box::new(Expr::Identifier(Ident::new("id"))),
20122018
data_type: DataType::Varbinary(Some(50)),
2019+
format: None,
20132020
},
20142021
expr_from_projection(only(&select.projection))
20152022
);
@@ -2020,6 +2027,7 @@ fn parse_cast() {
20202027
&Expr::Cast {
20212028
expr: Box::new(Expr::Identifier(Ident::new("id"))),
20222029
data_type: DataType::Blob(None),
2030+
format: None,
20232031
},
20242032
expr_from_projection(only(&select.projection))
20252033
);
@@ -2030,6 +2038,7 @@ fn parse_cast() {
20302038
&Expr::Cast {
20312039
expr: Box::new(Expr::Identifier(Ident::new("id"))),
20322040
data_type: DataType::Blob(Some(50)),
2041+
format: None,
20332042
},
20342043
expr_from_projection(only(&select.projection))
20352044
);
@@ -2043,6 +2052,7 @@ fn parse_try_cast() {
20432052
&Expr::TryCast {
20442053
expr: Box::new(Expr::Identifier(Ident::new("id"))),
20452054
data_type: DataType::BigInt(None),
2055+
format: None,
20462056
},
20472057
expr_from_projection(only(&select.projection))
20482058
);

tests/sqlparser_postgres.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1782,7 +1782,8 @@ fn parse_array_index_expr() {
17821782
})),
17831783
data_type: DataType::Array(Some(Box::new(DataType::Array(Some(Box::new(
17841784
DataType::Int(None)
1785-
))))))
1785+
)))))),
1786+
format: None,
17861787
}))),
17871788
indexes: vec![num[1].clone(), num[2].clone()],
17881789
},

tests/sqlparser_snowflake.rs

+1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ fn parse_array() {
167167
&Expr::Cast {
168168
expr: Box::new(Expr::Identifier(Ident::new("a"))),
169169
data_type: DataType::Array(None),
170+
format: None,
170171
},
171172
expr_from_projection(only(&select.projection))
172173
);

0 commit comments

Comments
 (0)