Skip to content

feat: replace regex+macro MV external target with config.get_rendered#645

Open
dataders wants to merge 3 commits into
ClickHouse:mainfrom
dataders:feat/fusion-compat-mv-and-incremental
Open

feat: replace regex+macro MV external target with config.get_rendered#645
dataders wants to merge 3 commits into
ClickHouse:mainfrom
dataders:feat/fusion-compat-mv-and-incremental

Conversation

@dataders
Copy link
Copy Markdown

@dataders dataders commented May 14, 2026

Summary

Replaces the materialization_target_table() macro + SQL comment regex approach for external-target materialized views with config.get_rendered('target_table'), a clean dbt-native config key.

Closes #644.

Requires dbt-labs/dbt-core#12965 (bridging config.get_rendered across the compile→materialization boundary).


Before

Users had to call a macro that injected a SQL comment, which the materialization then regex-parsed:

{{ config(materialized='materialized_view') }}
{{ materialization_target_table(ref('my_target')) }}  {# outputs: -- materialization_target_table: schema.table #}
SELECT ...

This was incompatible with dbt-fusion (which does static DAG analysis, not SQL comment parsing) and required the regex:

{%- set target_match = modules.re.search('--\\s*materialization_target_table:\\s*(.+?)\\s*$', sql, modules.re.MULTILINE) -%}

After

{{ config(
    materialized='materialized_view',
    target_table=ref('my_target')
) }}
SELECT ...

The target_table value is a RelationProxy captured at render time via config.get_rendered('target_table') and used directly in the TO clause — no comment injection, no regex, no | string coercion.


Changes

macros/materializations/materialized_view.sql

  • Top-level dispatcher: config.get_rendered('target_table') replaces config.get() + regex fallback
  • clickhouse__materialized_view_with_external_target now receives a RelationProxy directly — uses .schema / .identifier for target-change comparison, passes relation straight to TO clause and INSERT INTO
  • strip_identifier_quotes and get_relation_from_string helpers removed (no longer needed)
  • materialization_target_table() macro kept but emits a deprecation warning

Tests

  • All test models updated to config(target_table=ref(...)) — no | string
  • Docstrings updated to reflect new approach

Test plan

  • TestBasicExternalTargetMV — create MV, verify catchup, verify live inserts flow through
  • TestExternalTargetMVDisabledCatchup — target starts empty
  • TestUpdateExternalTargetMVWithSchemaChange — full refresh with schema evolution
  • TestExternalTargetMVTargetChange — target-change validation error, full-refresh swap
  • TestMultipleExternalTargetMV — multiple MVs writing to same target
  • TestUpdateMultipleExternalTargetMVFullRefresh — full refresh with multiple MVs
  • Refreshable MV with external target

🤖 Generated with Claude Code

dataders and others added 2 commits May 14, 2026 09:10
- materialized_view: add `target_table` config key as ergonomic alternative to
  materialization_target_table() macro call. Config takes precedence; comment
  pattern retained for backward compat. Preferred path for dbt-fusion which
  resolves DAG deps via static analysis (issue ClickHouse#644).
- incremental: accept 'delete+insert' as alias for 'delete_insert' strategy,
  matching the hyphen-style name used in dbt-fusion.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Removes the materialization_target_table() macro + SQL comment regex
approach for pointing a ClickHouse materialized view at an external
target table, replacing it with config.get_rendered('target_table').

Requires dbt-labs/dbt-core#12965.

Before:
  {{ materialization_target_table(ref('my_target')) }}
  SELECT ...

After:
  {{ config(materialized='materialized_view', target_table=ref('my_target')) }}
  SELECT ...

Changes:
- materialized_view.sql: top-level dispatcher uses config.get_rendered()
  instead of config.get() + regex fallback. strip_identifier_quotes and
  get_relation_from_string helpers removed; target_table_relation is now
  a RelationProxy used directly in TO clause and INSERT.
- materialization_target_table() macro kept but emits a deprecation warning.
- Tests updated throughout to use the new config(target_table=ref(...))
  syntax without | string (the dbt-core fix makes that unnecessary).

Closes ClickHouse#644

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@dataders dataders marked this pull request as draft May 14, 2026 23:55
@dataders dataders changed the title feat: dbt-fusion compatibility — target_table config key for MVs + delete+insert alias feat: replace regex+macro MV external target with config.get_rendered May 14, 2026
Copy link
Copy Markdown
Author

@dataders dataders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just sloppy stuff Claude. do better or I'm gonna make Codex take your job

{% do clickhouse__incremental_legacy(existing_relation, intermediate_relation, column_changes, unique_key) %}
{% set need_swap = true %}
{% elif incremental_strategy == 'delete_insert' %}
{% elif incremental_strategy == 'delete+insert' or incremental_strategy == 'delete_insert' %}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude, did you add this just be you spelled the config wrong?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — dbt-fusion spells this strategy (hyphen) rather than (underscore), so this alias lets models written for dbt-fusion run unmodified against the OSS adapter. Happy to split it into a separate PR if you'd prefer to keep the scope tighter.

{{ materialization_target_table(ref('my_target_table')) }}
{{ config(
materialized='materialized_view',
target_table=ref('my_target') | string
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no longer needed

Suggested change
target_table=ref('my_target') | string
target_table=ref('my_target')

Comment on lines -17 to -21
{#- First check config, then try to extract from SQL comment -#}
{#- Extract target table from comment. Handles formats like:
`schema`.`table`, "schema"."table", schema.table -#}
{%- set target_match = modules.re.search('--\\s*materialization_target_table:\\s*(.+?)\\s*$', sql, modules.re.MULTILINE) -%}
{%- set materialization_target_table = target_match.group(1).strip() if target_match else none -%}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes me day to put this to bed (one day)


{#-
Deprecated: materialization_target_table() macro.
Use config(target_table=ref('my_target') | string) instead.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Use config(target_table=ref('my_target') | string) instead.
Use config(target_table=ref('my_target')) instead.

{% macro materialization_target_table(target_relation) %}
{%- do exceptions.warn(
"materialization_target_table() is deprecated. "
~ "Use {{ config(target_table=ref('my_target') | string) }} instead. "
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
~ "Use {{ config(target_table=ref('my_target') | string) }} instead. "
~ "Use {{ config(target_table=ref('my_target')) }} instead. "

Comment on lines +40 to +42
"materialization_target_table() is deprecated. "
~ "Use {{ config(target_table=ref('my_target') | string) }} instead. "
~ "See https://github.com/ClickHouse/dbt-clickhouse/issues/644"
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this string can be set and used in both the comment block as well as this exceptions.warn piece?

{% endmacro %}

{% macro clickhouse__materialized_view_with_external_target(materialization_target_table, sql) %}
{% macro clickhouse__materialized_view_with_external_target(target_table_str, sql) %}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all instances of target_table_str should be replcaed with materialization_target_table to minimize the diff of this PR

Suggested change
{% macro clickhouse__materialized_view_with_external_target(target_table_str, sql) %}
{% macro clickhouse__materialized_view_with_external_target(materialization_target_table, sql) %}

Comment on lines +165 to +167
{{ config(materialized='materialized_view', catchup=False) }}

{{ materialization_target_table(ref('mv_target_table')) }}
{{ config(target_table=ref('mv_target_table')) }}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these config blocks need to be combined to look like

{{ config(
  materialized='materialized_view',
  target_table=ref('mv_target_table').
  catchup=False)
}}

Comment on lines 21 to 24
{{ config(materialized='materialized_view') }}

{{ materialization_target_table(ref('hackers_target')) }}
{{ config(target_table=ref('hackers_target')) }}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same consolidate the config blocks! there's many instances in this file fwiw

…idation

- Rename target_table_str → materialization_target_table (minimize diff)
- Replace string-split comparison with .schema/.identifier (RelationProxy is now
  passed directly, so strip_identifier_quotes and split() are unneeded)
- Remove | string from header docblock and deprecation text
- Extract _deprecation_url variable in materialization_target_table() so
  comment and warn message share a single source of truth for the URL
- Consolidate split config() blocks into single calls in test models

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@dataders dataders marked this pull request as ready for review May 15, 2026 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor materialized_view materialization for Fusion-compatible config-first support

2 participants