Skip to content

feat(plugin-iceberg): Add $snapshot_sequence_number as hidden column in iceberg table#26408

Open
agrawalreetika wants to merge 1 commit intoprestodb:masterfrom
agrawalreetika:snapshot_sequence_number
Open

feat(plugin-iceberg): Add $snapshot_sequence_number as hidden column in iceberg table#26408
agrawalreetika wants to merge 1 commit intoprestodb:masterfrom
agrawalreetika:snapshot_sequence_number

Conversation

@agrawalreetika
Copy link
Copy Markdown
Member

@agrawalreetika agrawalreetika commented Oct 22, 2025

Description

Add $snapshot_sequence_number as a hidden column in the iceberg table

Motivation and Context

  • Add $snapshot_sequence_number extra column in $snapshots metadata table
  • Add $snapshot_sequence_number as a hidden column in the iceberg table

As $snapshot_id is not incremental so while calculating Delta between 2 snapshots (With query like WHERE $snapshot_id BETWEEN snap1 AND snap2) would return wrong results since comparison is not valid.

Impact

Get the delta between 2 snapshots using $snapshot_sequence_number as column.

Here these are the covered options -

  1. WHERE "$snapshot_sequence_number" >= X (Get delta from start snapshot to X)
  2. WHERE "$snapshot_sequence_number" BETWEEN X AND Y (Get delta from start snapshot to X)
  3. WHERE "$snapshot_sequence_number" = X
  4. WHERE "$snapshot_sequence_number" > X

Limitation -
Above are using newIncrementalAppendScan() Iceberg API which is append-only snapshot operation mode. Also IncrementalChangelogScan has limitation and doesn't support delta for snapshots with operation modes delete & overwrite and fails here.

Test Plan

Tests Added

  • Get $snapshot_sequence_number from $snapshots metadata table
  • Now we can use $snapshot_sequence_number column for filter to find delta on a table

Contributor checklist

  • Please make sure your submission complies with our contributing guide, in particular code style and commit standards.
  • PR description addresses the issue accurately and concisely. If the change is non-trivial, a GitHub Issue is referenced.
  • Documented new properties (with its default value), SQL syntax, functions, or other functionality.
  • If release notes are required, they follow the release notes guidelines.
  • Adequate tests were added if applicable.
  • CI passed.
  • If adding new dependencies, verified they have an OpenSSF Scorecard score of 5.0 or higher (or obtained explicit TSC approval for lower scores).

Release Notes

Please follow release notes guidelines and fill in the release notes below.

== RELEASE NOTES ==

Iceberg Connector Changes
* Add ``$snapshot_sequence_number`` as a hidden column in the Iceberg table.

@agrawalreetika agrawalreetika self-assigned this Oct 22, 2025
@prestodb-ci prestodb-ci added the from:IBM PR from IBM label Oct 22, 2025
@prestodb-ci prestodb-ci requested review from a team, nishithakbhaskaran and pramodsatya and removed request for a team October 22, 2025 21:45
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Oct 22, 2025

Reviewer's Guide

This PR adds $snapshot_sequence_number as a hidden metadata column in Iceberg tables and snapshots, implements utility methods and metadata handling to support sequence-based snapshot filtering, extends split management for incremental scans with sequence numbers, updates the snapshots system table, and includes tests and documentation for predicate pushdown and hidden column enforcement.

Sequence diagram for incremental scan with $snapshot_sequence_number filtering

sequenceDiagram
    participant Q as "Query Engine"
    participant M as "IcebergAbstractMetadata"
    participant U as "IcebergUtil"
    participant S as "IcebergSplitManager"
    participant T as "Iceberg Table"
    Q->>M: Request table layout with $snapshot_sequence_number filter
    M->>U: Validate and fetch snapshots by sequence number
    U->>T: Get snapshot by sequence number
    U-->>M: Return start/end snapshots
    M->>S: Create IcebergTableHandle with fromInclusive flag
    S->>T: Plan incremental scan from start to end snapshot
    S-->>Q: Return splits with snapshotSequenceNumber
Loading

Class diagram for new and updated Iceberg metadata and split classes

classDiagram
    class IcebergMetadataColumn {
        <<enum>>
        DATA_SEQUENCE_NUMBER
        IS_DELETED
        DELETE_FILE_PATH
        SNAPSHOT_SEQUENCE_NUMBER
    }
    class IcebergColumnHandle {
        +isSnapshotSequenceNumberColumn()
        +SNAPSHOT_SEQUENCE_NUMBER_COLUMN_HANDLE
        +SNAPSHOT_SEQUENCE_NUMBER_COLUMN_METADATA
    }
    class IcebergTableHandle {
        +boolean fromInclusive
        +withUpdatedIcebergTableName(IcebergTableName, boolean)
    }
    class IcebergSplit {
        +long snapshotSequenceNumber
        +getSnapshotSequenceNumber()
    }
    class IcebergSplitSource {
        +long snapshotSequenceNumber
    }
    class ChangelogSplitSource {
        +long snapshotSequenceNumber
    }
    class EqualityDeletesSplitSource {
        +long snapshotSequenceNumber
    }
    IcebergMetadataColumn <|-- IcebergColumnHandle
    IcebergTableHandle --> IcebergSplitSource
    IcebergSplitSource --> IcebergSplit
    ChangelogSplitSource --> IcebergSplit
    EqualityDeletesSplitSource --> IcebergSplit
Loading

File-Level Changes

