Skip to content

Commit

Permalink
refine
Browse files Browse the repository at this point in the history
Signed-off-by: TennyZhuang <[email protected]>
  • Loading branch information
TennyZhuang committed Mar 29, 2024
1 parent cf836b0 commit 420273b
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 48 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/frontend/planner_test/tests/testdata/output/expr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
Failed to bind expression: 1 NOT LIKE 1.23
Caused by:
function like(integer, numeric) does not exist
function LIKE(integer, numeric) does not exist
- sql: |
select length(trim(trailing '1' from '12'))+length(trim(leading '2' from '23'))+length(trim(both '3' from '34'));
batch_plan: 'BatchValues { rows: [[4:Int32]] }'
Expand Down
45 changes: 34 additions & 11 deletions src/frontend/src/binder/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use itertools::Itertools;
use risingwave_common::catalog::{ColumnDesc, ColumnId};
use risingwave_common::types::DataType;
use risingwave_common::util::iter_util::zip_eq_fast;
use risingwave_common::{bail_not_implemented, not_implemented};
use risingwave_common::{bail_no_function, bail_not_implemented, not_implemented};
use risingwave_pb::plan_common::{AdditionalColumn, ColumnDescVersion};
use risingwave_sqlparser::ast::{
Array, BinaryOperator, DataType as AstDataType, Expr, Function, JsonPredicateType, ObjectName,
Expand Down Expand Up @@ -152,13 +152,13 @@ impl Binder {
expr,
pattern,
escape_char,
} => self.bind_like::<false>(*expr, negated, *pattern, escape_char),
} => self.bind_like(ExprType::Like, *expr, negated, *pattern, escape_char),
Expr::ILike {
negated,
expr,
pattern,
escape_char,
} => self.bind_like::<true>(*expr, negated, *pattern, escape_char),
} => self.bind_like(ExprType::ILike, *expr, negated, *pattern, escape_char),
Expr::SimilarTo {
expr,
negated,
Expand Down Expand Up @@ -455,23 +455,46 @@ impl Binder {
Ok(func_call.into())
}

fn bind_like<const CASE_INSENSITIVE: bool>(
fn bind_like(
&mut self,
expr_type: ExprType,
expr: Expr,
negated: bool,
pattern: Expr,
escape_char: Option<char>,
) -> Result<ExprImpl> {
let expr = self.bind_expr_inner(expr)?;
let pattern = self.bind_expr_inner(pattern)?;
if matches!(pattern, Expr::AllOp(_) | Expr::SomeOp(_)) {
if escape_char.is_some() {
// PostgreSQL also don't support the pattern due to the complexity of implementation.
// The SQL will failed on PostgreSQL 16.1:
// ```sql
// select 'a' like any(array[null]) escape '';
// ```
bail_not_implemented!(
"LIKE with both ALL|ANY pattern and escape character is not supported"
)
}
// Use the `bind_binary_op` path to handle the ALL|ANY pattern.
let op = match (expr_type, negated) {
(ExprType::Like, false) => BinaryOperator::PGLikeMatch,
(ExprType::Like, true) => BinaryOperator::PGNotLikeMatch,
(ExprType::ILike, false) => BinaryOperator::PGILikeMatch,
(ExprType::ILike, true) => BinaryOperator::PGNotILikeMatch,
_ => unreachable!(),
};
return self.bind_binary_op(expr, op, pattern);
}
if escape_char.is_some() {
bail_not_implemented!(issue = 15701, "LIKE with escape character is not supported");
}
let expr_type = if CASE_INSENSITIVE {
ExprType::ILike
} else {
ExprType::Like
};
let expr = self.bind_expr_inner(expr)?;
let pattern = self.bind_expr_inner(pattern)?;
match (expr.return_type(), pattern.return_type()) {
(DataType::Varchar, DataType::Varchar) => {}
(string_ty, pattern_ty) => {
bail_no_function!("LIKE({}, {})", string_ty, pattern_ty);
}
}
let func_call =
FunctionCall::new_unchecked(expr_type, vec![expr, pattern], DataType::Boolean);
let func_call = if negated {
Expand Down
1 change: 1 addition & 0 deletions src/sqlparser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ normal = ["workspace-hack"]
itertools = "0.12"
serde = { version = "1.0", features = ["derive"], optional = true }
tracing = "0.1"
tracing-subscriber = "0.3"

[target.'cfg(not(madsim))'.dependencies]
workspace-hack = { path = "../workspace-hack" }
Expand Down
2 changes: 2 additions & 0 deletions src/sqlparser/src/bin/sqlparser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use risingwave_sqlparser::parser::Parser;
/// echo "SELECT 1;" | cargo run --bin sqlparser
/// ```
fn main() {
tracing_subscriber::fmt::init();

let mut buffer = String::new();
io::stdin().read_line(&mut buffer).unwrap();
let result = Parser::parse_sql(&buffer);
Expand Down
114 changes: 78 additions & 36 deletions src/sqlparser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,12 +517,12 @@ impl Parser {

/// Parse tokens until the precedence changes
pub fn parse_subexpr(&mut self, precedence: Precedence) -> Result<Expr, ParserError> {
debug!("parsing expr");
debug!("parsing expr, current token: {:?}", self.peek_token().token);
let mut expr = self.parse_prefix()?;
debug!("prefix: {:?}", expr);
loop {
let next_precedence = self.get_next_precedence()?;
debug!("next precedence: {:?}", next_precedence);
debug!("precedence: {precedence:?}, next precedence: {next_precedence:?}");

if precedence >= next_precedence {
break;
Expand Down Expand Up @@ -613,6 +613,25 @@ impl Parser {
expr: Box::new(self.parse_subexpr(Precedence::Other)?),
})
}
keyword @ (Keyword::ALL | Keyword::ANY | Keyword::SOME) => {
self.expect_token(&Token::LParen)?;
// In upstream's PR of parser-rs, there is `self.parser_subexpr(precedence)` here.
// But it will fail to parse `select 1 = any(null and true);`.
let sub = self.parse_expr()?;
self.expect_token(&Token::RParen)?;

// TODO: support `all/any/some(subquery)`.
if let Expr::Subquery(_) = &sub {
parser_err!("ANY/SOME/ALL(Subquery) is not implemented")?;
}

Ok(match keyword {
Keyword::ALL => Expr::AllOp(Box::new(sub)),
// `SOME` is a synonym for `ANY`.
Keyword::ANY | Keyword::SOME => Expr::SomeOp(Box::new(sub)),
_ => unreachable!(),
})
}
k if keywords::RESERVED_FOR_COLUMN_OR_TABLE_NAME.contains(&k) => {
parser_err!(format!("syntax error at or near {token}"))
}
Expand Down Expand Up @@ -1384,6 +1403,7 @@ impl Parser {
/// Parse an operator following an expression
pub fn parse_infix(&mut self, expr: Expr, precedence: Precedence) -> Result<Expr, ParserError> {
let tok = self.next_token();
debug!("parsing infix {:?}", tok.token);
let regular_binary_operator = match &tok.token {
Token::Spaceship => Some(BinaryOperator::Spaceship),
Token::DoubleEq => Some(BinaryOperator::Eq),
Expand Down Expand Up @@ -1439,40 +1459,40 @@ impl Parser {
};

if let Some(op) = regular_binary_operator {
// `all/any/some` only appears to the right of the binary op.
if let Some(keyword) =
self.parse_one_of_keywords(&[Keyword::ANY, Keyword::ALL, Keyword::SOME])
{
self.expect_token(&Token::LParen)?;
// In upstream's PR of parser-rs, there is `self.parser_subexpr(precedence)` here.
// But it will fail to parse `select 1 = any(null and true);`.
let right = self.parse_expr()?;
self.expect_token(&Token::RParen)?;

// TODO: support `all/any/some(subquery)`.
if let Expr::Subquery(_) = &right {
parser_err!("ANY/SOME/ALL(Subquery) is not implemented")?;
}

let right = match keyword {
Keyword::ALL => Box::new(Expr::AllOp(Box::new(right))),
// `SOME` is a synonym for `ANY`.
Keyword::ANY | Keyword::SOME => Box::new(Expr::SomeOp(Box::new(right))),
_ => unreachable!(),
};

Ok(Expr::BinaryOp {
left: Box::new(expr),
op,
right,
})
} else {
Ok(Expr::BinaryOp {
left: Box::new(expr),
op,
right: Box::new(self.parse_subexpr(precedence)?),
})
}
// // `all/any/some` only appears to the right of the binary op.
// if let Some(keyword) =
// self.parse_one_of_keywords(&[Keyword::ANY, Keyword::ALL, Keyword::SOME])
// {
// self.expect_token(&Token::LParen)?;
// // In upstream's PR of parser-rs, there is `self.parser_subexpr(precedence)` here.
// // But it will fail to parse `select 1 = any(null and true);`.
// let right = self.parse_expr()?;
// self.expect_token(&Token::RParen)?;

// // TODO: support `all/any/some(subquery)`.
// if let Expr::Subquery(_) = &right {
// parser_err!("ANY/SOME/ALL(Subquery) is not implemented")?;
// }

// let right = match keyword {
// Keyword::ALL => Box::new(Expr::AllOp(Box::new(right))),
// // `SOME` is a synonym for `ANY`.
// Keyword::ANY | Keyword::SOME => Box::new(Expr::SomeOp(Box::new(right))),
// _ => unreachable!(),
// };

// Ok(Expr::BinaryOp {
// left: Box::new(expr),
// op,
// right,
// })
// } else {
Ok(Expr::BinaryOp {
left: Box::new(expr),
op,
right: Box::new(self.parse_subexpr(precedence)?),
})
// }
} else if let Token::Word(w) = &tok.token {
match w.keyword {
Keyword::IS => {
Expand Down Expand Up @@ -1529,6 +1549,25 @@ impl Parser {
self.expected("Expected Token::Word after AT", tok)
}
}
keyword @ (Keyword::ALL | Keyword::ANY | Keyword::SOME) => {
self.expect_token(&Token::LParen)?;
// In upstream's PR of parser-rs, there is `self.parser_subexpr(precedence)` here.
// But it will fail to parse `select 1 = any(null and true);`.
let sub = self.parse_expr()?;
self.expect_token(&Token::RParen)?;

// TODO: support `all/any/some(subquery)`.
if let Expr::Subquery(_) = &sub {
parser_err!("ANY/SOME/ALL(Subquery) is not implemented")?;
}

Ok(match keyword {
Keyword::ALL => Expr::AllOp(Box::new(sub)),
// `SOME` is a synonym for `ANY`.
Keyword::ANY | Keyword::SOME => Expr::SomeOp(Box::new(sub)),
_ => unreachable!(),
})
}
Keyword::NOT
| Keyword::IN
| Keyword::BETWEEN
Expand Down Expand Up @@ -1787,6 +1826,9 @@ impl Parser {
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(P::Like),
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(P::Like),
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(P::Like),
Token::Word(w) if w.keyword == Keyword::ALL => Ok(P::Other),
Token::Word(w) if w.keyword == Keyword::ANY => Ok(P::Other),
Token::Word(w) if w.keyword == Keyword::SOME => Ok(P::Other),
Token::Tilde
| Token::TildeAsterisk
| Token::ExclamationMarkTilde
Expand Down
9 changes: 9 additions & 0 deletions src/sqlparser/tests/testdata/select.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,12 @@
- input: select '123' IS NOT JSON SCALAR WITHOUT UNIQUE;
formatted_sql: SELECT '123' IS NOT JSON SCALAR
formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(IsJson { expr: Value(SingleQuotedString("123")), negated: true, item_type: Scalar, unique_keys: false })], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })'
- input: select 'a' like 'a';
formatted_sql: SELECT 'a' LIKE 'a'
formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Like { negated: false, expr: Value(SingleQuotedString("a")), pattern: Value(SingleQuotedString("a")), escape_char: None })], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })'
- input: select 'a' like 'a' escape '\';
formatted_sql: SELECT 'a' LIKE 'a' ESCAPE '\'
formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Like { negated: false, expr: Value(SingleQuotedString("a")), pattern: Value(SingleQuotedString("a")), escape_char: Some(''\\'') })], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })'
- input: select 'a' not like ANY(array['a', null]);
formatted_sql: SELECT 'a' NOT LIKE SOME(ARRAY['a', NULL])
formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Like { negated: true, expr: Value(SingleQuotedString("a")), pattern: SomeOp(Array(Array { elem: [Value(SingleQuotedString("a")), Value(Null)], named: true })), escape_char: None })], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })'

0 comments on commit 420273b

Please sign in to comment.