Skip to content

Commit 6f0b2dc

Browse files
authored
Implement SUBSTRING(col [FROM <expr>] [FOR <expr>]) syntax (apache#293)
1 parent 8a214f9 commit 6f0b2dc

File tree

4 files changed

+63
-3
lines changed

4 files changed

+63
-3
lines changed

src/ast/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,17 @@ pub enum Expr {
201201
expr: Box<Expr>,
202202
data_type: DataType,
203203
},
204+
/// EXTRACT(DateTimeField FROM <expr>)
204205
Extract {
205206
field: DateTimeField,
206207
expr: Box<Expr>,
207208
},
209+
/// SUBSTRING(<expr> [FROM <expr>] [FOR <expr>])
210+
Substring {
211+
expr: Box<Expr>,
212+
substring_from: Option<Box<Expr>>,
213+
substring_for: Option<Box<Expr>>,
214+
},
208215
/// `expr COLLATE collation`
209216
Collate {
210217
expr: Box<Expr>,
@@ -333,6 +340,21 @@ impl fmt::Display for Expr {
333340
Expr::Exists(s) => write!(f, "EXISTS ({})", s),
334341
Expr::Subquery(s) => write!(f, "({})", s),
335342
Expr::ListAgg(listagg) => write!(f, "{}", listagg),
343+
Expr::Substring {
344+
expr,
345+
substring_from,
346+
substring_for,
347+
} => {
348+
write!(f, "SUBSTRING({}", expr)?;
349+
if let Some(from_part) = substring_from {
350+
write!(f, " FROM {}", from_part)?;
351+
}
352+
if let Some(from_part) = substring_for {
353+
write!(f, " FOR {}", from_part)?;
354+
}
355+
356+
write!(f, ")")
357+
}
336358
}
337359
}
338360
}

src/parser.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ impl<'a> Parser<'a> {
350350
Keyword::CAST => self.parse_cast_expr(),
351351
Keyword::EXISTS => self.parse_exists_expr(),
352352
Keyword::EXTRACT => self.parse_extract_expr(),
353+
Keyword::SUBSTRING => self.parse_substring_expr(),
353354
Keyword::INTERVAL => self.parse_literal_interval(),
354355
Keyword::LISTAGG => self.parse_listagg_expr(),
355356
Keyword::NOT => Ok(Expr::UnaryOp {
@@ -606,6 +607,27 @@ impl<'a> Parser<'a> {
606607
})
607608
}
608609

610+
pub fn parse_substring_expr(&mut self) -> Result<Expr, ParserError> {
611+
// PARSE SUBSTRING (EXPR [FROM 1] [FOR 3])
612+
self.expect_token(&Token::LParen)?;
613+
let expr = self.parse_expr()?;
614+
let mut from_expr = None;
615+
let mut to_expr = None;
616+
if self.parse_keyword(Keyword::FROM) {
617+
from_expr = Some(self.parse_expr()?);
618+
}
619+
if self.parse_keyword(Keyword::FOR) {
620+
to_expr = Some(self.parse_expr()?);
621+
}
622+
self.expect_token(&Token::RParen)?;
623+
624+
Ok(Expr::Substring {
625+
expr: Box::new(expr),
626+
substring_from: from_expr.map(Box::new),
627+
substring_for: to_expr.map(Box::new),
628+
})
629+
}
630+
609631
/// Parse a SQL LISTAGG expression, e.g. `LISTAGG(...) WITHIN GROUP (ORDER BY ...)`.
610632
pub fn parse_listagg_expr(&mut self) -> Result<Expr, ParserError> {
611633
self.expect_token(&Token::LParen)?;

tests/sqlparser_common.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2598,6 +2598,23 @@ fn parse_scalar_subqueries() {
25982598
);
25992599
}
26002600

2601+
#[test]
2602+
fn parse_substring() {
2603+
one_statement_parses_to("SELECT SUBSTRING('1')", "SELECT SUBSTRING('1')");
2604+
2605+
one_statement_parses_to(
2606+
"SELECT SUBSTRING('1' FROM 1)",
2607+
"SELECT SUBSTRING('1' FROM 1)",
2608+
);
2609+
2610+
one_statement_parses_to(
2611+
"SELECT SUBSTRING('1' FROM 1 FOR 3)",
2612+
"SELECT SUBSTRING('1' FROM 1 FOR 3)",
2613+
);
2614+
2615+
one_statement_parses_to("SELECT SUBSTRING('1' FOR 3)", "SELECT SUBSTRING('1' FOR 3)");
2616+
}
2617+
26012618
#[test]
26022619
fn parse_exists_subquery() {
26032620
let expected_inner = verified_query("SELECT 1");

tests/sqlparser_regression.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ macro_rules! tpch_tests {
2525
#[test]
2626
fn $name() {
2727
let dialect = GenericDialect {};
28-
2928
let res = Parser::parse_sql(&dialect, QUERIES[$value -1]);
30-
// Ignore 6.sql and 22.sql
31-
if $value != 6 && $value != 22 {
29+
// Ignore 6.sql
30+
if $value != 6 {
3231
assert!(res.is_ok());
3332
}
3433
}

0 commit comments

Comments
 (0)