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,648 @@
|
|
|
1
|
+
from collections.abc import Hashable
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Dict,
|
|
7
|
+
FrozenSet,
|
|
8
|
+
Iterator,
|
|
9
|
+
List,
|
|
10
|
+
Optional,
|
|
11
|
+
Set,
|
|
12
|
+
Tuple,
|
|
13
|
+
Type,
|
|
14
|
+
TypeVar,
|
|
15
|
+
Union,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from dbt_common.exceptions import CompilationError, DbtRuntimeError
|
|
19
|
+
from dbt_common.utils import deep_merge, filter_null_values
|
|
20
|
+
|
|
21
|
+
from dbt.adapters.contracts.relation import (
|
|
22
|
+
ComponentName,
|
|
23
|
+
HasQuoting,
|
|
24
|
+
FakeAPIObject,
|
|
25
|
+
Path,
|
|
26
|
+
Policy,
|
|
27
|
+
RelationConfig,
|
|
28
|
+
RelationType,
|
|
29
|
+
)
|
|
30
|
+
from dbt.adapters.relation_configs import (
|
|
31
|
+
RelationConfigBase,
|
|
32
|
+
RelationConfigValidationMixin,
|
|
33
|
+
RelationConfigValidationRule,
|
|
34
|
+
)
|
|
35
|
+
from dbt.adapters.exceptions import (
|
|
36
|
+
ApproximateMatchError,
|
|
37
|
+
MultipleDatabasesNotAllowedError,
|
|
38
|
+
)
|
|
39
|
+
from dbt.adapters.utils import classproperty
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Self = TypeVar("Self", bound="BaseRelation")
|
|
43
|
+
SerializableIterable = Union[Tuple, FrozenSet]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class EventTimeFilter(FakeAPIObject):
|
|
48
|
+
field_name: str
|
|
49
|
+
start: Optional[datetime] = None
|
|
50
|
+
end: Optional[datetime] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
54
|
+
class FunctionConfig(RelationConfigBase, RelationConfigValidationMixin):
|
|
55
|
+
language: str
|
|
56
|
+
type: str
|
|
57
|
+
runtime_version: Optional[str] = None
|
|
58
|
+
entry_point: Optional[str] = None
|
|
59
|
+
|
|
60
|
+
def _validate_runtime_version(self) -> bool:
|
|
61
|
+
if self.language == "python":
|
|
62
|
+
return self.runtime_version is not None
|
|
63
|
+
else:
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
def _validate_entry_point(self) -> bool:
|
|
67
|
+
if self.language == "python":
|
|
68
|
+
return self.entry_point is not None
|
|
69
|
+
else:
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def validation_rules(self) -> Set[RelationConfigValidationRule]:
|
|
74
|
+
return {
|
|
75
|
+
RelationConfigValidationRule(
|
|
76
|
+
validation_check=self.language != "" and self.language is not None,
|
|
77
|
+
validation_error=DbtRuntimeError("A `language` is required for functions"),
|
|
78
|
+
),
|
|
79
|
+
RelationConfigValidationRule(
|
|
80
|
+
validation_check=self.type != "" and self.type is not None,
|
|
81
|
+
validation_error=DbtRuntimeError("A `type` is required for functions"),
|
|
82
|
+
),
|
|
83
|
+
RelationConfigValidationRule(
|
|
84
|
+
validation_check=self.language != "python" or self.runtime_version is not None,
|
|
85
|
+
validation_error=DbtRuntimeError(
|
|
86
|
+
"A `runtime_version` is required for python functions"
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
RelationConfigValidationRule(
|
|
90
|
+
validation_check=self.language != "python" or self.entry_point is not None,
|
|
91
|
+
validation_error=DbtRuntimeError(
|
|
92
|
+
"An `entry_point` is required for python functions"
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
99
|
+
class BaseRelation(FakeAPIObject, Hashable):
|
|
100
|
+
path: Path
|
|
101
|
+
type: Optional[RelationType] = None
|
|
102
|
+
quote_character: str = '"'
|
|
103
|
+
# Python 3.11 requires that these use default_factory instead of simple default
|
|
104
|
+
# ValueError: mutable default <class 'dbt.contracts.relation.Policy'> for field include_policy is not allowed: use default_factory
|
|
105
|
+
include_policy: Policy = field(default_factory=lambda: Policy())
|
|
106
|
+
quote_policy: Policy = field(default_factory=lambda: Policy())
|
|
107
|
+
dbt_created: bool = False
|
|
108
|
+
limit: Optional[int] = None
|
|
109
|
+
event_time_filter: Optional[EventTimeFilter] = None
|
|
110
|
+
require_alias: bool = (
|
|
111
|
+
True # used to govern whether to add an alias when render_limited is called
|
|
112
|
+
)
|
|
113
|
+
catalog: Optional[str] = None
|
|
114
|
+
|
|
115
|
+
# register relation types that can be renamed for the purpose of replacing relations using stages and backups
|
|
116
|
+
# adding a relation type here also requires defining the associated rename macro
|
|
117
|
+
# e.g. adding RelationType.View in dbt-postgres requires that you define:
|
|
118
|
+
# include/postgres/macros/relations/view/rename.sql::postgres__get_rename_view_sql()
|
|
119
|
+
renameable_relations: SerializableIterable = field(default_factory=frozenset)
|
|
120
|
+
|
|
121
|
+
# register relation types that are atomically replaceable, e.g. they have "create or replace" syntax
|
|
122
|
+
# adding a relation type here also requires defining the associated replace macro
|
|
123
|
+
# e.g. adding RelationType.View in dbt-postgres requires that you define:
|
|
124
|
+
# include/postgres/macros/relations/view/replace.sql::postgres__get_replace_view_sql()
|
|
125
|
+
replaceable_relations: SerializableIterable = field(default_factory=frozenset)
|
|
126
|
+
|
|
127
|
+
def _is_exactish_match(self, field: ComponentName, value: str) -> bool:
|
|
128
|
+
if self.dbt_created and self.quote_policy.get_part(field) is False:
|
|
129
|
+
return self.path.get_lowered_part(field) == value.lower()
|
|
130
|
+
else:
|
|
131
|
+
return self.path.get_part(field) == value
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def _get_field_named(cls, field_name):
|
|
135
|
+
for f, _ in cls._get_fields():
|
|
136
|
+
if f.name == field_name:
|
|
137
|
+
return f
|
|
138
|
+
# this should be unreachable
|
|
139
|
+
raise ValueError(f"BaseRelation has no {field_name} field!")
|
|
140
|
+
|
|
141
|
+
def __eq__(self, other):
|
|
142
|
+
if not isinstance(other, self.__class__):
|
|
143
|
+
return False
|
|
144
|
+
return self.to_dict(omit_none=True) == other.to_dict(omit_none=True)
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def get_default_quote_policy(cls) -> Policy:
|
|
148
|
+
return cls._get_field_named("quote_policy").default_factory()
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def get_default_include_policy(cls) -> Policy:
|
|
152
|
+
return cls._get_field_named("include_policy").default_factory()
|
|
153
|
+
|
|
154
|
+
def get(self, key, default=None):
|
|
155
|
+
"""Override `.get` to return a metadata object so we don't break
|
|
156
|
+
dbt_utils.
|
|
157
|
+
"""
|
|
158
|
+
if key == "metadata":
|
|
159
|
+
return {"type": self.__class__.__name__}
|
|
160
|
+
return super().get(key, default)
|
|
161
|
+
|
|
162
|
+
def matches(
|
|
163
|
+
self,
|
|
164
|
+
database: Optional[str] = None,
|
|
165
|
+
schema: Optional[str] = None,
|
|
166
|
+
identifier: Optional[str] = None,
|
|
167
|
+
) -> bool:
|
|
168
|
+
search = filter_null_values(
|
|
169
|
+
{
|
|
170
|
+
ComponentName.Database: database,
|
|
171
|
+
ComponentName.Schema: schema,
|
|
172
|
+
ComponentName.Identifier: identifier,
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if not search:
|
|
177
|
+
# nothing was passed in
|
|
178
|
+
raise DbtRuntimeError("Tried to match relation, but no search path was passed!")
|
|
179
|
+
|
|
180
|
+
exact_match = True
|
|
181
|
+
approximate_match = True
|
|
182
|
+
|
|
183
|
+
for k, v in search.items():
|
|
184
|
+
if not self._is_exactish_match(k, v):
|
|
185
|
+
exact_match = False
|
|
186
|
+
if str(self.path.get_lowered_part(k)).strip(self.quote_character) != v.lower().strip(
|
|
187
|
+
self.quote_character
|
|
188
|
+
):
|
|
189
|
+
approximate_match = False
|
|
190
|
+
|
|
191
|
+
if approximate_match and not exact_match:
|
|
192
|
+
target = self.create(database=database, schema=schema, identifier=identifier)
|
|
193
|
+
raise ApproximateMatchError(target, self)
|
|
194
|
+
|
|
195
|
+
return exact_match
|
|
196
|
+
|
|
197
|
+
def replace_path(self, **kwargs):
|
|
198
|
+
return self.replace(path=self.path.replace(**kwargs))
|
|
199
|
+
|
|
200
|
+
def quote(
|
|
201
|
+
self: Self,
|
|
202
|
+
database: Optional[bool] = None,
|
|
203
|
+
schema: Optional[bool] = None,
|
|
204
|
+
identifier: Optional[bool] = None,
|
|
205
|
+
) -> Self:
|
|
206
|
+
policy = filter_null_values(
|
|
207
|
+
{
|
|
208
|
+
ComponentName.Database: database,
|
|
209
|
+
ComponentName.Schema: schema,
|
|
210
|
+
ComponentName.Identifier: identifier,
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
new_quote_policy = self.quote_policy.replace_dict(policy)
|
|
215
|
+
return self.replace(quote_policy=new_quote_policy)
|
|
216
|
+
|
|
217
|
+
def include(
|
|
218
|
+
self: Self,
|
|
219
|
+
database: Optional[bool] = None,
|
|
220
|
+
schema: Optional[bool] = None,
|
|
221
|
+
identifier: Optional[bool] = None,
|
|
222
|
+
) -> Self:
|
|
223
|
+
policy = filter_null_values(
|
|
224
|
+
{
|
|
225
|
+
ComponentName.Database: database,
|
|
226
|
+
ComponentName.Schema: schema,
|
|
227
|
+
ComponentName.Identifier: identifier,
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
new_include_policy = self.include_policy.replace_dict(policy)
|
|
232
|
+
return self.replace(include_policy=new_include_policy)
|
|
233
|
+
|
|
234
|
+
def information_schema(self, view_name=None) -> "InformationSchema":
|
|
235
|
+
# some of our data comes from jinja, where things can be `Undefined`.
|
|
236
|
+
if not isinstance(view_name, str):
|
|
237
|
+
view_name = None
|
|
238
|
+
|
|
239
|
+
# Kick the user-supplied schema out of the information schema relation
|
|
240
|
+
# Instead address this as <database>.information_schema by default
|
|
241
|
+
info_schema = InformationSchema.from_relation(self, view_name)
|
|
242
|
+
return info_schema.incorporate(path={"schema": None})
|
|
243
|
+
|
|
244
|
+
def information_schema_only(self) -> "InformationSchema":
|
|
245
|
+
return self.information_schema()
|
|
246
|
+
|
|
247
|
+
def without_identifier(self) -> "BaseRelation":
|
|
248
|
+
"""Return a form of this relation that only has the database and schema
|
|
249
|
+
set to included. To get the appropriately-quoted form the schema out of
|
|
250
|
+
the result (for use as part of a query), use `.render()`. To get the
|
|
251
|
+
raw database or schema name, use `.database` or `.schema`.
|
|
252
|
+
|
|
253
|
+
The hash of the returned object is the result of render().
|
|
254
|
+
"""
|
|
255
|
+
return self.include(identifier=False).replace_path(identifier=None)
|
|
256
|
+
|
|
257
|
+
def _render_iterator(
|
|
258
|
+
self,
|
|
259
|
+
) -> Iterator[Tuple[Optional[ComponentName], Optional[str]]]:
|
|
260
|
+
for key in ComponentName: # type: ignore
|
|
261
|
+
path_part: Optional[str] = None
|
|
262
|
+
if self.include_policy.get_part(key):
|
|
263
|
+
path_part = self.path.get_part(key)
|
|
264
|
+
if path_part is not None and self.quote_policy.get_part(key):
|
|
265
|
+
path_part = self.quoted(path_part)
|
|
266
|
+
yield key, path_part
|
|
267
|
+
|
|
268
|
+
def render(self) -> str:
|
|
269
|
+
# if there is nothing set, this will return the empty string.
|
|
270
|
+
return ".".join(part for _, part in self._render_iterator() if part is not None)
|
|
271
|
+
|
|
272
|
+
def _render_subquery_alias(self, namespace: str) -> str:
|
|
273
|
+
"""Some databases require an alias for subqueries (postgres, mysql) for all others we want to avoid adding
|
|
274
|
+
an alias as it has the potential to introduce issues with the query if the user also defines an alias.
|
|
275
|
+
"""
|
|
276
|
+
if self.require_alias:
|
|
277
|
+
return f" _dbt_{namespace}_subq_{self.table}"
|
|
278
|
+
return ""
|
|
279
|
+
|
|
280
|
+
def _render_limited_alias(
|
|
281
|
+
self,
|
|
282
|
+
) -> str:
|
|
283
|
+
return self._render_subquery_alias(namespace="limit")
|
|
284
|
+
|
|
285
|
+
def render_limited(self) -> str:
|
|
286
|
+
rendered = self.render()
|
|
287
|
+
if self.limit is None:
|
|
288
|
+
return rendered
|
|
289
|
+
elif self.limit == 0:
|
|
290
|
+
return f"(select * from {rendered} where false limit 0){self._render_limited_alias()}"
|
|
291
|
+
else:
|
|
292
|
+
return f"(select * from {rendered} limit {self.limit}){self._render_limited_alias()}"
|
|
293
|
+
|
|
294
|
+
def render_event_time_filtered(self, rendered: Optional[str] = None) -> str:
|
|
295
|
+
rendered = rendered or self.render()
|
|
296
|
+
if self.event_time_filter is None:
|
|
297
|
+
return rendered
|
|
298
|
+
|
|
299
|
+
filter = self._render_event_time_filtered(self.event_time_filter)
|
|
300
|
+
if not filter:
|
|
301
|
+
return rendered
|
|
302
|
+
|
|
303
|
+
return f"(select * from {rendered} where {filter}){self._render_subquery_alias(namespace='et_filter')}"
|
|
304
|
+
|
|
305
|
+
def _render_event_time_filtered(self, event_time_filter: EventTimeFilter) -> str:
|
|
306
|
+
"""
|
|
307
|
+
Returns "" if start and end are both None
|
|
308
|
+
"""
|
|
309
|
+
filter = ""
|
|
310
|
+
if event_time_filter.start and event_time_filter.end:
|
|
311
|
+
filter = f"{event_time_filter.field_name} >= '{event_time_filter.start}' and {event_time_filter.field_name} < '{event_time_filter.end}'"
|
|
312
|
+
elif event_time_filter.start:
|
|
313
|
+
filter = f"{event_time_filter.field_name} >= '{event_time_filter.start}'"
|
|
314
|
+
elif event_time_filter.end:
|
|
315
|
+
filter = f"{event_time_filter.field_name} < '{event_time_filter.end}'"
|
|
316
|
+
|
|
317
|
+
return filter
|
|
318
|
+
|
|
319
|
+
def quoted(self, identifier):
|
|
320
|
+
return "{quote_char}{identifier}{quote_char}".format(
|
|
321
|
+
quote_char=self.quote_character,
|
|
322
|
+
identifier=identifier,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
def add_ephemeral_prefix(name: str):
|
|
327
|
+
return f"__dbt__cte__{name}"
|
|
328
|
+
|
|
329
|
+
@classmethod
|
|
330
|
+
def create_ephemeral_from(
|
|
331
|
+
cls: Type[Self],
|
|
332
|
+
relation_config: RelationConfig,
|
|
333
|
+
limit: Optional[int] = None,
|
|
334
|
+
event_time_filter: Optional[EventTimeFilter] = None,
|
|
335
|
+
) -> Self:
|
|
336
|
+
# Note that ephemeral models are based on the identifier, which will
|
|
337
|
+
# point to the model's alias if one exists and otherwise fall back to
|
|
338
|
+
# the filename. This is intended to give the user more control over
|
|
339
|
+
# the way that the CTE name is constructed
|
|
340
|
+
identifier = cls.add_ephemeral_prefix(relation_config.identifier)
|
|
341
|
+
return cls.create(
|
|
342
|
+
type=cls.CTE,
|
|
343
|
+
identifier=identifier,
|
|
344
|
+
limit=limit,
|
|
345
|
+
event_time_filter=event_time_filter,
|
|
346
|
+
).quote(identifier=False)
|
|
347
|
+
|
|
348
|
+
@classmethod
|
|
349
|
+
def create_from(
|
|
350
|
+
cls: Type[Self],
|
|
351
|
+
quoting: HasQuoting,
|
|
352
|
+
relation_config: RelationConfig,
|
|
353
|
+
**kwargs: Any,
|
|
354
|
+
) -> Self:
|
|
355
|
+
quote_policy = kwargs.pop("quote_policy", {})
|
|
356
|
+
|
|
357
|
+
config_quoting = relation_config.quoting_dict
|
|
358
|
+
config_quoting.pop("column", None)
|
|
359
|
+
|
|
360
|
+
catalog_name = (
|
|
361
|
+
relation_config.catalog_name
|
|
362
|
+
if hasattr(relation_config, "catalog_name")
|
|
363
|
+
else relation_config.config.get("catalog", None) # type: ignore
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# precedence: kwargs quoting > relation config quoting > base quoting > default quoting
|
|
367
|
+
quote_policy = deep_merge(
|
|
368
|
+
cls.get_default_quote_policy().to_dict(omit_none=True),
|
|
369
|
+
quoting.quoting,
|
|
370
|
+
config_quoting,
|
|
371
|
+
quote_policy,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return cls.create(
|
|
375
|
+
database=relation_config.database,
|
|
376
|
+
schema=relation_config.schema,
|
|
377
|
+
identifier=relation_config.identifier,
|
|
378
|
+
quote_policy=quote_policy,
|
|
379
|
+
catalog_name=catalog_name,
|
|
380
|
+
**kwargs,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
@classmethod
|
|
384
|
+
def create(
|
|
385
|
+
cls: Type[Self],
|
|
386
|
+
database: Optional[str] = None,
|
|
387
|
+
schema: Optional[str] = None,
|
|
388
|
+
identifier: Optional[str] = None,
|
|
389
|
+
type: Optional[RelationType] = None,
|
|
390
|
+
**kwargs,
|
|
391
|
+
) -> Self:
|
|
392
|
+
kwargs.update(
|
|
393
|
+
{
|
|
394
|
+
"path": {
|
|
395
|
+
"database": database,
|
|
396
|
+
"schema": schema,
|
|
397
|
+
"identifier": identifier,
|
|
398
|
+
},
|
|
399
|
+
"type": type,
|
|
400
|
+
}
|
|
401
|
+
)
|
|
402
|
+
return cls.from_dict(kwargs)
|
|
403
|
+
|
|
404
|
+
@classmethod
|
|
405
|
+
def scd_args(cls: Type[Self], primary_key: Union[str, List[str]], updated_at) -> List[str]:
|
|
406
|
+
scd_args = []
|
|
407
|
+
if isinstance(primary_key, list):
|
|
408
|
+
scd_args.extend(primary_key)
|
|
409
|
+
else:
|
|
410
|
+
scd_args.append(primary_key)
|
|
411
|
+
scd_args.append(updated_at)
|
|
412
|
+
return scd_args
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def can_be_renamed(self) -> bool:
|
|
416
|
+
return self.type in self.renameable_relations
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def can_be_replaced(self) -> bool:
|
|
420
|
+
return self.type in self.replaceable_relations
|
|
421
|
+
|
|
422
|
+
def __repr__(self) -> str:
|
|
423
|
+
return "<{} {}>".format(self.__class__.__name__, self.render())
|
|
424
|
+
|
|
425
|
+
def __hash__(self) -> int:
|
|
426
|
+
return hash(self.render())
|
|
427
|
+
|
|
428
|
+
def __str__(self) -> str:
|
|
429
|
+
# TODO: This function seems to have more if's than it needs to. We should see if we can simplify it.
|
|
430
|
+
if self.is_function:
|
|
431
|
+
# If it's a function we skip all special rendering logic and just return the raw render
|
|
432
|
+
rendered = self.render()
|
|
433
|
+
else:
|
|
434
|
+
rendered = self.render() if self.limit is None else self.render_limited()
|
|
435
|
+
|
|
436
|
+
# Limited subquery is wrapped by the event time filter subquery, and not the other way around.
|
|
437
|
+
# This is because in the context of resolving limited refs, we care more about performance than reliably producing a sample of a certain size.
|
|
438
|
+
if self.event_time_filter:
|
|
439
|
+
rendered = self.render_event_time_filtered(rendered)
|
|
440
|
+
|
|
441
|
+
return rendered
|
|
442
|
+
|
|
443
|
+
@property
|
|
444
|
+
def database(self) -> Optional[str]:
|
|
445
|
+
return self.path.database
|
|
446
|
+
|
|
447
|
+
@property
|
|
448
|
+
def schema(self) -> Optional[str]:
|
|
449
|
+
return self.path.schema
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
def identifier(self) -> Optional[str]:
|
|
453
|
+
return self.path.identifier
|
|
454
|
+
|
|
455
|
+
@property
|
|
456
|
+
def table(self) -> Optional[str]:
|
|
457
|
+
return self.path.identifier
|
|
458
|
+
|
|
459
|
+
# Here for compatibility with old Relation interface
|
|
460
|
+
@property
|
|
461
|
+
def name(self) -> Optional[str]:
|
|
462
|
+
return self.identifier
|
|
463
|
+
|
|
464
|
+
@property
|
|
465
|
+
def is_table(self) -> bool:
|
|
466
|
+
return self.type == RelationType.Table
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def is_cte(self) -> bool:
|
|
470
|
+
return self.type == RelationType.CTE
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def is_view(self) -> bool:
|
|
474
|
+
return self.type == RelationType.View
|
|
475
|
+
|
|
476
|
+
@property
|
|
477
|
+
def is_materialized_view(self) -> bool:
|
|
478
|
+
return self.type == RelationType.MaterializedView
|
|
479
|
+
|
|
480
|
+
@property
|
|
481
|
+
def is_pointer(self) -> bool:
|
|
482
|
+
return self.type == RelationType.PointerTable
|
|
483
|
+
|
|
484
|
+
@property
|
|
485
|
+
def is_function(self) -> bool:
|
|
486
|
+
return self.type == RelationType.Function
|
|
487
|
+
|
|
488
|
+
@classproperty
|
|
489
|
+
def Table(cls) -> str:
|
|
490
|
+
return str(RelationType.Table)
|
|
491
|
+
|
|
492
|
+
@classproperty
|
|
493
|
+
def CTE(cls) -> str:
|
|
494
|
+
return str(RelationType.CTE)
|
|
495
|
+
|
|
496
|
+
@classproperty
|
|
497
|
+
def View(cls) -> str:
|
|
498
|
+
return str(RelationType.View)
|
|
499
|
+
|
|
500
|
+
@classproperty
|
|
501
|
+
def External(cls) -> str:
|
|
502
|
+
return str(RelationType.External)
|
|
503
|
+
|
|
504
|
+
@classproperty
|
|
505
|
+
def MaterializedView(cls) -> str:
|
|
506
|
+
return str(RelationType.MaterializedView)
|
|
507
|
+
|
|
508
|
+
@classproperty
|
|
509
|
+
def PointerTable(cls) -> str:
|
|
510
|
+
return str(RelationType.PointerTable)
|
|
511
|
+
|
|
512
|
+
@classproperty
|
|
513
|
+
def Function(cls) -> str:
|
|
514
|
+
return str(RelationType.Function)
|
|
515
|
+
|
|
516
|
+
@classproperty
|
|
517
|
+
def get_relation_type(cls) -> Type[RelationType]:
|
|
518
|
+
return RelationType
|
|
519
|
+
|
|
520
|
+
def get_function_config(self, model: Dict[str, Any]) -> Optional[FunctionConfig]:
|
|
521
|
+
# TODO: We shouldn't have to check the model.resource_type here. We should be alble to do self.is_function instead.
|
|
522
|
+
# However, somehow when we get here self.type is None, and thus self.is_function is False.
|
|
523
|
+
if model.get("resource_type") == "function":
|
|
524
|
+
return FunctionConfig(
|
|
525
|
+
language=model.get("language", ""),
|
|
526
|
+
type=model.get("config", {}).get("type", ""),
|
|
527
|
+
runtime_version=model.get("config", {}).get("runtime_version", None),
|
|
528
|
+
entry_point=model.get("config", {}).get("entry_point", None),
|
|
529
|
+
)
|
|
530
|
+
else:
|
|
531
|
+
return None
|
|
532
|
+
|
|
533
|
+
def get_function_macro_name(self, config: FunctionConfig) -> str:
|
|
534
|
+
return f"{config.type}_function_{config.language}"
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
Info = TypeVar("Info", bound="InformationSchema")
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
541
|
+
class InformationSchema(BaseRelation):
|
|
542
|
+
information_schema_view: Optional[str] = None
|
|
543
|
+
|
|
544
|
+
def __post_init__(self):
|
|
545
|
+
if not isinstance(self.information_schema_view, (type(None), str)):
|
|
546
|
+
raise CompilationError("Got an invalid name: {}".format(self.information_schema_view))
|
|
547
|
+
|
|
548
|
+
@classmethod
|
|
549
|
+
def get_path(cls, relation: BaseRelation, information_schema_view: Optional[str]) -> Path:
|
|
550
|
+
return Path(
|
|
551
|
+
database=relation.database,
|
|
552
|
+
schema=relation.schema,
|
|
553
|
+
identifier="INFORMATION_SCHEMA",
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
@classmethod
|
|
557
|
+
def get_include_policy(
|
|
558
|
+
cls,
|
|
559
|
+
relation,
|
|
560
|
+
information_schema_view: Optional[str],
|
|
561
|
+
) -> Policy:
|
|
562
|
+
return relation.include_policy.replace(
|
|
563
|
+
database=relation.database is not None,
|
|
564
|
+
schema=False,
|
|
565
|
+
identifier=True,
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
@classmethod
|
|
569
|
+
def get_quote_policy(
|
|
570
|
+
cls,
|
|
571
|
+
relation,
|
|
572
|
+
information_schema_view: Optional[str],
|
|
573
|
+
) -> Policy:
|
|
574
|
+
return relation.quote_policy.replace(
|
|
575
|
+
identifier=False,
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
@classmethod
|
|
579
|
+
def from_relation(
|
|
580
|
+
cls: Type[Info],
|
|
581
|
+
relation: BaseRelation,
|
|
582
|
+
information_schema_view: Optional[str],
|
|
583
|
+
) -> Info:
|
|
584
|
+
include_policy = cls.get_include_policy(relation, information_schema_view)
|
|
585
|
+
quote_policy = cls.get_quote_policy(relation, information_schema_view)
|
|
586
|
+
path = cls.get_path(relation, information_schema_view)
|
|
587
|
+
return cls(
|
|
588
|
+
type=RelationType.View, # type: ignore
|
|
589
|
+
path=path,
|
|
590
|
+
include_policy=include_policy,
|
|
591
|
+
quote_policy=quote_policy,
|
|
592
|
+
information_schema_view=information_schema_view,
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
def _render_iterator(self):
|
|
596
|
+
for k, v in super()._render_iterator():
|
|
597
|
+
yield k, v
|
|
598
|
+
yield None, self.information_schema_view
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
class SchemaSearchMap(Dict[InformationSchema, Set[Optional[str]]]):
|
|
602
|
+
"""A utility class to keep track of what information_schema tables to
|
|
603
|
+
search for what schemas. The schema values are all lowercased to avoid
|
|
604
|
+
duplication.
|
|
605
|
+
"""
|
|
606
|
+
|
|
607
|
+
def add(self, relation: BaseRelation):
|
|
608
|
+
key = relation.information_schema_only()
|
|
609
|
+
if key not in self:
|
|
610
|
+
self[key] = set()
|
|
611
|
+
schema: Optional[str] = None
|
|
612
|
+
if relation.schema is not None:
|
|
613
|
+
schema = relation.schema.lower()
|
|
614
|
+
self[key].add(schema)
|
|
615
|
+
|
|
616
|
+
def search(self) -> Iterator[Tuple[InformationSchema, Optional[str]]]:
|
|
617
|
+
for information_schema, schemas in self.items():
|
|
618
|
+
for schema in schemas:
|
|
619
|
+
yield information_schema, schema
|
|
620
|
+
|
|
621
|
+
def flatten(self, allow_multiple_databases: bool = False) -> "SchemaSearchMap":
|
|
622
|
+
new = self.__class__()
|
|
623
|
+
|
|
624
|
+
# make sure we don't have multiple databases if allow_multiple_databases is set to False
|
|
625
|
+
if not allow_multiple_databases:
|
|
626
|
+
seen = {r.database.lower() for r in self if r.database}
|
|
627
|
+
if len(seen) > 1:
|
|
628
|
+
raise MultipleDatabasesNotAllowedError(seen)
|
|
629
|
+
|
|
630
|
+
for information_schema_name, schema in self.search():
|
|
631
|
+
path = {"database": information_schema_name.database, "schema": schema}
|
|
632
|
+
new.add(
|
|
633
|
+
information_schema_name.incorporate(
|
|
634
|
+
path=path,
|
|
635
|
+
quote_policy={"database": False},
|
|
636
|
+
include_policy={"database": False},
|
|
637
|
+
)
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
return new
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
644
|
+
class AdapterTrackingRelationInfo(FakeAPIObject, Hashable):
|
|
645
|
+
adapter_name: str
|
|
646
|
+
base_adapter_version: str
|
|
647
|
+
adapter_version: str
|
|
648
|
+
model_adapter_details: Any
|