This is an automated email from the ASF dual-hosted git repository.

iffyio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new bde269b5 Add ODBC escape syntax support for time expressions (#1953)
bde269b5 is described below

commit bde269b56f5af5e16759403ae3efda03029616e8
Author: etgarperets <etgar.per...@satoricyber.com>
AuthorDate: Tue Jul 29 13:37:04 2025 +0300

    Add ODBC escape syntax support for time expressions (#1953)
---
 src/ast/mod.rs                |  58 +++++++++++++---
 src/ast/spans.rs              |   4 +-
 src/parser/mod.rs             |  56 ++++++++++++++--
 tests/sqlparser_bigquery.rs   |  87 +++++++++++++-----------
 tests/sqlparser_common.rs     | 150 ++++++++++++++++++++++++++++--------------
 tests/sqlparser_databricks.rs |   7 +-
 tests/sqlparser_postgres.rs   |   7 +-
 7 files changed, 259 insertions(+), 110 deletions(-)

diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 00e7f86e..42e4d3b0 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -1014,12 +1014,7 @@ pub enum Expr {
     /// A constant of form `<data_type> '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 [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] 
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<DropBehavior>,
 }
 
+/// A constant of form `<data_type> '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 7f96465b..46f7a9ed 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 5ea57f6f..3b1db0f6 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<Expr, ParserError> {
-        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 
<https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/escape-sequences-in-odbc?view=sql-server-2017>
+    fn maybe_parse_odbc_body(&mut self) -> Result<Option<Expr>, 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<Option<Expr>, 
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<Expr, ParserError> {
         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 10a35671..839ea6b8 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 e6548d3e..7dc6fa81 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 a27e0699..e01611b6 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 d163096c..6ce4a483 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(


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@datafusion.apache.org
For additional commands, e-mail: commits-h...@datafusion.apache.org

Reply via email to