GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — [iceberg] Read the current schema in snapshot mode by swanandx · Pull Request #7835 · feldera/feldera

Conversation

@swanandx

@swanandx swanandx commented Nov 4, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Linked Issue

None.

Describe Manual Test Plan

Added an engine test that renames a column after the snapshot it pins, and ran it with the fix reverted to confirm it fails. The whole Iceberg follow suite passes against a local REST catalog and S3 mock.

Checklist

  • Unit tests added/updated
  • Integration tests added/updated
  • Documentation updated
  • Changelog updated

Breaking Changes?

  • OpenAPI / REST HTTP API / feldera-types / manager
  • Feldera SQL (Syntax, Semantics)
  • feldera-sqllib (incl. dependencies fxp, etc.)
  • Python SDK
  • fda (CLI arguments)
  • Adapters (including configuration)
  • Storage Format / Checkpoints
  • Others (specify)

Describe Incompatible Changes

None. No configuration or stored format changes.

Snapshot mode pins the snapshot it starts from, and a pinned id made the reader travel back to that snapshot's schema. A column renamed or added since then is absent from it, so the read lost the column: NULL when the column is nullable, a parse failure when it is required. When the pin names the current snapshot there is nothing to travel back to, so the reader now reads the table as it is.

Distributed reads keep the old behavior, because every host has to read the same snapshot and the share scan pins it. That difference is now in the connector docs.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 4, 2026
View reviewed changes
Comment thread crates/iceberg/src/input.rs Outdated
})?;
// Pin the snapshot on the table, not on the scan: a pinned scan would
// use the snapshot's schema too, hiding any column added since.
let scan_table = match snapshot_id {

@mythical-fred-oss mythical-fred-oss Bot Nov 4, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (medium): distributed hosts can now disagree on the schema. Hosts agree on the snapshot id through startup_choice, but each host calls open_table_with_retries() on its own, so each host picks up whatever schema is current when it loads the table. Here is how that goes wrong: host 0 loads the table, then ALTER TABLE ADD COLUMN c DEFAULT 'x' commits, then host 1 loads it. Host 1's used_columns includes c and reads 'x'. Host 0 doesn't see c and emits NULL. The result is mixed rows from one immutable snapshot. A drop or rename between the two loads is worse: a NOT NULL SQL column fails on only some hosts. Before this PR, the snapshot's schema made the read deterministic. Could host 0 also publish the schema id in startup_choice, so every host pins to it with set_current_schema? Resume has the same issue: a restart re-reads the pinned snapshot with whatever schema is current then.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 4, 2026
View reviewed changes
Comment thread crates/iceberg/src/share.rs Outdated
builder = builder.set_current_schema(schema_id)?;
}
Table::builder()
.runtime(Runtime::try_current()?)

@mythical-fred-oss mythical-fred-oss Bot Nov 4, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

pitfall (low-medium): this rebuilds the table with Runtime::try_current() and default readonly, so it drops the source table's runtime (for example, a split io/cpu runtime set by the catalog) and its readonly flag. If the iceberg fork exposes them, carry the original table's runtime and readonly over. At the least, note in the comment that they are reset on purpose.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mythical-fred-oss

mythical-fred-oss Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

What I ran

Check Result
cargo test --locked -p feldera-iceberg share:: 3/3 pass
pre-commit run --files on the two changed files (clippy, fmt, machete, docs) pass. One unrelated hook couldn't download postgresql_embedded in my sandbox.
Extra test I wrote (diff below) pass

Breaking change, undeclared (medium): reads now use the current schema, so a column that was dropped or renamed after the last write stops returning the snapshot's data. It reads as NULL, or fails for a NOT NULL SQL column, or triggers "the connector would read no columns" if every mapped column changed. That matches Spark's behaviour, but the PR says "Incompatible changes: None". Please declare it, and add a line to the Iceberg source docs about which schema each mode reads: latest reads the current schema, while snapshot_id/datetime read the snapshot's schema.

Missing tests (medium):

  • pin_snapshot_chooses_the_schema only checks metadata. No test reads rows through the pinned table to show the added column comes back with its initial_default, which is the behaviour this PR is for.
  • The time_travel flag in input.rs (Latest vs snapshot_id/datetime) is untested.
  • Nothing covers the expired-snapshot error path. Here is a test I added for it, and it passes:
