Skip to content

[RLC] Case 1 — conflict-time data skipping (disjoint data ranges) - #8

Open
sezruby wants to merge 30 commits into
masterfrom
conflict-data-skipping
Open

sezruby wants to merge 30 commits into
masterfrom
conflict-data-skipping

Conversation

@sezruby

@sezruby sezruby commented Jul 8, 2026 •

Copy link
Copy Markdown
Owner

Part of the row-level-concurrency umbrella (#3) — Case 1. Full design and rationale: #4.

Summary

Reader-side conflict-time data skipping. Conflict detection currently filters candidate files by partition only. On an unpartitioned table — which includes every liquid-clustered table — getFirstFileMatchingPartitionPredicates returns files.headOption, so the append check conflicts on any concurrently-added file, even when the two operations touch entirely disjoint data ranges. This is the dominant source of false conflicts for liquid clustering, where incremental clustering and DML usually touch different clustering-key regions.

This teaches conflict detection to apply the reader's column-statistics data skipping: an added file whose stats prove it cannot match the current transaction's data predicates is excluded from the conflict check.

What it does

  • DataSkippingReaderBase.filterFilesByDataSkipping(files, dataFilters) — reuses the reader's DataFiltersBuilder + verifyStatsForFilter to build the skipping predicate, parses each file's stats against the snapshot's statsSchema, and applies expr || !verifyStatsForFilter(...).
  • ConflictChecker.getFirstFileMatchingPartitionPredicates consumes the already-captured DeltaTableReadPredicate.dataPredicates (previously ignored). Skipping is applied per read predicate with the survivors unioned (OR across independent reads; a read's own predicates are ANDed), and is not applied for a whole-table read — so it can only ever narrow, never miss, a conflict. Emits a delta.conflictDetection.dataSkipping.filesSkipped event.
  • Gated by spark.databricks.delta.conflictDetection.dataSkipping.enabled (internal, default off). No protocol / reader / action changes.

Which operations benefit (not just append)

The improved check is the added-files (append) check, which fires whenever the winner adds data files — so it is not only INSERT. UPDATE/MERGE add new row-image files, and because those are non-blind changed-data files they are checked under the default WriteSerializable (a blind INSERT is only checked under Serializable). So the headline beneficiaries are concurrent UPDATE / DELETE / MERGE on disjoint clustering ranges (different files), under the default isolation.

This complements the same-file DV merge (#5), splitting the space by which check fires:

Concurrent DML shape Check that fires Handled by
different files (disjoint ranges) append check this PR (Case 1, #4)
same file, disjoint rows delete/delete, delete/read #5 (Case 2, DV merge)
same file, same rows delete/* neither (genuine conflict)

Roles: the loser supplies the read predicate (WHERE / MERGE ON); the winner supplies the added files whose stats are pruned. (A DV-DELETE winner re-adds the same file, so that's #5's same-file case, not this PR's.)

One-way safety

Data skipping here is sound in one direction only — a file is skipped only when its stats prove no overlap; a wrongly-skipped file would be a missed conflict. Guaranteed by reusing the reader's machinery:

  • expr || !verifyStatsForFilter(referencedStats) keeps any file whose referenced stats are missing/NULL.
  • Deletion-vector (tightBounds=false) stats are safe: updateStatsToWideBounds keeps min/max as valid outer bounds (a superset of live values), so wide bounds only cause fewer skips, never an unsafe one — the same property the reader uses to skip DV'd files. The effect on DV-heavy files is reduced benefit (conservative keep), not incorrectness.
  • Subquery / non-deterministic / metadata-column filters are dropped up front (mirrors filesForScan).
  • Data predicates from independent reads are OR-combined, never AND-combined; whole-table reads disable skipping entirely; an empty read-predicate set on a non-blind txn keeps all files.

Correct across clusters/JVMs by construction — conflict detection is log-based: the loser reads the winner's committed stats from _delta_log and evaluates skipping against its own local predicates (no shared state; deterministic; other-engine/no-stats writers are kept).

Supersedes #5's op-type append-suppression

The #2 POC (issue #5) shortcut the concurrent-UPDATE case with a blanket append-suppression for rewrite-only DML winners — skipping all their new image files in the append check, keyed only on the winner's op type. That is unsound: an UPDATE can flip a row into the loser's predicate (winner SET x=15, loser DELETE WHERE x>10, row was x=5), a genuine write-skew the suppression hides. Those image files are ordinary non-blind changed-data files and are correctly arbitrated by this PR's stats-based skip — abort on stats overlap (real flip), reconcile only on proven no-overlap, keep on missing stats. #5 drops the suppression and depends on this PR; they stack (this data skipping under the DV merge).

Testing

ConflictDataSkippingSuite (new), phase-locking harness on a Serializable table (10 files, id in [0,1000)):

  • disjoint (DELETE id<50 vs append [1000,1100)) → added file skipped, both commit.
  • overlapping (DELETE id>=950 vs append [1000,1100)) → still conflicts (ConcurrentAppendException).
  • feature disabled → disjoint ranges still conflict.
  • missing stats (dataSkippingNumIndexedCols=0) → disjoint ranges still conflict (one-way-safety invariant).
  • partitioned table → data skipping on a non-partition column avoids the conflict.
  • empty read predicates on a non-blind txn → all added files kept (no unsafe skip).
  • plus unit coverage for the per-read AND-combine and the whole-table-read guard.

With the flag off, getFirstFileMatchingPartitionPredicates is equivalent to before (candidateFiles = files), so no default-path regression.

🤖 Generated with Claude Code

… conflicts

Conflict detection filters candidate files by partition only; for unpartitioned (incl.
liquid-clustered) tables the append check conflicts on any concurrently-added file. This adds
column-statistics data skipping: added files whose stats prove they cannot match the current
transaction's read predicates are excluded, so operations touching disjoint data ranges no longer
falsely conflict.

- DataSkippingReaderBase.buildDataSkippingPredicate + filterFilesByDataSkipping reuse the reader's
  DataFiltersBuilder / verifyStatsForFilter and the shared parseAndDecodeStats, applying
  'expr || !verifyStatsForFilter(...)' for one-way safety (skip only when stats prove no match;
  missing stats keep the file).
- ConflictChecker.getFirstFileMatchingPartitionPredicates applies skipping per read predicate and
  unions survivors (OR across reads; a read's own data predicates are ANDed), and never skips for a
  whole-table read. Emits a delta.conflictDetection.dataSkipping.filesSkipped event.
- Gated by spark.databricks.delta.conflictDetection.dataSkipping.enabled (internal, default off).
- Adds ConflictDataSkippingSuite (disjoint / overlapping / disabled / missing-stats / partitioned).

Part of #3. Implements #4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sezruby
sezruby force-pushed the conflict-data-skipping branch from cacf70d to a2ad862 Compare July 8, 2026 20:38
sezruby added a commit that referenced this pull request Jul 30, 2026
Remove the unsound "Layer 2" append-suppression for rewrite-only DML
winners. Keying the append-check skip on the winner's op type
(DELETE/UPDATE) is unsound: an UPDATE can move a row into the loser's
predicate (winner `SET x=15`, loser `DELETE WHERE x>10`, row was x=5), a
genuine write-skew that suppressing the winner's image file would hide.

`canSkipAddedFileForRowLevelConcurrency` now skips only the same-file DV
union paths (`rowLevelResolvedPaths`). A rewrite-only DML winner's new
image files flow into the standard added-files check (conservative abort,
one-way safe); reconciling the provably-disjoint case via conflict-time
data skipping is owned by Case 1 (#4/#8).

Tests reframed to the Layer-1 envelope (9 tests, all passing): the two
"both commit" UPDATE cases move to Case 1; DELETE-vs-UPDATE now asserts
the image file conservatively conflicts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix a Serializable soundness gap in reader-side conflict-time data
skipping. A non-blind-append transaction can record no read predicates
(e.g. it removes files without reading the table). With skipping enabled
its empty read set produced an empty survivor set, so
getFirstFileMatchingPartitionPredicates returned None and a real
ConcurrentAppendException was silently suppressed. Skipping is now
applied only when the transaction has at least one read predicate;
otherwise every concurrently added file stays a conflict candidate,
matching behavior with the feature disabled.

Add ConflictDataSkippingSuite coverage:
- empty read predicates on a non-blind-append txn still conflict
  (regression test for the fix above);
- a whole-table read is never skipped and conflicts with a concurrent
  append;
- filterFilesByDataSkipping AND-combines predicates within a single read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sezruby sezruby changed the title [Spark] Reader-side conflict-time data skipping to reduce unnecessary conflicts [RLC] Case 1 — conflict-time data skipping (disjoint data ranges) Aug 3, 2026
sezruby and others added 20 commits August 7, 2026 18:16
Combine per-read data-skipping into one Spark job and fail safe on
evaluation errors, per review comments on delta-io#7358.

- filterFilesMatchingAnyReadPredicate(files, dataFiltersPerRead): builds
  one skipping predicate per read and OR-combines them into a single
  `where`, so all read predicates are evaluated in one Spark job instead
  of one job per read. A read with no usable predicate matches
  everything -> short-circuit and keep all files, no job.
- filterFilesByDataSkipping is now the single-read case delegating to it.
- Wrap build+eval in try/catch(NonFatal): on failure log a warning and
  return all candidate files (default conflict behavior), so a skipping
  error can never abort a valid commit.
- ConflictChecker calls the new method once instead of flatMap-per-read.
- Test: OR-combines independent reads in one call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move buildDataSkippingPredicate / filterFilesByDataSkipping /
filterFilesMatchingAnyReadPredicate out of DataSkippingReaderBase into a new
self-typed trait ConflictDataSkippingReader (self: DataSkippingReaderBase),
mixed into the base. Existing data-skipping code is left untouched:
withStatsInternal0 is reverted to its original inline form, so the only change
to DataSkippingReader.scala is the one-line trait mixin.

parseAndDecodeStats is duplicated (trait-private) rather than shared with
withStatsInternal0, keeping the feature a fully isolated additive unit per
review feedback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…io#7818)

#### Which Delta project/connector is this regarding?

- [x] Spark

## Description

Retain the loading `KernelContext` when wrapping a Kernel snapshot in
`DeltaV2Snapshot`, and use its default engine for subsequent snapshot
operations. This keeps snapshot operations associated with the table's
context and configured filesystem options instead of constructing an
engine from session defaults alone.

Thread the context through snapshot managers, transactions, and snapshot
copies, and update the affected callers and test fixtures. The snapshot
managers, table, and transaction adapter retain context instead of a
default-engine field. The context creates a default engine when
requested, materializing Hadoop configuration from the active Spark
session plus the table's invariant filesystem options. No per-session
engine cache or credential-provider refresh policy is introduced.

## How was this patch tested?

- `spark/testOnly
org.apache.spark.sql.delta.v2.interop.DeltaV2SnapshotSuite`: all 23
tests passed.
- `sparkV2/Test/compile` passed, including Java formatting and style
checks.
- Negative control: restoring the old lazy getter makes the new
filesystem regression fail with stale session properties; restoring the
fix makes it pass.
- Added regression coverage for context-engine delegation on latest and
historical snapshots and context retention in catalog copies.
- Added a real recording-filesystem regression across sessions A/B/A and
inherited child-thread sessions, exercising latest/historical snapshots,
allFiles, and transaction-version lookup while checking session
properties and table-option precedence.

## Does this PR introduce _any_ user-facing changes?

No changes to released APIs. This fixes engine selection in the
unreleased Spark connector's snapshot facade.

---------

Signed-off-by: Vishnu Chandrashekhar <seewishnew@users.noreply.github.com>
Co-authored-by: Vishnu Chandrashekhar <seewishnew@users.noreply.github.com>
#### Which Delta project/connector is this regarding?

- [x] Spark

## Description

AMT checkpoints currently serialize as an object with separate fields.
Change the checkpoint payload to an array of single-action envelopes, as
specified by the [adaptive metadata
RFC](delta-io#6978 (comment)).

Introduce `AMTCheckpointAction` and `AMTCheckpointSingleAction`, carry
the checkpoint version in `CheckpointMetadata`, and preserve the field
accessors and field-based constructor. Validate that each required
singleton action appears exactly once, allow unknown envelopes, and
preserve action order. Align Jackson serialization and Spark decoding,
including the sidecar `type` field.

## How was this patch tested?

Adds and updates coverage in `AdaptiveMetadataActionsSerializerSuite`,
`ActionSerializerSuite`, and `LastCheckpointInfoSuite` for action
wrapping, JSON round trips, required-action validation, unknown
envelopes, Spark schemas, and decoding with sidecars. Updates AMT
checkpoint, snapshot, inheritance, writer, and field-spec tests for the
new representation.

Whitespace and patch-integrity checks pass. Scala tests have not been
run locally for this revision; CI validation is pending.

## Does this PR introduce _any_ user-facing changes?

The AMT checkpoint JSON payload changes from a field object to an array
of action envelopes, matching the RFC. The checkpoint version is stored
in its `checkpointMetadata` action.

Signed-off-by: Yumingxuan Guo <171533467+yumingxuanguo-db@users.noreply.github.com>
Co-authored-by: Yumingxuan Guo <171533467+yumingxuanguo-db@users.noreply.github.com>
…tries (delta-io#7851)

## Description

Generates `snapshot_id` and `modified_snapshot_id` for data entries
written to adaptive metadata tree (AMT) checkpoints, so each data entry
records the snapshot that added it and the snapshot that last modified
it. This implements the file-modification tracking described in the
protocol RFC delta-io#7820.

Key changes:
- Populate `snapshot_id` / `modified_snapshot_id` on AMT data entries in
`AMTWriteHelper` and `IncrementalAMTWriter`, and carry them through the
AMT action model.
- Propagate the fields through the commit path (`OptimisticTransaction`,
`Checkpoints`, `Snapshot`) and through commands that rewrite or re-add
files (`CloneTableBase`, `RestoreTableCommand`, `TahoeRemoveFileIndex`).

## How was this patch tested?

Added/updated coverage across the AMT suites (`AMTCheckpointWriteSuite`,
`AMTIncrementalWriteInvariantSuite`, `AMTInheritanceSuite`,
`AMTInheritanceReadSuite`, `AMTSnapshotSuite`, `AMTBackReferenceSuite`,
`AMTPartitionValuesSuite`, `AMTSingleActionSerializerSuite`,
`AMTUtilsSuite`) plus `ActionSerializerSuite`, `CheckpointsSuite`, and
`LastCheckpointInfoSuite`.

## Does this PR introduce _any_ user-facing changes?

No.

Co-authored-by: Jinhua Song <jinhua.song@databricks.com>
…elta-io#7852)

## Description

Spark's optimizer can request scan statistics (`estimateSizeInBytes` /
`estimateStatistics`) before physical planning. Previously the Delta V2
scan always selected files to answer these requests, even for unfiltered
scans where the result is already known.

For an unfiltered scan (no data filters, no partition filters, no pushed
limit, not a CDC read), the snapshot checksum already records the exact
size of every file the scan would select. The scan now returns that size
directly before planning, so an early optimizer statistics request no
longer triggers file selection. Physical planning stays lazy and still
selects files exactly once.

Filtered, limit, and CDC scans continue to plan files so Spark receives
their accurate post-pruning size. If the checksum does not record a
table size, the scan falls back to planning rather than reporting an
unknown size.

## How was this patch tested?

Added tests to `DeltaV2ScanTest`:
- Unfiltered scans report statistics without selecting files, and later
physical planning selects files exactly once and reuses that selection.
- Filtered scans and positive-limit scans plan files exactly once and
reuse the selection for repeated statistics requests.
- Tables written without checksums fall back to file planning for the
size estimate.

Existing statistics tests were updated to reflect that full statistics
for unfiltered scans now come from snapshot metadata.

## Does this PR introduce _any_ user-facing changes?

No.
…ta-io#7847)

<!--
Thanks for sending a pull request!  Here are some tips for you:
1. If this is your first time, please read our contributor guidelines:
https://github.com/delta-io/delta/blob/master/CONTRIBUTING.md
2. If the PR is unfinished, add '[WIP]' in your PR title, e.g., '[WIP]
Your PR title ...'.
  3. Be sure to keep the PR description updated to reflect all changes.
  4. Please write your PR title to summarize what this PR proposes.
5. If possible, provide a concise example to reproduce the issue for a
faster review.
6. If applicable, include the corresponding issue number in the PR title
and link it in the body.
-->

#### Which Delta project/connector is this regarding?
<!--
Please add the component selected below to the beginning of the pull
request title
For example: [Spark] Title of my pull request
-->

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description
Stop pushing down non deterministic predicates to CDF nodes, these
predicates will get reevaluated at query level thus potentially
returning inconsistent results.
<!--
- Describe what this PR changes.
- Describe why we need the change.
 
If this PR resolves an issue be sure to include "Resolves #XXX" to
correctly link and close the issue upon merge.
-->

## How was this patch tested?
New UTs
<!--
If tests were added, say they were added here. Please make sure to test
the changes thoroughly including negative and positive cases if
possible.
If the changes were tested in any way other than unit tests, please
clarify how you tested step by step (ideally copy and paste-able, so
that other reviewers can test and check, and descendants can verify in
the future).
If the changes were not tested, please explain why.
-->

## Does this PR introduce _any_ user-facing changes?
No
<!--
If yes, please clarify the previous behavior and the change this PR
proposes - provide the console output, description and/or an example to
show the behavior difference if possible.
If possible, please also clarify if this is a user-facing change
compared to the released Delta Lake versions or within the unreleased
branches such as master.
If no, write 'No'.
-->

---------

Co-authored-by: Omar Elhadidy <omar.elhadidy@databricks.com>
…ed relative DVs in VACUUM (delta-io#7853)

#### Which Delta project/connector is this regarding?

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description

A few more places that identify deletion vectors (DVs) now support
object identity, so that the
same DV blob is recognized whether it is described by a table-relative
(`r`) descriptor or a
UUID-based (`u`) descriptor.

1. `DeletionVectorDescriptor.uniqueFileId` was a lazy val that always
used the legacy
   `storageType + pathOrInlineDv` identity. It is replaced by
`uniqueFileId(tableRoot, useObjectIdentity)`, which mirrors `uniqueId`:
with object identity it
returns the normalized table-relative identity of the DV file, without
the offset. The legacy
value is kept as the private `legacyUniqueFileId`, which
`legacyUniqueId` is built on.
2. `getDeletionVectorRelativePathAndSize`, used by VACUUM, now treats
unencoded relative (`r`)
DVs like UUID-based (`u`) ones and returns their table-relative path.
Before this change, `r`
DVs fell through to the absolute-path branch, which asserts that the DV
path is absolute.
3. Adds a `FileAction.useDeletionVectorObjectIdentity(snapshot, spark)`
overload and uses it in
   the OPTIMIZE check that compares candidate file sets.

## How was this patch tested?

Existing tests. `DeletionVectorDescriptorSuite` and
`AMTCheckpointTestBase` are updated for the
new `uniqueFileId` signature.

## Does this PR introduce _any_ user-facing changes?

No.
…delta-io#7850)

#### Which Delta project/connector is this regarding?

- [ ] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [x] V4 RFC

## Description

Align the adaptive metadata tree RFC's manifest deletion vector encoding
to use the Mumbling bitmap spec in place of Roaring.

## How was this patch tested?

NA

## Does this PR introduce _any_ user-facing changes?

No
…ormat (delta-io#7855)

The AMT manifest deletion vectors (the masking MDV in `manifest_info.dv`
and the CDF `tracking.deleted_positions` / `replaced_positions`) were
backed by a Roaring bitmap. This switches `ManifestBitmap` to an
implementation backed by the immutable Mumbling bitmap format:
`MumblingManifestBitmap` wraps a `MumblingBitmap`, and its mutable
counterpart `MutableMumblingManifestBitmap` wraps a
`MutableMumblingBitmap`, so `ManifestBitmap.fromPositions` /
`fromSerializedByteArray` now produce and consume the Mumbling on-disk
bytes.

It also adds `MumblingRowIndexFilter` and
`MumblingRowIndexFilterProvider`: a `RowIndexFilter` that drops a leaf
row when its row index is contained in the MDV and keeps it otherwise,
probing the bitmap per row (leaf row indices are dense and small,
matching the point-membership shape of the Mumbling bitmap). The
provider is self-contained and serializable so a scan task owns its own
filter.

The test-only position-dump helper (`toArrayForTesting`) is removed; the
manifest-bitmap and AMT incremental-write tests now assert through
`cardinality` / `contains` and exercise all three `RowIndexFilter`
materialization entry points.
#### Which Delta project/connector is this regarding?

- [x] Spark

## Description

Convert `DeltaV2Table` from Java to Scala. Preserve its Java-facing
collection and optional
interfaces, synchronized schema initialization, and
`KernelContext`-backed engine lookup. Constants and static helpers move
to a Scala companion.
Existing tests remain unchanged.

Compatibility notes:

- Scala `protected` methods are JVM-public. Java subclasses that
override `protocol()`,
`metadata()`, `kernelEngine()`, `initialSnapshot()`, or `catalogTable()`
with protected
  visibility must widen their overrides to public when recompiling.
- Helper visibility and reflection metadata differ from the Java
implementation, including
  the public static forwarder for `getDecodedPath`.
- Scala introduces some tuple and callback allocations; performance
impact is unmeasured.

## How was this patch tested?

Existing tests are unchanged. Local tests have not been run in this
checkout; CI validation
is pending.

## Does this PR introduce _any_ user-facing changes?

No intended SQL behavior change. Java subclass and reflection
compatibility differences are
listed above.

Signed-off-by: Hao Sun <86338940+HaoSun1993@users.noreply.github.com>
…rop dv_cardinality (delta-io#7816)

<!--
Thanks for sending a pull request!  Here are some tips for you:
1. If this is your first time, please read our contributor guidelines:
https://github.com/delta-io/delta/blob/master/CONTRIBUTING.md
2. If the PR is unfinished, add '[WIP]' in your PR title, e.g., '[WIP]
Your PR title ...'.
  3. Be sure to keep the PR description updated to reflect all changes.
  4. Please write your PR title to summarize what this PR proposes.
5. If possible, provide a concise example to reproduce the issue for a
faster review.
6. If applicable, include the corresponding issue number in the PR title
and link it in the body.
-->

#### Which Delta project/connector is this regarding?
<!--
Please add the component selected below to the beginning of the pull
request title
For example: [Spark] Title of my pull request
-->

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description


Renumber the AMT V4 manifest_info file/row count field IDs to match the
spec PR:
  replaced_files_count 520 -> 523
  modified_files_count 524 -> 525
  replaced_rows_count  521 -> 524
  modified_rows_count  525 -> 526

Separate DV cardinality Field id 523 is dropped now, readers have to
read the cardinality from the bitmap itself. This field ID is now used
for replaced_files_count. dv/dv_cardinality consistency invariant is now
moot since there's only a single place so that logic is all dropped.

format_version moves from top level field 157 into manifest_info as
field 521, matching the spec PR.

## How was this patch tested?

Existing unit tests, dropped no longer relevant assertions/tests for the
fields that were removed.

## Does this PR introduce _any_ user-facing changes?

<!--
If yes, please clarify the previous behavior and the change this PR
proposes - provide the console output, description and/or an example to
show the behavior difference if possible.
If possible, please also clarify if this is a user-facing change
compared to the released Delta Lake versions or within the unreleased
branches such as master.
If no, write 'No'.
-->

---------

Co-authored-by: Isaac <no-reply@databricks.com>
## Description

Use `snapshotAtSourceInit` to provide table tags for four existing
streaming source events: the unsafe column-mapping read warning, missing
trailing commits, schema changes, and the `ignoreFileDeletion`
deprecation warning.

This keeps event attribution tied to the snapshot the source was
initialized to read instead of the latest snapshot cached by `DeltaLog`.
Event names, payloads, exception handling, and streaming behavior are
unchanged. No additional snapshot fetches are introduced.

## Why this is safe

`recordDeltaEvent` uses its provider only for table-context tags.
Snapshots delegate to the same `DeltaLog.getCommonTags` helper; only the
source of the table identifier tag changes. This affects attribution,
not logging mechanics or streaming decisions.

The table identifier is the table's `metadata.id`, not its version.
Ordinary data writes and schema updates preserve it. Table replacement
or drop-and-recreation can create a new table identity and change the
ID, even at the same path.

- **Source construction:** `snapshotAtSourceInit` is the snapshot used
to construct the source. Initialization and deprecation warnings should
identify that source, regardless of later table changes.
- **Commit-processing diagnostics:** events describe the existing source
and should carry its pinned `tableId`, obtained from
`snapshotAtSourceInit.metadata.id`. When commit metadata validation
runs, an ID mismatch fails before that commit's files are processed;
schema-change logging also follows the ID check. Existing ID and
schema-tracking checks remain unchanged.

## Testing

- `git diff --check`
- Verified that `SnapshotDescriptor` implements `DeltaLoggingProvider`,
the provider type accepted by `recordDeltaEvent`.
- Builds and tests have not been run locally.
#### Which Delta project/connector is this regarding?

- [x] Spark

## Description

Decode CDC actions from Kernel commit batches into `AddCDCFile` actions,
preserving the path, partition values, size, and optional tags. These
actions previously caused an `UnsupportedOperationException` during
commit-batch conversion.

Rename the shared string-map converter to reflect its use for both
partition values and tags.

## How was this patch tested?

Added coverage in `KernelActionUtilsSuite` for two CDC cases in one test
method: an action without tags and an action with all supported fields
populated. Local tests have not been run in this checkout; CI validation
is pending.

## Does this PR introduce _any_ user-facing changes?

Kernel commit-batch conversion now supports CDC actions.

Signed-off-by: Hao Sun <86338940+HaoSun1993@users.noreply.github.com>
…elta-io#7848)

## Description

Update CDF schema change check to reject partition column changes, which
can otherwise result in `null` values in older versions for columns
upgraded to a partition column.

This also adds logging for incompatible schema changes encountered
during CDF reads.

## How was this patch tested?

Added tests in `CDCReaderSuite` (`CDCSchemaChangeTests`) that cover:
- A partition column change, with the check disabled (the usage event is
logged and the read succeeds) and enabled (the read fails).
- An intermediate partition change.
- A partition change followed by `RESTORE`.
- Additional test coverage for other schema changes: column dropped,
renamed, or changes type after the start version.

Co-authored-by: Isaac <no-reply@databricks.com>
…ta-io#7860)

#### Which Delta project/connector is this regarding?

  - [x] Spark
  - [ ] Standalone
  - [ ] Flink
  - [ ] Kernel
  - [ ] Other

  ## Description

  Converts `TableNotFoundException` to Scala as part of the V2
  connector migration. Preserves its constructor, getter, error
  message, subclassability, and field initialization order.

  The path field initializes after the superclass constructor so a
  getter called from an overridden `fillInStackTrace()` sees the
  same default value as before.

  ## How was this patch tested?

  No new test cases. Validation in this repository is pending;
  existing tests will exercise the converted class through its
  existing callers.

  ## Does this PR introduce any user-facing changes?

  No.

Signed-off-by: Rakesh Veeramacheneni <rakesh.veeramacheneni@databricks.com>
…or classes (delta-io#7618)

<!--
Thanks for sending a pull request!  Here are some tips for you:
1. If this is your first time, please read our contributor guidelines:
https://github.com/delta-io/delta/blob/master/CONTRIBUTING.md
2. If the PR is unfinished, add '[WIP]' in your PR title, e.g., '[WIP]
Your PR title ...'.
  3. Be sure to keep the PR description updated to reflect all changes.
  4. Please write your PR title to summarize what this PR proposes.
5. If possible, provide a concise example to reproduce the issue for a
faster review.
6. If applicable, include the corresponding issue number in the PR title
and link it in the body.
-->

#### Which Delta project/connector is this regarding?
<!--
Please add the component selected below to the beginning of the pull
request title
For example: [Spark] Title of my pull request
-->

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description
This PR is the first out of a stack of seven PRs in total. 
The combined stack adds support for the Concurrent Identity Column (CIC)
feature following the RFC
[here](delta-io#7272).
The TLDR for this RFC/feature: It allows for two concurrent 'inserters'
to both succeed by adding an identity sequence service that hands out
disjoint ranges of id values. This way the two writers do not conflict
and can both commit.

**_Stack overview:_** 
1. Foundation <- this PR
    (delta-io#7618)
2. Service abstraction + In-Memory one
    (delta-io#7619)
3. Transport and buffering for the service requests 
    (delta-io#7620)
4. Table feature and the insert path
    (delta-io#7621)
5. DDL integration 
    (delta-io#7622)
6. Merge support
    (delta-io#7623)
7. UC Client addition
    (delta-io#7785) 


This PR introduces no behaviour changes, just scaffolding. It adds:
- `ConcurrentIdentityColumnSchema`: the
`delta.identity.concurrent.sequenceId` column-metadata key that marks a
column as a CIC.
- `ConcurrentIdentityColumnErrors` + 8
DELTA_CONCURRENT_IDENTITY_COLUMN_* error classes (disabled,
too-many-columns, sequence-not-found, metadata-mismatch,
empty-reserve-range, service-timeout, conversion-incomplete,
using-wrong-generator).
- Config
`spark.databricks.delta.identityColumn.concurrent.maxColumnsPerTable`

## How was this patch tested?

<!--
If tests were added, say they were added here. Please make sure to test
the changes thoroughly including negative and positive cases if
possible.
If the changes were tested in any way other than unit tests, please
clarify how you tested step by step (ideally copy and paste-able, so
that other reviewers can test and check, and descendants can verify in
the future).
If the changes were not tested, please explain why.
-->
The ConcurrentIdentityColumnErrorsSuite adds three tests for the
`EMPTY_RESERVE_RANGE`, `METADATA_MISMATCH`, and `USING_WRONG_GENERATOR`
errors. The tests are only added for these errors because these errors
can not occur 'naturally' in the code and are therefore tested this way.


## Does this PR introduce _any_ user-facing changes?
<!--
If yes, please clarify the previous behavior and the change this PR
proposes - provide the console output, description and/or an example to
show the behavior difference if possible.
If possible, please also clarify if this is a user-facing change
compared to the released Delta Lake versions or within the unreleased
branches such as master.
If no, write 'No'.
-->
No

---------

Signed-off-by: Robert Weeke <weeke.robert@gmail.com>
Co-authored-by: mkroll-db <marco.kroll@databricks.com>
…taSQLCommandTest (delta-io#7863)

#### Which Delta project/connector is this regarding?

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description

In `DeleteSuiteBase`, replaces the `SharedSparkSession` test util with
`DeltaSQLCommandTest` as the latter is the 'delta wrapper' of the
former, to slightly decouple from Spark OSS.
Also replaces `withSQLConf` with the more generic `withConf` to set
confs via public APIs.

## How was this patch tested?

test-only change.

## Does this PR introduce _any_ user-facing changes?

No

Co-authored-by: Matthis Gördel <matthis.goerdel@databricks.com>
…ta-io#7854)

<!--
Thanks for sending a pull request!  Here are some tips for you:
1. If this is your first time, please read our contributor guidelines:
https://github.com/delta-io/delta/blob/master/CONTRIBUTING.md
2. If the PR is unfinished, add '[WIP]' in your PR title, e.g., '[WIP]
Your PR title ...'.
  3. Be sure to keep the PR description updated to reflect all changes.
  4. Please write your PR title to summarize what this PR proposes.
5. If possible, provide a concise example to reproduce the issue for a
faster review.
6. If applicable, include the corresponding issue number in the PR title
and link it in the body.
-->

#### Which Delta project/connector is this regarding?
<!--
Please add the component selected below to the beginning of the pull
request title
For example: [Spark] Title of my pull request
-->

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description

<!--
- Describe what this PR changes.
- Describe why we need the change.
 
If this PR resolves an issue be sure to include "Resolves #XXX" to
correctly link and close the issue upon merge.
-->

Problem:

Widening file statistics through the shared
AddFile.withoutTightBoundStats helper can round DECIMAL bounds inward,
causing data skipping to discard matching rows. The SQL below
demonstrates this through DELETE rebasing over a concurrent OPTIMIZE;
the fix also affects other callers of the shared helper.

```
CREATE TABLE t (id BIGINT, amount DECIMAL(18, 2)) USING DELTA
TBLPROPERTIES ('delta.enableDeletionVectors' = 'true',
               'delta.autoOptimize.autoCompact' = 'false');

INSERT INTO t
SELECT /*+ COALESCE(1) */ * FROM VALUES
  (1L, 0.64), (2L, 3647609576981584.64), (3L, 1.00),
  (4L, 2.00), (5L, -3647609576981584.64) AS test_data(id, amount);
DELETE FROM t WHERE id = 1;
INSERT INTO t VALUES (6L, 3.00);

-- Session 1: the test harness pauses this DELETE before commit.
DELETE FROM t WHERE id = 3;
-- Session 2: OPTIMIZE commits while Session 1 is paused.
OPTIMIZE t;
-- Resume Session 1 and let DELETE commit before running these queries.
-- With the fix disabled and data skipping enabled, both incorrectly return no rows.
SELECT id, amount FROM t WHERE amount = -3647609576981584.64; -- Expected: id=5
SELECT id, amount FROM t WHERE amount = 3647609576981584.64;  -- Expected: id=2
```

Root cause:

AddFile.withoutTightBoundStats parses fractional numbers as Double in
its JSON-tree rewrite while setting tightBounds=false. In the example,
DV conflict rebasing reaches this helper through
removeRows(updateStats=true). The amount bounds round inward from
[-3647609576981584.64, 3647609576981584.64] to [-3647609576981584.50,
3647609576981584.50]. Both surviving endpoints then fall outside the
recorded bounds, so data skipping discards their file. The precision
loss is in the shared helper and can affect any caller that rewrites
numeric bounds.

Fix:

Reuse Jackson's `copyCurrentEventExact` in `withoutTightBoundStats` to
preserve numeric precision while replacing tightBounds.


## How was this patch tested?

<!--
If tests were added, say they were added here. Please make sure to test
the changes thoroughly including negative and positive cases if
possible.
If the changes were tested in any way other than unit tests, please
clarify how you tested step by step (ideally copy and paste-able, so
that other reviewers can test and check, and descendants can verify in
the future).
If the changes were not tested, please explain why.
-->
UT
## Does this PR introduce _any_ user-facing changes?

<!--
If yes, please clarify the previous behavior and the change this PR
proposes - provide the console output, description and/or an example to
show the behavior difference if possible.
If possible, please also clarify if this is a user-facing change
compared to the released Delta Lake versions or within the unreleased
branches such as master.
If no, write 'No'.
-->
No

Signed-off-by: zhipeng.mao <zhipeng.mao@databricks.com>
…LCommandTest (delta-io#7864)

#### Which Delta project/connector is this regarding?

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description

Replaces `SharedSparkSession` with `DeltaSQLCommandTest`, which is a
'delta wrapper' of the former.
Also replaces `withSQLConf` with the more generic `withConf`.

## How was this patch tested?

test-only change.

## Does this PR introduce _any_ user-facing changes?

No

Co-authored-by: Matthis Gördel <matthis.goerdel@databricks.com>
dhruvarya-db and others added 8 commits October 14, 2026 16:19
…ta-io#7865)

#### Which Delta project/connector is this regarding?

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description

`delta.inCommitTimestampEnablementVersion` and
`delta.inCommitTimestampEnablementTimestamp` record the version and
timestamp at which In-Commit Timestamps (ICT) were enabled on a table.
They are written by the commit that enables ICT and describe that
table's log. A user-specified value, for example one copied from another
table, can point to a version that is not in the table's log. That
breaks later commits and ICT-based time travel.

This PR:
- Marks both properties as not user-configurable, so CREATE, REPLACE,
CTAS, RTAS, ALTER TABLE SET TBLPROPERTIES, DataFrameWriter options,
session defaults and the `DeltaTable` builder API fail with
`DELTA_CANNOT_MODIFY_TABLE_PROPERTY`.
- Stops CREATE TABLE LIKE and CLONE from copying them from the source
table. The target still gets `delta.enableInCommitTimestamps` from the
source, and the commit derives the target's own enablement info when
needed.
- Adds `InCommitTimestampUtils.ENABLEMENT_INFO_PROPERTY_KEYS` so the
filtering and the tests share one list of keys.

## How was this patch tested?

New tests in `InCommitTimestampSuite`:
- For each property, setting it fails through CREATE/REPLACE/CTAS/RTAS
TBLPROPERTIES, ALTER TABLE SET TBLPROPERTIES, DataFrameWriter options,
session defaults and the `DeltaTable` builder API.
- CREATE TABLE LIKE from a table whose ICT enablement info is set
produces a target that has ICT enabled but no copied enablement info.

## Does this PR introduce _any_ user-facing changes?

Yes. Setting `delta.inCommitTimestampEnablementVersion` or
`delta.inCommitTimestampEnablementTimestamp` now fails with
`DELTA_CANNOT_MODIFY_TABLE_PROPERTY`; before, the value was accepted.
CREATE TABLE LIKE and CLONE no longer copy these properties from the
source table. Tables that already have these properties are unaffected.

This pull request and its description were written by Isaac.

---------

Co-authored-by: Murali Ramanujam <murali.ramanujam@databricks.com>
… DeltaV2Table (delta-io#7866)

<!--
Thanks for sending a pull request!  Here are some tips for you:
1. If this is your first time, please read our contributor guidelines:
https://github.com/delta-io/delta/blob/master/CONTRIBUTING.md
2. If the PR is unfinished, add '[WIP]' in your PR title, e.g., '[WIP]
Your PR title ...'.
  3. Be sure to keep the PR description updated to reflect all changes.
  4. Please write your PR title to summarize what this PR proposes.
5. If possible, provide a concise example to reproduce the issue for a
faster review.
6. If applicable, include the corresponding issue number in the PR title
and link it in the body.
-->

#### Which Delta project/connector is this regarding?
<!--
Please add the component selected below to the beginning of the pull
request title
For example: [Spark] Title of my pull request
-->

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description

DeltaV2Table` built its identity partition transforms with
`Expressions.identity(String)`, which re-parses each partition column
name as a SQL multipart identifier. Partition column names are exact
top-level field names, so this goes wrong for names that need quoting:

- A dotted name such as `customer.id` silently becomes the two-part
reference `[customer, id]`. Consumers that expect one name part per
identity partition column (for example
`DataSourceV2Relation.partitionColumns`, which takes
`fieldNames().head`) see the wrong column.
- Names with spaces or backticks, which column mapping allows (for
example `order date`), throw a `ParseException` as soon as
`partitioning()` is called.

This PR builds each transform from `FieldReference.column(name)`, a
single exact name part. Partition-column order, case, and column-mapping
logical names are unchanged.

<!--
- Describe what this PR changes.
- Describe why we need the change.
 
If this PR resolves an issue be sure to include "Resolves #XXX" to
correctly link and close the issue upon merge.
-->

## How was this patch tested?

CI and new tests

<!--
If tests were added, say they were added here. Please make sure to test
the changes thoroughly including negative and positive cases if
possible.
If the changes were tested in any way other than unit tests, please
clarify how you tested step by step (ideally copy and paste-able, so
that other reviewers can test and check, and descendants can verify in
the future).
If the changes were not tested, please explain why.
-->

## Does this PR introduce _any_ user-facing changes?

<!--
If yes, please clarify the previous behavior and the change this PR
proposes - provide the console output, description and/or an example to
show the behavior difference if possible.
If possible, please also clarify if this is a user-facing change
compared to the released Delta Lake versions or within the unreleased
branches such as master.
If no, write 'No'.
-->
No
#### Which Delta project/connector is this regarding?

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description

Adds `DeleteAMT` and `RowTrackingDeleteAMT` dimensions to the
delta-suite-generator, plus a generated `deleteamt` group that runs the
DELETE tests against AMT (`adaptiveMetadata-preview`) tables, following
the UPDATE AMT suites.

- `DeleteAMTMixin` brackets each `executeDelete` with the
`AMTDMLTestUtils` checkpoints. `RowTrackingDeleteAMTMixin` brackets each
test table's body instead, since its row tracking checks inspect the
latest commit.
- AMT requires deletion vectors, so `AMTDMLTestUtils` now unsets a
suite's `delta.enableDeletionVectors = false` default for new tables for
all AMT DML suites (moved from the UPDATE AMT base), and keeps DELETE,
UPDATE and MERGE from writing deletion vectors where the tests assert
file rewrites.
- The DELETE schema pruning tests only collect scans over a
`TahoeFileIndex`, so they skip the checkpoint's tree read.
- Excludes the tests the row tracking and deletion vector DELETE suites
already exclude, and the row tracking tests that create tables with row
tracking disabled, which AMT rejects. `RowTrackingDeleteDvBase` is not
generated, since its only test disables deletion vectors on new tables.

## How was this patch tested?

Test-only change. The new generated `deleteamt` suites run the existing
DELETE tests on AMT tables.

## Does this PR introduce _any_ user-facing changes?

No.
…a-io#7870)

<!--
Thanks for sending a pull request!  Here are some tips for you:
1. If this is your first time, please read our contributor guidelines:
https://github.com/delta-io/delta/blob/master/CONTRIBUTING.md
2. If the PR is unfinished, add '[WIP]' in your PR title, e.g., '[WIP]
Your PR title ...'.
  3. Be sure to keep the PR description updated to reflect all changes.
  4. Please write your PR title to summarize what this PR proposes.
5. If possible, provide a concise example to reproduce the issue for a
faster review.
6. If applicable, include the corresponding issue number in the PR title
and link it in the body.
-->

#### Which Delta project/connector is this regarding?
<!--
Please add the component selected below to the beginning of the pull
request title
For example: [Spark] Title of my pull request
-->

- [x] Spark
- [ ] Standalone
- [ ] Flink
- [ ] Kernel
- [ ] Other (fill in here)

## Description

The V1 read path uses a LIMIT to prune files only when
DeltaSQLConf.DELTA_LIMIT_PUSHDOWN_ENABLED is on, but
DeltaV2ScanBuilder.pushLimit always accepted the limit, so the V2
connector ignored the config.

pushLimit now declines the limit and records nothing when the config is
off, so Spark applies its own LIMIT to an unlimited scan. Nothing
changes when the config is on, which is the default.

<!--
- Describe what this PR changes.
- Describe why we need the change.
 
If this PR resolves an issue be sure to include "Resolves #XXX" to
correctly link and close the issue upon merge.
-->

## How was this patch tested?

Added new tests

<!--
If tests were added, say they were added here. Please make sure to test
the changes thoroughly including negative and positive cases if
possible.
If the changes were tested in any way other than unit tests, please
clarify how you tested step by step (ideally copy and paste-able, so
that other reviewers can test and check, and descendants can verify in
the future).
If the changes were not tested, please explain why.
-->

## Does this PR introduce _any_ user-facing changes?

<!--
If yes, please clarify the previous behavior and the change this PR
proposes - provide the console output, description and/or an example to
show the behavior difference if possible.
If possible, please also clarify if this is a user-facing change
compared to the released Delta Lake versions or within the unreleased
branches such as master.
If no, write 'No'.
-->
No
… probe

Fuse the conflict-time data-skipping filter into the per-read partition
probe so a file conflicts iff it could match (P_i AND D_i) for some read,
instead of the looser (P_1 OR P_2) AND (D_1 OR D_2). Partition pruning now
runs first and no extra Spark job is launched. Move the evaluation fallback
into ConflictChecker and record it as a delta event; record the
noConflict event only after all checks against the winning commit pass.
Add multi-read, null-stats, and event tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	spark/src/main/scala/org/apache/spark/sql/delta/ConflictChecker.scala
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, report on commit

- Check whether a skipping condition can be built before constructing any
  DataFrame; an unpartitioned check with nothing to skip returns
  files.headOption directly.
- Evaluate candidates in bounded batches (internal conf
  conflictDetection.dataSkipping.batchSize, default 1000) and stop at the
  first batch holding a conflicting file: a filter over a local relation is
  evaluated over every row before the limit applies.
- Group reads by their partition predicate after widening.
- Report delta.conflictDetection.dataSkipping.applied once, from
  OptimisticTransaction, when the transaction commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Eunjin Song <sezruby@gmail.com>
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.