Summary
When dbt-duckdb targets an attached DuckDB Iceberg REST catalog (e.g. Polaris), the default generate_schema_name macro produces main_sales_gold instead of the configured namespace sales_gold. The result is that dbt tries to materialise tables into a namespace that does not exist in the catalog, causing runtime failures.
Environment
| Component |
Version |
| dbt-duckdb |
1.9.6 |
| DuckDB |
1.4.x |
| DuckDB extensions |
iceberg, httpfs, parquet |
| Iceberg catalog |
Polaris REST catalog, attached as alias polaris |
Configuration
profiles.yml (relevant section):
outputs:
local_runtime:
type: duckdb
path: /tmp/openlakeforge-sales-dbt.duckdb
extensions: [httpfs, parquet, iceberg]
secrets:
- type: iceberg
name: openlakeforge_polaris_secret
client_id: "{{ env_var('POLARIS_DBT_CLIENT_ID') }}"
client_secret: "{{ env_var('POLARIS_DBT_CLIENT_SECRET') }}"
oauth2_server_uri: "{{ env_var('POLARIS_TOKEN_URI') }}"
oauth2_scope: "PRINCIPAL_ROLE:ALL"
attach:
- path: lakehouse
alias: polaris
options:
type: iceberg
secret: openlakeforge_polaris_secret
endpoint: "{{ env_var('POLARIS_REST_URI') }}"
access_delegation_mode: none
dbt_project.yml:
models:
sales_poc:
+database: polaris
+schema: sales_gold
+materialized: table
Expected vs. Actual
|
Resolved relation |
| Expected |
"polaris"."sales_gold"."mart_sales_by_day" |
| Actual |
"polaris"."main_sales_gold"."mart_sales_by_day" |
The main_ prefix comes from target.schema (which is main by default for DuckDB profiles). dbt's default generate_schema_name macro concatenates it with the custom schema: {target.schema}_{custom_schema_name}.
Root cause
default__generate_schema_name in dbt-core is intentionally designed to prefix the target schema onto custom schema names. This is the right behaviour for ordinary DuckDB databases, where schemas are flat namespaces within a file and the prefix avoids collisions between different dbt projects.
For an attached Iceberg REST catalog, the situation is different:
database: polaris refers to the attached catalog alias, not a local DuckDB file.
schema: sales_gold refers to an Iceberg namespace, a discrete catalog object registered in Polaris.
- The namespace
main_sales_gold does not exist in Polaris and cannot be created by dbt through the REST API.
- Even if it could be created, it would be the wrong namespace — the intent is
sales_gold.
The prefix is semantically wrong for this class of attached database, and it makes dbt-duckdb unusable against Iceberg REST catalog targets without a project-level workaround.
Current workaround
Every dbt-duckdb project targeting an Iceberg catalog must add this macro to bypass the default behaviour:
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- if custom_schema_name is none -%}
{{ target.schema }}
{%- else -%}
{{ custom_schema_name | trim }}
{%- endif -%}
{%- endmacro %}
This is boilerplate that should not be required for every project.
Proposed fix
dbt-duckdb already handles an analogous case for DuckLake databases: credentials.py populates _ducklake_dbs at init time, and impl.py exposes is_ducklake(relation) so macros can dispatch differently for DuckLake targets.
The same pattern can be applied to Iceberg REST catalog attachments:
credentials.py — scan self.attach entries whose resolved type is "iceberg" and populate self._iceberg_catalog_dbs: set.
impl.py — expose is_iceberg_catalog_db(database_name: str) -> bool decorated with @available. (Takes a string rather than a DuckDBRelation because generate_schema_name receives a node, not a materialised relation.)
macros/adapters.sql — add duckdb__generate_schema_name that returns custom_schema_name unchanged when the node's configured database is an Iceberg catalog attachment, and falls back to the standard {target.schema}_{custom_schema_name} otherwise.
Because project-level generate_schema_name overrides dispatch before adapter macros, existing projects with the workaround above are completely unaffected.
Related
Summary
When
dbt-duckdbtargets an attached DuckDB Iceberg REST catalog (e.g. Polaris), the defaultgenerate_schema_namemacro producesmain_sales_goldinstead of the configured namespacesales_gold. The result is that dbt tries to materialise tables into a namespace that does not exist in the catalog, causing runtime failures.Environment
iceberg,httpfs,parquetpolarisConfiguration
profiles.yml(relevant section):dbt_project.yml:Expected vs. Actual
"polaris"."sales_gold"."mart_sales_by_day""polaris"."main_sales_gold"."mart_sales_by_day"The
main_prefix comes fromtarget.schema(which ismainby default for DuckDB profiles). dbt's defaultgenerate_schema_namemacro concatenates it with the custom schema:{target.schema}_{custom_schema_name}.Root cause
default__generate_schema_namein dbt-core is intentionally designed to prefix the target schema onto custom schema names. This is the right behaviour for ordinary DuckDB databases, where schemas are flat namespaces within a file and the prefix avoids collisions between different dbt projects.For an attached Iceberg REST catalog, the situation is different:
database: polarisrefers to the attached catalog alias, not a local DuckDB file.schema: sales_goldrefers to an Iceberg namespace, a discrete catalog object registered in Polaris.main_sales_golddoes not exist in Polaris and cannot be created by dbt through the REST API.sales_gold.The prefix is semantically wrong for this class of attached database, and it makes dbt-duckdb unusable against Iceberg REST catalog targets without a project-level workaround.
Current workaround
Every dbt-duckdb project targeting an Iceberg catalog must add this macro to bypass the default behaviour:
{% macro generate_schema_name(custom_schema_name, node) -%} {%- if custom_schema_name is none -%} {{ target.schema }} {%- else -%} {{ custom_schema_name | trim }} {%- endif -%} {%- endmacro %}This is boilerplate that should not be required for every project.
Proposed fix
dbt-duckdb already handles an analogous case for DuckLake databases:
credentials.pypopulates_ducklake_dbsat init time, andimpl.pyexposesis_ducklake(relation)so macros can dispatch differently for DuckLake targets.The same pattern can be applied to Iceberg REST catalog attachments:
credentials.py— scanself.attachentries whose resolved type is"iceberg"and populateself._iceberg_catalog_dbs: set.impl.py— exposeis_iceberg_catalog_db(database_name: str) -> booldecorated with@available. (Takes a string rather than aDuckDBRelationbecausegenerate_schema_namereceives anode, not a materialised relation.)macros/adapters.sql— addduckdb__generate_schema_namethat returnscustom_schema_nameunchanged when the node's configured database is an Iceberg catalog attachment, and falls back to the standard{target.schema}_{custom_schema_name}otherwise.Because project-level
generate_schema_nameoverrides dispatch before adapter macros, existing projects with the workaround above are completely unaffected.Related
CREATE OR REPLACEnot supported for Iceberg (same class of attached-catalog semantic mismatch, different symptom)