+    #[test]
+    fn pin_snapshot_older_and_expired() {
+        let table = table_with_column_added_after_last_write();
+        let _guard = TOKIO.enter();
+        let older = pin_snapshot(&table, 1, false).unwrap();
+        assert_eq!(older.metadata().current_snapshot_id(), Some(1));
+        assert_eq!(table.metadata().current_snapshot_id(), Some(2));
+        assert_eq!(older.metadata().current_schema_id(), 1);
+        let err = pin_snapshot(&table, 42, false).unwrap_err();
+        assert!(err.to_string().contains("no longer has snapshot 42"), "{err}");
+        assert!(pin_snapshot(&table, 42, true).is_err());
+    }

The distributed schema-divergence issue I flagged inline is the main blocker for me.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@swanandx
swanandx marked this pull request as draft November 4, 2026 15:37
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 4, 2026
View reviewed changes
Comment thread crates/iceberg/src/input.rs Outdated
}) => {
// A checkpoint written before schemas were pinned has none; the
// current schema is what that run read.
*self.pinned_schema_id.lock().unwrap() = *schema_id;

@mythical-fred-oss mythical-fred-oss Bot Nov 4, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (high): follow mode freezes the startup schema across restarts. push_follow_boundary writes the schema pinned at startup into every follow checkpoint. On restart, this arm restores it, and prepare_snapshot_query then builds used_columns from it, and the follow loop projects changed files by those columns. So a column added while following never gets ingested again, even after a restart. Before this PR, the resume used the last followed snapshot's schema, so a restart did pick up additive changes, as the comment in follow_loop_inner promises. And if that old schema is ever removed from the metadata, the restart fails with "no longer has schema N". Reproduced with this test, which fails with Some(0) != Some(1):

*endpoint.last_resume_status.lock().unwrap() =
    Some(super::IcebergResumeInfo::follow(2, Some(0)));
endpoint.resolved_snapshot_id(&table).unwrap();
assert_eq!(endpoint.pinned_schema_id(), Some(table.metadata().current_schema_id()));

Suggestion: when following is set, record None (or resolve the current schema on resume), and keep the pin only for snapshot-phase checkpoints.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mythical-fred-oss

mythical-fred-oss Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

What I ran: cargo test --locked -p feldera-iceberg: 30/30 pass. cargo clippy and rustfmt --check are clean on both files.

Label Finding
bug (high) Follow-mode checkpoints keep the schema from startup, so after a restart a column added while following is never ingested. Details and a failing test are inline.
missing (medium) Tests don't cover: a follow resume after an additive schema change; a restart from a pre-PR checkpoint (schema_id absent) where prepare_snapshot_query must read the current schema; a datetime selector pinning the snapshot's own schema (only snapshot_id is tested); pinning a snapshot that has expired.
missing (medium) No integration test reads actual data through pin_snapshot. The new tests only check metadata (current_schema / current_snapshot_id), so nothing confirms the added column reads its initial-default instead of NULL. That is the fix itself, and today only the manual Spark workload shows it.
missing (low→medium) A column that used to read NULL now reads its default, which users can see. A line in the Iceberg connector docs or the changelog would help, and the PR's "Documentation updated" box is unchecked.

Gates: commit messages, unsafe code (none) and dependencies (none) are fine. The follow-mode bug should be fixed before merge.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 4, 2026
View reviewed changes
Comment thread crates/iceberg/src/share.rs Outdated
format!("the table no longer has schema {schema_id}"),
));
}
builder = builder.set_current_schema(schema_id)?;

@mythical-fred-oss mythical-fred-oss Bot Nov 4, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (high): build() re-binds the default sort order and partition spec to the schema set here. If either references a column added after schema_id (e.g. Spark ALTER TABLE … WRITE ORDERED BY added, or partition evolution on a new column), build() fails with Cannot find source column for sort field. This breaks every path that pins an older schema: explicit snapshot_id/datetime, resuming from a pre-PR checkpoint, and resuming a follow checkpoint. Before this PR, scan().snapshot_id() read these fine. I reproduced it with this test, which fails on this branch:

#[test]
fn pin_snapshot_survives_sort_order_on_added_column() {
    use iceberg::spec::{NullOrder, SortDirection, SortField, Transform};
    let table = table_with_column_added_after_last_write();
    let _guard = TOKIO.enter();
    let order = SortOrder::builder().with_order_id(1)
        .with_sort_field(SortField { source_id: 2, transform: Transform::Identity,
            direction: SortDirection::Ascending, null_order: NullOrder::First })
        .build_unbound().unwrap();
    let metadata = TableMetadataBuilder::new_from_metadata(table.metadata().clone(), None)
        .add_sort_order(order).unwrap().set_default_sort_order(-1).unwrap()
        .build().unwrap().metadata;
    let table = Table::builder().runtime(Runtime::try_current().unwrap()).metadata(metadata)
        .identifier(table.identifier().clone()).file_io(table.file_io().clone())
        .build().unwrap();
    pin_snapshot(&table, 1, Some(0)).unwrap(); // Err: Cannot find source column for sort field
}

Reads don't need the write order or default spec, so you could reset them to unsorted/unpartitioned before calling build(). Please add a partition-spec variant of the test too.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 4, 2026
View reviewed changes
Comment thread crates/iceberg/src/input.rs Outdated
schema_id: Option<i32>,
}

// A host running an older version published a bare snapshot id.

