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.

Files changed (147) hide show
  1. dbt/__init__.py +0 -0
  2. dbt/adapters/__about__.py +1 -0
  3. dbt/adapters/__init__.py +7 -0
  4. dbt/adapters/base/README.md +13 -0
  5. dbt/adapters/base/__init__.py +15 -0
  6. dbt/adapters/base/column.py +166 -0
  7. dbt/adapters/base/connections.py +426 -0
  8. dbt/adapters/base/impl.py +1654 -0
  9. dbt/adapters/base/meta.py +131 -0
  10. dbt/adapters/base/plugin.py +32 -0
  11. dbt/adapters/base/query_headers.py +101 -0
  12. dbt/adapters/base/relation.py +471 -0
  13. dbt/adapters/cache.py +521 -0
  14. dbt/adapters/capability.py +52 -0
  15. dbt/adapters/clients/__init__.py +0 -0
  16. dbt/adapters/clients/jinja.py +24 -0
  17. dbt/adapters/contracts/__init__.py +0 -0
  18. dbt/adapters/contracts/connection.py +228 -0
  19. dbt/adapters/contracts/macros.py +11 -0
  20. dbt/adapters/contracts/relation.py +125 -0
  21. dbt/adapters/events/README.md +57 -0
  22. dbt/adapters/events/__init__.py +0 -0
  23. dbt/adapters/events/adapter_types.proto +517 -0
  24. dbt/adapters/events/adapter_types_pb2.py +208 -0
  25. dbt/adapters/events/base_types.py +40 -0
  26. dbt/adapters/events/logging.py +83 -0
  27. dbt/adapters/events/types.py +423 -0
  28. dbt/adapters/exceptions/__init__.py +40 -0
  29. dbt/adapters/exceptions/alias.py +24 -0
  30. dbt/adapters/exceptions/cache.py +68 -0
  31. dbt/adapters/exceptions/compilation.py +255 -0
  32. dbt/adapters/exceptions/connection.py +16 -0
  33. dbt/adapters/exceptions/database.py +51 -0
  34. dbt/adapters/factory.py +246 -0
  35. dbt/adapters/protocol.py +173 -0
  36. dbt/adapters/reference_keys.py +39 -0
  37. dbt/adapters/relation_configs/README.md +25 -0
  38. dbt/adapters/relation_configs/__init__.py +12 -0
  39. dbt/adapters/relation_configs/config_base.py +44 -0
  40. dbt/adapters/relation_configs/config_change.py +24 -0
  41. dbt/adapters/relation_configs/config_validation.py +57 -0
  42. dbt/adapters/sql/__init__.py +2 -0
  43. dbt/adapters/sql/connections.py +195 -0
  44. dbt/adapters/sql/impl.py +273 -0
  45. dbt/adapters/utils.py +69 -0
  46. dbt/include/global_project/__init__.py +4 -0
  47. dbt/include/global_project/dbt_project.yml +7 -0
  48. dbt/include/global_project/docs/overview.md +43 -0
  49. dbt/include/global_project/macros/adapters/apply_grants.sql +167 -0
  50. dbt/include/global_project/macros/adapters/columns.sql +137 -0
  51. dbt/include/global_project/macros/adapters/freshness.sql +16 -0
  52. dbt/include/global_project/macros/adapters/indexes.sql +41 -0
  53. dbt/include/global_project/macros/adapters/metadata.sql +96 -0
  54. dbt/include/global_project/macros/adapters/persist_docs.sql +33 -0
  55. dbt/include/global_project/macros/adapters/relation.sql +79 -0
  56. dbt/include/global_project/macros/adapters/schema.sql +20 -0
  57. dbt/include/global_project/macros/adapters/show.sql +22 -0
  58. dbt/include/global_project/macros/adapters/timestamps.sql +44 -0
  59. dbt/include/global_project/macros/adapters/validate_sql.sql +10 -0
  60. dbt/include/global_project/macros/etc/datetime.sql +62 -0
  61. dbt/include/global_project/macros/etc/statement.sql +52 -0
  62. dbt/include/global_project/macros/generic_test_sql/accepted_values.sql +27 -0
  63. dbt/include/global_project/macros/generic_test_sql/not_null.sql +9 -0
  64. dbt/include/global_project/macros/generic_test_sql/relationships.sql +23 -0
  65. dbt/include/global_project/macros/generic_test_sql/unique.sql +12 -0
  66. dbt/include/global_project/macros/get_custom_name/get_custom_alias.sql +36 -0
  67. dbt/include/global_project/macros/get_custom_name/get_custom_database.sql +32 -0
  68. dbt/include/global_project/macros/get_custom_name/get_custom_schema.sql +60 -0
  69. dbt/include/global_project/macros/materializations/configs.sql +21 -0
  70. dbt/include/global_project/macros/materializations/hooks.sql +35 -0
  71. dbt/include/global_project/macros/materializations/models/clone/can_clone_table.sql +7 -0
  72. dbt/include/global_project/macros/materializations/models/clone/clone.sql +67 -0
  73. dbt/include/global_project/macros/materializations/models/clone/create_or_replace_clone.sql +7 -0
  74. dbt/include/global_project/macros/materializations/models/incremental/column_helpers.sql +80 -0
  75. dbt/include/global_project/macros/materializations/models/incremental/incremental.sql +92 -0
  76. dbt/include/global_project/macros/materializations/models/incremental/is_incremental.sql +13 -0
  77. dbt/include/global_project/macros/materializations/models/incremental/merge.sql +131 -0
  78. dbt/include/global_project/macros/materializations/models/incremental/on_schema_change.sql +144 -0
  79. dbt/include/global_project/macros/materializations/models/incremental/strategies.sql +79 -0
  80. dbt/include/global_project/macros/materializations/models/materialized_view.sql +121 -0
  81. dbt/include/global_project/macros/materializations/models/table.sql +64 -0
  82. dbt/include/global_project/macros/materializations/models/view.sql +72 -0
  83. dbt/include/global_project/macros/materializations/seeds/helpers.sql +128 -0
  84. dbt/include/global_project/macros/materializations/seeds/seed.sql +60 -0
  85. dbt/include/global_project/macros/materializations/snapshots/helpers.sql +181 -0
  86. dbt/include/global_project/macros/materializations/snapshots/snapshot.sql +99 -0
  87. dbt/include/global_project/macros/materializations/snapshots/snapshot_merge.sql +25 -0
  88. dbt/include/global_project/macros/materializations/snapshots/strategies.sql +174 -0
  89. dbt/include/global_project/macros/materializations/tests/helpers.sql +14 -0
  90. dbt/include/global_project/macros/materializations/tests/test.sql +60 -0
  91. dbt/include/global_project/macros/materializations/tests/where_subquery.sql +15 -0
  92. dbt/include/global_project/macros/python_model/python.sql +103 -0
  93. dbt/include/global_project/macros/relations/column/columns_spec_ddl.sql +89 -0
  94. dbt/include/global_project/macros/relations/create.sql +23 -0
  95. dbt/include/global_project/macros/relations/create_backup.sql +17 -0
  96. dbt/include/global_project/macros/relations/create_intermediate.sql +17 -0
  97. dbt/include/global_project/macros/relations/drop.sql +41 -0
  98. dbt/include/global_project/macros/relations/drop_backup.sql +14 -0
  99. dbt/include/global_project/macros/relations/materialized_view/alter.sql +55 -0
  100. dbt/include/global_project/macros/relations/materialized_view/create.sql +10 -0
  101. dbt/include/global_project/macros/relations/materialized_view/drop.sql +14 -0
  102. dbt/include/global_project/macros/relations/materialized_view/refresh.sql +9 -0
  103. dbt/include/global_project/macros/relations/materialized_view/rename.sql +10 -0
  104. dbt/include/global_project/macros/relations/materialized_view/replace.sql +10 -0
  105. dbt/include/global_project/macros/relations/rename.sql +35 -0
  106. dbt/include/global_project/macros/relations/rename_intermediate.sql +14 -0
  107. dbt/include/global_project/macros/relations/replace.sql +50 -0
  108. dbt/include/global_project/macros/relations/schema.sql +8 -0
  109. dbt/include/global_project/macros/relations/table/create.sql +60 -0
  110. dbt/include/global_project/macros/relations/table/drop.sql +14 -0
  111. dbt/include/global_project/macros/relations/table/rename.sql +10 -0
  112. dbt/include/global_project/macros/relations/table/replace.sql +10 -0
  113. dbt/include/global_project/macros/relations/view/create.sql +27 -0
  114. dbt/include/global_project/macros/relations/view/drop.sql +14 -0
  115. dbt/include/global_project/macros/relations/view/rename.sql +10 -0
  116. dbt/include/global_project/macros/relations/view/replace.sql +66 -0
  117. dbt/include/global_project/macros/utils/any_value.sql +9 -0
  118. dbt/include/global_project/macros/utils/array_append.sql +8 -0
  119. dbt/include/global_project/macros/utils/array_concat.sql +7 -0
  120. dbt/include/global_project/macros/utils/array_construct.sql +12 -0
  121. dbt/include/global_project/macros/utils/bool_or.sql +9 -0
  122. dbt/include/global_project/macros/utils/cast_bool_to_text.sql +7 -0
  123. dbt/include/global_project/macros/utils/concat.sql +7 -0
  124. dbt/include/global_project/macros/utils/data_types.sql +129 -0
  125. dbt/include/global_project/macros/utils/date_spine.sql +75 -0
  126. dbt/include/global_project/macros/utils/date_trunc.sql +7 -0
  127. dbt/include/global_project/macros/utils/dateadd.sql +14 -0
  128. dbt/include/global_project/macros/utils/datediff.sql +14 -0
  129. dbt/include/global_project/macros/utils/escape_single_quotes.sql +8 -0
  130. dbt/include/global_project/macros/utils/except.sql +9 -0
  131. dbt/include/global_project/macros/utils/generate_series.sql +53 -0
  132. dbt/include/global_project/macros/utils/hash.sql +7 -0
  133. dbt/include/global_project/macros/utils/intersect.sql +9 -0
  134. dbt/include/global_project/macros/utils/last_day.sql +15 -0
  135. dbt/include/global_project/macros/utils/length.sql +11 -0
  136. dbt/include/global_project/macros/utils/listagg.sql +30 -0
  137. dbt/include/global_project/macros/utils/literal.sql +7 -0
  138. dbt/include/global_project/macros/utils/position.sql +11 -0
  139. dbt/include/global_project/macros/utils/replace.sql +14 -0
  140. dbt/include/global_project/macros/utils/right.sql +12 -0
  141. dbt/include/global_project/macros/utils/safe_cast.sql +9 -0
  142. dbt/include/global_project/macros/utils/split_part.sql +26 -0
  143. dbt/include/global_project/tests/generic/builtin.sql +30 -0
  144. dbt_adapters-0.1.0a1.dist-info/METADATA +81 -0
  145. dbt_adapters-0.1.0a1.dist-info/RECORD +147 -0
  146. dbt_adapters-0.1.0a1.dist-info/WHEEL +4 -0
  147. 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