Skip to content

Commit 969c6c3

Browse files
authored
Merge pull request #412 from ClickHouse/support-replace-view
Support replace view
2 parents 486d5e9 + e4ae3ec commit 969c6c3

File tree

5 files changed

+83
-40
lines changed

5 files changed

+83
-40
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
### Release [x.x.x]
1+
### Release [1.8.8], 2025-02-05
22
### Improvements
3-
* Ignores incompatible settings based on the configured Engine.
43
* Materialized view now attempts to use `ALTER TABLE...MODIFY QUERY` to update existing materialized views. This is an atomic operation so data is not lost. ([#390](https://github.com/ClickHouse/dbt-clickhouse/pull/390))
4+
* Make view materialization updates atomic. ([#412](https://github.com/ClickHouse/dbt-clickhouse/pull/412))
55

66

77
#### New Features
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = '1.8.7'
1+
version = '1.8.8'

dbt/include/clickhouse/macros/materializations/view.sql

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,24 @@
22

33
{%- set existing_relation = load_cached_relation(this) -%}
44
{%- set target_relation = this.incorporate(type='view') -%}
5-
{%- set backup_relation = none -%}
6-
{%- set preexisting_backup_relation = none -%}
7-
{%- set preexisting_intermediate_relation = none -%}
8-
9-
{% if existing_relation is not none %}
10-
{%- set backup_relation_type = existing_relation.type -%}
11-
{%- set backup_relation = make_backup_relation(target_relation, backup_relation_type) -%}
12-
{%- set preexisting_backup_relation = load_cached_relation(backup_relation) -%}
13-
{% if not existing_relation.can_exchange %}
14-
{%- set intermediate_relation = make_intermediate_relation(target_relation) -%}
15-
{%- set preexisting_intermediate_relation = load_cached_relation(intermediate_relation) -%}
16-
{% endif %}
17-
{% endif %}
185

196
{% set grant_config = config.get('grants') %}
207

218
{{ run_hooks(pre_hooks, inside_transaction=False) }}
229

23-
-- drop the temp relations if they exist already in the database
24-
{{ drop_relation_if_exists(preexisting_intermediate_relation) }}
25-
{{ drop_relation_if_exists(preexisting_backup_relation) }}
26-
2710
-- `BEGIN` happens here:
2811
{{ run_hooks(pre_hooks, inside_transaction=True) }}
2912

30-
{% if backup_relation is none %}
13+
{% if existing_relation is none %}
3114
{{ log('Creating new relation ' + target_relation.name )}}
32-
-- There is not existing relation, so we can just create
33-
{% call statement('main') -%}
34-
{{ get_create_view_as_sql(target_relation, sql) }}
35-
{%- endcall %}
36-
{% elif existing_relation.can_exchange %}
37-
-- We can do an atomic exchange, so no need for an intermediate
38-
{% call statement('main') -%}
39-
{{ get_create_view_as_sql(backup_relation, sql) }}
40-
{%- endcall %}
41-
{% do exchange_tables_atomic(backup_relation, existing_relation) %}
4215
{% else %}
43-
-- We have to use an intermediate and rename accordingly
44-
{% call statement('main') -%}
45-
{{ get_create_view_as_sql(intermediate_relation, sql) }}
46-
{%- endcall %}
47-
{{ adapter.rename_relation(existing_relation, backup_relation) }}
48-
{{ adapter.rename_relation(intermediate_relation, target_relation) }}
16+
{{ log('Relation ' + target_relation.name + ' already exists, replacing it' )}}
4917
{% endif %}
5018

19+
{% call statement('main') -%}
20+
{{ get_create_view_as_sql(target_relation, sql) }}
21+
{%- endcall %}
22+
5123
-- cleanup
5224
{% set should_revoke = should_revoke(existing_relation, full_refresh_mode=True) %}
5325
{% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}
@@ -58,8 +30,6 @@
5830

5931
{{ adapter.commit() }}
6032

61-
{{ drop_relation_if_exists(backup_relation) }}
62-
6333
{{ run_hooks(post_hooks, inside_transaction=False) }}
6434

6535
{{ return({'relations': [target_relation]}) }}
@@ -71,7 +41,7 @@
7141
{%- set sql_header = config.get('sql_header', none) -%}
7242
{{ sql_header if sql_header is not none }}
7343

74-
create view {{ relation.include(database=False) }} {{ on_cluster_clause(relation)}}
44+
create or replace view {{ relation.include(database=False) }} {{ on_cluster_clause(relation)}}
7545
{% set contract_config = config.get('contract') %}
7646
{% if contract_config.enforced %}
7747
{{ get_assert_columns_equivalent(sql) }}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""
2+
Test ClickHouse view materialization in dbt-clickhouse
3+
"""
4+
5+
import json
6+
7+
import pytest
8+
from dbt.tests.util import run_dbt
9+
10+
PEOPLE_SEED_CSV = """
11+
id,name,age,department
12+
1231,Dade,33,engineering
13+
6666,Ksenia,48,engineering
14+
8888,Kate,50,engineering
15+
""".lstrip()
16+
17+
PEOPLE_VIEW_MODEL = """
18+
{{ config(
19+
materialized='view'
20+
) }}
21+
22+
{% if var('run_type', '') == '' %}
23+
select id, name, age from {{ source('raw', 'people') }}
24+
{% elif var('run_type', '') == 'update_view' %}
25+
select id, name, age, department from {{ source('raw', 'people') }}
26+
{% endif %}
27+
"""
28+
29+
30+
SEED_SCHEMA_YML = """
31+
version: 2
32+
33+
sources:
34+
- name: raw
35+
schema: "{{ target.schema }}"
36+
tables:
37+
- name: people
38+
"""
39+
40+
41+
class TestClickHouseView:
42+
@pytest.fixture(scope="class")
43+
def seeds(self):
44+
return {
45+
"people.csv": PEOPLE_SEED_CSV,
46+
"schema.yml": SEED_SCHEMA_YML,
47+
}
48+
49+
@pytest.fixture(scope="class")
50+
def models(self):
51+
return {"people_view.sql": PEOPLE_VIEW_MODEL}
52+
53+
def test_create_view(self, project):
54+
# Load seed data
55+
run_dbt(["seed"])
56+
57+
# Run dbt to create the view
58+
run_dbt()
59+
60+
# Query the view and check if it returns expected data
61+
result = project.run_sql("SELECT COUNT(*) FROM people_view", fetch="one")
62+
assert result[0] == 3 # 3 records in the seed data
63+
64+
# Run dbt again to apply the update
65+
run_dbt(["run", "--vars", json.dumps({"run_type": "update_view"})])
66+
67+
# Verify the new column is present
68+
result = project.run_sql("DESCRIBE TABLE people_view", fetch="all")
69+
columns = {row[0] for row in result}
70+
assert "department" in columns # New column should be present

tests/integration/docker-compose.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ services:
2222
- SERVER_INDEX=1
2323
- SHARD_NUM=${SHARD_NUM:-1}
2424
- REPLICA_NUM=${REPLICA_NUM:-1}
25+
- CLICKHOUSE_SKIP_USER_SETUP=1
2526
ports:
2627
- "8123:8123"
2728
- "8443:8443"
@@ -37,13 +38,15 @@ services:
3738
- SERVER_INDEX=2
3839
- SHARD_NUM=${SHARD_NUM:-2}
3940
- REPLICA_NUM=${REPLICA_NUM:-2}
41+
- CLICKHOUSE_SKIP_USER_SETUP=1
4042
<<: *ch-common
4143
ch2:
4244
image: clickhouse/clickhouse-server:${DBT_CH_TEST_CH_VERSION:-latest}
4345
environment:
4446
- SERVER_INDEX=3
4547
- SHARD_NUM=${SHARD_NUM:-3}
4648
- REPLICA_NUM=${REPLICA_NUM:-3}
49+
- CLICKHOUSE_SKIP_USER_SETUP=1
4750
<<: *ch-common
4851

4952
networks:

0 commit comments

Comments
 (0)