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

ADAP-2: Materialized Views #7239

Merged
merged 70 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
6e2476e
changie
mikealfare Mar 29, 2023
2ee2f1e
updating to main
McKnight-42 Apr 4, 2023
486a7bb
Merge branch 'feature/materialized-views/ADAP-2' of github.com:dbt-la…
McKnight-42 Apr 4, 2023
454d85f
updating to main
McKnight-42 Apr 4, 2023
6a460ef
ADAP-387: Stub materialized view as a materialization (#7211)
mikealfare Apr 7, 2023
1efadbd
Merge branch 'main' of github.com:dbt-labs/dbt into feature/materiali…
McKnight-42 Apr 10, 2023
30b73f5
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare Apr 10, 2023
619c138
Merge branch 'main' of github.com:dbt-labs/dbt into feature/materiali…
McKnight-42 Apr 11, 2023
2fe362e
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare Apr 11, 2023
8f3cc58
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare Apr 12, 2023
9fae72b
ADAP-387: reverting db_api implementation (#7322)
McKnight-42 Apr 12, 2023
8e4b1a7
Merge branch 'main' of github.com:dbt-labs/dbt into feature/materiali…
McKnight-42 Apr 17, 2023
e6d59e4
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare Apr 19, 2023
7747f8b
Merge branch 'main' of github.com:dbt-labs/dbt into feature/materiali…
McKnight-42 Apr 21, 2023
aabd97e
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare Apr 25, 2023
269888c
Merge branch 'main' of github.com:dbt-labs/dbt into feature/materiali…
McKnight-42 Apr 27, 2023
3f46e7d
ADAP-391: Add configuration change option (#7272)
McKnight-42 Apr 27, 2023
9b8e8cb
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare Apr 27, 2023
f4aab05
ADAP-388: Stub materialized view as a materialization - postgres (#7244)
mikealfare Apr 28, 2023
bba3a6c
Merge branch 'main' of github.com:dbt-labs/dbt into feature/materiali…
McKnight-42 Apr 28, 2023
205b57b
Merge branch 'main' of github.com:dbt-labs/dbt into feature/materiali…
McKnight-42 May 9, 2023
88c4a97
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare May 9, 2023
0d341dc
ADAP-402: Add configuration change option - postgres (#7334)
mikealfare May 11, 2023
ed65ce6
Merge branch 'feature/materialized-views/ADAP-2' of github.com:dbt-la…
McKnight-42 May 11, 2023
9262774
Merge branch 'main' of github.com:dbt-labs/dbt into feature/materiali…
McKnight-42 May 11, 2023
96d9439
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare May 11, 2023
f7aefd1
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare May 15, 2023
a20a3b5
Merge branch 'main' of github.com:dbt-labs/dbt into feature/materiali…
McKnight-42 May 15, 2023
76c0b00
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare May 16, 2023
5ae8238
ADAP-395: Implement native materialized view DDL (#7336)
mikealfare May 16, 2023
3c95e0c
Merge branch 'feature/materialized-views/ADAP-2' of github.com:dbt-la…
McKnight-42 May 16, 2023
76eba9c
removing returns from tests to stop logs from printing
McKnight-42 May 16, 2023
4f72934
moved test cases into postgres tests, left non-test functionality in …
mikealfare May 17, 2023
39b8b0a
fixed overwrite issue, simplified assertion method
mikealfare May 17, 2023
dac6088
updated import order to standard
mikealfare May 17, 2023
0070877
fixed test import paths
mikealfare May 17, 2023
96ba1b1
updated naming convention for proper test collection with the test ru…
mikealfare May 17, 2023
22a5f12
still trying to make the test runner happy
mikealfare May 17, 2023
59de331
rewrite index updates to use a better source in Postgres
mikealfare May 19, 2023
cca6246
break out a large test suite as a separate run
mikealfare May 19, 2023
249f5f7
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare May 19, 2023
f158a81
update `skip` and `fail` scenarios with more descriptive results
mikealfare May 23, 2023
e884abd
typo
mikealfare May 23, 2023
1b4a231
removed call to skip status
mikealfare May 23, 2023
01d42ff
reverting `exceptions_jinja.py`
mikealfare May 23, 2023
720da38
added FailFastError back, the right way
mikealfare May 23, 2023
9b54d85
removed PostgresIndex in favor of the already existing PostgresIndexC…
mikealfare May 23, 2023
c1ae265
removed assumed models in method calls, removed odd insert records an…
mikealfare May 23, 2023
df21475
fixed index issue, removed some indirection in testing
mikealfare May 23, 2023
d1c0a4f
made test more readable
mikealfare May 23, 2023
d57495f
remove the "apply" from the tests and put it on the base as the default
mikealfare May 23, 2023
c9c1a2e
generalized assertion for reuse with dbt-snowflake, fixed bug in reco…
mikealfare May 23, 2023
c51059e
fixed type to be more generic to accommodate adapters with their own …
mikealfare May 23, 2023
83041f4
fixed all the broken index stuff
mikealfare May 24, 2023
8eb95af
updated on_configuration_change to use existing patterns
mikealfare May 30, 2023
7cb8302
updated on_configuration_change to use existing patterns
mikealfare May 30, 2023
5280b06
reflected update in tests and materialization logic
mikealfare May 30, 2023
d2142bf
reflected update in tests and materialization logic
mikealfare May 31, 2023
c50e873
reverted the change to create a config object from the option object,…
mikealfare May 31, 2023
c2ba887
reverted the change to create a config object from the option object,…
mikealfare May 31, 2023
fdb59f8
modelled database objects to support monitoring all configuration cha…
mikealfare Jun 2, 2023
856704b
updated "skip" to "continue", throw an error on non-implemented macro…
mikealfare Jun 2, 2023
feff055
updated "skip" to "continue", throw an error on non-implemented macro…
mikealfare Jun 2, 2023
9fe6cc6
updated "skip" to "continue", throw an error on non-implemented macro…
mikealfare Jun 2, 2023
817a809
updated "skip" to "continue", throw an error on non-implemented macro…
mikealfare Jun 4, 2023
40849ba
reverted centralized framework, retained a few reusable base classes
mikealfare Jun 4, 2023
d65392f
updated names to be more consistent
mikealfare Jun 5, 2023
ad66bd9
Merge branch 'main' into feature/materialized-views/ADAP-2
mikealfare Jun 7, 2023
2fea818
readability updates
mikealfare Jun 7, 2023
c3ddda6
added readme specifying that `relation_configs` only supports materia…
mikealfare Jun 7, 2023
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
6 changes: 6 additions & 0 deletions .changes/unreleased/Features-20230329-120313.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Add support for materialized views
time: 2023-03-29T12:03:13.862041-04:00
custom:
Author: mikealfare McKnight-42
Issue: "6911"
8 changes: 8 additions & 0 deletions core/dbt/adapters/base/relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ def is_cte(self) -> bool:
def is_view(self) -> bool:
return self.type == RelationType.View

@property
def is_materialized_view(self) -> bool:
return self.type == RelationType.MaterializedView

@classproperty
def Table(cls) -> str:
return str(RelationType.Table)
Expand All @@ -344,6 +348,10 @@ def View(cls) -> str:
def External(cls) -> str:
return str(RelationType.External)

@classproperty
def MaterializedView(cls) -> str:
return str(RelationType.MaterializedView)

@classproperty
def get_relation_type(cls) -> Type[RelationType]:
return RelationType
Expand Down
25 changes: 25 additions & 0 deletions core/dbt/adapters/relation_configs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# RelationConfig
This package serves as an initial abstraction for managing the inspection of existing relations and determining
changes on those relations. It arose from the materialized view work and is currently only supporting
materialized views for Postgres and Redshift as well as dynamic tables for Snowflake. There are three main
classes in this package.

## RelationConfigBase
This is a very small class that only has a `from_dict()` method and a default `NotImplementedError()`. At some
point this could be replaced by a more robust framework, like `mashumaro` or `pydantic`.

## RelationConfigChange
This class inherits from `RelationConfigBase` ; however, this can be thought of as a separate class. The subclassing
merely points to the idea that both classes would likely inherit from the same class in a `mashumaro` or
`pydantic` implementation. This class is much more restricted in attribution. It should really only
ever need an `action` and a `context`. This can be though of as being analogous to a web request. You need to
know what you're doing (`action`: 'create' = GET, 'drop' = DELETE, etc.) and the information (`context`) needed
to make the change. In our scenarios, the context tends to be an instance of `RelationConfigBase` corresponding
to the new state.

## RelationConfigValidationMixin
This mixin provides optional validation mechanics that can be applied to either `RelationConfigBase` or
`RelationConfigChange` subclasses. A validation rule is a combination of a `validation_check`, something
that should evaluate to `True`, and an optional `validation_error`, an instance of `DbtRuntimeError`
that should be raised in the event the `validation_check` fails. While optional, it's recommended that
the `validation_error` be provided for clearer transparency to the end user.
12 changes: 12 additions & 0 deletions core/dbt/adapters/relation_configs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from dbt.adapters.relation_configs.config_base import ( # noqa: F401
RelationConfigBase,
RelationResults,
)
from dbt.adapters.relation_configs.config_change import ( # noqa: F401
RelationConfigChangeAction,
RelationConfigChange,
)
from dbt.adapters.relation_configs.config_validation import ( # noqa: F401
RelationConfigValidationMixin,
RelationConfigValidationRule,
)
44 changes: 44 additions & 0 deletions core/dbt/adapters/relation_configs/config_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from dataclasses import dataclass
from typing import Union, Dict

import agate
from dbt.utils import filter_null_values


"""
This is what relation metadata from the database looks like. It's a dictionary because there will be
multiple grains of data for a single object. For example, a materialized view in Postgres has base level information,
like name. But it also can have multiple indexes, which needs to be a separate query. It might look like this:

{
"base": agate.Row({"table_name": "table_abc", "query": "select * from table_def"})
"indexes": agate.Table("rows": [
agate.Row({"name": "index_a", "columns": ["column_a"], "type": "hash", "unique": False}),
agate.Row({"name": "index_b", "columns": ["time_dim_a"], "type": "btree", "unique": False}),
])
}
"""
RelationResults = Dict[str, Union[agate.Row, agate.Table]]


@dataclass(frozen=True)
class RelationConfigBase:
@classmethod
def from_dict(cls, kwargs_dict) -> "RelationConfigBase":
"""
This assumes the subclass of `RelationConfigBase` is flat, in the sense that no attribute is
itself another subclass of `RelationConfigBase`. If that's not the case, this should be overriden
to manually manage that complexity.

Args:
kwargs_dict: the dict representation of this instance

Returns: the `RelationConfigBase` representation associated with the provided dict
"""
return cls(**filter_null_values(kwargs_dict)) # type: ignore

@classmethod
def _not_implemented_error(cls) -> NotImplementedError:
return NotImplementedError(
"This relation type has not been fully configured for this adapter."
)
23 changes: 23 additions & 0 deletions core/dbt/adapters/relation_configs/config_change.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Hashable

from dbt.adapters.relation_configs.config_base import RelationConfigBase
from dbt.dataclass_schema import StrEnum


class RelationConfigChangeAction(StrEnum):
alter = "alter"
create = "create"
drop = "drop"


@dataclass(frozen=True, eq=True, unsafe_hash=True)
class RelationConfigChange(RelationConfigBase, ABC):
action: RelationConfigChangeAction
context: Hashable # this is usually a RelationConfig, e.g. IndexConfig, but shouldn't be limited
mikealfare marked this conversation as resolved.
Show resolved Hide resolved

@property
@abstractmethod
def requires_full_refresh(self) -> bool:
raise self._not_implemented_error()
57 changes: 57 additions & 0 deletions core/dbt/adapters/relation_configs/config_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from dataclasses import dataclass
from typing import Set, Optional

from dbt.exceptions import DbtRuntimeError


@dataclass(frozen=True, eq=True, unsafe_hash=True)
class RelationConfigValidationRule:
validation_check: bool
validation_error: Optional[DbtRuntimeError]

@property
def default_error(self):
return DbtRuntimeError(
"There was a validation error in preparing this relation config."
"No additional context was provided by this adapter."
)


@dataclass(frozen=True)
class RelationConfigValidationMixin:
def __post_init__(self):
self.run_validation_rules()

@property
def validation_rules(self) -> Set[RelationConfigValidationRule]:
"""
A set of validation rules to run against the object upon creation.

A validation rule is a combination of a validation check (bool) and an optional error message.

This defaults to no validation rules if not implemented. It's recommended to override this with values,
but that may not always be necessary.

Returns: a set of validation rules
"""
return set()

def run_validation_rules(self):
for validation_rule in self.validation_rules:
try:
assert validation_rule.validation_check
except AssertionError:
if validation_rule.validation_error:
raise validation_rule.validation_error
else:
raise validation_rule.default_error
self.run_child_validation_rules()

def run_child_validation_rules(self):
for attr_value in vars(self).values():
if hasattr(attr_value, "validation_rules"):
attr_value.run_validation_rules()
if isinstance(attr_value, set):
for member in attr_value:
if hasattr(member, "validation_rules"):
member.run_validation_rules()
6 changes: 6 additions & 0 deletions core/dbt/context/exceptions_jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
RelationWrongTypeError,
ContractError,
ColumnTypeMissingError,
FailFastError,
)


Expand Down Expand Up @@ -107,6 +108,10 @@ def column_type_missing(column_names) -> NoReturn:
raise ColumnTypeMissingError(column_names)


def raise_fail_fast_error(msg, node=None) -> NoReturn:
raise FailFastError(msg, node=node)


# Update this when a new function should be added to the
# dbt context's `exceptions` key!
CONTEXT_EXPORTS = {
Expand All @@ -131,6 +136,7 @@ def column_type_missing(column_names) -> NoReturn:
relation_wrong_type,
raise_contract_error,
column_type_missing,
raise_fail_fast_error,
]
}

Expand Down
27 changes: 24 additions & 3 deletions core/dbt/contracts/graph/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
from enum import Enum
from itertools import chain
from typing import Any, List, Optional, Dict, Union, Type, TypeVar, Callable

from dbt.dataclass_schema import (
dbtClassMixin,
ValidationError,
register_pattern,
StrEnum,
)
from dbt.contracts.graph.unparsed import AdditionalPropertiesAllowed, Docs
from dbt.contracts.graph.utils import validate_color
from dbt.exceptions import DbtInternalError, CompilationError
from dbt.contracts.util import Replaceable, list_str
from dbt.exceptions import DbtInternalError, CompilationError
from dbt import hooks
from dbt.node_types import NodeType

Expand Down Expand Up @@ -189,6 +191,16 @@ class Severity(str):
register_pattern(Severity, insensitive_patterns("warn", "error"))


class OnConfigurationChangeOption(StrEnum):
Apply = "apply"
Continue = "continue"
Fail = "fail"

@classmethod
def default(cls) -> "OnConfigurationChangeOption":
return cls.Apply


@dataclass
class ContractConfig(dbtClassMixin, Replaceable):
enforced: bool = False
Expand Down Expand Up @@ -287,11 +299,17 @@ def same_contents(cls, unrendered: Dict[str, Any], other: Dict[str, Any]) -> boo
return False
return True

# This is used in 'add_config_call' to created the combined config_call_dict.
# This is used in 'add_config_call' to create the combined config_call_dict.
# 'meta' moved here from node
mergebehavior = {
"append": ["pre-hook", "pre_hook", "post-hook", "post_hook", "tags"],
"update": ["quoting", "column_types", "meta", "docs", "contract"],
"update": [
"quoting",
"column_types",
"meta",
"docs",
"contract",
],
"dict_key_append": ["grants"],
}

Expand Down Expand Up @@ -445,6 +463,9 @@ class NodeConfig(NodeAndTestConfig):
# sometimes getting the Union order wrong, causing serialization failures.
unique_key: Union[str, List[str], None] = None
on_schema_change: Optional[str] = "ignore"
on_configuration_change: OnConfigurationChangeOption = field(
mikealfare marked this conversation as resolved.
Show resolved Hide resolved
default_factory=OnConfigurationChangeOption.default
)
grants: Dict[str, Any] = field(
default_factory=dict, metadata=MergeBehavior.DictKeyAppend.meta()
)
Expand Down
2 changes: 1 addition & 1 deletion core/dbt/contracts/relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class RelationType(StrEnum):
Table = "table"
View = "view"
CTE = "cte"
MaterializedView = "materializedview"
MaterializedView = "materialized_view"
External = "external"


Expand Down
18 changes: 18 additions & 0 deletions core/dbt/include/global_project/macros/adapters/indexes.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,21 @@
{% endif %}
{% endfor %}
{% endmacro %}


{% macro get_drop_index_sql(relation, index_name) -%}
{{ adapter.dispatch('get_drop_index_sql', 'dbt')(relation, index_name) }}
{%- endmacro %}

{% macro default__get_drop_index_sql(relation, index_name) -%}
{{ exceptions.raise_compiler_error("`get_drop_index_sql has not been implemented for this adapter.") }}
{%- endmacro %}


{% macro get_show_indexes_sql(relation) -%}
{{ adapter.dispatch('get_show_indexes_sql', 'dbt')(relation) }}
{%- endmacro %}

{% macro default__get_show_indexes_sql(relation) -%}
{{ exceptions.raise_compiler_error("`get_show_indexes_sql has not been implemented for this adapter.") }}
{%- endmacro %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{% macro get_alter_materialized_view_as_sql(
relation,
configuration_changes,
sql,
existing_relation,
backup_relation,
intermediate_relation
) %}
{{- log('Applying ALTER to: ' ~ relation) -}}
{{- adapter.dispatch('get_alter_materialized_view_as_sql', 'dbt')(
relation,
configuration_changes,
sql,
existing_relation,
backup_relation,
intermediate_relation
) -}}
{% endmacro %}


{% macro default__get_alter_materialized_view_as_sql(
relation,
configuration_changes,
sql,
existing_relation,
backup_relation,
intermediate_relation
) %}
{{ exceptions.raise_compiler_error("Materialized views have not been implemented for this adapter.") }}
{% endmacro %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% macro get_create_materialized_view_as_sql(relation, sql) -%}
{{- log('Applying CREATE to: ' ~ relation) -}}
mikealfare marked this conversation as resolved.
Show resolved Hide resolved
{{- adapter.dispatch('get_create_materialized_view_as_sql', 'dbt')(relation, sql) -}}
{%- endmacro %}


{% macro default__get_create_materialized_view_as_sql(relation, sql) -%}
{{ exceptions.raise_compiler_error("Materialized views have not been implemented for this adapter.") }}
{% endmacro %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% macro get_materialized_view_configuration_changes(existing_relation, new_config) %}
mikealfare marked this conversation as resolved.
Show resolved Hide resolved
/* {#
It's recommended that configuration changes be formatted as follows:
mikealfare marked this conversation as resolved.
Show resolved Hide resolved
{"<change_category>": [{"action": "<name>", "context": ...}]}

For example:
{
"indexes": [
{"action": "drop", "context": "index_abc"},
{"action": "create", "context": {"columns": ["column_1", "column_2"], "type": "hash", "unique": True}},
],
}

Either way, `get_materialized_view_configuration_changes` needs to align with `get_alter_materialized_view_as_sql`.
#} */
{{- log('Determining configuration changes on: ' ~ existing_relation) -}}
{%- do return(adapter.dispatch('get_materialized_view_configuration_changes', 'dbt')(existing_relation, new_config)) -%}
{% endmacro %}


{% macro default__get_materialized_view_configuration_changes(existing_relation, new_config) %}
{{ exceptions.raise_compiler_error("Materialized views have not been implemented for this adapter.") }}
{% endmacro %}
Loading