Change Details Files
Define and register $snapshot_sequence_number as a hidden metadata column
  • Add new metadata enum entry and column handle
  • Register column in getColumnHandles and ConnectorTableMetadata
  • Prevent direct selection in PageSourceProvider when projected but not in filters
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergMetadataColumn.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergColumnHandle.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergPageSourceProvider.java
Extend IcebergUtil with sequence number support and scan preparation
  • Implement getSnapshotBySequenceNumber, validateEndSnapshotId, validateAndFetchEndSnapshot
  • Enforce append-only operations via hasOnlyAppendOperations
  • Add prepareIncrementalScan and prepareBetweenScan methods
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergUtil.java
Push down predicates on $snapshot_sequence_number in metadata layout
  • Detect single-value and range domains on the sequence column
  • Update IcebergTableHandle to use INCREMENTAL scans via utility methods
  • Throw for unsupported predicate patterns
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java
Support INCREMENTAL table type and carry sequence numbers through splits
  • Add INCREMENTAL enum and fromInclusive flag to table handle
  • Branch in SplitManager to use newIncrementalAppendScan for INCREMENTAL type
  • Propagate snapshotSequenceNumber in SplitSource and IcebergSplit
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergTableType.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergTableHandle.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergSplitManager.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergSplitSource.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/changelog/ChangelogSplitSource.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/equalitydeletes/EqualityDeletesSplitSource.java
Enhance SnapshotsTable to include sequence numbers
  • Add snapshot_sequence_number column metadata
  • Compute sequence number for each snapshot row in buildPages
presto-iceberg/src/main/java/com/facebook/presto/iceberg/SnapshotsTable.java
Update tests for hidden column and predicate pushdown
  • Add tests for hidden column enforcement and basic filtering
  • Add predicate pushdown tests for =, >=, >, BETWEEN and unsupported cases
  • Extend system table test to verify new column and update StatisticsUtil test
presto-iceberg/src/test/java/com/facebook/presto/iceberg/IcebergDistributedTestBase.java
presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergSystemTables.java
presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestStatisticsUtil.java
Update documentation for connector and release notes
  • Document new hidden column in connector Sphinx docs
  • Add release note entry for $snapshot_sequence_number
presto-docs/src/main/sphinx/connector/iceberg.rst

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • getSnapshotBySequenceNumber returns null on missing snapshots, which may lead to NPEs in callers; consider throwing a clear PrestoException for invalid sequence numbers instead of returning null.
  • There is repeated validation and append‐only checks across prepareIncrementalScan and prepareBetweenScan—extract and consolidate shared logic into a single helper to reduce duplication and ensure consistent error handling.
  • Streaming through icebergTable.snapshots() for each lookup can become costly on tables with many snapshots; consider caching the snapshot list or using a map for direct sequence‐number lookup to improve performance.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- getSnapshotBySequenceNumber returns null on missing snapshots, which may lead to NPEs in callers; consider throwing a clear PrestoException for invalid sequence numbers instead of returning null.
- There is repeated validation and append‐only checks across prepareIncrementalScan and prepareBetweenScan—extract and consolidate shared logic into a single helper to reduce duplication and ensure consistent error handling.
- Streaming through icebergTable.snapshots() for each lookup can become costly on tables with many snapshots; consider caching the snapshot list or using a map for direct sequence‐number lookup to improve performance.

## Individual Comments

### Comment 1
<location> `presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergUtil.java:460` </location>
<code_context>
                 .collect(toImmutableMap(Entry::getKey, Entry::getValue));
     }

+    public static Snapshot getSnapshotBySequenceNumber(Table icebergTable, long sequenceNumber)
+    {
+        if (icebergTable == null) {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Returning null instead of Optional may lead to NPEs.

Consider returning Optional<Snapshot> instead of null to clearly indicate when no snapshot is found and prevent potential NullPointerExceptions.

Suggested implementation:

```java
    public static Optional<Snapshot> getSnapshotBySequenceNumber(Table icebergTable, long sequenceNumber)
    {
        if (icebergTable == null) {
            throw new IllegalArgumentException("icebergTable is null");
        }
        return StreamSupport.stream(icebergTable.snapshots().spliterator(), false)
                .filter(s -> s.sequenceNumber() == sequenceNumber)
                .findFirst();
    }

```

If there are any callers of `getSnapshotBySequenceNumber`, you will need to update them to handle the `Optional<Snapshot>` return type instead of a potentially null `Snapshot`.
</issue_to_address>

### Comment 2
<location> `presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java:328` </location>
<code_context>
+                long seqNum = ((Number) range.getLowBoundedValue()).longValue();
+                handle = handle.withUpdatedIcebergTableName(prepareIncrementalScan(icebergTable, startSnapshot, name, seqNum));
+            }
+            else if (range.isLowInclusive() && range.isHighInclusive()) {
+                // WHERE $snapshot_id BETWEEN X AND Y
+                handle = handle.withUpdatedIcebergTableName(prepareBetweenScan(icebergTable, name, range));
</code_context>

<issue_to_address>
**question:** Check for BETWEEN logic may not handle exclusive bounds.

Exclusive bounds currently result in an unsupported predicate exception. Please clarify this in the error message or consider supporting exclusive bounds.
</issue_to_address>

### Comment 3
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/IcebergDistributedTestBase.java:2286-2287` </location>
<code_context>
                 "VALUES (true, 1, 'a'), (false, 1, 'b'), (false, 2, 'a'), (false, 3, 'a')");
     }

+    @Test
+    public void testSnapshotSequenceNumberHiddenColumnSimple()
+    {
+        String tableName = "test_snapshot_seq_num_hidden_" + randomTableSuffix();
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding tests for tables with no snapshots.

Please add a test for a newly created table with no snapshots to verify the behavior of $snapshot_sequence_number in this scenario.
</issue_to_address>

### Comment 4
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/IcebergDistributedTestBase.java:2299-2307` </location>
<code_context>
+            long sequenceNumber = iceberTable.currentSnapshot().sequenceNumber();
+            iceberTable.refresh();
+
+            assertQuery("SELECT COUNT(\"$snapshot_sequence_number\") FROM " + tableName, "VALUES 2");
+            assertQuery("SELECT \"$snapshot_sequence_number\", * FROM " + tableName, "VALUES " +
+                    "(" + sequenceNumber + ", 100, 'a')," +
+                    "(" + sequenceNumber + ", 200, 'b')");
</code_context>

<issue_to_address>
**suggestion (testing):** Test for $snapshot_sequence_number after table modifications (e.g., DELETE, UPDATE, OVERWRITE).

Please add tests for DELETE, UPDATE, and OVERWRITE operations to ensure $snapshot_sequence_number behaves as expected, given these are not supported for delta queries.

```suggestion
            assertQuery("SELECT COUNT(\"$snapshot_sequence_number\") FROM " + tableName, "VALUES 2");
            assertQuery("SELECT \"$snapshot_sequence_number\", * FROM " + tableName, "VALUES " +
                    "(" + sequenceNumber + ", 100, 'a')," +
                    "(" + sequenceNumber + ", 200, 'b')");

            // Test DELETE
            assertUpdate("DELETE FROM " + tableName + " WHERE id = 100", 1);
            iceberTable.refresh();
            long deleteSequenceNumber = iceberTable.currentSnapshot().sequenceNumber();
            assertQuery("SELECT COUNT(\"$snapshot_sequence_number\") FROM " + tableName, "VALUES 1");
            assertQuery("SELECT \"$snapshot_sequence_number\", * FROM " + tableName, "VALUES " +
                    "(" + deleteSequenceNumber + ", 200, 'b')");

            // Test UPDATE
            assertUpdate("UPDATE " + tableName + " SET data = 'bb' WHERE id = 200", 1);
            iceberTable.refresh();
            long updateSequenceNumber = iceberTable.currentSnapshot().sequenceNumber();
            assertQuery("SELECT COUNT(\"$snapshot_sequence_number\") FROM " + tableName, "VALUES 1");
            assertQuery("SELECT \"$snapshot_sequence_number\", * FROM " + tableName, "VALUES " +
                    "(" + updateSequenceNumber + ", 200, 'bb')");

            // Test OVERWRITE
            assertUpdate("INSERT OVERWRITE " + tableName + " VALUES (300, 'c')", 1);
            iceberTable.refresh();
            long overwriteSequenceNumber = iceberTable.currentSnapshot().sequenceNumber();
            assertQuery("SELECT COUNT(\"$snapshot_sequence_number\") FROM " + tableName, "VALUES 1");
            assertQuery("SELECT \"$snapshot_sequence_number\", * FROM " + tableName, "VALUES " +
                    "(" + overwriteSequenceNumber + ", 300, 'c')");
        }
        finally {
            assertQuerySucceeds("DROP TABLE " + tableName);
        }
    }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

.collect(toImmutableMap(Entry::getKey, Entry::getValue));
}

public static Snapshot getSnapshotBySequenceNumber(Table icebergTable, long sequenceNumber)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Returning null instead of Optional may lead to NPEs.

Consider returning Optional instead of null to clearly indicate when no snapshot is found and prevent potential NullPointerExceptions.

Suggested implementation:

    public static Optional<Snapshot> getSnapshotBySequenceNumber(Table icebergTable, long sequenceNumber)
    {
        if (icebergTable == null) {
            throw new IllegalArgumentException("icebergTable is null");
        }
        return StreamSupport.stream(icebergTable.snapshots().spliterator(), false)
                .filter(s -> s.sequenceNumber() == sequenceNumber)
                .findFirst();
    }

If there are any callers of getSnapshotBySequenceNumber, you will need to update them to handle the Optional<Snapshot> return type instead of a potentially null Snapshot.

@agrawalreetika agrawalreetika changed the title Add $snapshot_sequence_number as hidden column in iceberg table feat(plugin-iceberg): Add $snapshot_sequence_number as hidden column in iceberg table Oct 22, 2025
@hantangwangd
Copy link
Copy Markdown
Member

@agrawalreetika thanks for this change. A quick question: can you help me understand the decision behind using an inclusive lower bound for the incremental query rather than an exclusive one? For our use cases like refreshing materialized views, wouldn't the latter be more appropriate?

@agrawalreetika
Copy link
Copy Markdown
Member Author

@agrawalreetika thanks for this change. A quick question: can you help me understand the decision behind using an inclusive lower bound for the incremental query rather than an exclusive one? For our use cases like refreshing materialized views, wouldn't the latter be more appropriate?

@hantangwangd So my understanding here is when we have -

  • For WHERE $snapshot_sequence_number >= X, we should take the table state from start to X (So used inclusive)
  • For WHERE $snapshot_sequence_number BETWEEN X AND Y , we need to have table state from X+1 to Y here

Lmk if I missed something here?

@hantangwangd
Copy link
Copy Markdown
Member

For WHERE $snapshot_sequence_number >= X, we should take the table state from start to X (So used inclusive)

Thanks for the explanation, I'm trying to make sure I understand the filter semantics correctly. As I understand it, WHERE $snapshot_sequence_number >= X means getting the incremental data of the table from X-1 to the newest, is this correct?

If that's the case, what do you think about supporting and using WHERE $snapshot_sequence_number > X? It seems to map more directly to getting the incremental data of the table from X to the newest. What do you think? Please correct me if I've misunderstood anything.

@PingLiuPing
Copy link
Copy Markdown
Contributor

@agrawalreetika What's the strategy for the $snapshot_id hidden column PR #26189? will it be discarded?

@agrawalreetika agrawalreetika force-pushed the snapshot_sequence_number branch from b7ab30d to 07ba3fd Compare October 23, 2025 11:10
@agrawalreetika
Copy link
Copy Markdown
Member Author

agrawalreetika commented Oct 23, 2025

For WHERE $snapshot_sequence_number >= X, we should take the table state from start to X (So used inclusive)

Thanks for the explanation, I'm trying to make sure I understand the filter semantics correctly. As I understand it, WHERE $snapshot_sequence_number >= X means getting the incremental data of the table from X-1 to the newest, is this correct?

If that's the case, what do you think about supporting and using WHERE $snapshot_sequence_number > X? It seems to map more directly to getting the incremental data of the table from X to the newest. What do you think? Please correct me if I've misunderstood anything.

WHERE $snapshot_sequence_number >= X - @hantangwangd, you are right about this, I think committed different condition changes, updated it now. Also covered WHERE $snapshot_sequence_number > X - Where we take X+1 to the latest for delta.

I have updated description with the covered scenario, please take a look and lmk if anything is missing.

@agrawalreetika
Copy link
Copy Markdown
Member Author

@agrawalreetika What's the strategy for the $snapshot_id hidden column PR #26189? will it be discarded?

I have moved it back to Draft, as the current implementation has limitations with regard to comparison. And snapshot_sequence_number can cover those in this PR.

Copy link
Copy Markdown
Contributor

@steveburnett steveburnett left a comment

Choose a reason for hiding this comment

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

Thanks for the doc! What's here looks good in a local build.

Should $snapshot_sequence_number also be added in https://github.com/prestodb/presto/blob/master/presto-docs/src/main/sphinx/connector/iceberg.rst#extra-hidden-metadata-columns?

@steveburnett
Copy link
Copy Markdown
Contributor

Thanks for the release note! Formatting nit:

== RELEASE NOTES ==

Iceberg Connector Changes
* Add ``$snapshot_sequence_number`` as a hidden column in the Iceberg table.

@PingLiuPing
Copy link
Copy Markdown
Contributor

@agrawalreetika Can you point me where is the snapshot_sequence_number in the Iceberg spec? Is it https://iceberg.apache.org/spec/?h=sequence+number#sequence-numbers ?

@agrawalreetika agrawalreetika force-pushed the snapshot_sequence_number branch from 07ba3fd to 6df6146 Compare October 24, 2025 07:49
@hantangwangd
Copy link
Copy Markdown
Member

hantangwangd commented Oct 24, 2025

Hi @agrawalreetika, here are a few high-level thoughts that came to my mind. They're pretty preliminary and are intended for discussion, so please let me know what you think:

  1. The $snapshot_sequence_number column doesn't appear to be a user-facing field, as its returned values for the same data can change between queries with different filter conditions on it. The returned values of this column can be very confusing for users. Therefore, is it better to make $snapshot_sequence_number a completely hidden/internal column?

  2. Regarding filters on $snapshot_sequence_number, is it better to make sure that they either are fully pushed down or result in an error? This way, the TableScanNode needn't to return results that include this column to the Presto engine, while the FilterNode do not hold any filter conditions involving it. By enforcing this, we can directly use the $snapshot_id column without encountering the potential comparison issues you mentioned above.

  3. Given that a filter on $snapshot_sequence_number switches the table to INCREMENTAL mode, I'm wondering if the current semantics of WHERE "$snapshot_sequence_number" = X are quite correct. Shouldn't it actually represent the newly appended data present in that specified snapshot?

  4. The data sequence numbers for consecutive snapshots are not strictly consecutive (there can be gaps). Therefore, simply adding 1 to a sequence number to find the next snapshot is unreliable and may cause issues. A more robust approach would be to use .fromSnapshotExclusive(fromSnapshot) to handle conditions like WHERE "$snapshot_sequence_number" > X.

@tdcmeehan tdcmeehan self-assigned this Oct 24, 2025
@PingLiuPing
Copy link
Copy Markdown
Contributor

I agree with @hantangwangd comment's. Especially on 3. I wonder the semantic correctness of WHERE "$snapshot_sequence_number" = X. From iceberg spec, I think it is used to track the relative “age” of data and delete files. From Iceberg source code I think 4 is correct.

@agrawalreetika
Copy link
Copy Markdown
Member Author

agrawalreetika commented Oct 29, 2025

@hantangwangd Thank you for your detailed feedback!

  1. $snapshot_sequence_number is currently defined as a hidden column (via ColumnMetadata.setHidden(true)), which ensures it doesn’t appear in SHOW COLUMNS, DESCRIBE, or SELECT *. However, it remains explicitly queryable (e.g., in WHERE clauses) — like other hidden metadata columns.

  2. I’ll revisit this once we finalize the semantics and correctness aspects discussed in points 3 and 4

  3. Your observation makes sense — we can make the incremental scan’s lower bound exclusive and upper bound inclusive.

Here’s how the supported scenarios would look:

a. `WHERE "$snapshot_sequence_number" >= X` - Get delta from snapshot (X – 1) (exclusive) to the latest snapshot.
b. `WHERE "$snapshot_sequence_number" BETWEEN X AND Y `- Get delta between snapshots X (exclusive) and Y (inclusive)
c. `WHERE "$snapshot_sequence_number" = X `(Get delta from X-1 snapshot to X) - lower bound exclusive X-1 snapshot to X snapshot
d. `WHERE "$snapshot_sequence_number" > X` (Get delta from X+1 snapshot to latest) - lower bound exclusive X to latest snapshot

Please let me know if this interpretation aligns with your expectation.

  1. The data sequence numbers for consecutive snapshots are not strictly consecutive (there can be gaps) - Could you please clarify this scenario a bit more? I just looked briefly in Iceberg repo, I thought sequence number is incremeneted as new Snapshot is created here? And I think if we apply above scenarios in point 3 then quering incremental scans with $snapshot_sequence_number should also be fine.

@hantangwangd
Copy link
Copy Markdown
Member

@agrawalreetika thanks for sharing your perspective. Your summary of the supported scenarios and expected behavior semantically aligns with my expectation.

$snapshot_sequence_number is currently defined as a hidden column (via ColumnMetadata.setHidden(true)), ... — like other hidden metadata columns.

As I see, currently we can query these hidden metadata columns as part of a SQL statement by including them in the SELECT part. This is fine for the other hidden metadata columns, as they contain useful information and are even documented in our Iceberg docs. My point is, for the new metadata column $snapshot_sequence_number we're adding, should we explicitly prevent users from including it in the SELECT part? Since the returned values would be meaningless or even confusing.

The data sequence numbers for consecutive snapshots are not strictly consecutive (there can be gaps) - Could you please clarify this scenario a bit more?

The first thing that comes to my mind is that we can directly specify which snapshots to expire by calling the expire_snapshots procedure. This would make the data sequence numbers appear discontinuous in seemingly consecutive snapshots. Considering this, do you think the following implementation would be more robust:

a. `WHERE "$snapshot_sequence_number" >= X` - Get delta from snapshot X (inclusive) to the latest snapshot
b. `WHERE "$snapshot_sequence_number" BETWEEN X AND Y `- Get delta between snapshots X (exclusive) and Y (inclusive)
c. `WHERE "$snapshot_sequence_number" = X ` - Get delta from snapshot X (inclusive) to X (inclusive)
d. `WHERE "$snapshot_sequence_number" > X` - Get delta from snapshot X (exclusive) to the latest snapshot

This is semantically equivalent to what you described above. The difference is that this implementation relies solely on the interfaces provided by Iceberg's IncrementalAppendScan, rather that performing the calculation manually using the data sequence number.

@agrawalreetika agrawalreetika force-pushed the snapshot_sequence_number branch from 6df6146 to 6231cd4 Compare November 11, 2025 13:15
@agrawalreetika
Copy link
Copy Markdown
Member Author

Got occupied, sorry for the delay.
@hantangwangd Sounds right about restricting it from SELECT "$snapshot_sequence_number". And the supported use case also lgtm.

Please take a look at the changes when you get time. Thank you!

@agrawalreetika
Copy link
Copy Markdown
Member Author

@sourcery-ai review

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergSplitManager.java:89-98` </location>
<code_context>
+        if (table.getIcebergTableName().getTableType() == INCREMENTAL) {
</code_context>

<issue_to_address>
**issue (bug_risk):** Potential risk if snapshotId is missing for INCREMENTAL table type.

If both getSnapshotId() and getChangelogEndSnapshot() are empty, and oldestAncestor or currentSnapshot are also missing, a NullPointerException may occur. Please add explicit checks or error handling for missing snapshots.
</issue_to_address>

### Comment 2
<location> `presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergSplit.java:95` </location>
<code_context>
+        this.snapshotSequenceNumber = snapshotSequenceNumber;
     }

     @JsonProperty
</code_context>

<issue_to_address>
**suggestion:** Returning primitive long for getSnapshotSequenceNumber may be problematic if unset.

Consider using OptionalLong for getSnapshotSequenceNumber, or clearly document how unset or invalid values are represented.

Suggested implementation:

```java
            @JsonProperty("snapshotSequenceNumber") OptionalLong snapshotSequenceNumber)
    {
        requireNonNull(nodeSelectionStrategy, "nodeSelectionStrategy is null");
        this.path = requireNonNull(path, "path is null");
        this.dataSequenceNumber = dataSequenceNumber;
        this.affinitySchedulingFileSectionSize = affinitySchedulingFileSectionSize;
        this.affinitySchedulingFileSectionIndex = start / affinitySchedulingFileSectionSize;
        this.snapshotSequenceNumber = requireNonNull(snapshotSequenceNumber, "snapshotSequenceNumber is null");
    }

```

```java
    @JsonProperty
    public OptionalLong getSnapshotSequenceNumber()
    {
        return snapshotSequenceNumber;
    }

```

You will need to:
1. Update the field declaration for `snapshotSequenceNumber` in the class to be `private final OptionalLong snapshotSequenceNumber;`.
2. Update any other usages of `snapshotSequenceNumber` in the class to handle `OptionalLong` (e.g., use `snapshotSequenceNumber.isPresent()` and `snapshotSequenceNumber.getAsLong()`).
3. Adjust JSON serialization/deserialization if you have custom logic elsewhere in the class or related classes.
4. Update any code that constructs this class to pass an `OptionalLong` instead of a primitive `long`.
</issue_to_address>

### Comment 3
<location> `presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergPageSourceProvider.java:748-750` </location>
<code_context>
                 .collect(toImmutableList());

+        // Reject if $snapshot_sequence_number is actually projected (not just used in WHERE)
+        if (icebergColumns.stream().anyMatch(colum -> colum.isSnapshotSequenceNumberColumn()
+                        && !icebergLayout.getPredicateColumns().containsKey(colum.getName()))) {
+            throw new PrestoException(
+                    NOT_SUPPORTED,
+                    "The column $snapshot_sequence_number is internal and cannot be selected directly");
</code_context>

<issue_to_address>
**issue:** The check for $snapshot_sequence_number projection may not handle case sensitivity.

If column names differ in case, this check may not detect all instances of $snapshot_sequence_number. Normalize column names or enforce case sensitivity to ensure the restriction is applied consistently.
</issue_to_address>

### Comment 4
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/IcebergDistributedTestBase.java:2286-2287` </location>
<code_context>
                 "VALUES (true, 1, 'a'), (false, 1, 'b'), (false, 2, 'a'), (false, 3, 'a')");
     }

+    @Test
+    public void testSnapshotSequenceNumberHiddenColumnSimple()
+    {
+        String tableName = "test_snapshot_seq_num_hidden_" + randomTableSuffix();
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding tests for error conditions when querying $snapshot_sequence_number with invalid sequence numbers.

Please add tests for queries using non-existent sequence numbers in the WHERE clause to verify correct error handling or empty results.
</issue_to_address>

### Comment 5
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/IcebergDistributedTestBase.java:2292-2293` </location>
<code_context>
+        String tableName = "test_snapshot_seq_num_hidden_" + randomTableSuffix();
+
+        try {
+            assertUpdate("CREATE TABLE " + tableName + "(id int, data varchar)");
+            assertUpdate("INSERT INTO " + tableName + " VALUES (100, 'a')", 1);
+            assertUpdate("INSERT INTO " + tableName + " VALUES (200, 'b')", 1);
+            Table iceberTable = loadTable(tableName);
</code_context>

<issue_to_address>
**suggestion (testing):** Suggest adding a test for $snapshot_sequence_number on tables with overwrite or delete operations.

Please add a test that covers delete or overwrite operations, then queries $snapshot_sequence_number, to confirm the documented limitations are enforced.
</issue_to_address>

### Comment 6
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergSystemTables.java:163` </location>
<code_context>
         assertQuery("SHOW COLUMNS FROM test_schema.\"test_table$snapshots\"",
                 "VALUES ('committed_at', 'timestamp with time zone', '', '', null, null, null)," +
                         "('snapshot_id', 'bigint', '', '', 19L, null, null)," +
+                        "('snapshot_sequence_number', 'bigint', '', '', 19L, null, null)," +
                         "('parent_id', 'bigint', '', '', 19L, null, null)," +
                         "('operation', 'varchar', '', '', null, null, 2147483647L)," +
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a test to verify the actual values returned for $snapshot_sequence_number in the $snapshots system table.

Adding a test that queries $snapshots and asserts the correctness of $snapshot_sequence_number for various snapshot operations would improve coverage.

Suggested implementation:

```java
        assertQuery("SHOW COLUMNS FROM test_schema.\"test_table$snapshots\"",
                "VALUES ('committed_at', 'timestamp with time zone', '', '', null, null, null)," +
                        "('snapshot_id', 'bigint', '', '', 19L, null, null)," +
                        "('snapshot_sequence_number', 'bigint', '', '', 19L, null, null)," +
                        "('parent_id', 'bigint', '', '', 19L, null, null)," +
                        "('operation', 'varchar', '', '', null, null, 2147483647L)," +
                        "('manifest_list', 'varchar', '', '', null, null, 2147483647L)," +

        // Test: Verify snapshot_sequence_number values in $snapshots system table
        assertUpdate("CREATE TABLE test_schema.test_table (id INTEGER)");
        assertUpdate("INSERT INTO test_schema.test_table VALUES (1)");
        assertUpdate("INSERT INTO test_schema.test_table VALUES (2)");
        assertUpdate("DELETE FROM test_schema.test_table WHERE id = 1");

        // Query the $snapshots system table and check snapshot_sequence_number values
        assertQuery(
                "SELECT snapshot_sequence_number, operation FROM test_schema.\"test_table$snapshots\" ORDER BY snapshot_sequence_number",
                "VALUES (1, 'table creation')," +
                        "(2, 'insert')," +
                        "(3, 'insert')," +
                        "(4, 'delete')"
        );

```

- You may need to adjust the expected values in the `VALUES` clause depending on the actual operation names and sequence numbers produced by your Iceberg connector.
- If your test setup uses different table names or schema, update accordingly.
- If the assertion helper `assertQuery` expects a different format, adapt the query and expected results.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +89 to +98
if (table.getIcebergTableName().getTableType() == INCREMENTAL) {
long fromSnapshot = table.getIcebergTableName().getSnapshotId().orElseGet(() -> SnapshotUtil.oldestAncestor(icebergTable).snapshotId());
long toSnapshot = table.getIcebergTableName().getChangelogEndSnapshot()
.orElseGet(icebergTable.currentSnapshot()::snapshotId);

IncrementalAppendScan scan = icebergTable.newIncrementalAppendScan()
.metricsReporter(new RuntimeStatsMetricsReporter(session.getRuntimeStats()))
.filter(toIcebergExpression(predicate))
.planWith(executor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Potential risk if snapshotId is missing for INCREMENTAL table type.

If both getSnapshotId() and getChangelogEndSnapshot() are empty, and oldestAncestor or currentSnapshot are also missing, a NullPointerException may occur. Please add explicit checks or error handling for missing snapshots.

this.snapshotSequenceNumber = snapshotSequenceNumber;
}

@JsonProperty
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: Returning primitive long for getSnapshotSequenceNumber may be problematic if unset.

Consider using OptionalLong for getSnapshotSequenceNumber, or clearly document how unset or invalid values are represented.

Suggested implementation:

            @JsonProperty("snapshotSequenceNumber") OptionalLong snapshotSequenceNumber)
    {
        requireNonNull(nodeSelectionStrategy, "nodeSelectionStrategy is null");
        this.path = requireNonNull(path, "path is null");
        this.dataSequenceNumber = dataSequenceNumber;
        this.affinitySchedulingFileSectionSize = affinitySchedulingFileSectionSize;
        this.affinitySchedulingFileSectionIndex = start / affinitySchedulingFileSectionSize;
        this.snapshotSequenceNumber = requireNonNull(snapshotSequenceNumber, "snapshotSequenceNumber is null");
    }
    @JsonProperty
    public OptionalLong getSnapshotSequenceNumber()
    {
        return snapshotSequenceNumber;
    }

You will need to:

  1. Update the field declaration for snapshotSequenceNumber in the class to be private final OptionalLong snapshotSequenceNumber;.
  2. Update any other usages of snapshotSequenceNumber in the class to handle OptionalLong (e.g., use snapshotSequenceNumber.isPresent() and snapshotSequenceNumber.getAsLong()).
  3. Adjust JSON serialization/deserialization if you have custom logic elsewhere in the class or related classes.
  4. Update any code that constructs this class to pass an OptionalLong instead of a primitive long.

Comment on lines +748 to +750
if (icebergColumns.stream().anyMatch(colum -> colum.isSnapshotSequenceNumberColumn()
&& !icebergLayout.getPredicateColumns().containsKey(colum.getName()))) {
throw new PrestoException(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: The check for $snapshot_sequence_number projection may not handle case sensitivity.

If column names differ in case, this check may not detect all instances of $snapshot_sequence_number. Normalize column names or enforce case sensitivity to ensure the restriction is applied consistently.

Copy link
Copy Markdown
Member

@hantangwangd hantangwangd left a comment

Choose a reason for hiding this comment

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

Hi @agrawalreetika, thanks for the change. Based on your work in this PR, I tried a small experiment where I completely removed the predicate on snapshot_sequence_number from the table's constraint. This approach seems to avoid exposing the column in the optimized plan's TableScanNode and FilterNode altogether. See here.

Please take a look at this change when you have a moment. Looking forward to your feedback.

@agrawalreetika
Copy link
Copy Markdown
Member Author

Hi @agrawalreetika, thanks for the change. Based on your work in this PR, I tried a small experiment where I completely removed the predicate on snapshot_sequence_number from the table's constraint. This approach seems to avoid exposing the column in the optimized plan's TableScanNode and FilterNode altogether. See here.

Please take a look at this change when you have a moment. Looking forward to your feedback.

Thanks for the review, @hantangwangd. I went through the suggested change, and it does look more appropriate — especially since it removes the need to carry snapshot_sequence_number through the IcebergSplit. I’ll iterate on the PR accordingly.

@agrawalreetika agrawalreetika force-pushed the snapshot_sequence_number branch from 6231cd4 to ddcc304 Compare December 2, 2025 11:10
Copy link
Copy Markdown
Member

@hantangwangd hantangwangd left a comment

Choose a reason for hiding this comment

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

@agrawalreetika thanks for supporting this feature. The approach and implementation overall looks good to me. I've left some comments, mostly minor things.

@agrawalreetika agrawalreetika force-pushed the snapshot_sequence_number branch from ddcc304 to d3bb40e Compare December 10, 2025 11:07
@agrawalreetika
Copy link
Copy Markdown
Member Author

@agrawalreetika thanks for supporting this feature. The approach and implementation overall looks good to me. I've left some comments, mostly minor things.

Thanks for the review @hantangwangd . I have made the changes based on the review comments, please take a look when you get a chance.

@agrawalreetika agrawalreetika force-pushed the snapshot_sequence_number branch from d3bb40e to 6a0d42b Compare December 11, 2025 04:20
@agrawalreetika agrawalreetika force-pushed the snapshot_sequence_number branch from 6a0d42b to 4ca83f1 Compare December 12, 2025 06:47
hantangwangd
hantangwangd previously approved these changes Dec 12, 2025
Copy link
Copy Markdown
Member

@hantangwangd hantangwangd left a comment

Choose a reason for hiding this comment

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

@agrawalreetika thanks for the fix, looks good to me!

Comment on lines +367 to +423
TupleDomain<ColumnHandle> newDomainPredicate;
Optional<Map<ColumnHandle, Domain>> optionalDomains = constraint.getSummary().getDomains();
if (optionalDomains.isPresent()) {
Map<ColumnHandle, Domain> domains = optionalDomains.get();
ImmutableMap.Builder<ColumnHandle, Domain> newDomains = ImmutableMap.builder();
for (Map.Entry<ColumnHandle, Domain> entry : domains.entrySet()) {
IcebergColumnHandle column = (IcebergColumnHandle) entry.getKey();

if (!column.getName().equalsIgnoreCase("$snapshot_sequence_number")) {
newDomains.put(entry.getKey(), entry.getValue());
continue;
}

Domain domain = entry.getValue();
if (domain.isSingleValue()) {
long sequenceNumber = ((Number) domain.getSingleValue()).longValue();
Snapshot endSnapshot = validateAndFetchSnapshot(icebergTable, sequenceNumber);
handle = handle.withUpdatedIcebergTableName(prepareIncrementalScan(icebergTable, endSnapshot, name, endSnapshot, true), true);
continue;
}

List<Range> ranges = domain.getValues().getRanges().getOrderedRanges();
if (ranges.size() != 1) {
throw unsupportedPredicate();
}

Range range = ranges.get(0);
if (range.isSingleValue()) {
long seqNum = ((Number) range.getSingleValue()).longValue();
Snapshot endSnapshot = validateAndFetchSnapshot(icebergTable, seqNum);
handle = handle.withUpdatedIcebergTableName(prepareIncrementalScan(icebergTable, endSnapshot, name, endSnapshot, true), true);
}
else if (!range.isLowUnbounded() && range.isLowInclusive() && range.isHighUnbounded()) {
// WHERE $snapshot_sequence_number >= X (delta from x to latest)
long lowerSequenceNumber = ((Number) range.getLowBoundedValue()).longValue();
Snapshot start = validateAndFetchSnapshot(icebergTable, lowerSequenceNumber);
handle = handle.withUpdatedIcebergTableName(prepareIncrementalScan(icebergTable, start, name, latestSnapshot, true), true);
}
else if (!range.isLowUnbounded() && !range.isLowInclusive() && range.isHighUnbounded()) {
// WHERE $snapshot_sequence_number > X, excludes lower bound
long lowerSequenceNumber = ((Number) range.getLowBoundedValue()).longValue();
Snapshot start = validateAndFetchSnapshot(icebergTable, lowerSequenceNumber);
handle = handle.withUpdatedIcebergTableName(prepareIncrementalScan(icebergTable, start, name, latestSnapshot, false), false);
}
else if (range.isLowInclusive() && range.isHighInclusive()) {
// WHERE $snapshot_sequence_number BETWEEN X AND Y
handle = handle.withUpdatedIcebergTableName(prepareBetweenScan(icebergTable, name, range, false), false);
}
else {
throw unsupportedPredicate();
}
}
newDomainPredicate = TupleDomain.withColumnDomains(newDomains.build());
}
else {
newDomainPredicate = TupleDomain.none();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can this be extracted into a new method?

hantangwangd
hantangwangd previously approved these changes Dec 13, 2025
hantangwangd
hantangwangd previously approved these changes Dec 14, 2025
Comment on lines +502 to +503
final IcebergTableHandle newHandle;
final TupleDomain<ColumnHandle> newDomainPredicate;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Add visibility modifiers

}
else if (range.isLowInclusive() && range.isHighInclusive()) {
// WHERE $snapshot_sequence_number BETWEEN X AND Y
handle = handle.withUpdatedIcebergTableName(prepareBetweenScan(icebergTable, name, range, false), false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SQL between is inclusive, so shouldn't the fromInclusive flag be set to true?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, I agree — for BETWEEN, the lower bound (X) should be inclusive as per SQL semantics.

I’m not sure if we previously discussed making it exclusive for a specific reason, or if that was just an oversight in the earlier summary. Checking if @hantangwangd recalls any context here

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's no particular reason—the behavior for BETWEEN was directly carried over from @agrawalreetika's original design. I agree with keeping consistent with SQL semantics as well.

Comment on lines +535 to +551
if (domain.isSingleValue()) {
long sequenceNumber = ((Number) domain.getSingleValue()).longValue();
Snapshot endSnapshot = validateAndFetchSnapshot(icebergTable, sequenceNumber);
handle = handle.withUpdatedIcebergTableName(prepareIncrementalScan(icebergTable, endSnapshot, name, endSnapshot, true), true);
continue;
}

List<Range> ranges = domain.getValues().getRanges().getOrderedRanges();
if (ranges.size() != 1) {
throw unsupportedPredicate();
}

Range range = ranges.get(0);
if (range.isSingleValue()) {
long seqNum = ((Number) range.getSingleValue()).longValue();
Snapshot endSnapshot = validateAndFetchSnapshot(icebergTable, seqNum);
handle = handle.withUpdatedIcebergTableName(prepareIncrementalScan(icebergTable, endSnapshot, name, endSnapshot, true), true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will we ever actually hit range.isSingleValue()--would that be hit by domain.isSingleValue()?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, this range.isSingleValue() block seems unreachable.
Tried a couple of scenarios -

WHERE "$snapshot_sequence_number" BETWEEN 1 AND 1;
WHERE "$snapshot_sequence_number" = 1;
WHERE "$snapshot_sequence_number" in (1);

return new IcebergTableName(name.getTableName(), INCREMENTAL, Optional.of(lower.snapshotId()), Optional.of(upper.snapshotId()));
}

public static PrestoException unsupportedPredicate()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you make private whatever is not being used outside of IcebergUtil?

private final List<SortField> sortOrder;
private final List<IcebergColumnHandle> updatedColumns;
private final Optional<SchemaTableName> materializedViewName;
private final boolean fromInclusive;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could this field be easily moved into IcebergTableName?

.collect(toImmutableMap(Entry::getKey, Entry::getValue));
}

public static class IcebergPredicateRewriteResult
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Instead of creating this holder class, can we directly return the handle, and have a new public utility method which just filters out the snapshot sequence ID from the domains of the TupleDomain?

@agrawalreetika
Copy link
Copy Markdown
Member Author

Thanks for your review, @tdcmeehan. I have updated the PR based on the review comment. Please take a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

from:IBM PR from IBM

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants