Skip to content

Commit 64717ca

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 5f69df2 commit 64717ca

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: _,
@@ -7397,6 +7389,49 @@ pub struct DropDomain {
73977389
pub drop_behavior: Option<DropBehavior>,
73987390
}
73997391

7392+
/// A constant of form `<data_type> 'value'`.
7393+
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`),
7394+
/// as well as constants of other types (a non-standard PostgreSQL extension).
7395+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
7396+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7397+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
7398+
pub struct TypedString {
7399+
pub data_type: DataType,
7400+
/// The value of the constant.
7401+
/// Hint: you can unwrap the string value using `value.into_string()`.
7402+
pub value: ValueWithSpan,
7403+
/// Flags whether this TypedString uses the [ODBC syntax].
7404+
///
7405+
/// Example:
7406+
/// ```sql
7407+
/// -- An ODBC date literal:
7408+
/// SELECT {d '2025-07-16'}
7409+
/// -- This is equivalent to the standard ANSI SQL literal:
7410+
/// SELECT DATE '2025-07-16'
7411+
///
7412+
/// [ODBC syntax]: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals?view=sql-server-2017
7413+
pub uses_odbc_syntax: bool,
7414+
}
7415+
7416+
impl fmt::Display for TypedString {
7417+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7418+
let data_type = &self.data_type;
7419+
let value = &self.value;
7420+
match self.uses_odbc_syntax {
7421+
false => {
7422+
write!(f, "{data_type}")?;
7423+
write!(f, " {value}")
7424+
}
7425+
true => match data_type {
7426+
DataType::Date => write!(f, "{{d {}}}", value),
7427+
DataType::Time(..) => write!(f, "{{t {}}}", value),
7428+
DataType::Timestamp(..) => write!(f, "{{ts {}}}", value),
7429+
_ => write!(f, "{{? {}}}", value),
7430+
},
7431+
}
7432+
}
7433+
}
7434+
74007435
/// A function call
74017436
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
74027437
#[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
@@ -1527,10 +1527,11 @@ impl<'a> Parser<'a> {
15271527
// an unary negation `NOT ('a' LIKE 'b')`. To solve this, we don't accept the
15281528
// `type 'string'` syntax for the custom data types at all.
15291529
DataType::Custom(..) => parser_err!("dummy", loc),
1530-
data_type => Ok(Expr::TypedString {
1530+
data_type => Ok(Expr::TypedString(TypedString {
15311531
data_type,
15321532
value: parser.parse_value()?,
1533-
}),
1533+
uses_odbc_syntax: false,
1534+
})),
15341535
}
15351536
})?;
15361537

@@ -1716,10 +1717,11 @@ impl<'a> Parser<'a> {
17161717
}
17171718

17181719
fn parse_geometric_type(&mut self, kind: GeometricTypeKind) -> Result<Expr, ParserError> {
1719-
Ok(Expr::TypedString {
1720+
Ok(Expr::TypedString(TypedString {
17201721
data_type: DataType::GeometricType(kind),
17211722
value: self.parse_value()?,
1722-
})
1723+
uses_odbc_syntax: false,
1724+
}))
17231725
}
17241726

17251727
/// Try to parse an [Expr::CompoundFieldAccess] like `a.b.c` or `a.b[1].c`.
@@ -2016,6 +2018,46 @@ impl<'a> Parser<'a> {
20162018
})
20172019
}
20182020

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

2773-
if let Some(fn_expr) = self.maybe_parse_odbc_fn_body()? {
2815+
if let Some(fn_expr) = self.maybe_parse_odbc_body()? {
27742816
self.expect_token(&Token::RBrace)?;
27752817
return Ok(fn_expr);
27762818
}

0 commit comments

Comments
 (0)