fix: replace instead of drop when rebuilding a managed Iceberg table - #1674
fix: replace instead of drop when rebuilding a managed Iceberg table#1674cemsbr wants to merge 4 commits into
Conversation
A full refresh of an incremental model on a Unity Catalog managed Iceberg table dropped the table and then ran the CTAS, leaving it absent for the duration of the rebuild and losing its history. The replaceability check keyed its Iceberg arm off `file_format`, which an Iceberg model never sets to iceberg -- `iceberg_table_properties` raises for anything but delta, and `iceberg` is not an accepted `file_format` at all, so that arm was unreachable. The Delta arm then failed too, because the existing table reports `Provider = iceberg`. Key the arm off `table_format` plus the `use_managed_iceberg` flag instead, matching the condition `file_format_clause` uses to emit `using iceberg`. Extracted into `format_allows_create_or_replace` so both the V1 and V2 paths share it and it can be tested without a warehouse. Resolves databricks#1662 Co-authored-by: Isaac <no-reply@databricks.com>
Asserts on the table's history rather than its id or creation time: both of those change on `create or replace` as well, so only the first history entry surviving distinguishes a replace from a drop and recreate. Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
The `table` materialization gated `create or replace` on
`adapter.resolve_file_format(config) in ('delta', 'iceberg')`. With the
`use_managed_iceberg` behavior flag on, `resolve_file_format` returns
`parquet` for `table_format: iceberg`, so every rebuild of a Unity Catalog
managed Iceberg table dropped it first and then ran a
`create or replace table ... using iceberg` CTAS that would have been atomic
on its own. The table was unavailable for the whole CTAS on every run and
its history restarted each time.
Reuse `format_allows_create_or_replace` (from the incremental full-refresh
fix for databricks#1662) in both the v1 and v2 branches of `table.sql`. Shallow clones
and non-table relations are still dropped, and Delta targets keep their
previous behavior.
Resolves databricks#1662
Signed-off-by: Carlos Eduardo M. Santos <csantos@wincred.digital>
c17f33f to
9b4d076
Compare
|
Thanks for picking this up, and for reusing the macro and test shape from #1669. I ran your branch here against a serverless SQL warehouse, since fork PRs can't start CI on their own: One question. The old gate looked only at the target format, and Since this is stacked on #1669, that one needs to land first; it's waiting on a maintainer to approve its workflows. |
|
Thanks for running it end to end, and for the question — it made me check rather than assume. Yes, intentional. Reusing the predicate unchanged keeps
So in the case you describe (existing Iceberg table, project turns One thing the probe surfaced about the predicate itself, which is #1669's territory and your call: the Delta arm ( I've added a short "Behavior change" note to the PR description. |
The Delta arm matched a managed-Iceberg target over a legacy Delta table, because `file_format` reads delta for an Iceberg model too. `create or replace` cannot change a table's provider -- Databricks rejects it with `MANAGED_ICEBERG_OPERATION_NOT_SUPPORTED` and leaves the table alone -- so a project switching `use_managed_iceberg` on failed on its first full refresh until someone dropped the table by hand. Make the arms mutually exclusive, so a provider change drops and recreates. Reported by @cemsbr on databricks#1674, who probed the cross-provider REPLACE. Co-authored-by: Isaac <no-reply@databricks.com>
|
Your read was right, and thank you for probing it rather than assuming — I had the same two rows before pushing, so we agree on the facts:
On your two points: The "provider changed → drop" arm is intentional — agreed, keep it. Your probe settles what I was actually unsure about: the old gate never replaced successfully in that case, it just failed until someone dropped the table by hand. So this is not a behavior change worth hedging over; it turns an error into a working rebuild. The asymmetric Delta arm was a real bug, and I've folded the fix into #1669 rather than take you up on the follow-up — the predicate lives there, and leaving a known-broken arm in the commit that introduces it didn't seem right. I'd written the symmetric version first and then talked myself out of it on "don't change behavior beyond the reported bug", which was the wrong call: the behavior I preserved was one I hadn't verified.
I also rebased #1669 onto main, so the stack needs redoing — but |
Resolves #1662
Description
Stacked on #1669 — the first three commits are that PR's; this PR adds one commit on top and will be rebased onto
mainonce #1669 merges.#1662 reports the drop-then-create on
--full-refreshof an incremental model on a Unity Catalog managed Iceberg table. Thetablematerialization has the same gate and hits it on every run:not (existing_relation.can_be_replaced and adapter.resolve_file_format(config) in ('delta', 'iceberg'))With
use_managed_icebergon andtable_format: iceberg,resolve_file_formatreturnsparquet, so the existing table is always dropped before acreate or replace table ... using icebergCTAS that would have been atomic on its own. The table is unavailable for the whole CTAS (minutes on large tables) and concurrent readers fail withTABLE_OR_VIEW_NOT_FOUND; table history/time travel restarts on every run.This change reuses
format_allows_create_or_replace(catalog_relation, existing_relation)from #1669 in both the v1 and v2 branches oftable.sql. Theis_shallow_cloneandtype != 'table'arms are unchanged, and Delta targets keep the same behavior as before (catalog_relation.file_format == 'delta'andexisting_relation.is_delta).Not included:
materializations/seeds/helpers.sql(databricks__reset_csv_table) has the same gate; happy to fold it in here or in a follow-up, whichever you prefer.Behavior change
When the existing table's provider differs from the target (an existing managed Iceberg table with a Delta target, e.g. a project turning
use_managed_icebergback off), the old gate attemptedcreate or replace tableacross providers; Databricks rejects that with[MANAGED_ICEBERG_OPERATION_NOT_SUPPORTED] Managed Iceberg tables do not support REPLACE with different providers.and the rebuild failed. With the shared predicate that case drops and recreates the table instead, matching #1669'stest_delta_target_on_iceberg_relation. Probe results in the comments below.Checklist
tablematerialization with the same predicate, on dbt-databricks 1.12.5 against a UC managed Iceberg table (second run no longer emitsApplying DROP;information_schema.tables.createdis preserved)TestManagedIcebergTableRebuildintests/functional/adapter/iceberg/test_iceberg_support.py, mirroring fix: replace instead of drop on managed Iceberg full refresh #1669'sTestManagedIcebergFullRefresh; needs a workspace, so it relies on/integration-testCHANGELOG.mdand added information about my change to the "dbt-databricks next" section.dbt-databricks-pr-readyproject skill for this PR and addressed its merge-readiness feedback