diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 00e7f86e5..42e4d3b04 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1014,12 +1014,7 @@ pub enum Expr { /// A constant of form ` 'value'`. /// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`), /// as well as constants of other types (a non-standard PostgreSQL extension). - TypedString { - data_type: DataType, - /// The value of the constant. - /// Hint: you can unwrap the string value using `value.into_string()`. - value: ValueWithSpan, - }, + TypedString(TypedString), /// Scalar function call e.g. `LEFT(foo, 5)` Function(Function), /// `CASE [] WHEN THEN ... [ELSE ] END` @@ -1734,10 +1729,7 @@ impl fmt::Display for Expr { Expr::Nested(ast) => write!(f, "({ast})"), Expr::Value(v) => write!(f, "{v}"), Expr::Prefixed { prefix, value } => write!(f, "{prefix} {value}"), - Expr::TypedString { data_type, value } => { - write!(f, "{data_type}")?; - write!(f, " {value}") - } + Expr::TypedString(ts) => ts.fmt(f), Expr::Function(fun) => fun.fmt(f), Expr::Case { case_token: _, @@ -7450,6 +7442,52 @@ pub struct DropDomain { pub drop_behavior: Option, } +/// A constant of form ` 'value'`. +/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`), +/// as well as constants of other types (a non-standard PostgreSQL extension). +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct TypedString { + pub data_type: DataType, + /// The value of the constant. + /// Hint: you can unwrap the string value using `value.into_string()`. + pub value: ValueWithSpan, + /// Flags whether this TypedString uses the [ODBC syntax]. + /// + /// Example: + /// ```sql + /// -- An ODBC date literal: + /// SELECT {d '2025-07-16'} + /// -- This is equivalent to the standard ANSI SQL literal: + /// SELECT DATE '2025-07-16' + /// + /// [ODBC syntax]: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals?view=sql-server-2017 + pub uses_odbc_syntax: bool, +} + +impl fmt::Display for TypedString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let data_type = &self.data_type; + let value = &self.value; + match self.uses_odbc_syntax { + false => { + write!(f, "{data_type}")?; + write!(f, " {value}") + } + true => { + let prefix = match data_type { + DataType::Date => "d", + DataType::Time(..) => "t", + DataType::Timestamp(..) => "ts", + _ => "?", + }; + write!(f, "{{{prefix} {value}}}") + } + } + } +} + /// A function call #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 7f96465b6..46f7a9edf 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -use crate::ast::{query::SelectItemQualifiedWildcardKind, ColumnOptions, ExportData}; +use crate::ast::{query::SelectItemQualifiedWildcardKind, ColumnOptions, ExportData, TypedString}; use core::iter; use crate::tokenizer::Span; @@ -1525,7 +1525,7 @@ impl Spanned for Expr { .union(&union_spans(collation.0.iter().map(|i| i.span()))), Expr::Nested(expr) => expr.span(), Expr::Value(value) => value.span(), - Expr::TypedString { value, .. } => value.span(), + Expr::TypedString(TypedString { value, .. }) => value.span(), Expr::Function(function) => function.span(), Expr::GroupingSets(vec) => { union_spans(vec.iter().flat_map(|i| i.iter().map(|k| k.span()))) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 5ea57f6f8..3b1db0f64 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1543,10 +1543,11 @@ impl<'a> Parser<'a> { // an unary negation `NOT ('a' LIKE 'b')`. To solve this, we don't accept the // `type 'string'` syntax for the custom data types at all. DataType::Custom(..) => parser_err!("dummy", loc), - data_type => Ok(Expr::TypedString { + data_type => Ok(Expr::TypedString(TypedString { data_type, value: parser.parse_value()?, - }), + uses_odbc_syntax: false, + })), } })?; @@ -1732,10 +1733,11 @@ impl<'a> Parser<'a> { } fn parse_geometric_type(&mut self, kind: GeometricTypeKind) -> Result { - Ok(Expr::TypedString { + Ok(Expr::TypedString(TypedString { data_type: DataType::GeometricType(kind), value: self.parse_value()?, - }) + uses_odbc_syntax: false, + })) } /// Try to parse an [Expr::CompoundFieldAccess] like `a.b.c` or `a.b[1].c`. @@ -2032,6 +2034,50 @@ impl<'a> Parser<'a> { }) } + /// Tries to parse the body of an [ODBC escaping sequence] + /// i.e. without the enclosing braces + /// Currently implemented: + /// Scalar Function Calls + /// Date, Time, and Timestamp Literals + /// See + fn maybe_parse_odbc_body(&mut self) -> Result, ParserError> { + // Attempt 1: Try to parse it as a function. + if let Some(expr) = self.maybe_parse_odbc_fn_body()? { + return Ok(Some(expr)); + } + // Attempt 2: Try to parse it as a Date, Time or Timestamp Literal + self.maybe_parse_odbc_body_datetime() + } + + /// Tries to parse the body of an [ODBC Date, Time, and Timestamp Literals] call. + /// + /// ```sql + /// {d '2025-07-17'} + /// {t '14:12:01'} + /// {ts '2025-07-17 14:12:01'} + /// ``` + /// + /// [ODBC Date, Time, and Timestamp Literals]: + /// https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals?view=sql-server-2017 + fn maybe_parse_odbc_body_datetime(&mut self) -> Result, ParserError> { + self.maybe_parse(|p| { + let token = p.next_token().clone(); + let word_string = token.token.to_string(); + let data_type = match word_string.as_str() { + "t" => DataType::Time(None, TimezoneInfo::None), + "d" => DataType::Date, + "ts" => DataType::Timestamp(None, TimezoneInfo::None), + _ => return p.expected("ODBC datetime keyword (t, d, or ts)", token), + }; + let value = p.parse_value()?; + Ok(Expr::TypedString(TypedString { + data_type, + value, + uses_odbc_syntax: true, + })) + }) + } + /// Tries to parse the body of an [ODBC function] call. /// i.e. without the enclosing braces /// @@ -2786,7 +2832,7 @@ impl<'a> Parser<'a> { fn parse_lbrace_expr(&mut self) -> Result { let token = self.expect_token(&Token::LBrace)?; - if let Some(fn_expr) = self.maybe_parse_odbc_fn_body()? { + if let Some(fn_expr) = self.maybe_parse_odbc_body()? { self.expect_token(&Token::RBrace)?; return Ok(fn_expr); } diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 10a356717..839ea6b8c 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -906,13 +906,14 @@ fn parse_typed_struct_syntax_bigquery() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::Datetime(None), value: ValueWithSpan { value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()), span: Span::empty(), }, - }], + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::Datetime(None), @@ -968,15 +969,16 @@ fn parse_typed_struct_syntax_bigquery() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::JSON, value: ValueWithSpan { value: Value::SingleQuotedString( r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into() ), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::JSON, @@ -1004,7 +1006,7 @@ fn parse_typed_struct_syntax_bigquery() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString( @@ -1012,7 +1014,8 @@ fn parse_typed_struct_syntax_bigquery() { ), span: Span::empty(), }, - }], + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::Timestamp(None, TimezoneInfo::None), @@ -1024,13 +1027,14 @@ fn parse_typed_struct_syntax_bigquery() { assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::Time(None, TimezoneInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString("15:30:00".into()), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::Time(None, TimezoneInfo::None), @@ -1045,13 +1049,14 @@ fn parse_typed_struct_syntax_bigquery() { assert_eq!(2, select.projection.len()); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::Numeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString("1".into()), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::Numeric(ExactNumberInfo::None), @@ -1062,13 +1067,14 @@ fn parse_typed_struct_syntax_bigquery() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString("1".into()), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::BigNumeric(ExactNumberInfo::None), @@ -1239,13 +1245,14 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::Datetime(None), value: ValueWithSpan { value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::Datetime(None), @@ -1301,15 +1308,16 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::JSON, value: ValueWithSpan { value: Value::SingleQuotedString( r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into() ), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::JSON, @@ -1337,15 +1345,16 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString( "2008-12-25 15:30:00 America/Los_Angeles".into() ), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::Timestamp(None, TimezoneInfo::None), @@ -1357,13 +1366,14 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::Time(None, TimezoneInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString("15:30:00".into()), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::Time(None, TimezoneInfo::None), @@ -1378,13 +1388,14 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { assert_eq!(2, select.projection.len()); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::Numeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString("1".into()), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::Numeric(ExactNumberInfo::None), @@ -1395,13 +1406,14 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::TypedString { + values: vec![Expr::TypedString(TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString("1".into()), span: Span::empty(), - } - }], + }, + uses_odbc_syntax: false + })], fields: vec![StructField { field_name: None, field_type: DataType::BigNumeric(ExactNumberInfo::None), @@ -2433,13 +2445,14 @@ fn test_triple_quote_typed_strings() { let expr = bigquery().verified_expr(r#"JSON """{"foo":"bar's"}""""#); assert_eq!( - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::JSON, value: ValueWithSpan { value: Value::TripleDoubleQuotedString(r#"{"foo":"bar's"}"#.into()), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false + }), expr ); } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e6548d3e0..7dc6fa81a 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -5914,13 +5914,14 @@ fn parse_literal_date() { let sql = "SELECT DATE '1999-01-01'"; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::Date, value: ValueWithSpan { value: Value::SingleQuotedString("1999-01-01".into()), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); } @@ -5930,13 +5931,14 @@ fn parse_literal_time() { let sql = "SELECT TIME '01:23:34'"; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::Time(None, TimezoneInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString("01:23:34".into()), span: Span::empty(), }, - }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); } @@ -5946,13 +5948,14 @@ fn parse_literal_datetime() { let sql = "SELECT DATETIME '1999-01-01 01:23:34.45'"; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::Datetime(None), value: ValueWithSpan { value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()), span: Span::empty(), }, - }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); } @@ -5962,13 +5965,14 @@ fn parse_literal_timestamp_without_time_zone() { let sql = "SELECT TIMESTAMP '1999-01-01 01:23:34'"; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString("1999-01-01 01:23:34".into()), span: Span::empty(), }, - }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); @@ -5980,13 +5984,14 @@ fn parse_literal_timestamp_with_time_zone() { let sql = "SELECT TIMESTAMPTZ '1999-01-01 01:23:34Z'"; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::Tz), value: ValueWithSpan { value: Value::SingleQuotedString("1999-01-01 01:23:34Z".into()), span: Span::empty(), }, - }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); @@ -6556,7 +6561,7 @@ fn parse_json_keyword() { }'"#; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::JSON, value: ValueWithSpan { value: Value::SingleQuotedString( @@ -6583,8 +6588,9 @@ fn parse_json_keyword() { .to_string() ), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false, + }), expr_from_projection(only(&select.projection)), ); } @@ -6593,17 +6599,23 @@ fn parse_json_keyword() { fn parse_typed_strings() { let expr = verified_expr(r#"JSON '{"foo":"bar"}'"#); assert_eq!( - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::JSON, value: ValueWithSpan { value: Value::SingleQuotedString(r#"{"foo":"bar"}"#.into()), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false + }), expr ); - if let Expr::TypedString { data_type, value } = expr { + if let Expr::TypedString(TypedString { + data_type, + value, + uses_odbc_syntax: false, + }) = expr + { assert_eq!(DataType::JSON, data_type); assert_eq!(r#"{"foo":"bar"}"#, value.into_string().unwrap()); } @@ -6614,13 +6626,14 @@ fn parse_bignumeric_keyword() { let sql = r#"SELECT BIGNUMERIC '0'"#; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString(r#"0"#.into()), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); verified_stmt("SELECT BIGNUMERIC '0'"); @@ -6628,13 +6641,14 @@ fn parse_bignumeric_keyword() { let sql = r#"SELECT BIGNUMERIC '123456'"#; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString(r#"123456"#.into()), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); verified_stmt("SELECT BIGNUMERIC '123456'"); @@ -6642,13 +6656,14 @@ fn parse_bignumeric_keyword() { let sql = r#"SELECT BIGNUMERIC '-3.14'"#; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString(r#"-3.14"#.into()), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); verified_stmt("SELECT BIGNUMERIC '-3.14'"); @@ -6656,13 +6671,14 @@ fn parse_bignumeric_keyword() { let sql = r#"SELECT BIGNUMERIC '-0.54321'"#; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString(r#"-0.54321"#.into()), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); verified_stmt("SELECT BIGNUMERIC '-0.54321'"); @@ -6670,13 +6686,14 @@ fn parse_bignumeric_keyword() { let sql = r#"SELECT BIGNUMERIC '1.23456e05'"#; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString(r#"1.23456e05"#.into()), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); verified_stmt("SELECT BIGNUMERIC '1.23456e05'"); @@ -6684,13 +6701,14 @@ fn parse_bignumeric_keyword() { let sql = r#"SELECT BIGNUMERIC '-9.876e-3'"#; let select = verified_only_select(sql); assert_eq!( - &Expr::TypedString { + &Expr::TypedString(TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString(r#"-9.876e-3"#.into()), span: Span::empty(), - } - }, + }, + uses_odbc_syntax: false + }), expr_from_projection(only(&select.projection)), ); verified_stmt("SELECT BIGNUMERIC '-9.876e-3'"); @@ -15015,83 +15033,90 @@ fn test_geometry_type() { let sql = "point '1,2'"; assert_eq!( all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::GeometricType(GeometricTypeKind::Point), value: ValueWithSpan { value: Value::SingleQuotedString("1,2".to_string()), span: Span::empty(), }, - } + uses_odbc_syntax: false + }) ); let sql = "line '1,2,3,4'"; assert_eq!( all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::GeometricType(GeometricTypeKind::Line), value: ValueWithSpan { value: Value::SingleQuotedString("1,2,3,4".to_string()), span: Span::empty(), }, - } + uses_odbc_syntax: false + }) ); let sql = "path '1,2,3,4'"; assert_eq!( all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::GeometricType(GeometricTypeKind::GeometricPath), value: ValueWithSpan { value: Value::SingleQuotedString("1,2,3,4".to_string()), span: Span::empty(), }, - } + uses_odbc_syntax: false + }) ); let sql = "box '1,2,3,4'"; assert_eq!( all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::GeometricType(GeometricTypeKind::GeometricBox), value: ValueWithSpan { value: Value::SingleQuotedString("1,2,3,4".to_string()), span: Span::empty(), }, - } + uses_odbc_syntax: false + }) ); let sql = "circle '1,2,3'"; assert_eq!( all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::GeometricType(GeometricTypeKind::Circle), value: ValueWithSpan { value: Value::SingleQuotedString("1,2,3".to_string()), span: Span::empty(), }, - } + uses_odbc_syntax: false + }) ); let sql = "polygon '1,2,3,4'"; assert_eq!( all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::GeometricType(GeometricTypeKind::Polygon), value: ValueWithSpan { value: Value::SingleQuotedString("1,2,3,4".to_string()), span: Span::empty(), }, - } + uses_odbc_syntax: false + }) ); let sql = "lseg '1,2,3,4'"; assert_eq!( all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::GeometricType(GeometricTypeKind::LineSegment), value: ValueWithSpan { value: Value::SingleQuotedString("1,2,3,4".to_string()), span: Span::empty(), }, - } + uses_odbc_syntax: false + }) ); } #[test] @@ -16291,6 +16316,31 @@ fn parse_notnull() { notnull_unsupported_dialects.expr_parses_to("NOT NULL NOTNULL", "NOT NULL"); } +#[test] +fn parse_odbc_time_date_timestamp() { + // Supported statements + let sql_d = "SELECT {d '2025-07-17'}, category_name FROM categories"; + let _ = all_dialects().verified_stmt(sql_d); + let sql_t = "SELECT {t '14:12:01'}, category_name FROM categories"; + let _ = all_dialects().verified_stmt(sql_t); + let sql_ts = "SELECT {ts '2025-07-17 14:12:01'}, category_name FROM categories"; + let _ = all_dialects().verified_stmt(sql_ts); + // Unsupported statement + let supports_dictionary = all_dialects_where(|d| d.supports_dictionary_syntax()); + let dictionary_unsupported = all_dialects_where(|d| !d.supports_dictionary_syntax()); + let sql = "SELECT {tt '14:12:01'} FROM foo"; + let res = supports_dictionary.parse_sql_statements(sql); + let res_dict = dictionary_unsupported.parse_sql_statements(sql); + assert_eq!( + ParserError::ParserError("Expected: :, found: '14:12:01'".to_string()), + res.unwrap_err() + ); + assert_eq!( + ParserError::ParserError("Expected: an expression, found: {".to_string()), + res_dict.unwrap_err() + ); +} + #[test] fn parse_create_user() { let create = verified_stmt("CREATE USER u1"); diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index a27e0699b..e01611b6f 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -327,13 +327,14 @@ fn data_type_timestamp_ntz() { // Literal assert_eq!( databricks().verified_expr("TIMESTAMP_NTZ '2025-03-29T18:52:00'"), - Expr::TypedString { + Expr::TypedString(TypedString { data_type: DataType::TimestampNtz, value: ValueWithSpan { value: Value::SingleQuotedString("2025-03-29T18:52:00".to_owned()), span: Span::empty(), - } - } + }, + uses_odbc_syntax: false + }) ); // Cast diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index d163096c4..6ce4a4835 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4723,7 +4723,7 @@ fn parse_dollar_quoted_string() { quote_style: None, span: Span::empty(), }, - } + }, ); assert_eq!( @@ -5296,13 +5296,14 @@ fn parse_at_time_zone() { // check precedence let expr = Expr::BinaryOp { left: Box::new(Expr::AtTimeZone { - timestamp: Box::new(Expr::TypedString { + timestamp: Box::new(Expr::TypedString(TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), value: ValueWithSpan { value: Value::SingleQuotedString("2001-09-28 01:00".to_string()), span: Span::empty(), }, - }), + uses_odbc_syntax: false, + })), time_zone: Box::new(Expr::Cast { kind: CastKind::DoubleColon, expr: Box::new(Expr::Value(