feat(athena): add use_iceberg_write_to config for Iceberg Python models - #1881
feat(athena): add use_iceberg_write_to config for Iceberg Python models#1881dtaniwaki wants to merge 9 commits into
Conversation
Signed-off-by: Daisuke Taniwaki <daisuketaniwaki@gmail.com>
There was a problem hiding this comment.
Pull request overview
Adds a new config-driven write path for Iceberg Python table models that uses Spark DataFrameWriterV2 writeTo().createOrReplace() to support Iceberg-native partition transforms and avoid HA __ha retry failures.
Changes:
- Introduces
use_iceberg_write_toconfig and threads it through the Python submission path. - Adds a Python write branch that uses
writeTo().createOrReplace()and parses Iceberg partition transform expressions. - Updates Iceberg table materialization to skip the
__haintermediate table whenuse_iceberg_write_tois enabled and cleans up leftover__ha/__bkprelations.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| dbt-athena/src/dbt/include/athena/macros/materializations/models/table/table.sql | Skips Iceberg HA swap flow for Python models when use_iceberg_write_to is enabled; drops leftover temp/backup relations. |
| dbt-athena/src/dbt/include/athena/macros/materializations/models/table/create_table_as.sql | Passes use_iceberg_write_to, table_type, and extra_table_properties into Python submission; disables Spark CTAS when using WriterV2. |
| dbt-athena/src/dbt/include/athena/macros/adapters/python_submissions.sql | Implements the WriterV2 writeTo().createOrReplace() path with transform parsing and table property support. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Daisuke Taniwaki <daisuketaniwaki@gmail.com>
5ae656c to
e90c3af
Compare
- Gate use_iceberg_write_to by table_type='iceberg' with compiler error - Drop existing view before writeTo().createOrReplace() in table.sql - Use tojson filter for safe escaping of partition expressions
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| _writer = _writer.tableProperty("location", "{{ location }}/") | ||
| {% if extra_table_properties is not none %} | ||
| {% for prop_name, prop_value in extra_table_properties.items() %} | ||
| _writer = _writer.tableProperty("{{ prop_name }}", "{{ prop_value }}") |
There was a problem hiding this comment.
Fixed in commit ddf8f8b. Both prop_name and prop_value now go through | string | tojson (the | string coerces non-string values like ints/booleans before | tojson handles JSON escaping). location is also normalized to {{ (location ~ "/") | tojson }} for consistency.
| return F.col(expr_str) | ||
| func = m.group(1).lower() | ||
| args = [a.strip() for a in m.group(2).split(",")] | ||
| if func in ("day", "days"): | ||
| return F.days(F.col(args[0])) | ||
| if func in ("month", "months"): | ||
| return F.months(F.col(args[0])) | ||
| if func in ("year", "years"): | ||
| return F.years(F.col(args[0])) | ||
| if func in ("hour", "hours"): | ||
| return F.hours(F.col(args[0])) | ||
| if func == "bucket": | ||
| return F.bucket(int(args[1]), F.col(args[0])) | ||
| if func == "truncate": |
There was a problem hiding this comment.
Fixed in commit ddf8f8b. bucket and truncate now share a branch that validates len(args) == 2 and raises ValueError("... requires 2 arguments (column, n), got: <expr>") when the user supplies the wrong arity, instead of falling through to an IndexError on args[1]. Coverage: tests/unit/test_py_save_table_as.py::TestParseIcebergPartition::test_bucket_with_missing_arg_raises_clear_error (and the truncate counterpart) added in d32370c.
…e_to functional tests
Thank you for maintaining dbt-athena — I'd appreciate your review on this change.
resolves #1882
docs N/A
Problem
Iceberg Python (Spark) models currently have two write paths, both with limitations:
saveAsTabledoes not support Iceberg-native partition transforms (bucket(),day(), etc.) via the DataFrameWriter API.spark_ctasSQL path doesn't leverage Spark's DataFrameWriterV2 capabilities liketableProperty().Additionally, for Iceberg tables the
tablematerialization always uses the__haintermediate table flow (CTAS to__ha→ rename), regardless of thehaconfig. When a previous Spark run fails, the leftover__hatable causesTABLE_OR_VIEW_ALREADY_EXISTSon retry.Solution
Add a
use_iceberg_write_tomodel config that useswriteTo().createOrReplace()(DataFrameWriterV2 API). This enables Iceberg-native partition transforms and atomic replacement without the__haintermediate table.In
table.sql, whenuse_iceberg_write_tois enabled, the model writes directly totarget_relation, skipping the__ha→ rename flow. Leftover__ha/__bkptables from previous HA-flow failures are cleaned up.{{ config( materialized='table', table_type='iceberg', use_iceberg_write_to=True, partitioned_by=['day(created_at)', 'bucket(user_id, 256)'], ) }}Tests
tests/unit/test_py_save_table_as.py): rendersathena__py_save_table_asend-to-end withjinja2.FileSystemLoader(same pattern astest_get_partition_batches.py) and asserts on the generated Python —writeTo/createOrReplacebranch selection,tojsonescaping ofextra_table_propertiesand partition expressions, fall-through tospark_ctasandsaveAsTable. Also exec's the inline_parse_iceberg_partitionagainst a stubpyspark.sql.functionsto cover transform dispatch andbucket/truncatearity validation.tests/functional/adapter/test_use_iceberg_write_to.py, gated onDBT_TEST_ATHENA_SPARK_WORK_GROUP): runs Iceberg Python models end-to-end against a real Athena Spark workgroup. Covers partitioned + unpartitioned writes, idempotentcreateOrReplace,table_propertiespropagation, and the compiler-error path whenuse_iceberg_write_to=Trueis combined withtable_type != 'iceberg'.Test infrastructure (Terraform)
The functional test requires an Athena Spark workgroup with Iceberg support. This PR predates the Spark 3.5 / Spark Connect work in #1874, so the workgroup uses Athena's
PySpark engine version 3(Spark 3.2.1) — the engine the Calculations API targets. The Terraform below provisions the minimal AWS resources.Terraform (click to expand)
Checklist