Skip to content

generate_schema_name prefixes target.schema onto custom schemas for attached Iceberg REST catalog databases #754

Description

@malon64

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:

  1. credentials.py — scan self.attach entries whose resolved type is "iceberg" and populate self._iceberg_catalog_dbs: set.
  2. 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.)
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions