Skip to content

Commit d0b3ddc

Browse files
committed
SGA-11414 Added support for odbc escape sequencing for time date and timestamp literals. For this I modified TypedString by adding uses_odbc_syntax flag.
1 parent 40b187f commit d0b3ddc

File tree

7 files changed

+237
-110
lines changed

7 files changed

+237
-110
lines changed

src/ast/mod.rs

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,12 +1014,7 @@ pub enum Expr {
10141014
/// A constant of form `<data_type> 'value'`.
10151015
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`),
10161016
/// as well as constants of other types (a non-standard PostgreSQL extension).
1017-
TypedString {
1018-
data_type: DataType,
1019-
/// The value of the constant.
1020-
/// Hint: you can unwrap the string value using `value.into_string()`.
1021-
value: ValueWithSpan,
1022-
},
1017+
TypedString(TypedString),
10231018
/// Scalar function call e.g. `LEFT(foo, 5)`
10241019
Function(Function),
10251020
/// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
@@ -1734,10 +1729,7 @@ impl fmt::Display for Expr {
17341729
Expr::Nested(ast) => write!(f, "({ast})"),
17351730
Expr::Value(v) => write!(f, "{v}"),
17361731
Expr::Prefixed { prefix, value } => write!(f, "{prefix} {value}"),
1737-
Expr::TypedString { data_type, value } => {
1738-
write!(f, "{data_type}")?;
1739-
write!(f, " {value}")
1740-
}
1732+
Expr::TypedString(ts) => ts.fmt(f),
17411733
Expr::Function(fun) => fun.fmt(f),
17421734
Expr::Case {
17431735
case_token: _,
@@ -7429,6 +7421,49 @@ pub struct DropDomain {
74297421
pub drop_behavior: Option<DropBehavior>,
74307422
}
74317423

7424+
/// A constant of form `<data_type> 'value'`.
7425+
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`),
7426+
/// as well as constants of other types (a non-standard PostgreSQL extension).
7427+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
7428+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7429+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
7430+
pub struct TypedString {
7431+
pub data_type: DataType,
7432+
/// The value of the constant.
7433+
/// Hint: you can unwrap the string value using `value.into_string()`.
7434+
pub value: ValueWithSpan,
7435+
/// Flags whether this TypedString uses the [ODBC syntax].
7436+
///
7437+
/// Example:
7438+
/// ```sql
7439+
/// -- An ODBC date literal:
7440+
/// SELECT {d '2025-07-16'}
7441+
/// -- This is equivalent to the standard ANSI SQL literal:
7442+
/// SELECT DATE '2025-07-16'
7443+
///
7444+
/// [ODBC syntax]: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals?view=sql-server-2017
7445+
pub uses_odbc_syntax: bool,
7446+
}
7447+
7448+
impl fmt::Display for TypedString {
7449+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7450+
let data_type = &self.data_type;
7451+
let value = &self.value;
7452+
match self.uses_odbc_syntax {
7453+
false => {
7454+
write!(f, "{data_type}")?;
7455+
write!(f, " {value}")
7456+
}
7457+
true => match data_type {
7458+
DataType::Date => write!(f, "{{d {}}}", value),
7459+
DataType::Time(..) => write!(f, "{{t {}}}", value),
7460+
DataType::Timestamp(..) => write!(f, "{{ts {}}}", value),
7461+
_ => write!(f, "{{? {}}}", value),
7462+
},
7463+
}
7464+
}
7465+
}
7466+
74327467
/// A function call
74337468
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
74347469
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

src/ast/spans.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use crate::ast::{query::SelectItemQualifiedWildcardKind, ColumnOptions};
18+
use crate::ast::{query::SelectItemQualifiedWildcardKind, ColumnOptions, TypedString};
1919
use core::iter;
2020

