Skip to content

feat: Incremental append scan#2337

Open
xanderbailey wants to merge 32 commits into
apache:mainfrom
xanderbailey:xb/incremental_read
Open

feat: Incremental append scan#2337
xanderbailey wants to merge 32 commits into
apache:mainfrom
xanderbailey:xb/incremental_read

Conversation

@xanderbailey

@xanderbailey xanderbailey commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

  • Closes #.

What changes are included in this PR?

Adds incremental append scan support to iceberg-rust, allowing users to read only newly added data files between two snapshots. This is the Rust equivalent of Java's BaseIncrementalAppendScan.

Are these changes tested?

Yes

Java comparison notes:

  • AppendSnapshotSet::build mirrors Java's BaseIncrementalAppendScan.snapshotsBetween() — only APPEND snapshots are collected, non-APPEND are silently skipped
  • Exclusive from_snapshot semantics match Java's default IncrementalAppendScan behavior
  • Inclusive mode has no direct Java equivalent but follows the same ancestry walking logic
  • Manifest file filtering (skip deletes, check added_snapshot_id) and entry filtering (status == ADDED, snapshot_id in range) match BaseIncrementalAppendScan.doPlanFiles() in Java

@xanderbailey xanderbailey marked this pull request as draft April 15, 2026 22:56
Comment on lines +36 to +43
/// Filter applied to each [`ManifestFile`] before fetching it.
/// Returns `true` to include the manifest, `false` to skip it.
pub(crate) type ManifestFileFilter = Arc<dyn Fn(&ManifestFile) -> bool + Send + Sync>;

/// Filter applied to each manifest entry after loading a manifest.
/// Returns `true` to include the entry, `false` to skip it.
pub(crate) type ManifestEntryFilter = Arc<dyn Fn(&ManifestEntryRef) -> bool + Send + Sync>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this is a nice way to inject these filters and should extend to other future scans

pub manifest_entry_filter: Option<ManifestEntryFilter>,
}

impl std::fmt::Debug for PlanContext {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ManifestFileFilter and ManifestEntryFilter can't be debug

pub(crate) struct AppendSnapshotSet {
/// Snapshot IDs in the range
snapshot_ids: HashSet<i64>,
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ancestors_between is used here. We need to add validation and operation-type checking on top of it, we're not re-implementing the traversal. Does that make sense?

@xanderbailey xanderbailey marked this pull request as ready for review April 26, 2026 14:36
///
/// Use [`Table::incremental_append_scan`] or
/// [`Table::incremental_append_scan_inclusive`] to create an instance.
pub struct IncrementalAppendScanBuilder<'a> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is duplication here between the scan builders which I've left for now until maybe we have a 3rd variant and we can pick out the common patterns a bit better.

@xanderbailey xanderbailey changed the title Incremental scan feat: Incremental scan Apr 29, 2026
@xanderbailey xanderbailey changed the title feat: Incremental scan feat: Incremental append scan Apr 29, 2026
@xanderbailey

Copy link
Copy Markdown
Contributor Author

@CTTY would you be open to giving a review here?

@xanderbailey xanderbailey force-pushed the xb/incremental_read branch from 3a4f875 to 489084d Compare June 15, 2026 22:20
xanderbailey and others added 6 commits June 15, 2026 23:25
Resolve conflicts in scan/context.rs and scan/mod.rs by combining the
incremental-scan work (entry_filter, build_table_scan/ScanConfig refactor)
with main's name_mapping support and metadata-column validation. Adapt
incremental-scan test fixtures to the updated ManifestWriterBuilder::new
and ManifestListWriter::v2 signatures from the encryption changes.
…er tests

Arc<dyn ExecutionPlan> supports downcast_ref directly; the extra
as_any() call resolved to the unrelated as-any crate trait (not in
scope), which broke compilation and cascaded into spurious deref
errors on the matched i64 fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the Java and PyIceberg implementations: an incremental append
scan now projects onto the table's current schema rather than the
to-snapshot's schema. Rows written under an older schema within the
range are read against the current schema, so newer columns become
NULL.

Adds a schema_override to ScanConfig (None for regular scans, which
keeps the correct per-snapshot schema for time-travel; current schema
for incremental scans) and a stale-schema deep-history fixture plus a
test asserting the projected schema is the current one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Incorporate the substantive reviewer feedback from the py-iceberg
incremental append scan PR (apache/iceberg-python#3512):

- Document that this is not a CDC/net-changes/changelog scan and that
  compaction (replace) output is skipped, so appended rows are never
  double-counted (kevinjqliu).
- Document the ancestors_between footgun: a from-snapshot that is not in
  the lineage silently yields all ancestors of the to-snapshot, so
  callers must validate the lineage (kevinjqliu).
- Add an explicit compaction (replace) end-to-end test proving the
  rewritten file is not picked up (rambleraptor).

Deep-history fixture variants now share one helper and mutate the parsed
serde_json::Value structurally by field, instead of fragile raw string
substitution on the shared testdata file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the serde_json::Value mutation with dedicated templated metadata
files for the stale-schema and compaction deep-history fixtures, matching
the repo's existing testdata pattern. The three fixtures now share a
filename-parameterized helper that renders the manifest-list paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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.

2 participants