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
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
from collections.abc import Hashable
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Dict,
|
|
6
|
+
FrozenSet,
|
|
7
|
+
Iterator,
|
|
8
|
+
Optional,
|
|
9
|
+
Set,
|
|
10
|
+
Tuple,
|
|
11
|
+
Type,
|
|
12
|
+
TypeVar,
|
|
13
|
+
Union,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from dbt_common.exceptions import CompilationError, DbtRuntimeError
|
|
17
|
+
from dbt_common.utils import deep_merge, filter_null_values
|
|
18
|
+
|
|
19
|
+
from dbt.adapters.contracts.relation import (
|
|
20
|
+
ComponentName,
|
|
21
|
+
HasQuoting,
|
|
22
|
+
FakeAPIObject,
|
|
23
|
+
Path,
|
|
24
|
+
Policy,
|
|
25
|
+
RelationConfig,
|
|
26
|
+
RelationType,
|
|
27
|
+
)
|
|
28
|
+
from dbt.adapters.exceptions import (
|
|
29
|
+
ApproximateMatchError,
|
|
30
|
+
MultipleDatabasesNotAllowedError,
|
|
31
|
+
)
|
|
32
|
+
from dbt.adapters.utils import classproperty
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
Self = TypeVar("Self", bound="BaseRelation")
|
|
36
|
+
SerializableIterable = Union[Tuple, FrozenSet]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
40
|
+
class BaseRelation(FakeAPIObject, Hashable):
|
|
41
|
+
path: Path
|
|
42
|
+
type: Optional[RelationType] = None
|
|
43
|
+
quote_character: str = '"'
|
|
44
|
+
# Python 3.11 requires that these use default_factory instead of simple default
|
|
45
|
+
# ValueError: mutable default <class 'dbt.contracts.relation.Policy'> for field include_policy is not allowed: use default_factory
|
|
46
|
+
include_policy: Policy = field(default_factory=lambda: Policy())
|
|
47
|
+
quote_policy: Policy = field(default_factory=lambda: Policy())
|
|
48
|
+
dbt_created: bool = False
|
|
49
|
+
limit: Optional[int] = None
|
|
50
|
+
|
|
51
|
+
# register relation types that can be renamed for the purpose of replacing relations using stages and backups
|
|
52
|
+
# adding a relation type here also requires defining the associated rename macro
|
|
53
|
+
# e.g. adding RelationType.View in dbt-postgres requires that you define:
|
|
54
|
+
# include/postgres/macros/relations/view/rename.sql::postgres__get_rename_view_sql()
|
|
55
|
+
renameable_relations: SerializableIterable = ()
|
|
56
|
+
|
|
57
|
+
# register relation types that are atomically replaceable, e.g. they have "create or replace" syntax
|
|
58
|
+
# adding a relation type here also requires defining the associated replace macro
|
|
59
|
+
# e.g. adding RelationType.View in dbt-postgres requires that you define:
|
|
60
|
+
# include/postgres/macros/relations/view/replace.sql::postgres__get_replace_view_sql()
|
|
61
|
+
replaceable_relations: SerializableIterable = ()
|
|
62
|
+
|
|
63
|
+
def _is_exactish_match(self, field: ComponentName, value: str) -> bool:
|
|
64
|
+
if self.dbt_created and self.quote_policy.get_part(field) is False:
|
|
65
|
+
return self.path.get_lowered_part(field) == value.lower()
|
|
66
|
+
else:
|
|
67
|
+
return self.path.get_part(field) == value
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def _get_field_named(cls, field_name):
|
|
71
|
+
for f, _ in cls._get_fields():
|
|
72
|
+
if f.name == field_name:
|
|
73
|
+
return f
|
|
74
|
+
# this should be unreachable
|
|
75
|
+
raise ValueError(f"BaseRelation has no {field_name} field!")
|
|
76
|
+
|
|
77
|
+
def __eq__(self, other):
|
|
78
|
+
if not isinstance(other, self.__class__):
|
|
79
|
+
return False
|
|
80
|
+
return self.to_dict(omit_none=True) == other.to_dict(omit_none=True)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def get_default_quote_policy(cls) -> Policy:
|
|
84
|
+
return cls._get_field_named("quote_policy").default_factory()
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def get_default_include_policy(cls) -> Policy:
|
|
88
|
+
return cls._get_field_named("include_policy").default_factory()
|
|
89
|
+
|
|
90
|
+
def get(self, key, default=None):
|
|
91
|
+
"""Override `.get` to return a metadata object so we don't break
|
|
92
|
+
dbt_utils.
|
|
93
|
+
"""
|
|
94
|
+
if key == "metadata":
|
|
95
|
+
return {"type": self.__class__.__name__}
|
|
96
|
+
return super().get(key, default)
|
|
97
|
+
|
|
98
|
+
def matches(
|
|
99
|
+
self,
|
|
100
|
+
database: Optional[str] = None,
|
|
101
|
+
schema: Optional[str] = None,
|
|
102
|
+
identifier: Optional[str] = None,
|
|
103
|
+
) -> bool:
|
|
104
|
+
search = filter_null_values(
|
|
105
|
+
{
|
|
106
|
+
ComponentName.Database: database,
|
|
107
|
+
ComponentName.Schema: schema,
|
|
108
|
+
ComponentName.Identifier: identifier,
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if not search:
|
|
113
|
+
# nothing was passed in
|
|
114
|
+
raise DbtRuntimeError("Tried to match relation, but no search path was passed!")
|
|
115
|
+
|
|
116
|
+
exact_match = True
|
|
117
|
+
approximate_match = True
|
|
118
|
+
|
|
119
|
+
for k, v in search.items():
|
|
120
|
+
if not self._is_exactish_match(k, v):
|
|
121
|
+
exact_match = False
|
|
122
|
+
if str(self.path.get_lowered_part(k)).strip(self.quote_character) != v.lower().strip(
|
|
123
|
+
self.quote_character
|
|
124
|
+
):
|
|
125
|
+
approximate_match = False # type: ignore[union-attr]
|
|
126
|
+
|
|
127
|
+
if approximate_match and not exact_match:
|
|
128
|
+
target = self.create(database=database, schema=schema, identifier=identifier)
|
|
129
|
+
raise ApproximateMatchError(target, self)
|
|
130
|
+
|
|
131
|
+
return exact_match
|
|
132
|
+
|
|
133
|
+
def replace_path(self, **kwargs):
|
|
134
|
+
return self.replace(path=self.path.replace(**kwargs))
|
|
135
|
+
|
|
136
|
+
def quote(
|
|
137
|
+
self: Self,
|
|
138
|
+
database: Optional[bool] = None,
|
|
139
|
+
schema: Optional[bool] = None,
|
|
140
|
+
identifier: Optional[bool] = None,
|
|
141
|
+
) -> Self:
|
|
142
|
+
policy = filter_null_values(
|
|
143
|
+
{
|
|
144
|
+
ComponentName.Database: database,
|
|
145
|
+
ComponentName.Schema: schema,
|
|
146
|
+
ComponentName.Identifier: identifier,
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
new_quote_policy = self.quote_policy.replace_dict(policy)
|
|
151
|
+
return self.replace(quote_policy=new_quote_policy)
|
|
152
|
+
|
|
153
|
+
def include(
|
|
154
|
+
self: Self,
|
|
155
|
+
database: Optional[bool] = None,
|
|
156
|
+
schema: Optional[bool] = None,
|
|
157
|
+
identifier: Optional[bool] = None,
|
|
158
|
+
) -> Self:
|
|
159
|
+
policy = filter_null_values(
|
|
160
|
+
{
|
|
161
|
+
ComponentName.Database: database,
|
|
162
|
+
ComponentName.Schema: schema,
|
|
163
|
+
ComponentName.Identifier: identifier,
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
new_include_policy = self.include_policy.replace_dict(policy)
|
|
168
|
+
return self.replace(include_policy=new_include_policy)
|
|
169
|
+
|
|
170
|
+
def information_schema(self, view_name=None) -> "InformationSchema":
|
|
171
|
+
# some of our data comes from jinja, where things can be `Undefined`.
|
|
172
|
+
if not isinstance(view_name, str):
|
|
173
|
+
view_name = None
|
|
174
|
+
|
|
175
|
+
# Kick the user-supplied schema out of the information schema relation
|
|
176
|
+
# Instead address this as <database>.information_schema by default
|
|
177
|
+
info_schema = InformationSchema.from_relation(self, view_name)
|
|
178
|
+
return info_schema.incorporate(path={"schema": None})
|
|
179
|
+
|
|
180
|
+
def information_schema_only(self) -> "InformationSchema":
|
|
181
|
+
return self.information_schema()
|
|
182
|
+
|
|
183
|
+
def without_identifier(self) -> "BaseRelation":
|
|
184
|
+
"""Return a form of this relation that only has the database and schema
|
|
185
|
+
set to included. To get the appropriately-quoted form the schema out of
|
|
186
|
+
the result (for use as part of a query), use `.render()`. To get the
|
|
187
|
+
raw database or schema name, use `.database` or `.schema`.
|
|
188
|
+
|
|
189
|
+
The hash of the returned object is the result of render().
|
|
190
|
+
"""
|
|
191
|
+
return self.include(identifier=False).replace_path(identifier=None)
|
|
192
|
+
|
|
193
|
+
def _render_iterator(
|
|
194
|
+
self,
|
|
195
|
+
) -> Iterator[Tuple[Optional[ComponentName], Optional[str]]]:
|
|
196
|
+
for key in ComponentName:
|
|
197
|
+
path_part: Optional[str] = None
|
|
198
|
+
if self.include_policy.get_part(key):
|
|
199
|
+
path_part = self.path.get_part(key)
|
|
200
|
+
if path_part is not None and self.quote_policy.get_part(key):
|
|
201
|
+
path_part = self.quoted(path_part)
|
|
202
|
+
yield key, path_part
|
|
203
|
+
|
|
204
|
+
def render(self) -> str:
|
|
205
|
+
# if there is nothing set, this will return the empty string.
|
|
206
|
+
return ".".join(part for _, part in self._render_iterator() if part is not None)
|
|
207
|
+
|
|
208
|
+
def render_limited(self) -> str:
|
|
209
|
+
rendered = self.render()
|
|
210
|
+
if self.limit is None:
|
|
211
|
+
return rendered
|
|
212
|
+
elif self.limit == 0:
|
|
213
|
+
return f"(select * from {rendered} where false limit 0) _dbt_limit_subq"
|
|
214
|
+
else:
|
|
215
|
+
return f"(select * from {rendered} limit {self.limit}) _dbt_limit_subq"
|
|
216
|
+
|
|
217
|
+
def quoted(self, identifier):
|
|
218
|
+
return "{quote_char}{identifier}{quote_char}".format(
|
|
219
|
+
quote_char=self.quote_character,
|
|
220
|
+
identifier=identifier,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def add_ephemeral_prefix(name: str):
|
|
225
|
+
return f"__dbt__cte__{name}"
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def create_ephemeral_from(
|
|
229
|
+
cls: Type[Self],
|
|
230
|
+
relation_config: RelationConfig,
|
|
231
|
+
limit: Optional[int],
|
|
232
|
+
) -> Self:
|
|
233
|
+
# Note that ephemeral models are based on the name.
|
|
234
|
+
identifier = cls.add_ephemeral_prefix(relation_config.name)
|
|
235
|
+
return cls.create(
|
|
236
|
+
type=cls.CTE,
|
|
237
|
+
identifier=identifier,
|
|
238
|
+
limit=limit,
|
|
239
|
+
).quote(identifier=False)
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def create_from(
|
|
243
|
+
cls: Type[Self],
|
|
244
|
+
quoting: HasQuoting,
|
|
245
|
+
relation_config: RelationConfig,
|
|
246
|
+
**kwargs: Any,
|
|
247
|
+
) -> Self:
|
|
248
|
+
quote_policy = kwargs.pop("quote_policy", {})
|
|
249
|
+
|
|
250
|
+
config_quoting = relation_config.quoting_dict
|
|
251
|
+
config_quoting.pop("column", None)
|
|
252
|
+
# precedence: kwargs quoting > relation config quoting > base quoting > default quoting
|
|
253
|
+
quote_policy = deep_merge(
|
|
254
|
+
cls.get_default_quote_policy().to_dict(omit_none=True),
|
|
255
|
+
quoting.quoting,
|
|
256
|
+
config_quoting,
|
|
257
|
+
quote_policy,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return cls.create(
|
|
261
|
+
database=relation_config.database,
|
|
262
|
+
schema=relation_config.schema,
|
|
263
|
+
identifier=relation_config.identifier,
|
|
264
|
+
quote_policy=quote_policy,
|
|
265
|
+
**kwargs,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
@classmethod
|
|
269
|
+
def create(
|
|
270
|
+
cls: Type[Self],
|
|
271
|
+
database: Optional[str] = None,
|
|
272
|
+
schema: Optional[str] = None,
|
|
273
|
+
identifier: Optional[str] = None,
|
|
274
|
+
type: Optional[RelationType] = None,
|
|
275
|
+
**kwargs,
|
|
276
|
+
) -> Self:
|
|
277
|
+
kwargs.update(
|
|
278
|
+
{
|
|
279
|
+
"path": {
|
|
280
|
+
"database": database,
|
|
281
|
+
"schema": schema,
|
|
282
|
+
"identifier": identifier,
|
|
283
|
+
},
|
|
284
|
+
"type": type,
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
return cls.from_dict(kwargs)
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def can_be_renamed(self) -> bool:
|
|
291
|
+
return self.type in self.renameable_relations
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def can_be_replaced(self) -> bool:
|
|
295
|
+
return self.type in self.replaceable_relations
|
|
296
|
+
|
|
297
|
+
def __repr__(self) -> str:
|
|
298
|
+
return "<{} {}>".format(self.__class__.__name__, self.render())
|
|
299
|
+
|
|
300
|
+
def __hash__(self) -> int:
|
|
301
|
+
return hash(self.render())
|
|
302
|
+
|
|
303
|
+
def __str__(self) -> str:
|
|
304
|
+
return self.render() if self.limit is None else self.render_limited()
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def database(self) -> Optional[str]:
|
|
308
|
+
return self.path.database
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def schema(self) -> Optional[str]:
|
|
312
|
+
return self.path.schema
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def identifier(self) -> Optional[str]:
|
|
316
|
+
return self.path.identifier
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def table(self) -> Optional[str]:
|
|
320
|
+
return self.path.identifier
|
|
321
|
+
|
|
322
|
+
# Here for compatibility with old Relation interface
|
|
323
|
+
@property
|
|
324
|
+
def name(self) -> Optional[str]:
|
|
325
|
+
return self.identifier
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def is_table(self) -> bool:
|
|
329
|
+
return self.type == RelationType.Table
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def is_cte(self) -> bool:
|
|
333
|
+
return self.type == RelationType.CTE
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def is_view(self) -> bool:
|
|
337
|
+
return self.type == RelationType.View
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def is_materialized_view(self) -> bool:
|
|
341
|
+
return self.type == RelationType.MaterializedView
|
|
342
|
+
|
|
343
|
+
@classproperty
|
|
344
|
+
def Table(cls) -> str:
|
|
345
|
+
return str(RelationType.Table)
|
|
346
|
+
|
|
347
|
+
@classproperty
|
|
348
|
+
def CTE(cls) -> str:
|
|
349
|
+
return str(RelationType.CTE)
|
|
350
|
+
|
|
351
|
+
@classproperty
|
|
352
|
+
def View(cls) -> str:
|
|
353
|
+
return str(RelationType.View)
|
|
354
|
+
|
|
355
|
+
@classproperty
|
|
356
|
+
def External(cls) -> str:
|
|
357
|
+
return str(RelationType.External)
|
|
358
|
+
|
|
359
|
+
@classproperty
|
|
360
|
+
def MaterializedView(cls) -> str:
|
|
361
|
+
return str(RelationType.MaterializedView)
|
|
362
|
+
|
|
363
|
+
@classproperty
|
|
364
|
+
def get_relation_type(cls) -> Type[RelationType]:
|
|
365
|
+
return RelationType
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
Info = TypeVar("Info", bound="InformationSchema")
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
372
|
+
class InformationSchema(BaseRelation):
|
|
373
|
+
information_schema_view: Optional[str] = None
|
|
374
|
+
|
|
375
|
+
def __post_init__(self):
|
|
376
|
+
if not isinstance(self.information_schema_view, (type(None), str)):
|
|
377
|
+
raise CompilationError("Got an invalid name: {}".format(self.information_schema_view))
|
|
378
|
+
|
|
379
|
+
@classmethod
|
|
380
|
+
def get_path(cls, relation: BaseRelation, information_schema_view: Optional[str]) -> Path:
|
|
381
|
+
return Path(
|
|
382
|
+
database=relation.database,
|
|
383
|
+
schema=relation.schema,
|
|
384
|
+
identifier="INFORMATION_SCHEMA",
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
@classmethod
|
|
388
|
+
def get_include_policy(
|
|
389
|
+
cls,
|
|
390
|
+
relation,
|
|
391
|
+
information_schema_view: Optional[str],
|
|
392
|
+
) -> Policy:
|
|
393
|
+
return relation.include_policy.replace(
|
|
394
|
+
database=relation.database is not None,
|
|
395
|
+
schema=False,
|
|
396
|
+
identifier=True,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
@classmethod
|
|
400
|
+
def get_quote_policy(
|
|
401
|
+
cls,
|
|
402
|
+
relation,
|
|
403
|
+
information_schema_view: Optional[str],
|
|
404
|
+
) -> Policy:
|
|
405
|
+
return relation.quote_policy.replace(
|
|
406
|
+
identifier=False,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
@classmethod
|
|
410
|
+
def from_relation(
|
|
411
|
+
cls: Type[Info],
|
|
412
|
+
relation: BaseRelation,
|
|
413
|
+
information_schema_view: Optional[str],
|
|
414
|
+
) -> Info:
|
|
415
|
+
include_policy = cls.get_include_policy(relation, information_schema_view)
|
|
416
|
+
quote_policy = cls.get_quote_policy(relation, information_schema_view)
|
|
417
|
+
path = cls.get_path(relation, information_schema_view)
|
|
418
|
+
return cls(
|
|
419
|
+
type=RelationType.View,
|
|
420
|
+
path=path,
|
|
421
|
+
include_policy=include_policy,
|
|
422
|
+
quote_policy=quote_policy,
|
|
423
|
+
information_schema_view=information_schema_view,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def _render_iterator(self):
|
|
427
|
+
for k, v in super()._render_iterator():
|
|
428
|
+
yield k, v
|
|
429
|
+
yield None, self.information_schema_view
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class SchemaSearchMap(Dict[InformationSchema, Set[Optional[str]]]):
|
|
433
|
+
"""A utility class to keep track of what information_schema tables to
|
|
434
|
+
search for what schemas. The schema values are all lowercased to avoid
|
|
435
|
+
duplication.
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
def add(self, relation: BaseRelation):
|
|
439
|
+
key = relation.information_schema_only()
|
|
440
|
+
if key not in self:
|
|
441
|
+
self[key] = set()
|
|
442
|
+
schema: Optional[str] = None
|
|
443
|
+
if relation.schema is not None:
|
|
444
|
+
schema = relation.schema.lower()
|
|
445
|
+
self[key].add(schema)
|
|
446
|
+
|
|
447
|
+
def search(self) -> Iterator[Tuple[InformationSchema, Optional[str]]]:
|
|
448
|
+
for information_schema, schemas in self.items():
|
|
449
|
+
for schema in schemas:
|
|
450
|
+
yield information_schema, schema
|
|
451
|
+
|
|
452
|
+
def flatten(self, allow_multiple_databases: bool = False) -> "SchemaSearchMap":
|
|
453
|
+
new = self.__class__()
|
|
454
|
+
|
|
455
|
+
# make sure we don't have multiple databases if allow_multiple_databases is set to False
|
|
456
|
+
if not allow_multiple_databases:
|
|
457
|
+
seen = {r.database.lower() for r in self if r.database}
|
|
458
|
+
if len(seen) > 1:
|
|
459
|
+
raise MultipleDatabasesNotAllowedError(seen)
|
|
460
|
+
|
|
461
|
+
for information_schema_name, schema in self.search():
|
|
462
|
+
path = {"database": information_schema_name.database, "schema": schema}
|
|
463
|
+
new.add(
|
|
464
|
+
information_schema_name.incorporate(
|
|
465
|
+
path=path,
|
|
466
|
+
quote_policy={"database": False},
|
|
467
|
+
include_policy={"database": False},
|
|
468
|
+
)
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
return new
|