2121
use crate::tokenizer::Span;
@@ -1514,7 +1514,7 @@ impl Spanned for Expr {
15141514
.union(&union_spans(collation.0.iter().map(|i| i.span()))),
15151515
Expr::Nested(expr) => expr.span(),
15161516
Expr::Value(value) => value.span(),
1517-
Expr::TypedString { value, .. } => value.span(),
1517+
Expr::TypedString(TypedString { value, .. }) => value.span(),
15181518
Expr::Function(function) => function.span(),
15191519
Expr::GroupingSets(vec) => {
15201520
union_spans(vec.iter().flat_map(|i| i.iter().map(|k| k.span())))

src/parser/mod.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,10 +1539,11 @@ impl<'a> Parser<'a> {
15391539
// an unary negation `NOT ('a' LIKE 'b')`. To solve this, we don't accept the
15401540
// `type 'string'` syntax for the custom data types at all.
15411541
DataType::Custom(..) => parser_err!("dummy", loc),
1542-
data_type => Ok(Expr::TypedString {
1542+
data_type => Ok(Expr::TypedString(TypedString {
15431543
data_type,
15441544
value: parser.parse_value()?,
1545-
}),
1545+
uses_odbc_syntax: false,
1546+
})),
15461547
}
15471548
})?;
15481549

@@ -1728,10 +1729,11 @@ impl<'a> Parser<'a> {
17281729
}
17291730

17301731
fn parse_geometric_type(&mut self, kind: GeometricTypeKind) -> Result<Expr, ParserError> {
1731-
Ok(Expr::TypedString {
1732+
Ok(Expr::TypedString(TypedString {
17321733
data_type: DataType::GeometricType(kind),
17331734
value: self.parse_value()?,
1734-
})
1735+
uses_odbc_syntax: false,
1736+
}))
17351737
}
17361738

17371739
/// Try to parse an [Expr::CompoundFieldAccess] like `a.b.c` or `a.b[1].c`.
@@ -2028,6 +2030,46 @@ impl<'a> Parser<'a> {
20282030
})
20292031
}
20302032

2033+
// Tries to parse the body of an [ODBC escaping sequence]
2034+
/// i.e. without the enclosing braces
2035+
fn maybe_parse_odbc_body(&mut self) -> Result<Option<Expr>, ParserError> {
2036+
// Attempt 1: Try to parse it as a function.
2037+
if let Some(expr) = self.maybe_parse_odbc_fn_body()? {
2038+
return Ok(Some(expr));
2039+
}
2040+
// Attempt 2: Try to parse it as a Date, Time or Timestamp Literal
2041+
self.maybe_parse_odbc_body_datetime()
2042+
}
2043+
2044+
/// Tries to parse the body of an [ODBC Date, Time, and Timestamp Literals] call.
2045+
///
2046+
/// ```sql
2047+
/// {d '2025-07-17'}
2048+
/// {t '14:12:01'}
2049+
/// {ts '2025-07-17 14:12:01'}
2050+
/// ```
2051+
///
2052+
/// [ODBC Date, Time, and Timestamp Literals]:
2053+
/// https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals?view=sql-server-2017
2054+
fn maybe_parse_odbc_body_datetime(&mut self) -> Result<Option<Expr>, ParserError> {
2055+
self.maybe_parse(|p| {
2056+
let token = p.next_token().clone();
2057+
let word_string = token.token.to_string();
2058+
let data_type = match word_string.as_str() {
2059+
"t" => DataType::Time(None, TimezoneInfo::None),
2060+
"d" => DataType::Date,
2061+
"ts" => DataType::Timestamp(None, TimezoneInfo::None),
2062+
_ => return p.expected("ODBC datetime keyword (t, d, or ts)", token),
2063+
};
2064+
let value = p.parse_value()?;
2065+
Ok(Expr::TypedString(TypedString {
2066+
data_type,
2067+
value,
2068+
uses_odbc_syntax: true,
2069+
}))
2070+
})
2071+
}
2072+
20312073
/// Tries to parse the body of an [ODBC function] call.
20322074
/// i.e. without the enclosing braces
20332075
///
@@ -2782,7 +2824,7 @@ impl<'a> Parser<'a> {
27822824
fn parse_lbrace_expr(&mut self) -> Result<Expr, ParserError> {
27832825
let token = self.expect_token(&Token::LBrace)?;
27842826

2785-
if let Some(fn_expr) = self.maybe_parse_odbc_fn_body()? {
2827+
if let Some(fn_expr) = self.maybe_parse_odbc_body()? {
27862828
self.expect_token(&Token::RBrace)?;
27872829
return Ok(fn_expr);
27882830
}

0 commit comments

Comments
 (0)