Skip to content

DuckDB, Postgres, SQLite: NOT NULL and NOTNULL expressions #1927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

ryanschneider
Copy link
Contributor

@ryanschneider ryanschneider commented Jul 5, 2025

Fixes #1920 by adding NOT NULL generally as an alias for IS NOT NULL, (only in ParserState::Normal) and adds the non-standard NOTNULL keyword supported by DuckDB, SQLite, and Postgres. It also adds a new ParserState::ColumnDefinition to avoid incorrectly parsing NOT NULL as IS NOT NULL in column definitions.

Since this is my first non-trivial PR please provide any feedback! I confirmed all tests pass w/ cargo test and cargo test --all-features but if there's anything else I need to do please let me know, thanks!

@ryanschneider ryanschneider requested a review from iffyio July 8, 2025 20:33
@ryanschneider ryanschneider force-pushed the not-null-and-notnull-support branch from 307c465 to ab6607b Compare July 11, 2025 00:21
@ryanschneider
Copy link
Contributor Author

Rebased against main to resolve conflicts.

@ryanschneider
Copy link
Contributor Author

@iffyio I went with the new ParserState::ColumnDefinition idea mentioned here: #1927 (comment) I think it leads to the cleanest code changes, let me know what you think! Also merged in main and resolved conflicts.

Copy link
Contributor

@iffyio iffyio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ryanschneider! The changes look reasonable to me overall, left some comments

/// In those cases we use the following macro to parse instead of calling [parse_expr] directly.
macro_rules! parse_expr_normal {
($option:expr) => {
if matches!(self.peek_token().token, Token::LParen) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if matches!(self.peek_token().token, Token::LParen) {
if self.peek_token_ref().token == Token::LParen {

@ryanschneider
Copy link
Contributor Author

Thanks for the latest round of feedback @iffyio, I've been busy with some unrelated work so haven't had time to look at it all yet or start on the changes but hopefully will have some space cycles the next couple days, I'll re-request review when it's ready!

ryanschneider and others added 3 commits July 17, 2025 14:44
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
- use `concat!` for longer SQL strings
- replace macro w/ `parse_column_option_expr()`
- add SQL examples to docstrings
- use `peek_token_ref()` instead of `matches!`
- `in_normal_state` -> `pub(crate) in_column_definition_state`
- revert fmt change that moved use statements
- add `GENERATED` test and use [ParserState::Normal] for parsing inner expr.
@ryanschneider
Copy link
Contributor Author

Ok @iffyio I believe I addressed all your latest feedback in 1466e2a, let me know! Thanks!

@ryanschneider ryanschneider requested a review from iffyio July 17, 2025 22:53
Copy link
Contributor

@iffyio iffyio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ryanschneider! Could we do a pass through the options mentioned here and here to extend them with similar support for the NOT NULL clause? Beyond that I think the changes look good! (Also headsup that there's currently a docs failure in ci on the branch)

Comment on lines 16202 to 16236
#[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 dialects = all_dialects_except(|d| d.supports_notnull_operator());
let _ = dialects
.verified_only_select_with_canonical("SELECT NULL NOTNULL", "SELECT NULL AS NOTNULL");
}

#[test]
fn parse_notnull_supported() {
// Postgres, DuckDB and SQLite support `x NOTNULL` as an alias for `x IS NOT NULL`
let dialects = all_dialects_where(|d| d.supports_notnull_operator());
let _ = dialects.expr_parses_to("x NOTNULL", "x IS NOT NULL");
}

#[test]
fn test_notnull_precedence() {
// For dialects which support it, `NOT NULL NOTNULL` should
// parse as `(NOT (NULL IS NOT NULL))`
let supported_dialects = all_dialects_where(|d| d.supports_notnull_operator());
let unsupported_dialects = all_dialects_except(|d| d.supports_notnull_operator());

assert_matches!(
supported_dialects.expr_parses_to("NOT NULL NOTNULL", "NOT NULL IS NOT NULL"),
Expr::UnaryOp {
op: UnaryOperator::Not,
..
}
);

// for unsupported dialects, parsing should stop at `NOT NULL`
unsupported_dialects.expr_parses_to("NOT NULL NOTNULL", "NOT NULL");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since they're the same feature, can we merge these scenarios into the same fn parse_notnull(){} test function?.

Also for the comments, ideally we skip mentioning the individual dialects (Postgres, duckdb etc), so that the comment doesnt' become stale as other/new dialects are extended with support for this feature.

Comment on lines 16034 to 16056
#[test]
fn parse_not_null_supported() {
let _ = all_dialects().expr_parses_to("x NOT NULL", "x IS NOT NULL");
let _ = all_dialects().expr_parses_to("NULL NOT NULL", "NULL IS NOT NULL");
}

#[test]
fn test_not_null_precedence() {
assert_matches!(
all_dialects().expr_parses_to("NOT NULL NOT NULL", "NOT NULL IS NOT NULL"),
Expr::UnaryOp {
op: UnaryOperator::Not,
..
}
);
assert_matches!(
all_dialects().expr_parses_to("NOT x NOT NULL", "NOT x IS NOT NULL"),
Expr::UnaryOp {
op: UnaryOperator::Not,
..
}
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the notnull scenarios, can we merge these into the same function?

@ryanschneider
Copy link
Contributor Author

Sorry I should've mentioned that I did check the other ColumnOptions and confirmed that they don't need to use parse_column_option_expr but after thinking about it more I'll go ahead and use it anyways for clarity. Also fixing the doc failure and consolidating the tests.

@ryanschneider
Copy link
Contributor Author

Ok, I consolidated the tests, fixed the cargo doc error and use parse_column_option_expr in all spots for consistency.

@ryanschneider ryanschneider requested a review from iffyio July 18, 2025 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Parser: Cannot parse COUNT(CASE WHEN x NOT NULL THEN 1 END)
2 participants