moomindani opened a new pull request, #17765:
URL: https://github.com/apache/iceberg/pull/17765

   ## Summary
   
   Closes #15593
   
   This revives #15594, which the stale bot closed on 2026-05-21 after ten 
weeks without a single review. GitHub does not offer a reopen on that PR, so 
this is a fresh PR from the same branch, rebased onto current `main`. Spark 3.4 
support was removed upstream in #14122, so the v3.4 copies are dropped — this 
now touches Spark 3.5, 4.0 and 4.1 (6 files instead of 8). Nothing else changed 
from the closed PR.
   
   Introduces a new `scd_type2 => boolean` parameter to the 
`create_changelog_view` Spark procedure that natively produces a Slowly 
Changing Dimensions Type-2 (SCD Type-2) history view from an Iceberg changelog 
table, without requiring users to write custom SQL.
   
   ### New columns added to the view
   
   | Column | Type | Nullable | Description |
   |---|---|---|---|
   | `_valid_from` | TIMESTAMP | no | Commit timestamp when this version became 
active |
   | `_valid_to` | TIMESTAMP | yes | Commit timestamp when superseded; `NULL` 
if still active |
   | `_is_current` | BOOLEAN | no | `true` when `_valid_to IS NULL AND 
_change_type != 'DELETE'` |
   
   ### Example
   
   ```sql
   CALL catalog.system.create_changelog_view(
       table              => 'db.products',
       changelog_view     => 'products_history',
       identifier_columns => array('product_id'),
       scd_type2          => true
   )
   ```
   
   Given inserts and updates to `products`, the resulting view:
   
   | product_id | name | _change_type | _valid_from | _valid_to | _is_current |
   |---|---|---|---|---|---|
   | 1 | Widget | INSERT | 2024-01-01 | 2024-02-01 | false |
   | 1 | Widget Pro | UPDATE_AFTER | 2024-02-01 | NULL | true |
   
   ### Demo output (from actual Spark 4.1 test run)
   
   **Setup:**
   ```sql
   CREATE TABLE products (id INT NOT NULL, data STRING) USING iceberg;
   ALTER TABLE products SET IDENTIFIER FIELDS id;
   ALTER TABLE products ADD PARTITION FIELD id;
   
   INSERT INTO products VALUES (1, 'Widget');
   INSERT INTO products VALUES (2, 'Gadget');
   INSERT OVERWRITE products VALUES (1, 'Widget Pro');   -- update entity 1
   ```
   
   **Without `scd_type2` (existing behavior):**
   ```sql
   CALL catalog.system.create_changelog_view(
       table              => 'db.products',
       identifier_columns => array('id')
   )
   ```
   ```
   id   data         _change_type    _change_ordinal   _commit_snapshot_id
   ---------------------------------------------------------------------------
   1    Widget       INSERT          0                 5325177678658542000
   1    Widget Pro   UPDATE_AFTER    1                 263949438103749135
   1    Widget       UPDATE_BEFORE   1                 263949438103749135
   2    Gadget       INSERT          1                 1299271826648018743
   ```
   Users must manually write window functions to derive validity intervals.
   
   **With `scd_type2 => true` (this PR):**
   ```sql
   CALL catalog.system.create_changelog_view(
       table              => 'db.products',
       identifier_columns => array('id'),
       scd_type2          => true
   )
   ```
   ```
   id   data         _change_type   _change_ordinal   _commit_snapshot_id    
_valid_from               _valid_to                 _is_current
   
-------------------------------------------------------------------------------------------------------------------------------------------------
   1    Widget       INSERT         0                 5325177678658542000    
2026-04-13 11:23:44.977   2026-04-13 11:23:45.543   false
   1    Widget Pro   UPDATE_AFTER   2                 263949438103749135     
2026-04-13 11:23:45.543   null                      true
   2    Gadget       INSERT         1                 1299271826648018743    
2026-04-13 11:23:45.269   null                      true
   ```
   
   Key differences:
   - `UPDATE_BEFORE` rows filtered out (intermediate artifacts not meaningful 
in SCD Type-2)
   - `_valid_from` / `_valid_to` computed automatically via `LEAD()` window 
over snapshot timestamps
   - `_is_current` convenience flag: `true` only when `_valid_to IS NULL AND 
_change_type != 'DELETE'`
   
   **Querying current state:**
   ```sql
   SELECT id, data FROM products_changelog WHERE _is_current = true ORDER BY id
   ```
   ```
   id   data
   ------------------
   1    Widget Pro
   2    Gadget
   ```
   No custom window logic needed.
   
   ### Design choices
   
   - `NULL` sentinel for open-ended rows (not `9999-12-31`) — aligns with dbt 
and Databricks DLT
   - `_valid_from`/`_valid_to` naming — aligns with dbt 
`dbt_valid_from`/`dbt_valid_to` and Kimball semantics
   - `UPDATE_BEFORE` rows filtered out — intermediate artifacts not meaningful 
in an SCD Type-2 view
   - DELETE rows kept with `_is_current = false` — tracks hard deletes
   - `_is_current` convenience flag added — avoids the two-condition footgun 
(`_valid_to IS NULL AND _change_type != 'DELETE'`)
   
   ### Constraints
   
   - Requires `identifier_columns` (explicit or from table schema)
   - Incompatible with `net_changes = true`
   - Implicitly forces `compute_updates = true`
   
   ### Note on snapshot expiration
   
   The `scd_type2` mode inherits the same snapshot expiration behavior as the 
existing `create_changelog_view`. If snapshots have been expired, the changelog 
scan itself omits the corresponding rows — the SCD Type-2 layer does not 
introduce any additional data loss or new failure modes. Specifically:
   
   - **Expired snapshot, data files still present**: changelog scan silently 
omits rows from the expired snapshot. Both base and `scd_type2` modes return 
the same reduced set of rows. `_valid_from`/`_valid_to`/`_is_current` are 
correct relative to the rows the changelog scan returns.
   - **Expired snapshot, data files garbage-collected**: changelog scan fails 
with `FileNotFoundException`. Both base and `scd_type2` modes fail identically.
   
   ## Changes
   
   - `CreateChangelogViewProcedure.java` — Spark v3.5, v4.0, v4.1
   - `TestCreateChangelogViewProcedure.java` — Spark v3.5, v4.0, v4.1
   
   ## Test plan
   
   - [x] `testScdType2BasicInsertUpdateDelete` — INSERT → UPDATE → DELETE 
lifecycle, verifies all 3 new columns
   - [x] `testScdType2CurrentRows` — only non-deleted, most-recent rows have 
`_is_current = true`
   - [x] `testScdType2HardDelete` — DELETE row has `_valid_to = NULL`, 
`_is_current = false`
   - [x] `testScdType2RequiresIdentifierColumns` — error when no identifier 
columns
   - [x] `testScdType2IncompatibleWithNetChanges` — error when combined with 
`net_changes = true`
   - [x] `testScdType2OutputSchema` — verifies all 8 output columns in correct 
order
   
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to