Skip to content

Fix null-safe result joins - #1356

Merged
mwojtyczka merged 10 commits into
databrickslabs:mainfrom
mattfaltyn:agent/null-safe-result-joins
Jul 28, 2026
Merged

Fix null-safe result joins#1356
mwojtyczka merged 10 commits into
databrickslabs:mainfrom
mattfaltyn:agent/null-safe-result-joins

Conversation

@mattfaltyn

Copy link
Copy Markdown
Contributor

Changes

  • Reattach custom SQL and grouped-check results with null-safe key matching.
  • Reuse the existing null-safe row matcher through one focused left-join helper while preserving the original row and column shape.
  • Cover single and composite nullable SQL keys, nullable count_distinct groups, and nullable grouped aggregate-outlier violations.

Spark equality joins do not match NULL keys, so these paths could compute a real violation and then silently discard it while joining the result back to the input. The shared helper aliases both sides, uses null-safe equality for every key component, and selects only the original left-side columns plus the computed result columns.

Linked issues

Fixes #1354

Tests

  • manually tested — five focused regressions passed with stock PySpark 4.0.0
  • added unit tests — the affected behavior requires Spark
  • added integration tests
  • added end-to-end tests — no workflow or external-resource behavior changed
  • added performance tests — the change only replaces four existing result joins

Validation performed:

  • make fmt
  • make lint
  • Full unit suite: 1,446 passed
  • Focused Spark regressions: single nullable SQL key, two composite-key null positions, grouped count_distinct, and grouped aggregate outliers

Documentation and Demos

  • added/updated demos — not applicable
  • added/updated docs — no public API, configuration, or documented behavior changed
  • added/updated agent skills — no public API changed

@mattfaltyn
mattfaltyn marked this pull request as ready for review July 21, 2026 17:58
@mattfaltyn
mattfaltyn requested a review from a team as a code owner July 21, 2026 17:58
@mattfaltyn
mattfaltyn requested review from nehamilak-db and removed request for a team July 21, 2026 17:58
@mwojtyczka mwojtyczka mentioned this pull request Jul 22, 2026
8 tasks
@mwojtyczka mwojtyczka added the under-review This PR is currently being reviewed by one of DQX maintainers. label Jul 23, 2026
@mwojtyczka
mwojtyczka self-requested a review July 23, 2026 10:17

@mwojtyczka mwojtyczka 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.

Review — null-safe result joins

Solid, correct fix for the null-key silent-drop bug (#1354). I verified the four rewired joins are row-count-equivalent to the old left-joins for non-null keys (every result_df is a groupBy().agg() — one row per key) and now additionally match null keys, and ran the new regressions locally (3 passed). No blocking issues.

The notes below are robustness/hardening observations on the new shared helper and one pre-existing naming gap it now routes through — none block merge. Inline comments follow.

Comment thread src/databricks/labs/dqx/check_funcs.py Outdated
Comment thread src/databricks/labs/dqx/check_funcs.py
Comment thread src/databricks/labs/dqx/check_funcs.py
Comment thread src/databricks/labs/dqx/check_funcs.py Outdated
Comment thread tests/integration/test_dataset_checks.py

@mwojtyczka mwojtyczka 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.

Thank you for the PR! Generally looking good, left some small comments

@mattfaltyn

mattfaltyn commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Thank you @mwojtyczka for your review. All comments have been addressed in commit 2abf5c2.

@mattfaltyn
mattfaltyn requested a review from mwojtyczka July 24, 2026 17:48
Spell out the three caller responsibilities the helper relies on but does not
validate: non-empty join_columns (an empty list reduces the null-safe join to a
cross join), one row per join key in result_df (else df rows fan out), and
result_columns disjoint from df.columns (else duplicate output columns). All
current callers satisfy these; the note is for future reuse.

Co-authored-by: Isaac

@mwojtyczka mwojtyczka 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.

LGTM - adjusted docstring

@mwojtyczka mwojtyczka added Approved to Merge When PR is reviewed and approved. To be merged once all tests pass and removed under-review This PR is currently being reviewed by one of DQX maintainers. labels Jul 28, 2026
Two integration tests still encoded the pre-null-safe join behavior:

- test_apply_checks_with_sql_query: the multiple_key_check_violation check
  merges on [b, c]. With the old left join, rows whose b was NULL could not
  match their own query group (NULL == NULL is NULL, not True), so no
  violation attached. The null-safe join now matches them (NULL <=> NULL),
  so rows (2, NULL, 3) and (1, NULL, 4) correctly gain the violation.

- test_aggr_matches_dataset_count_distinct_group_by_null_key and the
  aggr_matches_dataset docstring claimed count_distinct's grouped join is
  not null-safe. Both the checked-side two-stage groupBy join and the
  reference-side join are in fact null-safe, so a NULL group key is compared
  like any other. Updated the test to assert the NULL group matches and
  passes, and corrected the docstring.

Verified both tests pass against a live workspace.

Co-authored-by: Isaac
@mwojtyczka
mwojtyczka merged commit 6e01964 into databrickslabs:main Jul 28, 2026
44 of 45 checks passed
mwojtyczka added a commit that referenced this pull request Jul 30, 2026
## Changes

- Reattach `has_no_gaps_per_time_window` results with the existing
null-safe result-join helper.
- Preserve gap violations for groups with null key components without
changing the public API or non-null behavior.
- Add a focused Spark integration regression for a null-key group with
an interior gap.

### Linked issues

Fixes #1386

Related: #1354, #1356

### Tests

- [x] manually tested — focused regression passed with stock PySpark
4.0.0
- [ ] added unit tests — the affected behavior requires Spark
- [x] added integration tests
- [ ] added end-to-end tests — no workflow or external-resource behavior
changed
- [ ] added performance tests — the change reuses the existing
result-join helper at one call site

Validation performed:

- `make fmt`
- `make lint`
- `make test` — 1,549 passed
- Isolated stock-PySpark 4.0.0 smoke reproduction — null-key boundary
row flagged, following row passed

The workspace-backed focused integration test cannot run locally without
`DATABRICKS_HOST`; upstream integration CI provides the authoritative
run.

### Documentation and Demos

- [ ] added/updated demos — not applicable
- [ ] added/updated docs — the fix restores existing documented behavior
without changing the API
- [ ] added/updated agent skills — no public API or usage contract
changed

---------

Co-authored-by: Marcin Wojtyczka <marcin.wojtyczka@databricks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approved to Merge When PR is reviewed and approved. To be merged once all tests pass

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null-unsafe result joins silently erase violations across custom SQL and grouped checks

2 participants