Skip to content

Commit 8a295da

Browse files
authored
Add Incremental Merge Strategy (#228)
### Summary Add implementation for merge functionality when using an incremental materialization. ### Description Dremio supports both merge and append now. More details on how to use merge strategies can be found here: https://docs.getdbt.com/docs/build/incremental-strategy#strategy-specific-configs. ### Test Results Only ran incremental tests as the new changes don't impact other parts of the adapter. ### Changelog - [x] Added a summary of what this PR accomplishes to CHANGELOG.md ### Related Issue (#224)
1 parent 04900e6 commit 8a295da

5 files changed

Lines changed: 141 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
# dbt-dremio MAIN
2+
3+
- [#223](https://github.com/dremio/dbt-dremio/issues/224) Implement merge strategy for incremental materializations
4+
15
# dbt-dremio v1.7.0
26

37
## Changes
4-
- [#8307](https://github.com/dbt-labs/dbt-core/discussions/8307) Allow source freshness to be evaluated from table metadata
5-
- [#8307](https://github.com/dbt-labs/dbt-core/discussions/8307) Catalog fetch performance improvements
6-
- [#8307](https://github.com/dbt-labs/dbt-core/discussions/8307) Migrate data_spine macros
7-
- [#195](https://github.com/dremio/dbt-dremio/issues/195) Ensure api call to create folders does not get called when creating a table
8+
9+
- [#195](https://github.com/dremio/dbt-dremio/issues/195) Ensure the adapter does not try and create folders in object storage source
810
- [#220](https://github.com/dremio/dbt-dremio/pull/220) Optimize networking performance with Dremio server
911

12+
1013
# dbt-dremio v1.5.1
1114

1215
## Changes

dbt/include/dremio/macros/materializations/incremental/incremental.sql

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,17 @@ limitations under the License.*/
6161
-- Process schema changes. Returns dict of changes if successful. Use source columns for upserting/merging
6262
{% set dest_columns = process_schema_changes(on_schema_change, temp_relation, existing_relation) %}
6363
{% if not dest_columns %}
64-
{% set dest_columns = adapter.get_columns_in_relation(existing_relation) %}
64+
{% set dest_columns = dremio__get_columns_in_relation(existing_relation) %}
6565
{% endif %}
6666

6767
-- Get the incremental_strategy, the macro to use for the strategy, and build the sql
6868
{%- set incremental_strategy = config.get('incremental_strategy', validator=validation.any[basestring]) or 'append' -%}
6969
{%- set raw_file_format = config.get('format', validator=validation.any[basestring]) or 'iceberg' -%}
7070
{%- set file_format = dbt_dremio_validate_get_file_format(raw_file_format) -%}
71-
{%- set strategy = dbt_dremio_validate_get_incremental_strategy(incremental_strategy, file_format) -%}
71+
{%- set incremental_predicates = config.get('predicates', none) or config.get('incremental_predicates', none) -%}
72+
{%- set strategy = dbt_dremio_validate_get_incremental_strategy(incremental_strategy) -%}
7273
{%- set raw_on_schema_change = config.get('on_schema_change', validator=validation.any[basestring]) or 'ignore' -%}
73-
{% set build_sql = dbt_dremio_get_incremental_sql(strategy, intermediate_relation, target_relation, unique_key) %}
74+
{% set build_sql = dbt_dremio_get_incremental_sql(strategy, intermediate_relation, target_relation, dest_columns, unique_key) %}
7475

7576
{% endif %}
7677

dbt/include/dremio/macros/materializations/incremental/strategies.sql

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,71 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
See the License for the specific language governing permissions and
1313
limitations under the License.*/
1414

15-
{% macro get_insert_into_sql(source_relation, target_relation) %}
1615

17-
{%- set dest_columns = adapter.get_columns_in_relation(target_relation) -%}
18-
{%- set src_columns = adapter.get_columns_in_relation(source_relation) -%}
19-
{%- set intersection = intersect_columns(src_columns, dest_columns) -%}
20-
{%- set dest_cols_csv = intersection | map(attribute='quoted') | join(', ') -%}
16+
{% macro dremio__get_incremental_append_sql(source_relation, target_relation, dest_columns) %}
17+
{%- set dest_cols_csv = dest_columns | map(attribute='quoted') | join(', ') -%}
2118
insert into {{ target_relation }}( {{dest_cols_csv}} )
2219
select {{dest_cols_csv}} from {{ source_relation }}
2320

2421
{% endmacro %}
2522

26-
{% macro dbt_dremio_get_incremental_sql(strategy, source, target, unique_key) %}
23+
24+
{% macro dremio__get_incremental_merge_sql(target, source, unique_key, dest_columns, incremental_predicates=none) -%}
25+
{%- set predicates = [] if incremental_predicates is none else [] + incremental_predicates -%}
26+
{%- set dest_cols_csv = get_quoted_csv(dest_columns | map(attribute="name")) -%}
27+
{%- set merge_update_columns = config.get('merge_update_columns') -%}
28+
{%- set merge_exclude_columns = config.get('merge_exclude_columns') -%}
29+
{%- set update_columns = get_merge_update_columns(merge_update_columns, merge_exclude_columns, dest_columns) -%}
30+
{%- set sql_header = config.get('sql_header', none) -%}
31+
32+
{% if unique_key %}
33+
{% if unique_key is sequence and unique_key is not mapping and unique_key is not string %}
34+
{% for key in unique_key %}
35+
{% set this_key_match %}
36+
DBT_INTERNAL_SOURCE.{{ key }} = DBT_INTERNAL_DEST.{{ key }}
37+
{% endset %}
38+
{% do predicates.append(this_key_match) %}
39+
{% endfor %}
40+
{% else %}
41+
{% set unique_key_match %}
42+
DBT_INTERNAL_SOURCE.{{ unique_key }} = DBT_INTERNAL_DEST.{{ unique_key }}
43+
{% endset %}
44+
{% do predicates.append(unique_key_match) %}
45+
{% endif %}
46+
{% else %}
47+
{% do predicates.append('FALSE') %}
48+
{% endif %}
49+
50+
{{ sql_header if sql_header is not none }}
51+
52+
merge into {{ target }} as DBT_INTERNAL_DEST
53+
using {{ source }} as DBT_INTERNAL_SOURCE
54+
on {{"(" ~ predicates | join(") and (") ~ ")"}}
55+
56+
{% if unique_key %}
57+
when matched then update set
58+
{% for column_name in update_columns -%}
59+
{{ column_name }} = DBT_INTERNAL_SOURCE.{{ column_name }}
60+
{%- if not loop.last %}, {%- endif %}
61+
{%- endfor %}
62+
{% endif %}
63+
64+
when not matched then insert
65+
({{ dest_cols_csv }})
66+
values
67+
({{ dest_cols_csv }})
68+
69+
{% endmacro %}
70+
71+
{% macro dbt_dremio_get_incremental_sql(strategy, source, target, dest_columns, unique_key) %}
2772
{%- if strategy == 'append' -%}
28-
{#-- insert new records into existing table, without updating or overwriting #}
29-
{{ get_insert_into_sql(source, target) }}
73+
{{ dremio__get_incremental_append_sql(source, target, dest_columns) }}
74+
{%- elif strategy == 'merge' -%}
75+
{{dremio__get_incremental_merge_sql(target, source, unique_key, dest_columns, incremental_predicates=none)}}
3076
{%- else -%}
3177
{% set no_sql_for_strategy_msg -%}
3278
No known SQL for the incremental strategy provided: {{ strategy }}
3379
{%- endset %}
3480
{%- do exceptions.CompilationError(no_sql_for_strategy_msg) -%}
3581
{%- endif -%}
36-
3782
{% endmacro %}

dbt/include/dremio/macros/materializations/incremental/validate.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ limitations under the License.*/
2929
{% do return(raw_file_format) %}
3030
{% endmacro %}
3131

32-
{% macro dbt_dremio_validate_get_incremental_strategy(raw_strategy, file_format) %}
32+
{% macro dbt_dremio_validate_get_incremental_strategy(raw_strategy) %}
3333
{#-- Validate the incremental strategy #}
3434

3535
{% set invalid_strategy_msg -%}
3636
Invalid incremental strategy provided: {{ raw_strategy }}
37-
Expected one of: 'append'
37+
Expected one of: 'append, merge'
3838
{%- endset %}
3939

40-
{% if raw_strategy not in ['append'] %}
40+
{% if raw_strategy not in ['append', 'merge'] %}
4141
{% do exceptions.CompilationError(invalid_strategy_msg) %}
4242
{% endif %}
4343

tests/functional/adapter/basic/test_incremental.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,54 @@
1717
BaseIncremental,
1818
BaseIncrementalNotSchemaChange,
1919
)
20-
from dbt.tests.util import run_dbt, check_relations_equal, relation_from_name
20+
from dbt.tests.adapter.incremental.test_incremental_merge_exclude_columns import (
21+
BaseMergeExcludeColumns,
22+
)
2123
from tests.fixtures.profiles import unique_schema, dbt_profile_data
24+
from tests.utils.util import BUCKET, SOURCE
25+
from dbt.tests.util import run_dbt, relation_from_name, check_relations_equal
26+
from collections import namedtuple
27+
28+
29+
models__merge_exclude_columns_sql = """
30+
{{ config(
31+
materialized = 'incremental',
32+
unique_key = 'id',
33+
incremental_strategy='merge',
34+
merge_exclude_columns='msg'
35+
) }}
36+
37+
{% if not is_incremental() %}
38+
39+
-- data for first invocation of model
40+
41+
select 1 as id, 'hello' as msg, 'blue' as color
42+
union all
43+
select 2 as id, 'goodbye' as msg, 'red' as color
44+
45+
{% else %}
46+
47+
-- data for subsequent incremental update
48+
49+
select 1 as id, 'hey' as msg, 'blue' as color
50+
union all
51+
select 2 as id, 'yo' as msg, 'green' as color
52+
union all
53+
select 3 as id, 'anyway' as msg, 'purple' as color
54+
55+
{% endif %}
56+
"""
57+
58+
ResultHolder = namedtuple(
59+
"ResultHolder",
60+
[
61+
"seed_count",
62+
"model_count",
63+
"seed_rows",
64+
"inc_test_model_count",
65+
"relation",
66+
],
67+
)
2268

2369

2470
# Need to modify test to not assert any sources for it to pass
@@ -65,3 +111,29 @@ def test_incremental(self, project):
65111

66112
class TestBaseIncrementalNotSchemaChange(BaseIncrementalNotSchemaChange):
67113
pass
114+
115+
116+
class TestBaseMergeExcludeColumnsDremio(BaseMergeExcludeColumns):
117+
def get_test_fields(self, project, seed, incremental_model, update_sql_file):
118+
seed_count = len(run_dbt(["seed", "--select", seed, "--full-refresh"]))
119+
120+
model_count = len(
121+
run_dbt(["run", "--select", incremental_model, "--full-refresh"])
122+
)
123+
124+
relation = incremental_model
125+
# update seed in anticipation of incremental model update
126+
row_count_query = "select * from {}.{}".format(
127+
f"{SOURCE}.{BUCKET}.{project.test_schema}", seed
128+
)
129+
130+
seed_rows = len(project.run_sql(row_count_query, fetch="all"))
131+
132+
# propagate seed state to incremental model according to unique keys
133+
inc_test_model_count = self.update_incremental_model(
134+
incremental_model=incremental_model
135+
)
136+
137+
return ResultHolder(
138+
seed_count, model_count, seed_rows, inc_test_model_count, relation
139+
)

0 commit comments

Comments
 (0)