Skip to content

feat: add skip_null to fragment update_columns - #11078

Open
bryancluo wants to merge 1 commit into
lance-format:mainfrom
bryancluo:skip-null
Open

bryancluo wants to merge 1 commit into
lance-format:mainfrom
bryancluo:skip-null

Conversation

@bryancluo

Copy link
Copy Markdown

Summary

Adds a skip_null option to fragment update_columns. With it set, a null in the update stream keeps the existing value of the matched row instead of overwriting it, so callers can send partial updates without reading the old values first.

Semantics:

  • Only top-level nulls are skipped. A non-null struct still replaces the old struct as a whole, including its null children.
  • An update row whose columns other than the join key are all null changes nothing, so it is treated as unmatched. It is left out of matched_offsets, and its _row_last_updated_at_version does not change after a rewrite_columns commit. If every update row is like this, the call is a no-op, the same as an empty update stream.
  • The default (false) keeps the current behavior.

Implementation

  • UpdateJoinOptions::with_skip_null / skip_null() carry the flag into the internal UpdateColumnsOptions.
  • All-null update rows are filtered out of the RHS before strategy selection, so they never join on either path.
  • Hash path: HashJoiner::with_skip_null computes the RHS null masks once. collect_with_fallback then points null cells at the left row in the interleave indices, so each column is still materialized only once.
  • Sort-merge path: SortedPatchCursor::merge_batch does the same per-column index rewrite on its patch batches.

Bindings

  • Python: keyword-only skip_null=False on LanceFragment.update_columns.
  • Java: a Fragment.updateColumns(stream, leftOn, rightOn, skipNull) overload. The 3-argument method delegates with skipNull = false.

Tests

  • Rust update_join::tests::test_update_skip_null: hash × sort-merge × skip_null, with two fragments and an RHS split across batches. It covers a top-level null string, a top-level null struct, a non-null struct with a null child, an all-null row, an unmatched row, matched_offsets, and _row_last_updated_at_version after commit.
  • Rust test_empty_rhs_is_noop: new case for an RHS made only of all-null rows.
  • Python test_fragment_update_columns_skip_null and Java UpdateTest.testUpdateColumns are parameterized on skip_null, including offsets and version metadata.

🤖 Generated with Claude Code

With skip_null set, a null in the update stream keeps the existing value of
the matched row instead of overwriting it, so callers can send partial
updates without reading old values first. Only top-level nulls are skipped:
a non-null struct still replaces the old struct as a whole.

An update row whose columns other than the join key are all null changes
nothing, so it is dropped before the join: the row is unmatched, is excluded
from matched_offsets, and keeps its _row_last_updated_at_version. Both the
hash and the sort-merge update join honor the option.

Exposed as UpdateJoinOptions::with_skip_null in Rust, the keyword-only
skip_null argument of LanceFragment.update_columns in Python, and a
Fragment.updateColumns(stream, leftOn, rightOn, skipNull) overload in Java.
The default keeps the current behavior.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions github-actions Bot added A-python Python bindings A-java Java bindings + JNI enhancement New feature or request labels Nov 14, 2026

@lance-gatekeeper lance-gatekeeper Bot 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.

✅ Gate recommendation: approve.

The opt-in null handling fits the existing fragment rewrite contract. The focused Rust tests passed for both join strategies, including struct replacement, matched offsets, committed row-version metadata, and all-null no-op inputs.

@lance-gatekeeper lance-gatekeeper Bot added the K-approved Latest Gatekeeper recommendation permits acceptance. label Nov 14, 2026

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

A-java Java bindings + JNI A-python Python bindings enhancement New feature or request K-approved Latest Gatekeeper recommendation permits acceptance.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant