Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dbt-materialize: Store Failures As #23025

Merged
merged 6 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions misc/dbt-materialize/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@
checks, but the value of this feature in a real-time data warehouse is
limited.

* Support for `store_failures_as` which can be `table`, `view`,`ephemeral` or `materialized_view` (like models, a `table` will be a `materialized_view`).

* Project level
```yaml
tests:
my_project:
+store_failures_as: table
```
* Model level
```yaml
models:
- name: my_model
columns:
- name: id
tests:
- not_null:
config:
store_failures_as: view
- unique:
config:
store_failures_as: materialized_view
```

## 1.6.1 - 2023-11-03

* Support the [`ASSERT NOT NULL` option](https://materialize.com/docs/sql/create-materialized-view/#non-null-assertions)
Expand Down
4 changes: 2 additions & 2 deletions misc/dbt-materialize/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ as well as [`--persist-docs`](https://docs.getdbt.com/reference/resource-configs

[`dbt test`](https://docs.getdbt.com/reference/commands/test) is supported.

If you set the optional `--store-failures` flag or [`store-failures` config](https://docs.getdbt.com/reference/resource-configs/store_failures),
dbt will save the results of a test query to a `materialized_view`. These will
If you set the optional flags `--store-failures` or `--store-failures-as` (or config [`store_failures` config](https://docs.getdbt.com/reference/resource-configs/store_failures) or [`store_failures_as` config](https://docs.getdbt.com/reference/resource-configs/store_failures_as)),
By default dbt will save the results of a test query to a `materialized_view`. These will
be created in a schema suffixed or named `dbt_test__audit` by default. Change
this value by setting a `schema` config.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,38 @@

{% if should_store_failures() %}

{% set store_failures_as = config.get('store_failures_as') %}
-- if `--store-failures` is invoked via command line and `store_failures_as` is not set,
-- config.get('store_failures_as', 'table') returns None, not 'table'
{% if store_failures_as == none %}{% set store_failures_as = 'table' %}{% endif %}
{% if store_failures_as not in ['table', 'view', 'materialized_view'] %}
morsapaes marked this conversation as resolved.
Show resolved Hide resolved
{{ exceptions.raise_compiler_error(
"'" ~ store_failures_as ~ "' is not a valid value for `store_failures_as`. "
"Accepted values are: ['table', 'view', 'materialized_view']"
morsapaes marked this conversation as resolved.
Show resolved Hide resolved
) }}
{% endif %}

{% set identifier = model['alias'] %}
{% set old_relation = adapter.get_relation(database=database, schema=schema, identifier=identifier) %}
{% set target_relation = api.Relation.create(
identifier=identifier, schema=schema, database=database, type='materializedview') -%} %}
identifier=identifier, schema=schema, database=database, type=store_failures_as) -%} %}

{% if old_relation %}
{% do adapter.drop_relation(old_relation) %}
{% endif %}

{% call statement(auto_begin=True) %}
{{ materialize__create_materialized_view_as(target_relation, sql) }}
{% endcall %}

{% do relations.append(target_relation) %}

{% if store_failures_as == 'view' %}
{% call statement(auto_begin=True) %}
{{ materialize__create_view_as(target_relation, sql) }}
{% endcall %}
{% else %}
{% call statement(auto_begin=True) %}
{{ materialize__create_materialized_view_as(target_relation, sql) }}
{% endcall %}
{% endif %}

{% set main_sql %}
select *
from {{ target_relation }}
Expand Down
135 changes: 135 additions & 0 deletions misc/dbt-materialize/tests/adapter/test_store_test_failures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Copyright Materialize, Inc. and contributors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License in the LICENSE file at the
# root of this repository, or online at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest
from dbt.contracts.results import TestStatus
from dbt.tests.adapter.store_test_failures_tests import basic
from dbt.tests.adapter.store_test_failures_tests.fixtures import (
models__file_model_but_with_a_no_good_very_long_name,
models__fine_model,
)
from dbt.tests.adapter.store_test_failures_tests.test_store_test_failures import (
StoreTestFailuresBase,
)
from dbt.tests.util import run_dbt

TEST__MATERIALIZED_VIEW_TRUE = """
{{ config(store_failures_as="materialized_view", store_failures=True) }}
select *
from {{ ref('chipmunks') }}
where shirt = 'green'
"""


TEST__MATERIALIZED_VIEW_FALSE = """
{{ config(store_failures_as="materialized_view", store_failures=False) }}
select *
from {{ ref('chipmunks') }}
where shirt = 'green'
"""


TEST__MATERIALIZED_VIEW_UNSET = """
{{ config(store_failures_as="materialized_view") }}
select *
from {{ ref('chipmunks') }}
where shirt = 'green'
"""


class TestStoreTestFailures(StoreTestFailuresBase):
@pytest.fixture(scope="class")
def models(self):
return {
"fine_model.sql": models__fine_model,
"fine_model_but_with_a_no_good_very_long_name.sql": models__file_model_but_with_a_no_good_very_long_name,
}


class TestMaterializeStoreTestFailures(TestStoreTestFailures):
pass


class TestStoreTestFailuresAsInteractions(basic.StoreTestFailuresAsInteractions):
pass


class TestStoreTestFailuresAsProjectLevelOff(basic.StoreTestFailuresAsProjectLevelOff):
pass


class TestStoreTestFailuresAsProjectLevelView(
basic.StoreTestFailuresAsProjectLevelView
):
pass


class TestStoreTestFailuresAsGeneric(basic.StoreTestFailuresAsGeneric):
pass


class TestStoreTestFailuresAsProjectLevelEphemeral(
basic.StoreTestFailuresAsProjectLevelEphemeral
):
pass


class TestStoreTestFailuresAsExceptions(basic.StoreTestFailuresAsExceptions):
def test_tests_run_unsuccessfully_and_raise_appropriate_exception(self, project):
results = run_dbt(["test"], expect_pass=False)
assert len(results) == 1
result = results[0]
assert "Compilation Error" in result.message
assert "'error' is not a valid value" in result.message
assert (
"Accepted values are: ['table', 'view', 'materialized_view']"
in result.message
)


class TestStoreTestFailuresAsProjectLevelMaterializeView(basic.StoreTestFailuresAsBase):
"""
These scenarios test that `store_failures_as` at the project level takes precedence over `store_failures`
at the model level.

Test Scenarios:

- If `store_failures_as = "materialized_view"` in the project and `store_failures = False` in the model,
then store the failures in a materialized view.
- If `store_failures_as = "materialized_view"` in the project and `store_failures = True` in the model,
then store the failures in a materialized view.
- If `store_failures_as = "materialized_view"` in the project and `store_failures` is not set,
then store the failures in a materialized view.
"""

@pytest.fixture(scope="class")
def tests(self):
return {
"results_true.sql": TEST__MATERIALIZED_VIEW_TRUE,
"results_false.sql": TEST__MATERIALIZED_VIEW_FALSE,
"results_unset.sql": TEST__MATERIALIZED_VIEW_UNSET,
}

@pytest.fixture(scope="class")
def project_config_update(self):
return {"tests": {"store_failures_as": "materialized_view"}}

def test_tests_run_successfully_and_are_stored_as_expected(self, project):
expected_results = {
basic.TestResult("results_true", TestStatus.Fail, "materialized_view"),
basic.TestResult("results_false", TestStatus.Fail, "materialized_view"),
basic.TestResult("results_unset", TestStatus.Fail, "materialized_view"),
}
self.run_and_assert(project, expected_results)