Skip to content

Commit 236bdaa

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 4921846 commit 236bdaa

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: _,
@@ -7423,6 +7415,49 @@ pub struct DropDomain {
74237415
pub drop_behavior: Option<DropBehavior>,
74247416
}
74257417

7418+
/// A constant of form `<data_type> 'value'`.
7419+
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`),
7420+
/// as well as constants of other types (a non-standard PostgreSQL extension).
7421+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
7422+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7423+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
7424+
pub struct TypedString {
7425+
pub data_type: DataType,
7426+
/// The value of the constant.
7427+
/// Hint: you can unwrap the string value using `value.into_string()`.
7428+
pub value: ValueWithSpan,
7429+
/// Flags whether this TypedString uses the [ODBC syntax].
7430+
///
7431+
/// Example:
7432+
/// ```sql
7433+
/// -- An ODBC date literal:
7434+
/// SELECT {d '2025-07-16'}
7435+
/// -- This is equivalent to the standard ANSI SQL literal:
7436+
/// SELECT DATE '2025-07-16'
7437+
///
7438+
/// [ODBC syntax]: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals?view=sql-server-2017
7439+
pub uses_odbc_syntax: bool,
7440+
}
7441+
7442+
impl fmt::Display for TypedString {
7443+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7444+
let data_type = &self.data_type;
7445+
let value = &self.value;
7446+
match self.uses_odbc_syntax {
7447+
false => {
7448+
write!(f, "{data_type}")?;
7449+
write!(f, " {value}")
7450+
}
7451+
true => match data_type {
7452+
DataType::Date => write!(f, "{{d {}}}", value),
7453+
DataType::Time(..) => write!(f, "{{t {}}}", value),
7454+
DataType::Timestamp(..) => write!(f, "{{ts {}}}", value),
7455+
_ => write!(f, "{{? {}}}", value),
7456+
},
7457+
}
7458+
}
7459+
}
7460+
74267461
/// A function call
74277462
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
74287463
#[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;
@@ -1510,7 +1510,7 @@ impl Spanned for Expr {
15101510
.union(&union_spans(collation.0.iter().map(|i| i.span()))),
15111511
Expr::Nested(expr) => expr.span(),
15121512
Expr::Value(value) => value.span(),
1513-
Expr::TypedString { value, .. } => value.span(),
1513+
Expr::TypedString(TypedString { value, .. }) => value.span(),
15141514
Expr::Function(function) => function.span(),
15151515
Expr::GroupingSets(vec) => {
15161516
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
@@ -1534,10 +1534,11 @@ impl<'a> Parser<'a> {
15341534
// an unary negation `NOT ('a' LIKE 'b')`. To solve this, we don't accept the
15351535
// `type 'string'` syntax for the custom data types at all.
15361536
DataType::Custom(..) => parser_err!("dummy", loc),
1537-
data_type => Ok(Expr::TypedString {
1537+
data_type => Ok(Expr::TypedString(TypedString {
15381538
data_type,
15391539
value: parser.parse_value()?,
1540-
}),
1540+
uses_odbc_syntax: false,
1541+
})),
15411542
}
15421543
})?;
15431544

@@ -1723,10 +1724,11 @@ impl<'a> Parser<'a> {
17231724
}
17241725

17251726
fn parse_geometric_type(&mut self, kind: GeometricTypeKind) -> Result<Expr, ParserError> {
1726-
Ok(Expr::TypedString {
1727+
Ok(Expr::TypedString(TypedString {
17271728
data_type: DataType::GeometricType(kind),
17281729
value: self.parse_value()?,
1729-
})
1730+
uses_odbc_syntax: false,
1731+
}))
17301732
}
17311733

17321734
/// Try to parse an [Expr::CompoundFieldAccess] like `a.b.c` or `a.b[1].c`.
@@ -2023,6 +2025,46 @@ impl<'a> Parser<'a> {
20232025
})
20242026
}
20252027

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

2780-
if let Some(fn_expr) = self.maybe_parse_odbc_fn_body()? {
2822+
if let Some(fn_expr) = self.maybe_parse_odbc_body()? {
27812823
self.expect_token(&Token::RBrace)?;
27822824
return Ok(fn_expr);
27832825
}

0 commit comments

Comments
 (0)