Skip to content

Keep rows where a delete filter evaluates to null - #4219

Open
Rahul-cs50 wants to merge 1 commit into
apache:mainfrom
Rahul-cs50:fix/4184-delete-null-or-filter
Open

Rahul-cs50 wants to merge 1 commit into
apache:mainfrom
Rahul-cs50:fix/4184-delete-null-or-filter

Conversation

@Rahul-cs50

Copy link
Copy Markdown

Closes #4184

Rationale for this change

When delete() (and overwrite() with an overwrite_filter) has to rewrite a data file, it keeps the rows that _expression_to_complementary_pyarrow selects. That filter was NOT f, OR'd with c IS NULL for every column c that f reads without a null check. Under pyarrow's three-valued logic that goes wrong both ways when a column is null:

  • NOT f is null where f is null, so the row is dropped even though f did not match it: x=2 in the issue (p == 2, v null) is deleted by (p == 1 AND v IS NULL) OR (p == 2 AND v == 'c').
  • c IS NULL keeps a row whenever c is null, even where f is still true: p == 1 OR v == 'c' keeps x=1 (p == 1, v null).

A row should be deleted exactly where f is true, which are the rows scan(row_filter=f) returns. The complement is now NOT coalesce(f, false). The NaN handling (OR is_nan(c) for columns the filter does not NaN-check) is left as it was.

Are these changes tested?

Yes. test_delete_and_overwrite_filter_with_null in tests/catalog/test_catalog_behaviors.py runs the issue's two filters plus v != 'c' and NOT (v == 'c') through delete() and overwrite() on every local catalog, and checks that the rows left are the ones scan(row_filter=...) does not return. It fails on main and passes with this change. test_expression_to_complementary_pyarrow is updated for the new expression. Unit tests in tests/io, tests/table, tests/catalog and tests/expressions: 2658 passed (the only failures are the four Kerberos tests in test_hive.py, which need the hive-kerberos extra I did not install). ruff check and ruff format --check are clean.

Are there any user-facing changes?

delete() and overwrite(..., overwrite_filter=...) no longer delete rows the filter does not match, or keep rows it does, when a column the filter reads is null.

delete() and overwrite() rewrite a data file with the rows the delete
filter does not select. The complementary filter was Not(filter) OR'd
with IS NULL for each column the filter does not null-check, so a null
kept rows the filter matches (p == 1 OR v == 'c' with a null v) and
dropped rows it does not match, where Not(filter) is null.

Coalesce the filter to false before inverting it, so exactly the rows
where the filter is true are deleted, the rows scan(row_filter=filter)
returns.

Closes apache#4184
Copilot AI balanced review requested due to automatic review settings October 31, 2026 07:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

This branch has not been deployed

No deployments
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.

Table.delete() with an OR filter deletes the wrong rows when a column is NULL

2 participants

⌂ Proxy