Skip to content

Commit ab6607b

Browse files
committed
fixup: handle expr NOT NULL directly in parse_subexpr.
1 parent 4009fa7 commit ab6607b

File tree

6 files changed

+52
-85
lines changed

6 files changed

+52
-85
lines changed

src/dialect/duckdb.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use crate::dialect::{Dialect, IsNotNullAlias};
18+
use crate::dialect::Dialect;
1919

2020
/// A [`Dialect`] for [DuckDB](https://duckdb.org/)
2121
#[derive(Debug, Default)]
@@ -95,12 +95,9 @@ impl Dialect for DuckDbDialect {
9595
true
9696
}
9797

98-
/// DuckDB supports `NOT NULL` and `NOTNULL` as aliases
99-
/// for `IS NOT NULL`, see DuckDB Comparisons <https://duckdb.org/docs/stable/sql/expressions/comparison_operators#between-and-is-not-null>
100-
fn supports_is_not_null_alias(&self, alias: IsNotNullAlias) -> bool {
101-
match alias {
102-
IsNotNullAlias::NotNull => true,
103-
IsNotNullAlias::NotSpaceNull => true,
104-
}
98+
/// DuckDB supports `NOTNULL` as an alias for `IS NOT NULL`,
99+
/// see DuckDB Comparisons <https://duckdb.org/docs/stable/sql/expressions/comparison_operators#between-and-is-not-null>
100+
fn supports_notnull_operator(&self) -> bool {
101+
true
105102
}
106103
}

src/dialect/mod.rs

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ use crate::keywords::Keyword;
5555
use crate::parser::{Parser, ParserError};
5656
use crate::tokenizer::Token;
5757

58-
use crate::dialect::IsNotNullAlias::{NotNull, NotSpaceNull};
5958
#[cfg(not(feature = "std"))]
6059
use alloc::boxed::Box;
6160

@@ -651,17 +650,9 @@ pub trait Dialect: Debug + Any {
651650
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
652651
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
653652
Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
654-
Token::Word(w)
655-
if w.keyword == Keyword::NULL
656-
&& self.supports_is_not_null_alias(NotSpaceNull) =>
657-
{
658-
Ok(p!(Is))
659-
}
660653
_ => Ok(self.prec_unknown()),
661654
},
662-
Token::Word(w)
663-
if w.keyword == Keyword::NOTNULL && self.supports_is_not_null_alias(NotNull) =>
664-
{
655+
Token::Word(w) if w.keyword == Keyword::NOTNULL && self.supports_notnull_operator() => {
665656
Ok(p!(Is))
666657
}
667658
Token::Word(w) if w.keyword == Keyword::IS => Ok(p!(Is)),
@@ -1102,13 +1093,10 @@ pub trait Dialect: Debug + Any {
11021093
false
11031094
}
11041095

1105-
/// Returns true if the dialect supports the passed in alias.
1106-
/// See [IsNotNullAlias].
1107-
fn supports_is_not_null_alias(&self, alias: IsNotNullAlias) -> bool {
1108-
match alias {
1109-
NotNull => false,
1110-
NotSpaceNull => false,
1111-
}
1096+
/// Returns true if the dialect supports the `x NOTNULL`
1097+
/// operator expression.
1098+
fn supports_notnull_operator(&self) -> bool {
1099+
false
11121100
}
11131101
}
11141102

@@ -1136,13 +1124,6 @@ pub enum Precedence {
11361124
Or,
11371125
}
11381126

1139-
/// Possible aliases for `IS NOT NULL` supported
1140-
/// by some non-standard dialects.
1141-
pub enum IsNotNullAlias {
1142-
NotNull,
1143-
NotSpaceNull,
1144-
}
1145-
11461127
impl dyn Dialect {
11471128
#[inline]
11481129
pub fn is<T: Dialect>(&self) -> bool {

src/dialect/postgresql.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
// limitations under the License.
2929
use log::debug;
3030

31-
use crate::dialect::{Dialect, IsNotNullAlias, Precedence};
31+
use crate::dialect::{Dialect, Precedence};
3232
use crate::keywords::Keyword;
3333
use crate::parser::{Parser, ParserError};
3434
use crate::tokenizer::Token;
@@ -264,11 +264,8 @@ impl Dialect for PostgreSqlDialect {
264264
}
265265

266266
/// Postgres supports `NOTNULL` as an alias for `IS NOT NULL`
267-
/// but does not support `NOT NULL`. See: <https://www.postgresql.org/docs/17/functions-comparison.html>
268-
fn supports_is_not_null_alias(&self, alias: IsNotNullAlias) -> bool {
269-
match alias {
270-
IsNotNullAlias::NotNull => true,
271-
IsNotNullAlias::NotSpaceNull => false,
272-
}
267+
/// See: <https://www.postgresql.org/docs/17/functions-comparison.html>
268+
fn supports_notnull_operator(&self) -> bool {
269+
true
273270
}
274271
}

src/dialect/sqlite.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use alloc::boxed::Box;
2020

2121
use crate::ast::BinaryOperator;
2222
use crate::ast::{Expr, Statement};
23-
use crate::dialect::{Dialect, IsNotNullAlias};
23+
use crate::dialect::Dialect;
2424
use crate::keywords::Keyword;
2525
use crate::parser::{Parser, ParserError};
2626

@@ -111,12 +111,9 @@ impl Dialect for SQLiteDialect {
111111
true
112112
}
113113

114-
/// SQLite supports ``NOT NULL` and `NOTNULL` as
115-
/// aliases for `IS NOT NULL`, see: <https://sqlite.org/syntax/expr.html>
116-
fn supports_is_not_null_alias(&self, alias: IsNotNullAlias) -> bool {
117-
match alias {
118-
IsNotNullAlias::NotNull => true,
119-
IsNotNullAlias::NotSpaceNull => true,
120-
}
114+
/// SQLite supports `NOTNULL` as aliases for `IS NOT NULL`
115+
/// See: <https://sqlite.org/syntax/expr.html>
116+
fn supports_notnull_operator(&self) -> bool {
117+
true
121118
}
122119
}

src/parser/mod.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ use IsOptional::*;
3535
use crate::ast::helpers::stmt_create_table::{CreateTableBuilder, CreateTableConfiguration};
3636
use crate::ast::Statement::CreatePolicy;
3737
use crate::ast::*;
38-
use crate::dialect::IsNotNullAlias::{NotNull, NotSpaceNull};
3938
use crate::dialect::*;
4039
use crate::keywords::{Keyword, ALL_KEYWORDS};
4140
use crate::tokenizer::*;
@@ -1244,6 +1243,21 @@ impl<'a> Parser<'a> {
12441243

12451244
expr = self.parse_infix(expr, next_precedence)?;
12461245
}
1246+
1247+
// Special case: if expr is an identifier or NULL, accept `expr NOT NULL`
1248+
// as an alias for `expr IS NOT NULL`.
1249+
if match &expr {
1250+
Expr::Identifier(_) if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) => true,
1251+
Expr::Value(v)
1252+
if v.value == Value::Null
1253+
&& self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) =>
1254+
{
1255+
true
1256+
}
1257+
_ => false,
1258+
} {
1259+
return Ok(Expr::IsNotNull(Box::new(expr)));
1260+
}
12471261
Ok(expr)
12481262
}
12491263

