Parse SUBSTR as alias for SUBSTRING (#1769)

This commit is contained in:
Michael Victor Zink 2025-03-21 22:34:43 -07:00 committed by GitHub
parent f487cbe004
commit 939fbdd4f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 32 additions and 4 deletions

View file

@ -890,6 +890,10 @@ pub enum Expr {
/// true if the expression is represented using the `SUBSTRING(expr, start, len)` syntax
/// This flag is used for formatting.
special: bool,
/// true if the expression is represented using the `SUBSTR` shorthand
/// This flag is used for formatting.
shorthand: bool,
},
/// ```sql
/// TRIM([BOTH | LEADING | TRAILING] [<expr> FROM] <expr>)
@ -1719,8 +1723,13 @@ impl fmt::Display for Expr {
substring_from,
substring_for,
special,
shorthand,
} => {
write!(f, "SUBSTRING({expr}")?;
f.write_str("SUBSTR")?;
if !*shorthand {
f.write_str("ING")?;
}
write!(f, "({expr}")?;
if let Some(from_part) = substring_from {
if *special {
write!(f, ", {from_part}")?;

View file

@ -1503,6 +1503,7 @@ impl Spanned for Expr {
substring_from,
substring_for,
special: _,
shorthand: _,
} => union_spans(
core::iter::once(expr.span())
.chain(substring_from.as_ref().map(|i| i.span()))

View file

@ -841,6 +841,7 @@ define_keywords!(
STRING,
STRUCT,
SUBMULTISET,
SUBSTR,
SUBSTRING,
SUBSTRING_REGEX,
SUCCEEDS,

View file

@ -1302,7 +1302,10 @@ impl<'a> Parser<'a> {
Keyword::POSITION if self.peek_token_ref().token == Token::LParen => {
Ok(Some(self.parse_position_expr(w.clone().into_ident(w_span))?))
}
Keyword::SUBSTRING => Ok(Some(self.parse_substring_expr()?)),
Keyword::SUBSTR | Keyword::SUBSTRING => {
self.prev_token();
Ok(Some(self.parse_substring()?))
}
Keyword::OVERLAY => Ok(Some(self.parse_overlay_expr()?)),
Keyword::TRIM => Ok(Some(self.parse_trim_expr()?)),
Keyword::INTERVAL => Ok(Some(self.parse_interval()?)),
@ -2412,8 +2415,16 @@ impl<'a> Parser<'a> {
}
}
pub fn parse_substring_expr(&mut self) -> Result<Expr, ParserError> {
// PARSE SUBSTRING (EXPR [FROM 1] [FOR 3])
// { SUBSTRING | SUBSTR } (<EXPR> [FROM 1] [FOR 3])
pub fn parse_substring(&mut self) -> Result<Expr, ParserError> {
let shorthand = match self.expect_one_of_keywords(&[Keyword::SUBSTR, Keyword::SUBSTRING])? {
Keyword::SUBSTR => true,
Keyword::SUBSTRING => false,
_ => {
self.prev_token();
return self.expected("SUBSTR or SUBSTRING", self.peek_token());
}
};
self.expect_token(&Token::LParen)?;
let expr = self.parse_expr()?;
let mut from_expr = None;
@ -2433,6 +2444,7 @@ impl<'a> Parser<'a> {
substring_from: from_expr.map(Box::new),
substring_for: to_expr.map(Box::new),
special,
shorthand,
})
}

View file

@ -7607,6 +7607,9 @@ fn parse_substring() {
verified_stmt("SELECT SUBSTRING('1', 1, 3)");
verified_stmt("SELECT SUBSTRING('1', 1)");
verified_stmt("SELECT SUBSTRING('1' FOR 3)");
verified_stmt("SELECT SUBSTRING('foo' FROM 1 FOR 2) FROM t");
verified_stmt("SELECT SUBSTR('foo' FROM 1 FOR 2) FROM t");
verified_stmt("SELECT SUBSTR('foo', 1, 2) FROM t");
}
#[test]

View file

@ -1133,6 +1133,7 @@ fn parse_substring_in_select() {
(number("1")).with_empty_span()
))),
special: true,
shorthand: false,
})],
into: None,
from: vec![TableWithJoins {

View file

@ -2590,6 +2590,7 @@ fn parse_substring_in_select() {
(number("1")).with_empty_span()
))),
special: true,
shorthand: false,
})],
into: None,
from: vec![TableWithJoins {