@mythical-fred-oss mythical-fred-oss Bot Nov 4, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (medium): An older host 0 scanned with scan().snapshot_id(id), so it used the snapshot's own schema. This path leaves pinned_schema_id as None, and pin_snapshot(.., None) then reads with the current schema. In a mixed-version rollout, the hosts would split one snapshot across two schemas, which is the problem this PR is meant to prevent. The fallback should use snapshot_by_id(id).schema_id(), as the old-checkpoint path at resolved_snapshot_id already does. The test comment at line 3723 ("the current schema is what it read") is also wrong.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 4, 2026
View reviewed changes
Comment thread crates/iceberg/src/input.rs Outdated
InputQueueEntry::new_with_aux(
Utc::now(),
QueueEntry::ResumeInfo(Some(IcebergResumeInfo::follow(snapshot_id))),
QueueEntry::ResumeInfo(Some(IcebergResumeInfo::follow(

@mythical-fred-oss mythical-fred-oss Bot Nov 4, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

pitfall (medium): Follow checkpoints now carry the startup schema id forward with each later snapshot id. On restart, prepare_snapshot_query pins (N, startup_schema), so used_columns never includes a column added while following. Before this PR, a restart picked up snapshot N's schema. If that's intended, please document it. Otherwise, follow boundaries should record the schema current at N. There's no test for restarting a follow-mode pipeline after a schema change.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mythical-fred-oss

mythical-fred-oss Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Review: pin the Iceberg schema alongside the snapshot

What I ran

Step Result
cargo check --locked -p feldera-iceberg --tests ✅
cargo test --locked -p feldera-iceberg --lib ✅ 31/31
pre-commit run --files on the two changed files ✅
New test pin_snapshot_survives_sort_order_on_added_column (in the inline comment) ❌ fails: Cannot find source column for sort field

Gates

  • missing (high): The change crosses a system boundary and changes state across restarts, so it needs an integration test (checklist box unchecked). The manual Spark v3 run is good, but nothing automated covers an actual scan returning the column default, or a checkpoint → restart → same schema.
  • missing (medium): The PR changes what users see: a column added after the last write now reads its default instead of NULL, and explicit snapshot_id/datetime keep the snapshot's schema. Neither the docs nor the changelog mention this (docs box unchecked). Please add a note to docs/connectors/sources/iceberg.md.

Uncovered cases

  1. Older schema pinned while the default sort order or partition spec references a newer column. This fails today; see the inline comment.
  2. A follow-mode restart after ADD COLUMN.
  3. A mixed-version distributed start where host 0 publishes a bare id. The test asserts the wrong schema.
  4. A pinned schema id removed by metadata cleanup. The error path exists, but no test covers it through prepare_snapshot_query.

Request changes for the high-severity bug and the missing integration test.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 4, 2026
View reviewed changes
Comment thread crates/iceberg/src/share.rs Outdated
// The sort order is bound to the schema, and it may sort by a column
// this one does not have. Reads ignore it, so drop it.
builder = builder
.set_default_sort_order(0)?

@mythical-fred-oss mythical-fred-oss Bot Nov 4, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (high): Sort order 0 isn't guaranteed to exist. Java and pyiceberg give a table created with a sort order id 1 and store only that order, and so does TableMetadataBuilder::new. On such a table this returns Cannot set default sort order to unknown order with id: '0'. Latest now always pins current_schema_id(), so every snapshot-mode start on a sorted-at-creation table fails, and that worked before this PR. I reproduced it locally with the test below, which fails on this head. Suggested fix: skip the whole block when schema_id == metadata.current_schema_id(). When the schema really changes, add SortOrder::unsorted_order() with add_sort_order before switching to it, instead of assuming id 0.

repro test
#[test]
fn qa_pin_snapshot_table_created_sorted() {
    // TableMetadataBuilder::new(schema, unpartitioned, SortOrder{order_id:1, field on id 1}, ...)
    // + add_snapshot(1); assert!(sort_order_by_id(0).is_none());
    pin_snapshot(&table, 1, Some(table.metadata().current_schema_id())).unwrap();
    // => DataInvalid: Cannot set default sort order to unknown order with id: '0'
}

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread crates/iceberg/src/share.rs Outdated
// one rather than this table's.
.runtime(Runtime::try_current()?)
.readonly(table.readonly())
.metadata(builder.build()?.metadata)

@mythical-fred-oss mythical-fred-oss Bot Nov 4, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (medium): The default partition spec has the same problem as the sort order. build() binds the default spec to the new current schema, so it fails after partition evolution onto a column added after the snapshot (ADD COLUMN region; ADD PARTITION FIELD region, no write since). That breaks snapshot_id/datetime time travel, resuming from a pre-PR checkpoint and the bare-choice fallback with Cannot find partition source field with id 2 in schema. Reads never use the default spec, so it can be swapped for an unpartitioned spec the same way. Repro: qa_pin_snapshot_partition_on_added_column (add the spec with add_default_partition_spec, then pin_snapshot(&table, 1, Some(0))).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread crates/iceberg/src/input.rs Outdated
// Host 0's table had no snapshot, so there is no schema either.
assert_eq!(chosen(startup_choice_value(None, None)), (Some(None), None));
// A host running an older version published a bare snapshot id; the
// current schema is what it read.

@mythical-fred-oss mythical-fred-oss Bot Nov 4, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

nit: This comment contradicts the code. resolved_snapshot_id gives a bare choice the snapshot's schema (see a_bare_choice_from_an_older_host_reads_the_snapshot_schema). Here pinned_schema_id is None only because chosen_snapshot_id leaves it for the caller to fill in. Likewise in share.rs:656, "only pinning an older schema fails" no longer matches: both calls succeed.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mythical-fred-oss

mythical-fred-oss Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

What I ran

Step Result
cargo fmt -p feldera-iceberg --check ✅ clean
cargo test --locked -p feldera-iceberg --lib (PR tests) ✅ 34/34 pass
Two QA tests I added for pin_snapshot ❌ both fail (details inline)

Bug (high), request changes: pin_snapshot now always runs set_current_schema and set_default_sort_order(0), even for Latest, where the schema doesn't change. A table created with a sort order has no order 0, so every snapshot-mode start on such a table fails. Before this PR that read worked. The default partition spec has the same problem when the read goes back to an older schema. Both repros are inline.

Missing (medium), test cases the PR doesn't cover:

  1. A table with no sort order 0 (sorted at creation), in snapshot latest mode.
  2. A default partition spec on a column added after the snapshot, read with snapshot_id/datetime or resumed from a pre-PR checkpoint.
  3. Distributed snapshot mode where the schema changes between host 0's choice and a follower opening the table. Only the JSON handoff is unit-tested, not that the follower actually scans with the chosen schema.
  4. A real scan (not only metadata) showing the added column reads its initial_default and not NULL. That's the headline fix, and it's covered only by the manual QA run.

Pitfall (medium): in a mixed-version distributed rollout, an old follower can't parse the new object-shaped startup choice and fails with "invalid snapshot ID". The PR only handles the reverse direction (new follower, old host 0). If hosts can run different builds, this needs a note in the PR's breaking-changes section.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@swanandx
swanandx force-pushed the iceberg-bump-4dd8c94 branch from 75d0c64 to 9291b9f Compare November 4, 2026 17:30
@swanandx
swanandx force-pushed the iceberg-snapshot-schema-pr branch from ea1ed1b to 2b7fd5c Compare November 4, 2026 19:58
Base automatically changed from iceberg-bump-4dd8c94 to main November 4, 2026 22:21
@swanandx
swanandx force-pushed the iceberg-snapshot-schema-pr branch from 2b7fd5c to b8107f2 Compare November 5, 2026 07:55
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 5, 2026
View reviewed changes
Comment thread crates/iceberg/src/input.rs
snapshot_id.is_some() && snapshot_id == table.metadata().current_snapshot_id();
let provider = match snapshot_id {
Some(snapshot_id) => {
Some(snapshot_id) if !pin_is_current => {

@mythical-fred-oss mythical-fred-oss Bot Nov 5, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (high): distributed snapshot mode now fails when the pin is current. provider.schema() is now the current schema, and it is handed to IcebergShareTableProvider below. IcebergShareScan::read still calls table.scan().snapshot_id(id), and with the bumped iceberg-rust that resolves column names against the snapshot's schema. So any renamed or added column in used_columns fails at build(). I confirmed this with a unit test in share.rs: the snapshot schema has id, the current schema renames field 1 to renamed, the current snapshot is pinned, and select(["renamed"]) returns:

DataInvalid => Column renamed not found in table

Before this PR, the same table gave a NULL column. Now the multihost pipeline fails. The share scan needs the same pin_is_current decision. One option is to pass a "read current" flag, or skip .snapshot_id() when it equals the host's current_snapshot_id(). Take care if another host's copy of the table has moved on. A distributed variant of rest_snapshot_pin_reads_current_schema would cover this.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mythical-fred-oss

mythical-fred-oss Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Review of #7835: snapshot mode reads the current schema when the pinned snapshot is the current one

What I ran

Step Result
cargo check --locked -p feldera-iceberg ✅
cargo test --locked -p feldera-iceberg ✅ 25 passed
pre-commit run on changed files ✅ (I checked out the merge commit before the bot's ruff-format fix, so ruff reformatted it again. The PR head already has that fix.)
rest_snapshot_pin_reads_current_schema ⚠️ not run. It needs the iceberg-tests-follow feature plus an S3/REST catalog.
QA unit test: distributed share scan on a renamed column (not pushed) ❌ Column renamed not found. See the inline comment.

Findings

  • bug (high): in multihost snapshot mode, a pinned current snapshot with a renamed or added column now fails. Before this PR it read NULL. Details are inline.
  • missing (medium): test coverage is incomplete:
    • No test runs the distributed (IcebergShareTableProvider) path.
    • No test adds a column; the only test renames one.
    • No test covers a NOT NULL column that is missing from the snapshot schema (the "parse failure" case in the description).
    • No test pins an older, non-current snapshot. That case still reads the old schema and still loses the column.
  • pitfall (medium): whether a pinned read sees a column now depends on whether anyone has committed a snapshot since the pin. One extra append turns a working pipeline back into NULLs or a parse error. Please state this limitation in the code comment or in the Iceberg connector docs. Even better, use the current schema for any pin that is an ancestor of the current snapshot.

Gates: tests ✅ (S3 integration test only), flakiness ✅ (bounded waits), SQL type coverage ✅ (reuses the full test_table_schema), no unsafe, deps, or CI changes. The correctness bug above should be fixed before merge.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@swanandx
swanandx force-pushed the iceberg-snapshot-schema-pr branch 2 times, most recently from fba7ce9 to 831caf0 Compare November 5, 2026 10:26
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 5, 2026
View reviewed changes
Comment thread crates/iceberg/src/input.rs
// different snapshot, so the hosts could disagree on the schema.
let pin_is_current = self.distributed.is_none()
&& snapshot_id.is_some()
&& snapshot_id == table.metadata().current_snapshot_id();

@mythical-fred-oss mythical-fred-oss Bot Nov 5, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

pitfall (medium): snapshot_id comes from resolved_snapshot_id, which prefers the checkpointed id on resume. If the table gets any new commit before a restart, pin != current and the connector falls back to the old snapshot schema. The same pipeline then reads s before the restart and NULL after it (or fails to parse if s is NOT NULL). The same applies to snapshot_and_follow after it switches to following: used_columns is computed from the old schema here, and follow_loop_inner then maps those names against the current schema in project_field_ids, so the renamed column is silently dropped from every followed snapshot. Could used_columns always come from table.metadata().current_schema(), mapped by field id, with only the DataFusion provider pinned? If not, please add a resume test (pin → rename → append → restart) so the behaviour is at least pinned down.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 5, 2026
View reviewed changes
Comment thread docs.feldera.com/docs/connectors/sources/iceberg.md Outdated
start.
- A distributed connector reads the starting snapshot's schema, so a column
renamed or added since that snapshot reads NULL. A single-host connector
reads the table's current schema. Every host has to read the same

@mythical-fred-oss mythical-fred-oss Bot Nov 5, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (low-medium, docs): "A single-host connector reads the table's current schema" is only true when the pinned snapshot is the current one. An explicit older snapshot_id, a datetime that resolves to an older snapshot, or a resume after the table has advanced all still read the snapshot's schema and lose renamed or added columns. The changelog entry claims the same. Please state the condition.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mythical-fred-oss

mythical-fred-oss Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

What I ran

Check Result
cargo test --locked -p feldera-iceberg (25 unit tests, including the new pinned_share_scan_resolves_names_in_the_snapshot_schema) ✅ pass
pre-commit run --files <changed> ✅ pass
rest_snapshot_pin_reads_current_schema (engine test, needs S3 and a REST catalog) ⏭️ not run here

Gates: tests, docs, changelog and commit message are all fine. No new deps, no unsafe, no CI changes.

Missing test coverage (medium):

  1. Resume after the table has advanced (pin → rename → any append → restart). The code falls back to the old schema here; see the inline comment.
  2. snapshot_and_follow with a rename: check that followed snapshots still populate the renamed column.
  3. A column added (not renamed) since the pin. The changelog claims this case is fixed, but only a rename is tested.
  4. A unit test for the pin_is_current decision itself. Today it is covered only by the S3-gated engine test.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@swanandx
swanandx force-pushed the iceberg-snapshot-schema-pr branch from 831caf0 to 2d86b8e Compare November 5, 2026 10:52
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 5, 2026
View reviewed changes
Comment thread docs.feldera.com/docs/connectors/sources/iceberg.md Outdated

A connector reading the table's current snapshot reads the table's current
schema. A connector reading an older snapshot reads that snapshot's schema, so
a column renamed or added after it reads NULL. This applies to `snapshot_id`

@mythical-fred-oss mythical-fred-oss Bot Nov 5, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (medium, docs): "reads NULL" only holds for nullable SQL columns. For a NOT NULL column the connector fails to parse instead. Your own rest_resume_past_the_pin_reads_the_snapshot_schema asserts missing field \s``, and the changelog entry says so too. Please say the same here and at line 334.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 5, 2026
View reviewed changes
Comment thread crates/iceberg/src/input.rs
let pin_is_current = self.distributed.is_none()
&& snapshot_id.is_some()
&& snapshot_id == table.metadata().current_snapshot_id();
let provider = match snapshot_id {

@mythical-fred-oss mythical-fred-oss Bot Nov 5, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

pitfall (medium): The current schema can also drop or retype a column, not just add or rename one. Example: a column is dropped and no snapshot is committed afterwards. Before this change, the pinned-current read still returned that column. Now used_columns leaves it out, so it reads NULL, or fails to parse if it is NOT NULL. That is probably the right semantics for "read the table as it is", but it changes behaviour in a way the changelog does not mention, and no test covers it. Please mention dropped columns in the changelog. Ideally also add a create-dropped fixture op next to create-renamed.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mythical-fred-oss

mythical-fred-oss Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

What I ran

Check Result
cargo test --locked -p feldera-iceberg share (includes new pinned_share_scan_resolves_names_in_the_snapshot_schema) pass
cargo check --locked -p dbsp_connectors --tests --features iceberg-tests-follow pass
pre-commit run --files <changed> pass
New engine tests (need S3 + REST catalog) not run here

The fix is narrow and looks correct. try_new_from_table uses the same Table object whose current_snapshot_id was compared, so the data cannot race to a different snapshot. Gates: the tests, docs and changelog are present, there are no new dependencies, and there is no unsafe.

Missing tests (medium):

  • A column added after the current snapshot, with no snapshot committed since. The docs and changelog headline this case, but only rename is tested.
  • A column dropped after the current snapshot (see inline comment).
  • Plain follow mode pinned to the current snapshot. used_columns now comes from the current schema there too, and that changes which columns the follow path projects from changed files.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@swanandx
swanandx force-pushed the iceberg-snapshot-schema-pr branch from 2d86b8e to 7e9e6e6 Compare November 5, 2026 11:19
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Nov 5, 2026
View reviewed changes
Comment thread crates/iceberg/src/input.rs
// resolves column names against that snapshot's schema. Each host also
// decides this from its own copy of the table, which may sit on a
// different snapshot, so the hosts could disagree on the schema.
let pin_is_current = self.distributed.is_none()

@mythical-fred-oss mythical-fred-oss Bot Nov 5, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

bug (medium): whether this is true depends on whether the table has moved since the pipeline started, so one logical read can switch schemas partway through. Example: snapshot mode with a nullable renamed column. The pipeline suspends in the middle of the initial snapshot, so the checkpoint is snapshot_progress(X, ts). Someone appends to the table, and the pipeline resumes. The first half of the snapshot used the current schema and got values. The rest falls back to X's schema, so the column reads NULL. For a dropped column it goes the other way: the stale values come back after resume. Before this PR the read was consistently wrong; now the output is silently inconsistent, which is harder to spot. One option is to record in the resume info that the read uses the current schema (or record the schema id) and reuse that on resume, rather than re-checking it against current_snapshot_id(). Whichever way you go, a test for resuming mid-snapshot with a nullable renamed column would cover it. rest_resume_past_the_pin_reads_the_snapshot_schema only covers the NOT NULL / post-EOI case, where both runs fail loudly.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mythical-fred-oss

mythical-fred-oss Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

What I ran

Check Result
cargo test --locked -p feldera-iceberg (25 tests, including the new pinned_share_scan_resolves_names_in_the_snapshot_schema) ✅ pass
cargo check --locked -p dbsp_connectors --tests --features iceberg-tests-follow ✅ compiles
pre-commit run on the changed files ✅ pass
New rest_* engine tests ⚠️ not run: they need a REST catalog and S3

Gates: the commit message, docs and changelog are fine. The tests are gated and have no unjustified sleeps. Schema resolution is not an encoding change, so the SQL type coverage gate doesn't apply.

Missing (medium): uncovered cases

  1. A suspend in the middle of a snapshot, then resume after the table advances, with a nullable renamed column. The output silently mixes values and NULLs (see the inline comment).
  2. follow mode starting at the current snapshot: used_columns now comes from the current schema, which matches the current_schema() used in follow_loop. Nothing tests this.
  3. A column added (not renamed) after the current snapshot. The changelog and docs call this out, but only rename and drop are tested.

Pitfall (medium): a NOT NULL column that was dropped from Iceberg used to keep returning its old values. Now the read fails with missing field. That is arguably correct and it is in the changelog, but existing pipelines will see it as a behaviour change. Consider tagging it as a behaviour change, not only a bug fix.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@swanandx
[iceberg] Read the current schema in snapshot mode
17a5786
Snapshot mode pins the snapshot it starts from, and a pinned id makes the
reader travel back to that snapshot's schema. A column renamed or added
since then is absent from it, so the read loses the column: it reads NULL
when the column is nullable, and fails to parse when it is required.

When the pin is the table's current snapshot there is nothing to travel
back to, so read the table as it is. Not for a distributed read, where
every host has to read the same pinned snapshot.

Signed-off-by: Swanand Mulay <[email protected]>
@swanandx
swanandx force-pushed the iceberg-snapshot-schema-pr branch from 7e9e6e6 to 17a5786 Compare November 5, 2026 13:14

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

Reviewers

@mythical-fred-oss mythical-fred-oss[bot] mythical-fred-oss[bot] left review comments

At least 1 approving review is required to merge this pull request.

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@swanandx
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.