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,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,2 @@
1
+ from dbt.adapters.sql.connections import SQLConnectionManager
2
+ from dbt.adapters.sql.impl import SQLAdapter
@@ -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