@@ -3563,7 +3577,6 @@ impl<'a> Parser<'a> {
35633577
let negated = self.parse_keyword(Keyword::NOT);
35643578
let regexp = self.parse_keyword(Keyword::REGEXP);
35653579
let rlike = self.parse_keyword(Keyword::RLIKE);
3566-
let null = self.parse_keyword(Keyword::NULL);
35673580
if regexp || rlike {
35683581
Ok(Expr::RLike {
35693582
negated,
@@ -3573,8 +3586,6 @@ impl<'a> Parser<'a> {
35733586
),
35743587
regexp,
35753588
})
3576-
} else if dialect.supports_is_not_null_alias(NotSpaceNull) && negated && null {
3577-
Ok(Expr::IsNotNull(Box::new(expr)))
35783589
} else if self.parse_keyword(Keyword::IN) {
35793590
self.parse_in(expr, negated)
35803591
} else if self.parse_keyword(Keyword::BETWEEN) {
@@ -3612,7 +3623,7 @@ impl<'a> Parser<'a> {
36123623
self.expected("IN or BETWEEN after NOT", self.peek_token())
36133624
}
36143625
}
3615-
Keyword::NOTNULL if dialect.supports_is_not_null_alias(NotNull) => {
3626+
Keyword::NOTNULL if dialect.supports_notnull_operator() => {
36163627
Ok(Expr::IsNotNull(Box::new(expr)))
36173628
}
36183629
Keyword::MEMBER => {

tests/sqlparser_common.rs

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ use sqlparser::ast::TableFactor::{Pivot, Unpivot};
3232
use sqlparser::ast::*;
3333
use sqlparser::dialect::{
3434
AnsiDialect, BigQueryDialect, ClickHouseDialect, DatabricksDialect, Dialect, DuckDbDialect,
35-
GenericDialect, HiveDialect, IsNotNullAlias, MsSqlDialect, MySqlDialect, PostgreSqlDialect,
36-
RedshiftSqlDialect, SQLiteDialect, SnowflakeDialect,
35+
GenericDialect, HiveDialect, MsSqlDialect, MySqlDialect, PostgreSqlDialect, RedshiftSqlDialect,
36+
SQLiteDialect, SnowflakeDialect,
3737
};
3838
use sqlparser::keywords::{Keyword, ALL_KEYWORDS};
3939
use sqlparser::parser::{Parser, ParserError, ParserOptions};
@@ -15989,69 +15989,53 @@ fn parse_create_procedure_with_parameter_modes() {
1598915989
}
1599015990
}
1599115991

15992-
#[test]
15993-
fn parse_not_null_unsupported() {
15994-
// Only DuckDB and SQLite support `x NOT NULL` as an expression
15995-
// All other dialects fail to parse the `NOT NULL` portion
15996-
let dialects =
15997-
all_dialects_except(|d| d.supports_is_not_null_alias(IsNotNullAlias::NotSpaceNull));
15998-
let _ = dialects.expr_parses_to("x NOT NULL", "x");
15999-
}
16000-
1600115992
#[test]
1600215993
fn parse_not_null_supported() {
16003-
// DuckDB and SQLite support `x NOT NULL` as an alias for `x IS NOT NULL`
16004-
let dialects =
16005-
all_dialects_where(|d| d.supports_is_not_null_alias(IsNotNullAlias::NotSpaceNull));
16006-
let _ = dialects.expr_parses_to("x NOT NULL", "x IS NOT NULL");
15994+
let _ = all_dialects().expr_parses_to("x NOT NULL", "x IS NOT NULL");
15995+
let _ = all_dialects().expr_parses_to("NULL NOT NULL", "NULL IS NOT NULL");
1600715996
}
1600815997

1600915998
#[test]
1601015999
fn test_not_null_precedence() {
16011-
// For dialects which support it, `NOT NULL NOT NULL` should
16012-
// parse as `(NOT (NULL IS NOT NULL))`
16013-
let supported_dialects =
16014-
all_dialects_where(|d| d.supports_is_not_null_alias(IsNotNullAlias::NotSpaceNull));
16015-
let unsuported_dialects =
16016-
all_dialects_except(|d| d.supports_is_not_null_alias(IsNotNullAlias::NotSpaceNull));
16017-
1601816000
assert_matches!(
16019-
supported_dialects.expr_parses_to("NOT NULL NOT NULL", "NOT NULL IS NOT NULL"),
16001+
all_dialects().expr_parses_to("NOT NULL NOT NULL", "NOT NULL IS NOT NULL"),
16002+
Expr::UnaryOp {
16003+
op: UnaryOperator::Not,
16004+
..
16005+
}
16006+
);
16007+
assert_matches!(
16008+
all_dialects().expr_parses_to("NOT x NOT NULL", "NOT x IS NOT NULL"),
1602016009
Expr::UnaryOp {
1602116010
op: UnaryOperator::Not,
1602216011
..
1602316012
}
1602416013
);
16025-
16026-
// for unsupported dialects, parsing should stop at `NOT NULL`
16027-
unsuported_dialects.expr_parses_to("NOT NULL NOT NULL", "NOT NULL");
1602816014
}
1602916015

1603016016
#[test]
1603116017
fn parse_notnull_unsupported() {
1603216018
// Only Postgres, DuckDB, and SQLite support `x NOTNULL` as an expression
1603316019
// All other dialects consider `x NOTNULL` like `x AS NOTNULL` and thus
1603416020
// consider `NOTNULL` an alias for x.
16035-
let dialects = all_dialects_except(|d| d.supports_is_not_null_alias(IsNotNullAlias::NotNull));
16021+
let dialects = all_dialects_except(|d| d.supports_notnull_operator());
1603616022
let _ = dialects
1603716023
.verified_only_select_with_canonical("SELECT NULL NOTNULL", "SELECT NULL AS NOTNULL");
1603816024
}
1603916025

1604016026
#[test]
1604116027
fn parse_notnull_supported() {
1604216028
// Postgres, DuckDB and SQLite support `x NOTNULL` as an alias for `x IS NOT NULL`
16043-
let dialects = all_dialects_where(|d| d.supports_is_not_null_alias(IsNotNullAlias::NotNull));
16029+
let dialects = all_dialects_where(|d| d.supports_notnull_operator());
1604416030
let _ = dialects.expr_parses_to("x NOTNULL", "x IS NOT NULL");
1604516031
}
1604616032

1604716033
#[test]
1604816034
fn test_notnull_precedence() {
1604916035
// For dialects which support it, `NOT NULL NOTNULL` should
1605016036
// parse as `(NOT (NULL IS NOT NULL))`
16051-
let supported_dialects =
16052-
all_dialects_where(|d| d.supports_is_not_null_alias(IsNotNullAlias::NotNull));
16053-
let unsuported_dialects =
16054-
all_dialects_except(|d| d.supports_is_not_null_alias(IsNotNullAlias::NotNull));
16037+
let supported_dialects = all_dialects_where(|d| d.supports_notnull_operator());
16038+
let unsupported_dialects = all_dialects_except(|d| d.supports_notnull_operator());
1605516039

1605616040
assert_matches!(
1605716041
supported_dialects.expr_parses_to("NOT NULL NOTNULL", "NOT NULL IS NOT NULL"),
@@ -16062,5 +16046,5 @@ fn test_notnull_precedence() {
1606216046
);
1606316047

1606416048
// for unsupported dialects, parsing should stop at `NOT NULL`
16065-
unsuported_dialects.expr_parses_to("NOT NULL NOTNULL", "NOT NULL");
16049+
unsupported_dialects.expr_parses_to("NOT NULL NOTNULL", "NOT NULL");
1606616050
}

0 commit comments

Comments
 (0)