mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-04 06:18:17 +00:00
DuckDB, Postgres, SQLite: NOT NULL and NOTNULL expressions
This commit is contained in:
parent
ed8757f2f0
commit
d6f51a13a8
9 changed files with 192 additions and 0 deletions
|
@ -756,6 +756,12 @@ pub enum Expr {
|
||||||
IsNull(Box<Expr>),
|
IsNull(Box<Expr>),
|
||||||
/// `IS NOT NULL` operator
|
/// `IS NOT NULL` operator
|
||||||
IsNotNull(Box<Expr>),
|
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
|
/// `IS UNKNOWN` operator
|
||||||
IsUnknown(Box<Expr>),
|
IsUnknown(Box<Expr>),
|
||||||
/// `IS NOT UNKNOWN` operator
|
/// `IS NOT UNKNOWN` operator
|
||||||
|
@ -1430,6 +1436,12 @@ impl fmt::Display for Expr {
|
||||||
Expr::IsNotFalse(ast) => write!(f, "{ast} IS NOT FALSE"),
|
Expr::IsNotFalse(ast) => write!(f, "{ast} IS NOT FALSE"),
|
||||||
Expr::IsNull(ast) => write!(f, "{ast} IS NULL"),
|
Expr::IsNull(ast) => write!(f, "{ast} IS NULL"),
|
||||||
Expr::IsNotNull(ast) => write!(f, "{ast} IS NOT 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::IsUnknown(ast) => write!(f, "{ast} IS UNKNOWN"),
|
||||||
Expr::IsNotUnknown(ast) => write!(f, "{ast} IS NOT UNKNOWN"),
|
Expr::IsNotUnknown(ast) => write!(f, "{ast} IS NOT UNKNOWN"),
|
||||||
Expr::InList {
|
Expr::InList {
|
||||||
|
|
|
@ -1437,6 +1437,7 @@ impl Spanned for Expr {
|
||||||
Expr::IsNotTrue(expr) => expr.span(),
|
Expr::IsNotTrue(expr) => expr.span(),
|
||||||
Expr::IsNull(expr) => expr.span(),
|
Expr::IsNull(expr) => expr.span(),
|
||||||
Expr::IsNotNull(expr) => expr.span(),
|
Expr::IsNotNull(expr) => expr.span(),
|
||||||
|
Expr::NotNull { expr, .. } => expr.span(),
|
||||||
Expr::IsUnknown(expr) => expr.span(),
|
Expr::IsUnknown(expr) => expr.span(),
|
||||||
Expr::IsNotUnknown(expr) => expr.span(),
|
Expr::IsNotUnknown(expr) => expr.span(),
|
||||||
Expr::IsDistinctFrom(lhs, rhs) => lhs.span().union(&rhs.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 {
|
fn supports_order_by_all(&self) -> bool {
|
||||||
true
|
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::MATCH => Ok(p!(Like)),
|
||||||
Token::Word(w) if w.keyword == Keyword::SIMILAR => 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::MEMBER => Ok(p!(Like)),
|
||||||
|
Token::Word(w) if w.keyword == Keyword::NULL && self.supports_not_null() => {
|
||||||
|
Ok(p!(Is))
|
||||||
|
}
|
||||||
_ => Ok(self.prec_unknown()),
|
_ => 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::IS => Ok(p!(Is)),
|
||||||
Token::Word(w) if w.keyword == Keyword::IN => Ok(p!(Between)),
|
Token::Word(w) if w.keyword == Keyword::IN => Ok(p!(Between)),
|
||||||
Token::Word(w) if w.keyword == Keyword::BETWEEN => 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 {
|
fn supports_comma_separated_drop_column_list(&self) -> bool {
|
||||||
false
|
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
|
/// 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 {
|
fn supports_alter_column_type_using(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_notnull(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,4 +110,12 @@ impl Dialect for SQLiteDialect {
|
||||||
fn supports_dollar_placeholder(&self) -> bool {
|
fn supports_dollar_placeholder(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_not_null(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_notnull(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -608,6 +608,7 @@ define_keywords!(
|
||||||
NOT,
|
NOT,
|
||||||
NOTHING,
|
NOTHING,
|
||||||
NOTIFY,
|
NOTIFY,
|
||||||
|
NOTNULL,
|
||||||
NOWAIT,
|
NOWAIT,
|
||||||
NO_WRITE_TO_BINLOG,
|
NO_WRITE_TO_BINLOG,
|
||||||
NTH_VALUE,
|
NTH_VALUE,
|
||||||
|
|
|
@ -3562,6 +3562,7 @@ impl<'a> Parser<'a> {
|
||||||
let negated = self.parse_keyword(Keyword::NOT);
|
let negated = self.parse_keyword(Keyword::NOT);
|
||||||
let regexp = self.parse_keyword(Keyword::REGEXP);
|
let regexp = self.parse_keyword(Keyword::REGEXP);
|
||||||
let rlike = self.parse_keyword(Keyword::RLIKE);
|
let rlike = self.parse_keyword(Keyword::RLIKE);
|
||||||
|
let null = self.parse_keyword(Keyword::NULL);
|
||||||
if regexp || rlike {
|
if regexp || rlike {
|
||||||
Ok(Expr::RLike {
|
Ok(Expr::RLike {
|
||||||
negated,
|
negated,
|
||||||
|
@ -3571,6 +3572,11 @@ impl<'a> Parser<'a> {
|
||||||
),
|
),
|
||||||
regexp,
|
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) {
|
} else if self.parse_keyword(Keyword::IN) {
|
||||||
self.parse_in(expr, negated)
|
self.parse_in(expr, negated)
|
||||||
} else if self.parse_keyword(Keyword::BETWEEN) {
|
} 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())
|
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 => {
|
Keyword::MEMBER => {
|
||||||
if self.parse_keyword(Keyword::OF) {
|
if self.parse_keyword(Keyword::OF) {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
|
|
|
@ -15974,3 +15974,135 @@ fn parse_create_procedure_with_parameter_modes() {
|
||||||
_ => unreachable!(),
|
_ => 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