Skip to content

Commit

Permalink
fix(sqlparser): support more PostgreSQL trim syntax (risingwavelabs…
Browse files Browse the repository at this point in the history
  • Loading branch information
xiangjinwu authored Apr 6, 2023
1 parent 8038315 commit 8e0bf7a
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 39 deletions.
50 changes: 50 additions & 0 deletions e2e_test/batch/functions/trim.slt.part
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,51 @@ select trim(trailing 'cba' from 'abcxyzabc');
----
abcxyz

query T
select trim('cba' from 'abcxyzabc');
----
xyz

query T
select trim(both from ' xyz ');
----
xyz

query T
select trim(from ' xyz ');
----
xyz

query T
select trim(both from 'abcxyzabc', 'cba');
----
xyz

query T
select trim(both 'abcxyzabc', 'cba');
----
xyz

query T
select trim(from 'abcxyzabc', 'cba');
----
xyz

query T
select trim('abcxyzabc', 'cba');
----
xyz

query T
select trim(both ' xyz ');
----
xyz

query T
select trim(' xyz ');
----
xyz

query T
select ltrim('abcxyzabc', 'bca');
----
Expand All @@ -22,3 +67,8 @@ query T
select rtrim('abcxyzabc', 'bca');
----
abcxyz

query T
select btrim('abcxyzabc', 'bca');
----
xyz
1 change: 1 addition & 0 deletions src/frontend/src/binder/expr/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ impl Binder {
("trim", raw_call(ExprType::Trim)),
("replace", raw_call(ExprType::Replace)),
("overlay", raw_call(ExprType::Overlay)),
("btrim", raw_call(ExprType::Trim)),
("ltrim", raw_call(ExprType::Ltrim)),
("rtrim", raw_call(ExprType::Rtrim)),
("md5", raw_call(ExprType::Md5)),
Expand Down
25 changes: 14 additions & 11 deletions src/frontend/src/binder/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ impl Binder {
time_zone,
} => self.bind_at_time_zone(*timestamp, time_zone),
// special syntaxt for string
Expr::Trim { expr, trim_where } => self.bind_trim(*expr, trim_where),
Expr::Trim {
expr,
trim_where,
trim_what,
} => self.bind_trim(*expr, trim_where, trim_what),
Expr::Substring {
expr,
substring_from,
Expand Down Expand Up @@ -245,21 +249,20 @@ impl Binder {
pub(super) fn bind_trim(
&mut self,
expr: Expr,
// ([BOTH | LEADING | TRAILING], <expr>)
trim_where: Option<(TrimWhereField, Box<Expr>)>,
// BOTH | LEADING | TRAILING
trim_where: Option<TrimWhereField>,
trim_what: Option<Box<Expr>>,
) -> Result<ExprImpl> {
let mut inputs = vec![self.bind_expr(expr)?];
let func_type = match trim_where {
Some(t) => {
inputs.push(self.bind_expr(*t.1)?);
match t.0 {
TrimWhereField::Both => ExprType::Trim,
TrimWhereField::Leading => ExprType::Ltrim,
TrimWhereField::Trailing => ExprType::Rtrim,
}
}
Some(TrimWhereField::Both) => ExprType::Trim,
Some(TrimWhereField::Leading) => ExprType::Ltrim,
Some(TrimWhereField::Trailing) => ExprType::Rtrim,
None => ExprType::Trim,
};
if let Some(t) = trim_what {
inputs.push(self.bind_expr(*t)?);
}
Ok(FunctionCall::new(func_type, inputs)?.into())
}

Expand Down
25 changes: 15 additions & 10 deletions src/sqlparser/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,13 +350,14 @@ pub enum Expr {
start: Box<Expr>,
count: Option<Box<Expr>>,
},
/// TRIM([BOTH | LEADING | TRAILING] <expr> [FROM <expr>])\
/// TRIM([BOTH | LEADING | TRAILING] [<expr>] FROM <expr>)\
/// Or\
/// TRIM(<expr>)
/// TRIM([BOTH | LEADING | TRAILING] [FROM] <expr> [, <expr>])
Trim {
expr: Box<Expr>,
// ([BOTH | LEADING | TRAILING], <expr>)
trim_where: Option<(TrimWhereField, Box<Expr>)>,
trim_where: Option<TrimWhereField>,
trim_what: Option<Box<Expr>>,
},
/// `expr COLLATE collation`
Collate {
Expand Down Expand Up @@ -576,15 +577,19 @@ impl fmt::Display for Expr {
}
Expr::IsDistinctFrom(a, b) => write!(f, "{} IS DISTINCT FROM {}", a, b),
Expr::IsNotDistinctFrom(a, b) => write!(f, "{} IS NOT DISTINCT FROM {}", a, b),
Expr::Trim { expr, trim_where } => {
Expr::Trim {
expr,
trim_where,
trim_what,
} => {
write!(f, "TRIM(")?;
if let Some((ident, trim_char)) = trim_where {
write!(f, "{} {} FROM {}", ident, trim_char, expr)?;
} else {
write!(f, "{}", expr)?;
if let Some(ident) = trim_where {
write!(f, "{} ", ident)?;
}

write!(f, ")")
if let Some(trim_char) = trim_what {
write!(f, "{} ", trim_char)?;
}
write!(f, "FROM {})", expr)
}
Expr::Row(exprs) => write!(
f,
Expand Down
31 changes: 22 additions & 9 deletions src/sqlparser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -975,28 +975,41 @@ impl Parser {
})
}

/// TRIM (WHERE 'text' FROM 'text')\
/// TRIM ('text')
/// TRIM ([WHERE] ['text'] FROM 'text')\
/// TRIM ([WHERE] [FROM] 'text' [, 'text'])
pub fn parse_trim_expr(&mut self) -> Result<Expr, ParserError> {
self.expect_token(&Token::LParen)?;
let mut where_expr = None;
let mut trim_where = None;
if let Token::Word(word) = self.peek_token().token {
if [Keyword::BOTH, Keyword::LEADING, Keyword::TRAILING]
.iter()
.any(|d| word.keyword == *d)
{
let trim_where = self.parse_trim_where()?;
let sub_expr = self.parse_expr()?;
self.expect_keyword(Keyword::FROM)?;
where_expr = Some((trim_where, Box::new(sub_expr)));
trim_where = Some(self.parse_trim_where()?);
}
}
let expr = self.parse_expr()?;

let (mut trim_what, expr) = if self.parse_keyword(Keyword::FROM) {
(None, self.parse_expr()?)
} else {
let mut expr = self.parse_expr()?;
if self.parse_keyword(Keyword::FROM) {
let trim_what = std::mem::replace(&mut expr, self.parse_expr()?);
(Some(Box::new(trim_what)), expr)
} else {
(None, expr)
}
};

if trim_what.is_none() && self.consume_token(&Token::Comma) {
trim_what = Some(Box::new(self.parse_expr()?));
}
self.expect_token(&Token::RParen)?;

Ok(Expr::Trim {
expr: Box::new(expr),
trim_where: where_expr,
trim_where,
trim_what,
})
}

Expand Down
2 changes: 1 addition & 1 deletion src/sqlparser/tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2869,7 +2869,7 @@ fn parse_trim() {
"SELECT TRIM(TRAILING 'xyz' FROM 'xyzfooxyz')",
);

one_statement_parses_to("SELECT TRIM(' foo ')", "SELECT TRIM(' foo ')");
one_statement_parses_to("SELECT TRIM(' foo ')", "SELECT TRIM(FROM ' foo ')");

let res = parse_sql_statements("SELECT TRIM(FOO 'xyz' FROM 'xyzfooxyz')");

Expand Down
10 changes: 5 additions & 5 deletions src/tests/regress/data/sql/strings.sql
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ SELECT E'De\\678dBeEf'::bytea;
--

-- E021-09 trim function
--@ SELECT TRIM(BOTH FROM ' bunch o blanks ') = 'bunch o blanks' AS "bunch o blanks";
--@
--@ SELECT TRIM(LEADING FROM ' bunch o blanks ') = 'bunch o blanks ' AS "bunch o blanks ";
--@
--@ SELECT TRIM(TRAILING FROM ' bunch o blanks ') = ' bunch o blanks' AS " bunch o blanks";
SELECT TRIM(BOTH FROM ' bunch o blanks ') = 'bunch o blanks' AS "bunch o blanks";

SELECT TRIM(LEADING FROM ' bunch o blanks ') = 'bunch o blanks ' AS "bunch o blanks ";

SELECT TRIM(TRAILING FROM ' bunch o blanks ') = ' bunch o blanks' AS " bunch o blanks";

SELECT TRIM(BOTH 'x' FROM 'xxxxxsome Xsxxxxx') = 'some Xs' AS "some Xs";

Expand Down
7 changes: 4 additions & 3 deletions src/tests/sqlsmith/src/sql_gen/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,14 +576,15 @@ fn make_trim(func: ExprType, exprs: Vec<Expr>) -> Expr {
E::Rtrim => TrimWhereField::Trailing,
_ => unreachable!(),
};
let trim_where = if exprs.len() > 1 {
Some((trim_type, Box::new(exprs[1].clone())))
let trim_what = if exprs.len() > 1 {
Some(Box::new(exprs[1].clone()))
} else {
None
};
Expr::Trim {
expr: Box::new(exprs[0].clone()),
trim_where,
trim_where: Some(trim_type),
trim_what,
}
}

Expand Down

0 comments on commit 8e0bf7a

Please sign in to comment.