forked from dbt-msft/dbt-sqlserver
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtable_dml_refresh.sql
More file actions
95 lines (76 loc) · 3.98 KB
/
table_dml_refresh.sql
File metadata and controls
95 lines (76 loc) · 3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
{% macro sqlserver__table_dml_refresh(target_relation, sql) %}
{#
DML-only table refresh for use under RCSI.
Instead of rename-swap (which uses DDL and creates a window where the
table name doesnt resolve), this macro:
1. Builds new data into a scratch table via SELECT INTO (minimally logged)
2. Compares schemas — if columns changed, falls back to rename-swap
3. Swaps data via DELETE + INSERT inside an explicit transaction
(RCSI ensures concurrent readers see old data until COMMIT)
4. Cleans up the scratch table
The scratch table is a regular table with a __dbt_refresh suffix,
not a global temp table. This avoids cross-session visibility issues
and ensures cleanup on failure (DROP IF EXISTS at the start of each run).
#}
{%- set refresh_relation = target_relation.incorporate(
path={"identifier": target_relation.identifier ~ '__dbt_refresh'}
) -%}
{%- set tmp_vw_relation = refresh_relation.incorporate(
path={"identifier": refresh_relation.identifier ~ '__dbt_tmp_vw'}
) -%}
{# Clean up any leftovers from a prior failed run #}
{% call statement('dml_refresh_cleanup_pre') -%}
DROP VIEW IF EXISTS {{ tmp_vw_relation.include(database=False) }};
DROP TABLE IF EXISTS {{ refresh_relation }};
{%- endcall %}
{# Build new data into scratch table via temp view (handles CTEs in model SQL) #}
{# Named 'main' because dbt requires a statement('main') call in every materialization #}
{% call statement('dml_refresh_create_view') -%}
{{ get_create_view_as_sql(tmp_vw_relation, sql) }}
{%- endcall %}
{% call statement('main') -%}
SELECT * INTO {{ refresh_relation }} FROM {{ tmp_vw_relation }};
{%- endcall %}
{% call statement('dml_refresh_drop_view') -%}
DROP VIEW IF EXISTS {{ tmp_vw_relation.include(database=False) }};
{%- endcall %}
{# Compare schemas: if columns differ, fall back to rename-swap #}
{%- set schema_changes = check_for_schema_changes(refresh_relation, target_relation) -%}
{%- set schema_match = not schema_changes['schema_changed'] -%}
{% if schema_match %}
{# Use the target's physical column order for both INSERT and SELECT. #}
{# The scratch table has the same columns but possibly in a different order, #}
{# so naming columns explicitly makes the swap order-independent. #}
{%- set target_columns = adapter.get_columns_in_relation(target_relation) -%}
{%- set column_list = target_columns | map(attribute='quoted') | join(', ') -%}
{# Atomic DML swap — RCSI protects concurrent readers #}
{# dbt-sqlserver uses autocommit=True and add_begin_query/add_commit_query #}
{# are no-ops, so this creates a simple (non-nested) transaction. #}
{% call statement('dml_refresh_swap') -%}
BEGIN TRANSACTION;
DELETE FROM {{ target_relation }};
INSERT INTO {{ target_relation }} ({{ column_list }})
SELECT {{ column_list }} FROM {{ refresh_relation }};
COMMIT TRANSACTION;
{%- endcall %}
{# Cleanup scratch table #}
{% call statement('dml_refresh_cleanup_post') -%}
DROP TABLE IF EXISTS {{ refresh_relation }};
{%- endcall %}
{% else %}
{# Schema changed — fall back to rename-swap for this run #}
{{ log("Schema change detected for " ~ target_relation ~ " — falling back to rename-swap", info=true) }}
{%- set backup_relation_type = target_relation.type -%}
{%- set backup_relation = make_backup_relation(target_relation, backup_relation_type) -%}
{{ drop_relation_if_exists(backup_relation) }}
{# Rename scratch table into position #}
{% set existing_relation = load_cached_relation(target_relation) %}
{% if existing_relation is not none %}
{{ adapter.rename_relation(existing_relation, backup_relation) }}
{% endif %}
{{ adapter.rename_relation(refresh_relation, target_relation) }}
{% do create_indexes(target_relation) %}
{{ drop_relation_if_exists(backup_relation) }}
{# scratch table is now the target, nothing to drop #}
{% endif %}
{% endmacro %}