dbt-adapters 1.22.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dbt/adapters/__about__.py +1 -0
- dbt/adapters/__init__.py +8 -0
- dbt/adapters/base/README.md +13 -0
- dbt/adapters/base/__init__.py +16 -0
- dbt/adapters/base/column.py +173 -0
- dbt/adapters/base/connections.py +429 -0
- dbt/adapters/base/impl.py +2036 -0
- dbt/adapters/base/meta.py +150 -0
- dbt/adapters/base/plugin.py +32 -0
- dbt/adapters/base/query_headers.py +106 -0
- dbt/adapters/base/relation.py +648 -0
- dbt/adapters/cache.py +521 -0
- dbt/adapters/capability.py +63 -0
- dbt/adapters/catalogs/__init__.py +14 -0
- dbt/adapters/catalogs/_client.py +54 -0
- dbt/adapters/catalogs/_constants.py +1 -0
- dbt/adapters/catalogs/_exceptions.py +39 -0
- dbt/adapters/catalogs/_integration.py +113 -0
- dbt/adapters/clients/__init__.py +0 -0
- dbt/adapters/clients/jinja.py +24 -0
- dbt/adapters/contracts/__init__.py +0 -0
- dbt/adapters/contracts/connection.py +229 -0
- dbt/adapters/contracts/macros.py +11 -0
- dbt/adapters/contracts/relation.py +160 -0
- dbt/adapters/events/README.md +51 -0
- dbt/adapters/events/__init__.py +0 -0
- dbt/adapters/events/adapter_types_pb2.py +2 -0
- dbt/adapters/events/base_types.py +36 -0
- dbt/adapters/events/logging.py +83 -0
- dbt/adapters/events/types.py +436 -0
- dbt/adapters/exceptions/__init__.py +40 -0
- dbt/adapters/exceptions/alias.py +24 -0
- dbt/adapters/exceptions/cache.py +68 -0
- dbt/adapters/exceptions/compilation.py +269 -0
- dbt/adapters/exceptions/connection.py +16 -0
- dbt/adapters/exceptions/database.py +51 -0
- dbt/adapters/factory.py +264 -0
- dbt/adapters/protocol.py +150 -0
- dbt/adapters/py.typed +0 -0
- dbt/adapters/record/__init__.py +2 -0
- dbt/adapters/record/base.py +291 -0
- dbt/adapters/record/cursor/cursor.py +69 -0
- dbt/adapters/record/cursor/description.py +37 -0
- dbt/adapters/record/cursor/execute.py +39 -0
- dbt/adapters/record/cursor/fetchall.py +69 -0
- dbt/adapters/record/cursor/fetchmany.py +23 -0
- dbt/adapters/record/cursor/fetchone.py +23 -0
- dbt/adapters/record/cursor/rowcount.py +23 -0
- dbt/adapters/record/handle.py +55 -0
- dbt/adapters/record/serialization.py +115 -0
- dbt/adapters/reference_keys.py +39 -0
- dbt/adapters/relation_configs/README.md +25 -0
- dbt/adapters/relation_configs/__init__.py +12 -0
- dbt/adapters/relation_configs/config_base.py +46 -0
- dbt/adapters/relation_configs/config_change.py +26 -0
- dbt/adapters/relation_configs/config_validation.py +57 -0
- dbt/adapters/sql/__init__.py +2 -0
- dbt/adapters/sql/connections.py +263 -0
- dbt/adapters/sql/impl.py +286 -0
- dbt/adapters/utils.py +69 -0
- dbt/include/__init__.py +3 -0
- dbt/include/global_project/__init__.py +4 -0
- dbt/include/global_project/dbt_project.yml +7 -0
- dbt/include/global_project/docs/overview.md +43 -0
- dbt/include/global_project/macros/adapters/apply_grants.sql +167 -0
- dbt/include/global_project/macros/adapters/columns.sql +144 -0
- dbt/include/global_project/macros/adapters/freshness.sql +32 -0
- dbt/include/global_project/macros/adapters/indexes.sql +41 -0
- dbt/include/global_project/macros/adapters/metadata.sql +105 -0
- dbt/include/global_project/macros/adapters/persist_docs.sql +33 -0
- dbt/include/global_project/macros/adapters/relation.sql +84 -0
- dbt/include/global_project/macros/adapters/schema.sql +20 -0
- dbt/include/global_project/macros/adapters/show.sql +26 -0
- dbt/include/global_project/macros/adapters/timestamps.sql +52 -0
- dbt/include/global_project/macros/adapters/validate_sql.sql +10 -0
- dbt/include/global_project/macros/etc/datetime.sql +62 -0
- dbt/include/global_project/macros/etc/statement.sql +52 -0
- dbt/include/global_project/macros/generic_test_sql/accepted_values.sql +27 -0
- dbt/include/global_project/macros/generic_test_sql/not_null.sql +9 -0
- dbt/include/global_project/macros/generic_test_sql/relationships.sql +23 -0
- dbt/include/global_project/macros/generic_test_sql/unique.sql +12 -0
- dbt/include/global_project/macros/get_custom_name/get_custom_alias.sql +36 -0
- dbt/include/global_project/macros/get_custom_name/get_custom_database.sql +32 -0
- dbt/include/global_project/macros/get_custom_name/get_custom_schema.sql +60 -0
- dbt/include/global_project/macros/materializations/configs.sql +21 -0
- dbt/include/global_project/macros/materializations/functions/aggregate.sql +65 -0
- dbt/include/global_project/macros/materializations/functions/function.sql +20 -0
- dbt/include/global_project/macros/materializations/functions/helpers.sql +20 -0
- dbt/include/global_project/macros/materializations/functions/scalar.sql +69 -0
- dbt/include/global_project/macros/materializations/hooks.sql +35 -0
- dbt/include/global_project/macros/materializations/models/clone/can_clone_table.sql +7 -0
- dbt/include/global_project/macros/materializations/models/clone/clone.sql +67 -0
- dbt/include/global_project/macros/materializations/models/clone/create_or_replace_clone.sql +7 -0
- dbt/include/global_project/macros/materializations/models/incremental/column_helpers.sql +80 -0
- dbt/include/global_project/macros/materializations/models/incremental/incremental.sql +99 -0
- dbt/include/global_project/macros/materializations/models/incremental/is_incremental.sql +13 -0
- dbt/include/global_project/macros/materializations/models/incremental/merge.sql +120 -0
- dbt/include/global_project/macros/materializations/models/incremental/on_schema_change.sql +159 -0
- dbt/include/global_project/macros/materializations/models/incremental/strategies.sql +92 -0
- dbt/include/global_project/macros/materializations/models/materialized_view.sql +121 -0
- dbt/include/global_project/macros/materializations/models/table.sql +64 -0
- dbt/include/global_project/macros/materializations/models/view.sql +72 -0
- dbt/include/global_project/macros/materializations/seeds/helpers.sql +128 -0
- dbt/include/global_project/macros/materializations/seeds/seed.sql +60 -0
- dbt/include/global_project/macros/materializations/snapshots/helpers.sql +345 -0
- dbt/include/global_project/macros/materializations/snapshots/snapshot.sql +109 -0
- dbt/include/global_project/macros/materializations/snapshots/snapshot_merge.sql +34 -0
- dbt/include/global_project/macros/materializations/snapshots/strategies.sql +184 -0
- dbt/include/global_project/macros/materializations/tests/helpers.sql +44 -0
- dbt/include/global_project/macros/materializations/tests/test.sql +66 -0
- dbt/include/global_project/macros/materializations/tests/unit.sql +40 -0
- dbt/include/global_project/macros/materializations/tests/where_subquery.sql +15 -0
- dbt/include/global_project/macros/python_model/python.sql +114 -0
- dbt/include/global_project/macros/relations/column/columns_spec_ddl.sql +89 -0
- dbt/include/global_project/macros/relations/create.sql +23 -0
- dbt/include/global_project/macros/relations/create_backup.sql +17 -0
- dbt/include/global_project/macros/relations/create_intermediate.sql +17 -0
- dbt/include/global_project/macros/relations/drop.sql +41 -0
- dbt/include/global_project/macros/relations/drop_backup.sql +14 -0
- dbt/include/global_project/macros/relations/materialized_view/alter.sql +55 -0
- dbt/include/global_project/macros/relations/materialized_view/create.sql +10 -0
- dbt/include/global_project/macros/relations/materialized_view/drop.sql +14 -0
- dbt/include/global_project/macros/relations/materialized_view/refresh.sql +9 -0
- dbt/include/global_project/macros/relations/materialized_view/rename.sql +10 -0
- dbt/include/global_project/macros/relations/materialized_view/replace.sql +10 -0
- dbt/include/global_project/macros/relations/rename.sql +35 -0
- dbt/include/global_project/macros/relations/rename_intermediate.sql +14 -0
- dbt/include/global_project/macros/relations/replace.sql +50 -0
- dbt/include/global_project/macros/relations/schema.sql +8 -0
- dbt/include/global_project/macros/relations/table/create.sql +60 -0
- dbt/include/global_project/macros/relations/table/drop.sql +14 -0
- dbt/include/global_project/macros/relations/table/rename.sql +10 -0
- dbt/include/global_project/macros/relations/table/replace.sql +10 -0
- dbt/include/global_project/macros/relations/view/create.sql +27 -0
- dbt/include/global_project/macros/relations/view/drop.sql +14 -0
- dbt/include/global_project/macros/relations/view/rename.sql +10 -0
- dbt/include/global_project/macros/relations/view/replace.sql +66 -0
- dbt/include/global_project/macros/unit_test_sql/get_fixture_sql.sql +107 -0
- dbt/include/global_project/macros/utils/any_value.sql +9 -0
- dbt/include/global_project/macros/utils/array_append.sql +8 -0
- dbt/include/global_project/macros/utils/array_concat.sql +7 -0
- dbt/include/global_project/macros/utils/array_construct.sql +12 -0
- dbt/include/global_project/macros/utils/bool_or.sql +9 -0
- dbt/include/global_project/macros/utils/cast.sql +7 -0
- dbt/include/global_project/macros/utils/cast_bool_to_text.sql +7 -0
- dbt/include/global_project/macros/utils/concat.sql +7 -0
- dbt/include/global_project/macros/utils/data_types.sql +129 -0
- dbt/include/global_project/macros/utils/date.sql +10 -0
- dbt/include/global_project/macros/utils/date_spine.sql +75 -0
- dbt/include/global_project/macros/utils/date_trunc.sql +7 -0
- dbt/include/global_project/macros/utils/dateadd.sql +14 -0
- dbt/include/global_project/macros/utils/datediff.sql +14 -0
- dbt/include/global_project/macros/utils/equals.sql +14 -0
- dbt/include/global_project/macros/utils/escape_single_quotes.sql +8 -0
- dbt/include/global_project/macros/utils/except.sql +9 -0
- dbt/include/global_project/macros/utils/generate_series.sql +53 -0
- dbt/include/global_project/macros/utils/hash.sql +7 -0
- dbt/include/global_project/macros/utils/intersect.sql +9 -0
- dbt/include/global_project/macros/utils/last_day.sql +15 -0
- dbt/include/global_project/macros/utils/length.sql +11 -0
- dbt/include/global_project/macros/utils/listagg.sql +30 -0
- dbt/include/global_project/macros/utils/literal.sql +7 -0
- dbt/include/global_project/macros/utils/position.sql +11 -0
- dbt/include/global_project/macros/utils/replace.sql +14 -0
- dbt/include/global_project/macros/utils/right.sql +12 -0
- dbt/include/global_project/macros/utils/safe_cast.sql +9 -0
- dbt/include/global_project/macros/utils/split_part.sql +26 -0
- dbt/include/global_project/tests/generic/builtin.sql +30 -0
- dbt/include/py.typed +0 -0
- dbt_adapters-1.22.2.dist-info/METADATA +124 -0
- dbt_adapters-1.22.2.dist-info/RECORD +173 -0
- dbt_adapters-1.22.2.dist-info/WHEEL +4 -0
- dbt_adapters-1.22.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from dbt_common.record import Record, Recorder
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclasses.dataclass
|
|
8
|
+
class CursorFetchOneParams:
|
|
9
|
+
connection_name: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclasses.dataclass
|
|
13
|
+
class CursorFetchOneResult:
|
|
14
|
+
result: Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@Recorder.register_record_type
|
|
18
|
+
class CursorFetchOneRecord(Record):
|
|
19
|
+
"""Implements record/replay support for the cursor.fetchone() method."""
|
|
20
|
+
|
|
21
|
+
params_cls = CursorFetchOneParams
|
|
22
|
+
result_cls = CursorFetchOneResult
|
|
23
|
+
group = "Database"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from dbt_common.record import Record, Recorder
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclasses.dataclass
|
|
8
|
+
class CursorGetRowCountParams:
|
|
9
|
+
connection_name: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclasses.dataclass
|
|
13
|
+
class CursorGetRowCountResult:
|
|
14
|
+
rowcount: Optional[int]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@Recorder.register_record_type
|
|
18
|
+
class CursorGetRowCountRecord(Record):
|
|
19
|
+
"""Implements record/replay support for the cursor.rowcount property."""
|
|
20
|
+
|
|
21
|
+
params_cls = CursorGetRowCountParams
|
|
22
|
+
result_cls = CursorGetRowCountResult
|
|
23
|
+
group = "Database"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from dbt_common.events.base_types import BaseEvent
|
|
4
|
+
from dbt_common.events.functions import fire_event
|
|
5
|
+
from dbt_common.events.types import RecordReplayIssue
|
|
6
|
+
|
|
7
|
+
from dbt.adapters.contracts.connection import Connection
|
|
8
|
+
from dbt.adapters.record.cursor.cursor import RecordReplayCursor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RecordReplayHandle:
|
|
12
|
+
"""A proxy object used for record/replay modes. What adapters call a
|
|
13
|
+
'handle' is typically a native database connection, but should not be
|
|
14
|
+
confused with the Connection protocol, which is a dbt-adapters concept.
|
|
15
|
+
|
|
16
|
+
Currently, the only function of the handle proxy is to provide a record/replay
|
|
17
|
+
aware cursor object when cursor() is called."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, native_handle: Any, connection: Connection) -> None:
|
|
20
|
+
self.native_handle = native_handle
|
|
21
|
+
self.connection = connection
|
|
22
|
+
|
|
23
|
+
def cursor(self) -> Any:
|
|
24
|
+
# The native handle could be None if we are in replay mode, because no
|
|
25
|
+
# actual database access should be performed in that mode.
|
|
26
|
+
cursor = None if self.native_handle is None else self.native_handle.cursor()
|
|
27
|
+
return RecordReplayCursor(cursor, self.connection)
|
|
28
|
+
|
|
29
|
+
def commit(self):
|
|
30
|
+
self.native_handle.commit()
|
|
31
|
+
|
|
32
|
+
def rollback(self):
|
|
33
|
+
self.native_handle.rollback()
|
|
34
|
+
|
|
35
|
+
def close(self):
|
|
36
|
+
self.native_handle.close()
|
|
37
|
+
|
|
38
|
+
def get_backend_pid(self):
|
|
39
|
+
return self.native_handle.get_backend_pid()
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def closed(self):
|
|
43
|
+
return self.native_handle.closed
|
|
44
|
+
|
|
45
|
+
def _fire_event(self, evt: BaseEvent) -> None:
|
|
46
|
+
"""Wraps fire_event for easier test mocking."""
|
|
47
|
+
fire_event(evt)
|
|
48
|
+
|
|
49
|
+
def __getattr__(self, name: str) -> Any:
|
|
50
|
+
self._fire_event(
|
|
51
|
+
RecordReplayIssue(
|
|
52
|
+
msg=f"Unexpected attribute '{name}' accessed on {self.__class__.__name__}"
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
return getattr(self.native_handle, name)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from datetime import datetime, date
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Any, Dict, TYPE_CHECKING, List, Union, Optional
|
|
5
|
+
|
|
6
|
+
from dbt_common.record import get_record_row_limit_from_env
|
|
7
|
+
|
|
8
|
+
RECORDER_ROW_LIMIT: Optional[int] = get_record_row_limit_from_env()
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from agate import Table
|
|
12
|
+
from dbt.adapters.base.relation import BaseRelation
|
|
13
|
+
from dbt.adapters.base.column import Column as BaseColumn
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _column_filter(val: Any) -> Any:
|
|
17
|
+
return (
|
|
18
|
+
float(val)
|
|
19
|
+
if isinstance(val, Decimal)
|
|
20
|
+
else (
|
|
21
|
+
str(val)
|
|
22
|
+
if isinstance(val, datetime)
|
|
23
|
+
else str(val) if isinstance(val, date) else str(val)
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def serialize_agate_table(table: "Table") -> Dict[str, Any]:
|
|
29
|
+
rows = []
|
|
30
|
+
|
|
31
|
+
if RECORDER_ROW_LIMIT and len(table.rows) > RECORDER_ROW_LIMIT:
|
|
32
|
+
rows = [
|
|
33
|
+
[
|
|
34
|
+
f"Recording Error: Agate table contains {len(table.rows)} rows, maximum is {RECORDER_ROW_LIMIT} rows."
|
|
35
|
+
]
|
|
36
|
+
]
|
|
37
|
+
else:
|
|
38
|
+
for row in table.rows:
|
|
39
|
+
row = list(map(_column_filter, row))
|
|
40
|
+
rows.append(row)
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
"column_names": table.column_names,
|
|
44
|
+
"column_types": [t.__class__.__name__ for t in table.column_types],
|
|
45
|
+
"rows": rows,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def serialize_bindings(bindings: Any) -> Union[None, List[Any], str]:
|
|
50
|
+
if bindings is None:
|
|
51
|
+
return None
|
|
52
|
+
elif isinstance(bindings, list):
|
|
53
|
+
return list(map(_column_filter, bindings))
|
|
54
|
+
else:
|
|
55
|
+
return "bindings"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def serialize_base_relation(relation: "BaseRelation") -> Dict[str, Any]:
|
|
59
|
+
"""Serialize a BaseRelation object for recording."""
|
|
60
|
+
return relation.to_dict(omit_none=True)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def serialize_base_relation_list(relations: List["BaseRelation"]) -> List[Dict[str, Any]]:
|
|
64
|
+
"""Serialize a list of BaseRelation objects for recording."""
|
|
65
|
+
if RECORDER_ROW_LIMIT and len(relations) > RECORDER_ROW_LIMIT:
|
|
66
|
+
return [
|
|
67
|
+
{
|
|
68
|
+
"error": f"Recording Error: List of BaseRelation objects contains {len(relations)} objects, maximum is {RECORDER_ROW_LIMIT} objects."
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
else:
|
|
72
|
+
return [serialize_base_relation(relation) for relation in relations]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def deserialize_base_relation(relation_dict: Dict[str, Any]) -> "BaseRelation":
|
|
76
|
+
"""Deserialize a BaseRelation object from a dictionary."""
|
|
77
|
+
from dbt.adapters.base.relation import BaseRelation
|
|
78
|
+
|
|
79
|
+
return BaseRelation.from_dict(relation_dict)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def deserialize_base_relation_list(relations_data: List[Dict[str, Any]]) -> List["BaseRelation"]:
|
|
83
|
+
"""Deserialize a list of BaseRelation objects from dictionaries."""
|
|
84
|
+
return [deserialize_base_relation(relation_dict) for relation_dict in relations_data]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def serialize_base_column_list(columns: List["BaseColumn"]) -> List[Dict[str, Any]]:
|
|
88
|
+
if RECORDER_ROW_LIMIT and len(columns) > RECORDER_ROW_LIMIT:
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
"error": f"Recording Error: List of BaseColumn objects contains {len(columns)} objects, maximum is {RECORDER_ROW_LIMIT} objects."
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
else:
|
|
95
|
+
return [serialize_base_column(column) for column in columns]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def serialize_base_column(column: "BaseColumn") -> Dict[str, Any]:
|
|
99
|
+
column_dict = dataclasses.asdict(column)
|
|
100
|
+
return column_dict
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def deserialize_base_column_list(columns_data: List[Dict[str, Any]]) -> List["BaseColumn"]:
|
|
104
|
+
return [deserialize_base_column(column_dict) for column_dict in columns_data]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def deserialize_base_column(column_dict: Dict[str, Any]) -> "BaseColumn":
|
|
108
|
+
# Only include fields that are present in the base column class
|
|
109
|
+
params_dict = {
|
|
110
|
+
field.name: column_dict[field.name]
|
|
111
|
+
for field in dataclasses.fields(BaseColumn)
|
|
112
|
+
if field.name in column_dict
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return BaseColumn(**params_dict)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# this module exists to resolve circular imports with the events module
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
_ReferenceKey = namedtuple("_ReferenceKey", "database schema identifier")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def lowercase(value: Optional[str]) -> Optional[str]:
|
|
10
|
+
if value is None:
|
|
11
|
+
return None
|
|
12
|
+
else:
|
|
13
|
+
return value.lower()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# For backwards compatibility. New code should use _make_ref_key
|
|
17
|
+
def _make_key(relation: Any) -> _ReferenceKey:
|
|
18
|
+
return _make_ref_key(relation)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _make_ref_key(relation: Any) -> _ReferenceKey:
|
|
22
|
+
"""
|
|
23
|
+
Make _ReferenceKeys with lowercase values for the cache,
|
|
24
|
+
so we don't have to keep track of quoting
|
|
25
|
+
"""
|
|
26
|
+
# databases and schemas can both be None
|
|
27
|
+
return _ReferenceKey(
|
|
28
|
+
lowercase(relation.database),
|
|
29
|
+
lowercase(relation.schema),
|
|
30
|
+
lowercase(relation.identifier),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _make_ref_key_dict(relation: Any):
|
|
35
|
+
return {
|
|
36
|
+
"database": relation.database,
|
|
37
|
+
"schema": relation.schema,
|
|
38
|
+
"identifier": relation.identifier,
|
|
39
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# RelationConfig
|
|
2
|
+
This package serves as an initial abstraction for managing the inspection of existing relations and determining
|
|
3
|
+
changes on those relations. It arose from the materialized view work and is currently only supporting
|
|
4
|
+
materialized views for Postgres and Redshift as well as dynamic tables for Snowflake. There are three main
|
|
5
|
+
classes in this package.
|
|
6
|
+
|
|
7
|
+
## RelationConfigBase
|
|
8
|
+
This is a very small class that only has a `from_dict()` method and a default `NotImplementedError()`. At some
|
|
9
|
+
point this could be replaced by a more robust framework, like `mashumaro` or `pydantic`.
|
|
10
|
+
|
|
11
|
+
## RelationConfigChange
|
|
12
|
+
This class inherits from `RelationConfigBase` ; however, this can be thought of as a separate class. The subclassing
|
|
13
|
+
merely points to the idea that both classes would likely inherit from the same class in a `mashumaro` or
|
|
14
|
+
`pydantic` implementation. This class is much more restricted in attribution. It should really only
|
|
15
|
+
ever need an `action` and a `context`. This can be though of as being analogous to a web request. You need to
|
|
16
|
+
know what you're doing (`action`: 'create' = GET, 'drop' = DELETE, etc.) and the information (`context`) needed
|
|
17
|
+
to make the change. In our scenarios, the context tends to be an instance of `RelationConfigBase` corresponding
|
|
18
|
+
to the new state.
|
|
19
|
+
|
|
20
|
+
## RelationConfigValidationMixin
|
|
21
|
+
This mixin provides optional validation mechanics that can be applied to either `RelationConfigBase` or
|
|
22
|
+
`RelationConfigChange` subclasses. A validation rule is a combination of a `validation_check`, something
|
|
23
|
+
that should evaluate to `True`, and an optional `validation_error`, an instance of `DbtRuntimeError`
|
|
24
|
+
that should be raised in the event the `validation_check` fails. While optional, it's recommended that
|
|
25
|
+
the `validation_error` be provided for clearer transparency to the end user.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from dbt.adapters.relation_configs.config_base import (
|
|
2
|
+
RelationConfigBase,
|
|
3
|
+
RelationResults,
|
|
4
|
+
)
|
|
5
|
+
from dbt.adapters.relation_configs.config_change import (
|
|
6
|
+
RelationConfigChange,
|
|
7
|
+
RelationConfigChangeAction,
|
|
8
|
+
)
|
|
9
|
+
from dbt.adapters.relation_configs.config_validation import (
|
|
10
|
+
RelationConfigValidationMixin,
|
|
11
|
+
RelationConfigValidationRule,
|
|
12
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict, Union, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from dbt_common.utils import filter_null_values
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
import agate
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
This is what relation metadata from the database looks like. It's a dictionary because there will be
|
|
12
|
+
multiple grains of data for a single object. For example, a materialized view in Postgres has base level information,
|
|
13
|
+
like name. But it also can have multiple indexes, which needs to be a separate query. It might look like this:
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
"base": agate.Row({"table_name": "table_abc", "query": "select * from table_def"})
|
|
17
|
+
"indexes": agate.Table("rows": [
|
|
18
|
+
agate.Row({"name": "index_a", "columns": ["column_a"], "type": "hash", "unique": False}),
|
|
19
|
+
agate.Row({"name": "index_b", "columns": ["time_dim_a"], "type": "btree", "unique": False}),
|
|
20
|
+
])
|
|
21
|
+
}
|
|
22
|
+
"""
|
|
23
|
+
RelationResults = Dict[str, Union["agate.Row", "agate.Table"]]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class RelationConfigBase:
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_dict(cls, kwargs_dict) -> "RelationConfigBase":
|
|
30
|
+
"""
|
|
31
|
+
This assumes the subclass of `RelationConfigBase` is flat, in the sense that no attribute is
|
|
32
|
+
itself another subclass of `RelationConfigBase`. If that's not the case, this should be overriden
|
|
33
|
+
to manually manage that complexity.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
kwargs_dict: the dict representation of this instance
|
|
37
|
+
|
|
38
|
+
Returns: the `RelationConfigBase` representation associated with the provided dict
|
|
39
|
+
"""
|
|
40
|
+
return cls(**filter_null_values(kwargs_dict))
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def _not_implemented_error(cls) -> NotImplementedError:
|
|
44
|
+
return NotImplementedError(
|
|
45
|
+
"This relation type has not been fully configured for this adapter."
|
|
46
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Hashable
|
|
4
|
+
|
|
5
|
+
from dbt_common.dataclass_schema import StrEnum
|
|
6
|
+
|
|
7
|
+
from dbt.adapters.relation_configs.config_base import RelationConfigBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RelationConfigChangeAction(StrEnum):
|
|
11
|
+
alter = "alter"
|
|
12
|
+
create = "create"
|
|
13
|
+
drop = "drop"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, eq=True, unsafe_hash=True)
|
|
17
|
+
class RelationConfigChange(RelationConfigBase, ABC):
|
|
18
|
+
action: RelationConfigChangeAction
|
|
19
|
+
context: (
|
|
20
|
+
Hashable # this is usually a RelationConfig, e.g. IndexConfig, but shouldn't be limited
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def requires_full_refresh(self) -> bool:
|
|
26
|
+
raise self._not_implemented_error()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional, Set
|
|
3
|
+
|
|
4
|
+
from dbt_common.exceptions import DbtRuntimeError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True, eq=True, unsafe_hash=True)
|
|
8
|
+
class RelationConfigValidationRule:
|
|
9
|
+
validation_check: bool
|
|
10
|
+
validation_error: Optional[DbtRuntimeError]
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def default_error(self):
|
|
14
|
+
return DbtRuntimeError(
|
|
15
|
+
"There was a validation error in preparing this relation config."
|
|
16
|
+
"No additional context was provided by this adapter."
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class RelationConfigValidationMixin:
|
|
22
|
+
def __post_init__(self):
|
|
23
|
+
self.run_validation_rules()
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def validation_rules(self) -> Set[RelationConfigValidationRule]:
|
|
27
|
+
"""
|
|
28
|
+
A set of validation rules to run against the object upon creation.
|
|
29
|
+
|
|
30
|
+
A validation rule is a combination of a validation check (bool) and an optional error message.
|
|
31
|
+
|
|
32
|
+
This defaults to no validation rules if not implemented. It's recommended to override this with values,
|
|
33
|
+
but that may not always be necessary.
|
|
34
|
+
|
|
35
|
+
Returns: a set of validation rules
|
|
36
|
+
"""
|
|
37
|
+
return set()
|
|
38
|
+
|
|
39
|
+
def run_validation_rules(self):
|
|
40
|
+
for validation_rule in self.validation_rules:
|
|
41
|
+
try:
|
|
42
|
+
assert validation_rule.validation_check
|
|
43
|
+
except AssertionError:
|
|
44
|
+
if validation_rule.validation_error:
|
|
45
|
+
raise validation_rule.validation_error
|
|
46
|
+
else:
|
|
47
|
+
raise validation_rule.default_error
|
|
48
|
+
self.run_child_validation_rules()
|
|
49
|
+
|
|
50
|
+
def run_child_validation_rules(self):
|
|
51
|
+
for attr_value in vars(self).values():
|
|
52
|
+
if hasattr(attr_value, "validation_rules"):
|
|
53
|
+
attr_value.run_validation_rules()
|
|
54
|
+
if isinstance(attr_value, set):
|
|
55
|
+
for member in attr_value:
|
|
56
|
+
if hasattr(member, "validation_rules"):
|
|
57
|
+
member.run_validation_rules()
|