mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00
Merge d6f51a13a8
into 93450cc250
This commit is contained in:
commit
bd6594327a
9 changed files with 192 additions and 0 deletions
|
@ -756,6 +756,12 @@ pub enum Expr {
|
|||
IsNull(Box<Expr>),
|
||||
/// `IS NOT NULL` operator
|
||||
IsNotNull(Box<Expr>),
|
||||
/// `NOTNULL` or `NOT NULL` operator
|
||||
NotNull {
|
||||
expr: Box<Expr>,
|
||||
/// true if `NOTNULL`, false if `NOT NULL`
|
||||
one_word: bool,
|
||||
},
|
||||
/// `IS UNKNOWN` operator
|
||||
IsUnknown(Box<Expr>),
|
||||
/// `IS NOT UNKNOWN` operator
|
||||
|
@ -1430,6 +1436,12 @@ impl fmt::Display for Expr {
|
|||
Expr::IsNotFalse(ast) => write!(f, "{ast} IS NOT FALSE"),
|
||||
Expr::IsNull(ast) => write!(f, "{ast} IS NULL"),
|
||||
Expr::IsNotNull(ast) => write!(f, "{ast} IS NOT NULL"),
|
||||
Expr::NotNull { expr, one_word } => write!(
|
||||
f,
|
||||
"{} {}",
|
||||
expr,
|
||||
if *one_word { "NOTNULL" } else { "NOT NULL" }
|
||||
),
|
||||
Expr::IsUnknown(ast) => write!(f, "{ast} IS UNKNOWN"),
|
||||
Expr::IsNotUnknown(ast) => write!(f, "{ast} IS NOT UNKNOWN"),
|
||||
Expr::InList {
|
||||
|
|
|
@ -1437,6 +1437,7 @@ impl Spanned for Expr {
|
|||
Expr::IsNotTrue(expr) => expr.span(),
|
||||
Expr::IsNull(expr) => expr.span(),
|
||||
Expr::IsNotNull(expr) => expr.span(),
|
||||
Expr::NotNull { expr, .. } => expr.span(),
|
||||
Expr::IsUnknown(expr) => expr.span(),
|
||||
Expr::IsNotUnknown(expr) => expr.span(),
|
||||
Expr::IsDistinctFrom(lhs, rhs) => lhs.span().union(&rhs.span()),
|
||||
|
|
|
@ -94,4 +94,12 @@ impl Dialect for DuckDbDialect {
|
|||
fn supports_order_by_all(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_not_null(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_notnull(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -650,8 +650,14 @@ pub trait Dialect: Debug + Any {
|
|||
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
|
||||
Token::Word(w) if w.keyword == Keyword::NULL && self.supports_not_null() => {
|
||||
Ok(p!(Is))
|
||||
}
|
||||
_ => Ok(self.prec_unknown()),
|
||||
},
|
||||
Token::Word(w) if w.keyword == Keyword::NOTNULL && self.supports_notnull() => {
|
||||
Ok(p!(Is))
|
||||
}
|
||||
Token::Word(w) if w.keyword == Keyword::IS => Ok(p!(Is)),
|
||||
Token::Word(w) if w.keyword == Keyword::IN => Ok(p!(Between)),
|
||||
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(p!(Between)),
|
||||
|
@ -1076,6 +1082,16 @@ pub trait Dialect: Debug + Any {
|
|||
fn supports_comma_separated_drop_column_list(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports `NOTNULL` in expressions.
|
||||
fn supports_notnull(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the dialect supports `NOT NULL` in expressions.
|
||||
fn supports_not_null(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the operators for which precedence must be defined
|
||||
|
|
|
@ -262,4 +262,8 @@ impl Dialect for PostgreSqlDialect {
|
|||
fn supports_alter_column_type_using(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_notnull(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,4 +110,12 @@ impl Dialect for SQLiteDialect {
|
|||
fn supports_dollar_placeholder(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_not_null(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_notnull(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -608,6 +608,7 @@ define_keywords!(
|
|||
NOT,
|
||||
NOTHING,
|
||||
NOTIFY,
|
||||
NOTNULL,
|
||||
NOWAIT,
|
||||
NO_WRITE_TO_BINLOG,
|
||||
NTH_VALUE,
|
||||
|
|
|
@ -3562,6 +3562,7 @@ impl<'a> Parser<'a> {
|
|||
let negated = self.parse_keyword(Keyword::NOT);
|
||||
let regexp = self.parse_keyword(Keyword::REGEXP);
|
||||
let rlike = self.parse_keyword(Keyword::RLIKE);
|
||||
let null = self.parse_keyword(Keyword::NULL);
|
||||
if regexp || rlike {
|
||||
Ok(Expr::RLike {
|
||||
negated,
|
||||
|
@ -3571,6 +3572,11 @@ impl<'a> Parser<'a> {
|
|||
),
|
||||
regexp,
|
||||
})
|
||||
} else if dialect.supports_not_null() && negated && null {
|
||||
Ok(Expr::NotNull {
|
||||
expr: Box::new(expr),
|
||||
one_word: false,
|
||||
})
|
||||
} else if self.parse_keyword(Keyword::IN) {
|
||||
self.parse_in(expr, negated)
|
||||
} else if self.parse_keyword(Keyword::BETWEEN) {
|
||||
|
@ -3608,6 +3614,10 @@ impl<'a> Parser<'a> {
|
|||
self.expected("IN or BETWEEN after NOT", self.peek_token())
|
||||
}
|
||||
}
|
||||
Keyword::NOTNULL if dialect.supports_notnull() => Ok(Expr::NotNull {
|
||||
expr: Box::new(expr),
|
||||
one_word: true,
|
||||
}),
|
||||
Keyword::MEMBER => {
|
||||
if self.parse_keyword(Keyword::OF) {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
|
|
|
@ -15982,3 +15982,135 @@ fn parse_create_procedure_with_parameter_modes() {
|
|||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_not_null_unsupported() {
|
||||
// Only DuckDB and SQLite support `x NOT NULL` as an expression
|
||||
// All other dialects fail to parse.
|
||||
let sql = r#"WITH t AS (SELECT NULL AS x) SELECT x NOT NULL FROM t"#;
|
||||
let dialects = all_dialects_except(|d| d.supports_not_null());
|
||||
let res = dialects.parse_sql_statements(sql);
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected: end of statement, found: NULL".to_string()),
|
||||
res.unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_not_null_supported() {
|
||||
// DuckDB and SQLite support `x NOT NULL` as an expression
|
||||
let sql = r#"WITH t AS (SELECT NULL AS x) SELECT x NOT NULL FROM t"#;
|
||||
let dialects = all_dialects_where(|d| d.supports_not_null());
|
||||
let stmt = dialects.one_statement_parses_to(sql, sql);
|
||||
match stmt {
|
||||
Statement::Query(qry) => match *qry.body {
|
||||
SetExpr::Select(select) => {
|
||||
assert_eq!(select.projection.len(), 1);
|
||||
match select.projection.first().unwrap() {
|
||||
UnnamedExpr(expr) => {
|
||||
let fake_span = Span {
|
||||
start: Location { line: 0, column: 0 },
|
||||
end: Location { line: 0, column: 0 },
|
||||
};
|
||||
assert_eq!(
|
||||
*expr,
|
||||
Expr::NotNull {
|
||||
expr: Box::new(Identifier(Ident {
|
||||
value: "x".to_string(),
|
||||
quote_style: None,
|
||||
span: fake_span,
|
||||
})),
|
||||
one_word: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_notnull_unsupported() {
|
||||
// Only Postgres, DuckDB, and SQLite support `x NOTNULL` as an expression
|
||||
// All other dialects consider `x NOTNULL` like `x AS NOTNULL` and thus
|
||||
// consider `NOTNULL` an alias for x.
|
||||
let sql = r#"WITH t AS (SELECT NULL AS x) SELECT x NOTNULL FROM t"#;
|
||||
let canonical = r#"WITH t AS (SELECT NULL AS x) SELECT x AS NOTNULL FROM t"#;
|
||||
let dialects = all_dialects_except(|d| d.supports_notnull());
|
||||
let stmt = dialects.one_statement_parses_to(sql, canonical);
|
||||
match stmt {
|
||||
Statement::Query(qry) => match *qry.body {
|
||||
SetExpr::Select(select) => {
|
||||
assert_eq!(select.projection.len(), 1);
|
||||
match select.projection.first().unwrap() {
|
||||
SelectItem::ExprWithAlias { expr, alias } => {
|
||||
let fake_span = Span {
|
||||
start: Location { line: 0, column: 0 },
|
||||
end: Location { line: 0, column: 0 },
|
||||
};
|
||||
assert_eq!(
|
||||
*expr,
|
||||
Identifier(Ident {
|
||||
value: "x".to_string(),
|
||||
quote_style: None,
|
||||
span: fake_span,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
*alias,
|
||||
Ident {
|
||||
value: "NOTNULL".to_string(),
|
||||
quote_style: None,
|
||||
span: fake_span,
|
||||
}
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_notnull_supported() {
|
||||
// DuckDB and SQLite support `x NOT NULL` as an expression
|
||||
let sql = r#"WITH t AS (SELECT NULL AS x) SELECT x NOTNULL FROM t"#;
|
||||
let dialects = all_dialects_where(|d| d.supports_notnull());
|
||||
let stmt = dialects.one_statement_parses_to(sql, "");
|
||||
match stmt {
|
||||
Statement::Query(qry) => match *qry.body {
|
||||
SetExpr::Select(select) => {
|
||||
assert_eq!(select.projection.len(), 1);
|
||||
match select.projection.first().unwrap() {
|
||||
UnnamedExpr(expr) => {
|
||||
let fake_span = Span {
|
||||
start: Location { line: 0, column: 0 },
|
||||
end: Location { line: 0, column: 0 },
|
||||
};
|
||||
assert_eq!(
|
||||
*expr,
|
||||
Expr::NotNull {
|
||||
expr: Box::new(Identifier(Ident {
|
||||
value: "x".to_string(),
|
||||
quote_style: None,
|
||||
span: fake_span,
|
||||
})),
|
||||
one_word: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue