dbt-adapters 0.1.0a1__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.
Potentially problematic release.
This version of dbt-adapters might be problematic. Click here for more details.
- dbt/__init__.py +0 -0
- dbt/adapters/__about__.py +1 -0
- dbt/adapters/__init__.py +7 -0
- dbt/adapters/base/README.md +13 -0
- dbt/adapters/base/__init__.py +15 -0
- dbt/adapters/base/column.py +166 -0
- dbt/adapters/base/connections.py +426 -0
- dbt/adapters/base/impl.py +1654 -0
- dbt/adapters/base/meta.py +131 -0
- dbt/adapters/base/plugin.py +32 -0
- dbt/adapters/base/query_headers.py +101 -0
- dbt/adapters/base/relation.py +471 -0
- dbt/adapters/cache.py +521 -0
- dbt/adapters/capability.py +52 -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 +228 -0
- dbt/adapters/contracts/macros.py +11 -0
- dbt/adapters/contracts/relation.py +125 -0
- dbt/adapters/events/README.md +57 -0
- dbt/adapters/events/__init__.py +0 -0
- dbt/adapters/events/adapter_types.proto +517 -0
- dbt/adapters/events/adapter_types_pb2.py +208 -0
- dbt/adapters/events/base_types.py +40 -0
- dbt/adapters/events/logging.py +83 -0
- dbt/adapters/events/types.py +423 -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 +255 -0
- dbt/adapters/exceptions/connection.py +16 -0
- dbt/adapters/exceptions/database.py +51 -0
- dbt/adapters/factory.py +246 -0
- dbt/adapters/protocol.py +173 -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 +44 -0
- dbt/adapters/relation_configs/config_change.py +24 -0
- dbt/adapters/relation_configs/config_validation.py +57 -0
- dbt/adapters/sql/__init__.py +2 -0
- dbt/adapters/sql/connections.py +195 -0
- dbt/adapters/sql/impl.py +273 -0
- dbt/adapters/utils.py +69 -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 +137 -0
- dbt/include/global_project/macros/adapters/freshness.sql +16 -0
- dbt/include/global_project/macros/adapters/indexes.sql +41 -0
- dbt/include/global_project/macros/adapters/metadata.sql +96 -0
- dbt/include/global_project/macros/adapters/persist_docs.sql +33 -0
- dbt/include/global_project/macros/adapters/relation.sql +79 -0
- dbt/include/global_project/macros/adapters/schema.sql +20 -0
- dbt/include/global_project/macros/adapters/show.sql +22 -0
- dbt/include/global_project/macros/adapters/timestamps.sql +44 -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/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 +92 -0
- dbt/include/global_project/macros/materializations/models/incremental/is_incremental.sql +13 -0
- dbt/include/global_project/macros/materializations/models/incremental/merge.sql +131 -0
- dbt/include/global_project/macros/materializations/models/incremental/on_schema_change.sql +144 -0
- dbt/include/global_project/macros/materializations/models/incremental/strategies.sql +79 -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 +181 -0
- dbt/include/global_project/macros/materializations/snapshots/snapshot.sql +99 -0
- dbt/include/global_project/macros/materializations/snapshots/snapshot_merge.sql +25 -0
- dbt/include/global_project/macros/materializations/snapshots/strategies.sql +174 -0
- dbt/include/global_project/macros/materializations/tests/helpers.sql +14 -0
- dbt/include/global_project/macros/materializations/tests/test.sql +60 -0
- dbt/include/global_project/macros/materializations/tests/where_subquery.sql +15 -0
- dbt/include/global_project/macros/python_model/python.sql +103 -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/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_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_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/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_adapters-0.1.0a1.dist-info/METADATA +81 -0
- dbt_adapters-0.1.0a1.dist-info/RECORD +147 -0
- dbt_adapters-0.1.0a1.dist-info/WHEEL +4 -0
- dbt_adapters-0.1.0a1.dist-info/licenses/LICENSE +201 -0
dbt/adapters/protocol.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import (
|
|
3
|
+
Any,
|
|
4
|
+
ContextManager,
|
|
5
|
+
Dict,
|
|
6
|
+
Generic,
|
|
7
|
+
Hashable,
|
|
8
|
+
List,
|
|
9
|
+
Optional,
|
|
10
|
+
Type,
|
|
11
|
+
TypeVar,
|
|
12
|
+
Tuple,
|
|
13
|
+
)
|
|
14
|
+
from typing_extensions import Protocol
|
|
15
|
+
|
|
16
|
+
import agate
|
|
17
|
+
from dbt_common.clients.jinja import MacroProtocol
|
|
18
|
+
from dbt_common.contracts.config.base import BaseConfig
|
|
19
|
+
|
|
20
|
+
from dbt.adapters.contracts.connection import (
|
|
21
|
+
AdapterRequiredConfig,
|
|
22
|
+
AdapterResponse,
|
|
23
|
+
Connection,
|
|
24
|
+
)
|
|
25
|
+
from dbt.adapters.contracts.macros import MacroResolverProtocol
|
|
26
|
+
from dbt.adapters.contracts.relation import HasQuoting, Policy, RelationConfig
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class AdapterConfig(BaseConfig):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ConnectionManagerProtocol(Protocol):
|
|
35
|
+
TYPE: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ColumnProtocol(Protocol):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Self = TypeVar("Self", bound="RelationProtocol")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class RelationProtocol(Protocol):
|
|
46
|
+
@classmethod
|
|
47
|
+
def get_default_quote_policy(cls) -> Policy:
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def create_from(
|
|
52
|
+
cls: Type[Self],
|
|
53
|
+
quoting: HasQuoting,
|
|
54
|
+
relation_config: RelationConfig,
|
|
55
|
+
**kwargs: Any,
|
|
56
|
+
) -> Self:
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
AdapterConfig_T = TypeVar("AdapterConfig_T", bound=AdapterConfig)
|
|
61
|
+
ConnectionManager_T = TypeVar("ConnectionManager_T", bound=ConnectionManagerProtocol)
|
|
62
|
+
Relation_T = TypeVar("Relation_T", bound=RelationProtocol)
|
|
63
|
+
Column_T = TypeVar("Column_T", bound=ColumnProtocol)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MacroContextGeneratorCallable(Protocol):
|
|
67
|
+
def __call__(
|
|
68
|
+
self,
|
|
69
|
+
macro_protocol: MacroProtocol,
|
|
70
|
+
config: AdapterRequiredConfig,
|
|
71
|
+
macro_resolver: MacroResolverProtocol,
|
|
72
|
+
package_name: Optional[str],
|
|
73
|
+
) -> Dict[str, Any]:
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# TODO CT-211
|
|
78
|
+
class AdapterProtocol( # type: ignore[misc]
|
|
79
|
+
Protocol,
|
|
80
|
+
Generic[
|
|
81
|
+
AdapterConfig_T,
|
|
82
|
+
ConnectionManager_T,
|
|
83
|
+
Relation_T,
|
|
84
|
+
Column_T,
|
|
85
|
+
],
|
|
86
|
+
):
|
|
87
|
+
# N.B. Technically these are ClassVars, but mypy doesn't support putting type vars in a
|
|
88
|
+
# ClassVar due to the restrictiveness of PEP-526
|
|
89
|
+
# See: https://github.com/python/mypy/issues/5144
|
|
90
|
+
AdapterSpecificConfigs: Type[AdapterConfig_T]
|
|
91
|
+
Column: Type[Column_T]
|
|
92
|
+
Relation: Type[Relation_T]
|
|
93
|
+
ConnectionManager: Type[ConnectionManager_T]
|
|
94
|
+
connections: ConnectionManager_T
|
|
95
|
+
|
|
96
|
+
def __init__(self, config: AdapterRequiredConfig) -> None:
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
def set_macro_resolver(self, macro_resolver: MacroResolverProtocol) -> None:
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
def get_macro_resolver(self) -> Optional[MacroResolverProtocol]:
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
def clear_macro_resolver(self) -> None:
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
def set_macro_context_generator(
|
|
109
|
+
self,
|
|
110
|
+
macro_context_generator: MacroContextGeneratorCallable,
|
|
111
|
+
) -> None:
|
|
112
|
+
...
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def type(cls) -> str:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
def set_query_header(self, query_header_context: Dict[str, Any]) -> None:
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def get_thread_identifier() -> Hashable:
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
def get_thread_connection(self) -> Connection:
|
|
126
|
+
...
|
|
127
|
+
|
|
128
|
+
def set_thread_connection(self, conn: Connection) -> None:
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
def get_if_exists(self) -> Optional[Connection]:
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
def clear_thread_connection(self) -> None:
|
|
135
|
+
...
|
|
136
|
+
|
|
137
|
+
def clear_transaction(self) -> None:
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
def exception_handler(self, sql: str) -> ContextManager:
|
|
141
|
+
...
|
|
142
|
+
|
|
143
|
+
def set_connection_name(self, name: Optional[str] = None) -> Connection:
|
|
144
|
+
...
|
|
145
|
+
|
|
146
|
+
def cancel_open(self) -> Optional[List[str]]:
|
|
147
|
+
...
|
|
148
|
+
|
|
149
|
+
def open(cls, connection: Connection) -> Connection:
|
|
150
|
+
...
|
|
151
|
+
|
|
152
|
+
def release(self) -> None:
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
def cleanup_all(self) -> None:
|
|
156
|
+
...
|
|
157
|
+
|
|
158
|
+
def begin(self) -> None:
|
|
159
|
+
...
|
|
160
|
+
|
|
161
|
+
def commit(self) -> None:
|
|
162
|
+
...
|
|
163
|
+
|
|
164
|
+
def close(cls, connection: Connection) -> Connection:
|
|
165
|
+
...
|
|
166
|
+
|
|
167
|
+
def commit_if_has_connection(self) -> None:
|
|
168
|
+
...
|
|
169
|
+
|
|
170
|
+
def execute(
|
|
171
|
+
self, sql: str, auto_begin: bool = False, fetch: bool = False
|
|
172
|
+
) -> Tuple[AdapterResponse, agate.Table]:
|
|
173
|
+
...
|
|
@@ -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,44 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict, Union
|
|
3
|
+
|
|
4
|
+
import agate
|
|
5
|
+
from dbt_common.utils import filter_null_values
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
This is what relation metadata from the database looks like. It's a dictionary because there will be
|
|
10
|
+
multiple grains of data for a single object. For example, a materialized view in Postgres has base level information,
|
|
11
|
+
like name. But it also can have multiple indexes, which needs to be a separate query. It might look like this:
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
"base": agate.Row({"table_name": "table_abc", "query": "select * from table_def"})
|
|
15
|
+
"indexes": agate.Table("rows": [
|
|
16
|
+
agate.Row({"name": "index_a", "columns": ["column_a"], "type": "hash", "unique": False}),
|
|
17
|
+
agate.Row({"name": "index_b", "columns": ["time_dim_a"], "type": "btree", "unique": False}),
|
|
18
|
+
])
|
|
19
|
+
}
|
|
20
|
+
"""
|
|
21
|
+
RelationResults = Dict[str, Union[agate.Row, agate.Table]]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class RelationConfigBase:
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_dict(cls, kwargs_dict) -> "RelationConfigBase":
|
|
28
|
+
"""
|
|
29
|
+
This assumes the subclass of `RelationConfigBase` is flat, in the sense that no attribute is
|
|
30
|
+
itself another subclass of `RelationConfigBase`. If that's not the case, this should be overriden
|
|
31
|
+
to manually manage that complexity.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
kwargs_dict: the dict representation of this instance
|
|
35
|
+
|
|
36
|
+
Returns: the `RelationConfigBase` representation associated with the provided dict
|
|
37
|
+
"""
|
|
38
|
+
return cls(**filter_null_values(kwargs_dict)) # type: ignore
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def _not_implemented_error(cls) -> NotImplementedError:
|
|
42
|
+
return NotImplementedError(
|
|
43
|
+
"This relation type has not been fully configured for this adapter."
|
|
44
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
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: Hashable # this is usually a RelationConfig, e.g. IndexConfig, but shouldn't be limited
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def requires_full_refresh(self) -> bool:
|
|
24
|
+
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()
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
|
4
|
+
|
|
5
|
+
import agate
|
|
6
|
+
from dbt_common.clients.agate_helper import empty_table, table_from_data_flat
|
|
7
|
+
from dbt_common.events.contextvars import get_node_info
|
|
8
|
+
from dbt_common.events.functions import fire_event
|
|
9
|
+
from dbt_common.exceptions import DbtInternalError, NotImplementedError
|
|
10
|
+
from dbt_common.utils import cast_to_str
|
|
11
|
+
|
|
12
|
+
from dbt.adapters.base import BaseConnectionManager
|
|
13
|
+
from dbt.adapters.contracts.connection import (
|
|
14
|
+
AdapterResponse,
|
|
15
|
+
Connection,
|
|
16
|
+
ConnectionState,
|
|
17
|
+
)
|
|
18
|
+
from dbt.adapters.events.types import (
|
|
19
|
+
ConnectionUsed,
|
|
20
|
+
SQLCommit,
|
|
21
|
+
SQLQuery,
|
|
22
|
+
SQLQueryStatus,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SQLConnectionManager(BaseConnectionManager):
|
|
27
|
+
"""The default connection manager with some common SQL methods implemented.
|
|
28
|
+
|
|
29
|
+
Methods to implement:
|
|
30
|
+
- exception_handler
|
|
31
|
+
- cancel
|
|
32
|
+
- get_response
|
|
33
|
+
- open
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
@abc.abstractmethod
|
|
37
|
+
def cancel(self, connection: Connection):
|
|
38
|
+
"""Cancel the given connection."""
|
|
39
|
+
raise NotImplementedError("`cancel` is not implemented for this adapter!")
|
|
40
|
+
|
|
41
|
+
def cancel_open(self) -> List[str]:
|
|
42
|
+
names = []
|
|
43
|
+
this_connection = self.get_if_exists()
|
|
44
|
+
with self.lock:
|
|
45
|
+
for connection in self.thread_connections.values():
|
|
46
|
+
if connection is this_connection:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
# if the connection failed, the handle will be None so we have
|
|
50
|
+
# nothing to cancel.
|
|
51
|
+
if connection.handle is not None and connection.state == ConnectionState.OPEN:
|
|
52
|
+
self.cancel(connection)
|
|
53
|
+
if connection.name is not None:
|
|
54
|
+
names.append(connection.name)
|
|
55
|
+
return names
|
|
56
|
+
|
|
57
|
+
def add_query(
|
|
58
|
+
self,
|
|
59
|
+
sql: str,
|
|
60
|
+
auto_begin: bool = True,
|
|
61
|
+
bindings: Optional[Any] = None,
|
|
62
|
+
abridge_sql_log: bool = False,
|
|
63
|
+
) -> Tuple[Connection, Any]:
|
|
64
|
+
connection = self.get_thread_connection()
|
|
65
|
+
if auto_begin and connection.transaction_open is False:
|
|
66
|
+
self.begin()
|
|
67
|
+
fire_event(
|
|
68
|
+
ConnectionUsed(
|
|
69
|
+
conn_type=self.TYPE,
|
|
70
|
+
conn_name=cast_to_str(connection.name),
|
|
71
|
+
node_info=get_node_info(),
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
with self.exception_handler(sql):
|
|
76
|
+
if abridge_sql_log:
|
|
77
|
+
log_sql = "{}...".format(sql[:512])
|
|
78
|
+
else:
|
|
79
|
+
log_sql = sql
|
|
80
|
+
|
|
81
|
+
fire_event(
|
|
82
|
+
SQLQuery(
|
|
83
|
+
conn_name=cast_to_str(connection.name),
|
|
84
|
+
sql=log_sql,
|
|
85
|
+
node_info=get_node_info(),
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
pre = time.time()
|
|
89
|
+
|
|
90
|
+
cursor = connection.handle.cursor()
|
|
91
|
+
cursor.execute(sql, bindings)
|
|
92
|
+
|
|
93
|
+
fire_event(
|
|
94
|
+
SQLQueryStatus(
|
|
95
|
+
status=str(self.get_response(cursor)),
|
|
96
|
+
elapsed=round((time.time() - pre)),
|
|
97
|
+
node_info=get_node_info(),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return connection, cursor
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
@abc.abstractmethod
|
|
105
|
+
def get_response(cls, cursor: Any) -> AdapterResponse:
|
|
106
|
+
"""Get the status of the cursor."""
|
|
107
|
+
raise NotImplementedError("`get_response` is not implemented for this adapter!")
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def process_results(
|
|
111
|
+
cls, column_names: Iterable[str], rows: Iterable[Any]
|
|
112
|
+
) -> List[Dict[str, Any]]:
|
|
113
|
+
# TODO CT-211
|
|
114
|
+
unique_col_names = dict() # type: ignore[var-annotated]
|
|
115
|
+
# TODO CT-211
|
|
116
|
+
for idx in range(len(column_names)): # type: ignore[arg-type]
|
|
117
|
+
# TODO CT-211
|
|
118
|
+
col_name = column_names[idx] # type: ignore[index]
|
|
119
|
+
if col_name in unique_col_names:
|
|
120
|
+
unique_col_names[col_name] += 1
|
|
121
|
+
# TODO CT-211
|
|
122
|
+
column_names[idx] = f"{col_name}_{unique_col_names[col_name]}" # type: ignore[index] # noqa
|
|
123
|
+
else:
|
|
124
|
+
# TODO CT-211
|
|
125
|
+
unique_col_names[column_names[idx]] = 1 # type: ignore[index]
|
|
126
|
+
return [dict(zip(column_names, row)) for row in rows]
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def get_result_from_cursor(cls, cursor: Any, limit: Optional[int]) -> agate.Table:
|
|
130
|
+
data: List[Any] = []
|
|
131
|
+
column_names: List[str] = []
|
|
132
|
+
|
|
133
|
+
if cursor.description is not None:
|
|
134
|
+
column_names = [col[0] for col in cursor.description]
|
|
135
|
+
if limit:
|
|
136
|
+
rows = cursor.fetchmany(limit)
|
|
137
|
+
else:
|
|
138
|
+
rows = cursor.fetchall()
|
|
139
|
+
data = cls.process_results(column_names, rows)
|
|
140
|
+
|
|
141
|
+
return table_from_data_flat(data, column_names)
|
|
142
|
+
|
|
143
|
+
def execute(
|
|
144
|
+
self,
|
|
145
|
+
sql: str,
|
|
146
|
+
auto_begin: bool = False,
|
|
147
|
+
fetch: bool = False,
|
|
148
|
+
limit: Optional[int] = None,
|
|
149
|
+
) -> Tuple[AdapterResponse, agate.Table]:
|
|
150
|
+
sql = self._add_query_comment(sql)
|
|
151
|
+
_, cursor = self.add_query(sql, auto_begin)
|
|
152
|
+
response = self.get_response(cursor)
|
|
153
|
+
if fetch:
|
|
154
|
+
table = self.get_result_from_cursor(cursor, limit)
|
|
155
|
+
else:
|
|
156
|
+
table = empty_table()
|
|
157
|
+
return response, table
|
|
158
|
+
|
|
159
|
+
def add_begin_query(self):
|
|
160
|
+
return self.add_query("BEGIN", auto_begin=False)
|
|
161
|
+
|
|
162
|
+
def add_commit_query(self):
|
|
163
|
+
return self.add_query("COMMIT", auto_begin=False)
|
|
164
|
+
|
|
165
|
+
def add_select_query(self, sql: str) -> Tuple[Connection, Any]:
|
|
166
|
+
sql = self._add_query_comment(sql)
|
|
167
|
+
return self.add_query(sql, auto_begin=False)
|
|
168
|
+
|
|
169
|
+
def begin(self):
|
|
170
|
+
connection = self.get_thread_connection()
|
|
171
|
+
if connection.transaction_open is True:
|
|
172
|
+
raise DbtInternalError(
|
|
173
|
+
'Tried to begin a new transaction on connection "{}", but '
|
|
174
|
+
"it already had one open!".format(connection.name)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
self.add_begin_query()
|
|
178
|
+
|
|
179
|
+
connection.transaction_open = True
|
|
180
|
+
return connection
|
|
181
|
+
|
|
182
|
+
def commit(self):
|
|
183
|
+
connection = self.get_thread_connection()
|
|
184
|
+
if connection.transaction_open is False:
|
|
185
|
+
raise DbtInternalError(
|
|
186
|
+
'Tried to commit transaction on connection "{}", but '
|
|
187
|
+
"it does not have one open!".format(connection.name)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
fire_event(SQLCommit(conn_name=connection.name, node_info=get_node_info()))
|
|
191
|
+
self.add_commit_query()
|
|
192
|
+
|
|
193
|
+
connection.transaction_open = False
|
|
194
|
+
|
|
195
|
+
return connection
|