diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 9ec5f8de9..a2a9da2ba 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -408,6 +408,26 @@ impl fmt::Display for MapAccessKey { } } +/// The syntax used for in a cast expression. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum CastKind { + /// The standard SQL cast syntax, e.g. `CAST( as )` + Cast, + /// A cast that returns `NULL` on failure, e.g. `TRY_CAST( as )`. + /// + /// See . + /// See . + TryCast, + /// A cast that returns `NULL` on failure, bigQuery-specific , e.g. `SAFE_CAST( as )`. + /// + /// See . + SafeCast, + /// ` :: ` + DoubleColon, +} + /// An SQL expression of any type. /// /// The parser does not distinguish between expressions of different types @@ -546,25 +566,7 @@ pub enum Expr { }, /// `CAST` an expression to a different data type e.g. `CAST(foo AS VARCHAR(123))` Cast { - expr: Box, - data_type: DataType, - // Optional CAST(string_expression AS type FORMAT format_string_expression) as used by BigQuery - // https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax - format: Option, - }, - /// `TRY_CAST` an expression to a different data type e.g. `TRY_CAST(foo AS VARCHAR(123))` - // this differs from CAST in the choice of how to implement invalid conversions - TryCast { - expr: Box, - data_type: DataType, - // Optional CAST(string_expression AS type FORMAT format_string_expression) as used by BigQuery - // https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax - format: Option, - }, - /// `SAFE_CAST` an expression to a different data type e.g. `SAFE_CAST(foo AS FLOAT64)` - // only available for BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#safe_casting - // this works the same as `TRY_CAST` - SafeCast { + kind: CastKind, expr: Box, data_type: DataType, // Optional CAST(string_expression AS type FORMAT format_string_expression) as used by BigQuery @@ -989,38 +991,36 @@ impl fmt::Display for Expr { write!(f, ")") } Expr::Cast { + kind, expr, data_type, format, - } => { - if let Some(format) = format { - write!(f, "CAST({expr} AS {data_type} FORMAT {format})") - } else { - write!(f, "CAST({expr} AS {data_type})") + } => match kind { + CastKind::Cast => { + if let Some(format) = format { + write!(f, "CAST({expr} AS {data_type} FORMAT {format})") + } else { + write!(f, "CAST({expr} AS {data_type})") + } } - } - Expr::TryCast { - expr, - data_type, - format, - } => { - if let Some(format) = format { - write!(f, "TRY_CAST({expr} AS {data_type} FORMAT {format})") - } else { - write!(f, "TRY_CAST({expr} AS {data_type})") + CastKind::TryCast => { + if let Some(format) = format { + write!(f, "TRY_CAST({expr} AS {data_type} FORMAT {format})") + } else { + write!(f, "TRY_CAST({expr} AS {data_type})") + } } - } - Expr::SafeCast { - expr, - data_type, - format, - } => { - if let Some(format) = format { - write!(f, "SAFE_CAST({expr} AS {data_type} FORMAT {format})") - } else { - write!(f, "SAFE_CAST({expr} AS {data_type})") + CastKind::SafeCast => { + if let Some(format) = format { + write!(f, "SAFE_CAST({expr} AS {data_type} FORMAT {format})") + } else { + write!(f, "SAFE_CAST({expr} AS {data_type})") + } } - } + CastKind::DoubleColon => { + write!(f, "{expr}::{data_type}") + } + }, Expr::Extract { field, expr } => write!(f, "EXTRACT({field} FROM {expr})"), Expr::Ceil { expr, field } => { if field == &DateTimeField::NoDateTime { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7d6a362e7..daae8199b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1004,9 +1004,9 @@ impl<'a> Parser<'a> { } Keyword::CASE => self.parse_case_expr(), Keyword::CONVERT => self.parse_convert_expr(), - Keyword::CAST => self.parse_cast_expr(), - Keyword::TRY_CAST => self.parse_try_cast_expr(), - Keyword::SAFE_CAST => self.parse_safe_cast_expr(), + Keyword::CAST => self.parse_cast_expr(CastKind::Cast), + Keyword::TRY_CAST => self.parse_cast_expr(CastKind::TryCast), + Keyword::SAFE_CAST => self.parse_cast_expr(CastKind::SafeCast), Keyword::EXISTS => self.parse_exists_expr(false), Keyword::EXTRACT => self.parse_extract_expr(), Keyword::CEIL => self.parse_ceil_floor_expr(true), @@ -1491,7 +1491,7 @@ impl<'a> Parser<'a> { } /// Parse a SQL CAST function e.g. `CAST(expr AS FLOAT)` - pub fn parse_cast_expr(&mut self) -> Result { + pub fn parse_cast_expr(&mut self, kind: CastKind) -> Result { self.expect_token(&Token::LParen)?; let expr = self.parse_expr()?; self.expect_keyword(Keyword::AS)?; @@ -1499,36 +1499,7 @@ impl<'a> Parser<'a> { let format = self.parse_optional_cast_format()?; self.expect_token(&Token::RParen)?; Ok(Expr::Cast { - expr: Box::new(expr), - data_type, - format, - }) - } - - /// Parse a SQL TRY_CAST function e.g. `TRY_CAST(expr AS FLOAT)` - pub fn parse_try_cast_expr(&mut self) -> Result { - self.expect_token(&Token::LParen)?; - let expr = self.parse_expr()?; - self.expect_keyword(Keyword::AS)?; - let data_type = self.parse_data_type()?; - let format = self.parse_optional_cast_format()?; - self.expect_token(&Token::RParen)?; - Ok(Expr::TryCast { - expr: Box::new(expr), - data_type, - format, - }) - } - - /// Parse a BigQuery SAFE_CAST function e.g. `SAFE_CAST(expr AS FLOAT64)` - pub fn parse_safe_cast_expr(&mut self) -> Result { - self.expect_token(&Token::LParen)?; - let expr = self.parse_expr()?; - self.expect_keyword(Keyword::AS)?; - let data_type = self.parse_data_type()?; - let format = self.parse_optional_cast_format()?; - self.expect_token(&Token::RParen)?; - Ok(Expr::SafeCast { + kind, expr: Box::new(expr), data_type, format, @@ -2528,7 +2499,12 @@ impl<'a> Parser<'a> { ), } } else if Token::DoubleColon == tok { - self.parse_pg_cast(expr) + Ok(Expr::Cast { + kind: CastKind::DoubleColon, + expr: Box::new(expr), + data_type: self.parse_data_type()?, + format: None, + }) } else if Token::ExclamationMark == tok { // PostgreSQL factorial operation Ok(Expr::UnaryOp { @@ -2702,6 +2678,7 @@ impl<'a> Parser<'a> { /// Parse a postgresql casting style which is in the form of `expr::datatype` pub fn parse_pg_cast(&mut self, expr: Expr) -> Result { Ok(Expr::Cast { + kind: CastKind::DoubleColon, expr: Box::new(expr), data_type: self.parse_data_type()?, format: None, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 6c95b6c56..3aa84b923 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2107,6 +2107,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::BigInt(None), format: None, @@ -2118,6 +2119,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::TinyInt(None), format: None, @@ -2145,6 +2147,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::Nvarchar(Some(50)), format: None, @@ -2156,6 +2159,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::Clob(None), format: None, @@ -2167,6 +2171,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::Clob(Some(50)), format: None, @@ -2178,6 +2183,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::Binary(Some(50)), format: None, @@ -2189,6 +2195,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::Varbinary(Some(50)), format: None, @@ -2200,6 +2207,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::Blob(None), format: None, @@ -2211,6 +2219,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::Blob(Some(50)), format: None, @@ -2222,6 +2231,7 @@ fn parse_cast() { let select = verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("details"))), data_type: DataType::JSONB, format: None, @@ -2235,7 +2245,8 @@ fn parse_try_cast() { let sql = "SELECT TRY_CAST(id AS BIGINT) FROM customer"; let select = verified_only_select(sql); assert_eq!( - &Expr::TryCast { + &Expr::Cast { + kind: CastKind::TryCast, expr: Box::new(Expr::Identifier(Ident::new("id"))), data_type: DataType::BigInt(None), format: None, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index ea5c9875b..38e32780d 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -328,6 +328,7 @@ fn parse_create_table_with_defaults() { location: None, .. } => { + use pretty_assertions::assert_eq; assert_eq!("public.customer", name.to_string()); assert_eq!( columns, @@ -422,9 +423,7 @@ fn parse_create_table_with_defaults() { options: vec![ ColumnOptionDef { name: None, - option: ColumnOption::Default( - pg().verified_expr("CAST(now() AS TEXT)") - ) + option: ColumnOption::Default(pg().verified_expr("now()::TEXT")) }, ColumnOptionDef { name: None, @@ -498,15 +497,15 @@ fn parse_create_table_from_pg_dump() { active int )"; pg().one_statement_parses_to(sql, "CREATE TABLE public.customer (\ - customer_id INTEGER DEFAULT nextval(CAST('public.customer_customer_id_seq' AS REGCLASS)) NOT NULL, \ + customer_id INTEGER DEFAULT nextval('public.customer_customer_id_seq'::REGCLASS) NOT NULL, \ store_id SMALLINT NOT NULL, \ first_name CHARACTER VARYING(45) NOT NULL, \ last_name CHARACTER VARYING(45) NOT NULL, \ info TEXT[], \ address_id SMALLINT NOT NULL, \ activebool BOOLEAN DEFAULT true NOT NULL, \ - create_date DATE DEFAULT CAST(now() AS DATE) NOT NULL, \ - create_date1 DATE DEFAULT CAST(CAST('now' AS TEXT) AS DATE) NOT NULL, \ + create_date DATE DEFAULT now()::DATE NOT NULL, \ + create_date1 DATE DEFAULT 'now'::TEXT::DATE NOT NULL, \ last_update TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), \ release_year public.year, \ active INT\ @@ -1448,11 +1447,13 @@ fn parse_execute() { parameters: vec![], using: vec![ Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Value(Value::Number("1337".parse().unwrap(), false))), data_type: DataType::SmallInt(None), format: None }, Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Value(Value::Number("7331".parse().unwrap(), false))), data_type: DataType::SmallInt(None), format: None @@ -1908,6 +1909,7 @@ fn parse_array_index_expr() { assert_eq!( &Expr::ArrayIndex { obj: Box::new(Expr::Nested(Box::new(Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Array(Array { elem: vec![Expr::Array(Array { elem: vec![num[2].clone(), num[3].clone(),], diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 5c13457b6..b76e84ed4 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -168,6 +168,7 @@ fn parse_array() { let select = snowflake().verified_only_select(sql); assert_eq!( &Expr::Cast { + kind: CastKind::Cast, expr: Box::new(Expr::Identifier(Ident::new("a"))), data_type: DataType::Array(ArrayElemTypeDef::None), format: None, @@ -228,7 +229,7 @@ fn parse_json_using_colon() { select.projection[0] ); - snowflake().one_statement_parses_to("SELECT a:b::int FROM t", "SELECT CAST(a:b AS INT) FROM t"); + snowflake().verified_stmt("SELECT a:b::INT FROM t"); let sql = "SELECT a:start, a:end FROM t"; let select = snowflake().verified_only_select(sql);