diff --git a/.changes/unreleased/Features-20230304-002708.yaml b/.changes/unreleased/Features-20230304-002708.yaml new file mode 100644 index 00000000..7e97ed8f --- /dev/null +++ b/.changes/unreleased/Features-20230304-002708.yaml @@ -0,0 +1,7 @@ +kind: Features +body: Support Materialized view replacement +time: 2023-03-04T00:27:08.158991+01:00 +custom: + Author: mdesmet + Issue: "" + PR: "256" diff --git a/dbt/include/trino/macros/adapters.sql b/dbt/include/trino/macros/adapters.sql index 4f24ff16..542e519e 100644 --- a/dbt/include/trino/macros/adapters.sql +++ b/dbt/include/trino/macros/adapters.sql @@ -29,15 +29,20 @@ {% macro trino__list_relations_without_caching(relation) %} {% call statement('list_relations_without_caching', fetch_result=True) -%} select - table_catalog as database, - table_name as name, - table_schema as schema, - case when table_type = 'BASE TABLE' then 'table' - when table_type = 'VIEW' then 'view' - else table_type + t.table_catalog as database, + t.table_name as name, + t.table_schema as schema, + case when mv.name is not null then 'materializedview' + when t.table_type = 'BASE TABLE' then 'table' + when t.table_type = 'VIEW' then 'view' + else t.table_type end as table_type - from {{ relation.information_schema() }}.tables - where table_schema = '{{ relation.schema | lower }}' + from {{ relation.information_schema() }}.tables t + left join system.metadata.materialized_views mv + on mv.catalog_name = t.table_catalog and mv.schema_name = t.table_schema and mv.name = t.table_name + where t.table_schema = '{{ relation.schema | lower }}' + and (mv.catalog_name is null or mv.catalog_name = '{{ relation.database | lower }}') + and (mv.schema_name is null or mv.schema_name = '{{ relation.schema | lower }}') {% endcall %} {{ return(load_result('list_relations_without_caching').table) }} {% endmacro %} @@ -111,8 +116,9 @@ {% macro trino__drop_relation(relation) -%} + {% set relation_type = 'materialized view' if relation.type == 'materializedview' else relation.type %} {% call statement('drop_relation', auto_begin=False) -%} - drop {{ relation.type }} if exists {{ relation }} + drop {{ relation_type }} if exists {{ relation }} {%- endcall %} {% endmacro %} @@ -144,8 +150,9 @@ {% macro trino__rename_relation(from_relation, to_relation) -%} + {% set from_relation_type = 'materialized view' if from_relation.type == 'materializedview' else from_relation.type %} {% call statement('rename_relation') -%} - alter {{ from_relation.type }} {{ from_relation }} rename to {{ to_relation }} + alter {{ from_relation_type }} {{ from_relation }} rename to {{ to_relation }} {%- endcall %} {% endmacro %} @@ -218,3 +225,35 @@ {% do run_query(sql) %} {% endfor %} {% endmacro %} + + +{% macro create_or_replace_view() %} + {%- set identifier = model['alias'] -%} + + {%- set old_relation = adapter.get_relation(database=database, schema=schema, identifier=identifier) -%} + {%- set exists_as_view = (old_relation is not none and old_relation.is_view) -%} + + {%- set target_relation = api.Relation.create( + identifier=identifier, schema=schema, database=database, + type='view') -%} + {% set grant_config = config.get('grants') %} + + {{ run_hooks(pre_hooks) }} + + -- If there is another object delete it + {%- if old_relation is not none and not old_relation.is_view -%} + {{ handle_existing_table(should_full_refresh(), old_relation) }} + {%- endif -%} + + -- build model + {% call statement('main') -%} + {{ get_create_view_as_sql(target_relation, sql) }} + {%- endcall %} + + {% set should_revoke = should_revoke(exists_as_view, full_refresh_mode=True) %} + {% do apply_grants(target_relation, grant_config, should_revoke=True) %} + + {{ run_hooks(post_hooks) }} + + {{ return({'relations': [target_relation]}) }} +{% endmacro %} diff --git a/tests/functional/adapter/materialization/test_materialized_view.py b/tests/functional/adapter/materialization/test_materialized_view.py new file mode 100644 index 00000000..df157fdd --- /dev/null +++ b/tests/functional/adapter/materialization/test_materialized_view.py @@ -0,0 +1,42 @@ +import pytest +from dbt.tests.util import check_relation_types, run_dbt + + +@pytest.mark.iceberg +class TestIcebergMaterializedViewExists: + @pytest.fixture(scope="class") + def project_config_update(self): + return { + "name": "materializedview", + } + + @pytest.fixture(scope="class") + def models(self): + return { + "my_view.sql": "select 1 a", + "my_table.sql": """ {{ + config(materialized='table') +}} +select 1 a""", + } + + def test_mv_is_dropped_when_model_runs_view(self, project): + project.adapter.execute("CREATE OR REPLACE MATERIALIZED VIEW my_view AS SELECT 2 b") + project.adapter.execute("CREATE OR REPLACE MATERIALIZED VIEW my_table AS SELECT 2 b") + + # check relation types + expected = { + "my_table": "materializedview", + "my_view": "materializedview", + } + check_relation_types(project.adapter, expected) + + model_count = len(run_dbt(["run"])) + assert model_count == 2 + + # check relation types + expected = { + "my_view": "view", + "my_table": "table", + } + check_relation_types(project.adapter, expected)