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 @@
|
|
|
1
|
+
version = "1.22.2"
|
dbt/adapters/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## Base adapters
|
|
2
|
+
|
|
3
|
+
### impl.py
|
|
4
|
+
|
|
5
|
+
The class `SQLAdapter` in [base/imply.py](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/adapters/base/impl.py)
|
|
6
|
+
is a (mostly) abstract object that adapter objects inherit from.
|
|
7
|
+
The base class scaffolds out methods that every adapter project
|
|
8
|
+
usually should implement for smooth communication between dbt and database.
|
|
9
|
+
|
|
10
|
+
Some target databases require more or fewer methods--
|
|
11
|
+
it all depends on what the warehouse's featureset is.
|
|
12
|
+
|
|
13
|
+
Look into the class for function-level comments.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dbt.adapters.base.meta import available
|
|
2
|
+
from dbt.adapters.base.column import Column
|
|
3
|
+
from dbt.adapters.base.connections import BaseConnectionManager
|
|
4
|
+
from dbt.adapters.base.impl import (
|
|
5
|
+
AdapterConfig,
|
|
6
|
+
BaseAdapter,
|
|
7
|
+
ConstraintSupport,
|
|
8
|
+
PythonJobHelper,
|
|
9
|
+
)
|
|
10
|
+
from dbt.adapters.base.plugin import AdapterPlugin
|
|
11
|
+
from dbt.adapters.base.relation import (
|
|
12
|
+
BaseRelation,
|
|
13
|
+
RelationType,
|
|
14
|
+
SchemaSearchMap,
|
|
15
|
+
AdapterTrackingRelationInfo,
|
|
16
|
+
)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import re
|
|
3
|
+
from typing import Any, ClassVar, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from dbt_common.exceptions import DbtRuntimeError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Column:
|
|
10
|
+
# Note: This is automatically used by contract code
|
|
11
|
+
# No-op conversions (INTEGER => INT) have been removed.
|
|
12
|
+
# Any adapter that wants to take advantage of "translate_type"
|
|
13
|
+
# should create a ClassVar with the appropriate conversions.
|
|
14
|
+
TYPE_LABELS: ClassVar[Dict[str, str]] = {
|
|
15
|
+
"STRING": "TEXT",
|
|
16
|
+
}
|
|
17
|
+
column: str
|
|
18
|
+
dtype: str
|
|
19
|
+
char_size: Optional[int] = None
|
|
20
|
+
numeric_precision: Optional[Any] = None
|
|
21
|
+
numeric_scale: Optional[Any] = None
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def translate_type(cls, dtype: str) -> str:
|
|
25
|
+
return cls.TYPE_LABELS.get(dtype.upper(), dtype)
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def create(cls, name, label_or_dtype: str) -> "Column":
|
|
29
|
+
column_type = cls.translate_type(label_or_dtype)
|
|
30
|
+
return cls(name, column_type)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def name(self) -> str:
|
|
34
|
+
return self.column
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def quoted(self) -> str:
|
|
38
|
+
return '"{}"'.format(self.column)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def data_type(self) -> str:
|
|
42
|
+
if self.is_string():
|
|
43
|
+
return self.string_type(self.string_size())
|
|
44
|
+
elif self.is_numeric():
|
|
45
|
+
return self.numeric_type(self.dtype, self.numeric_precision, self.numeric_scale)
|
|
46
|
+
else:
|
|
47
|
+
return self.dtype
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def expanded_data_type(self) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Adapter-overridable data type string that may include adapter-specific
|
|
53
|
+
expansions (e.g. collation clauses) for use in DDL/schema comparisons.
|
|
54
|
+
|
|
55
|
+
By default, this is identical to `data_type`.
|
|
56
|
+
"""
|
|
57
|
+
return self.data_type
|
|
58
|
+
|
|
59
|
+
def is_string(self) -> bool:
|
|
60
|
+
return self.dtype.lower() in [
|
|
61
|
+
"text",
|
|
62
|
+
"character varying",
|
|
63
|
+
"character",
|
|
64
|
+
"varchar",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
def is_number(self):
|
|
68
|
+
return any([self.is_integer(), self.is_numeric(), self.is_float()])
|
|
69
|
+
|
|
70
|
+
def is_float(self):
|
|
71
|
+
return self.dtype.lower() in [
|
|
72
|
+
# floats
|
|
73
|
+
"real",
|
|
74
|
+
"float4",
|
|
75
|
+
"float",
|
|
76
|
+
"double precision",
|
|
77
|
+
"float8",
|
|
78
|
+
"double",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
def is_integer(self) -> bool:
|
|
82
|
+
return self.dtype.lower() in [
|
|
83
|
+
# real types
|
|
84
|
+
"smallint",
|
|
85
|
+
"integer",
|
|
86
|
+
"bigint",
|
|
87
|
+
"smallserial",
|
|
88
|
+
"serial",
|
|
89
|
+
"bigserial",
|
|
90
|
+
# aliases
|
|
91
|
+
"int2",
|
|
92
|
+
"int4",
|
|
93
|
+
"int8",
|
|
94
|
+
"serial2",
|
|
95
|
+
"serial4",
|
|
96
|
+
"serial8",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
def is_numeric(self) -> bool:
|
|
100
|
+
return self.dtype.lower() in ["numeric", "decimal"]
|
|
101
|
+
|
|
102
|
+
def string_size(self) -> int:
|
|
103
|
+
if not self.is_string():
|
|
104
|
+
raise DbtRuntimeError("Called string_size() on non-string field!")
|
|
105
|
+
|
|
106
|
+
if self.dtype == "text" or self.char_size is None:
|
|
107
|
+
# char_size should never be None. Handle it reasonably just in case
|
|
108
|
+
return 256
|
|
109
|
+
else:
|
|
110
|
+
return int(self.char_size)
|
|
111
|
+
|
|
112
|
+
def can_expand_to(self, other_column: "Column") -> bool:
|
|
113
|
+
"""returns True if this column can be expanded to the size of the
|
|
114
|
+
other column"""
|
|
115
|
+
if not self.is_string() or not other_column.is_string():
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
return other_column.string_size() > self.string_size()
|
|
119
|
+
|
|
120
|
+
def literal(self, value: Any) -> str:
|
|
121
|
+
return "{}::{}".format(value, self.data_type)
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def string_type(cls, size: int) -> str:
|
|
125
|
+
return "character varying({})".format(size)
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def numeric_type(cls, dtype: str, precision: Any, scale: Any) -> str:
|
|
129
|
+
# This could be decimal(...), numeric(...), number(...)
|
|
130
|
+
# Just use whatever was fed in here -- don't try to get too clever
|
|
131
|
+
if precision is None or scale is None:
|
|
132
|
+
return dtype
|
|
133
|
+
else:
|
|
134
|
+
return "{}({},{})".format(dtype, precision, scale)
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def from_description(cls, name: str, raw_data_type: str) -> "Column":
|
|
138
|
+
match = re.match(r"([^(]+)(\([^)]+\))?", raw_data_type)
|
|
139
|
+
if match is None:
|
|
140
|
+
raise DbtRuntimeError(f'Could not interpret data type "{raw_data_type}"')
|
|
141
|
+
data_type, size_info = match.groups()
|
|
142
|
+
char_size = None
|
|
143
|
+
numeric_precision = None
|
|
144
|
+
numeric_scale = None
|
|
145
|
+
if size_info is not None:
|
|
146
|
+
# strip out the parentheses
|
|
147
|
+
size_info = size_info[1:-1]
|
|
148
|
+
parts = size_info.split(",")
|
|
149
|
+
if len(parts) == 1:
|
|
150
|
+
try:
|
|
151
|
+
char_size = int(parts[0])
|
|
152
|
+
except ValueError:
|
|
153
|
+
raise DbtRuntimeError(
|
|
154
|
+
f'Could not interpret data_type "{raw_data_type}": '
|
|
155
|
+
f'could not convert "{parts[0]}" to an integer'
|
|
156
|
+
)
|
|
157
|
+
elif len(parts) == 2:
|
|
158
|
+
try:
|
|
159
|
+
numeric_precision = int(parts[0])
|
|
160
|
+
except ValueError:
|
|
161
|
+
raise DbtRuntimeError(
|
|
162
|
+
f'Could not interpret data_type "{raw_data_type}": '
|
|
163
|
+
f'could not convert "{parts[0]}" to an integer'
|
|
164
|
+
)
|
|
165
|
+
try:
|
|
166
|
+
numeric_scale = int(parts[1])
|
|
167
|
+
except ValueError:
|
|
168
|
+
raise DbtRuntimeError(
|
|
169
|
+
f'Could not interpret data_type "{raw_data_type}": '
|
|
170
|
+
f'could not convert "{parts[1]}" to an integer'
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return cls(name, data_type, char_size, numeric_precision, numeric_scale)
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from time import sleep
|
|
5
|
+
import traceback
|
|
6
|
+
from multiprocessing.context import SpawnContext
|
|
7
|
+
from multiprocessing.synchronize import RLock
|
|
8
|
+
from threading import get_ident
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
Callable,
|
|
12
|
+
ContextManager,
|
|
13
|
+
Dict,
|
|
14
|
+
Hashable,
|
|
15
|
+
Iterable,
|
|
16
|
+
List,
|
|
17
|
+
Optional,
|
|
18
|
+
Tuple,
|
|
19
|
+
Type,
|
|
20
|
+
Union,
|
|
21
|
+
TYPE_CHECKING,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from dbt_common.events.contextvars import get_node_info
|
|
25
|
+
from dbt_common.events.functions import fire_event
|
|
26
|
+
from dbt_common.exceptions import DbtInternalError, NotImplementedError
|
|
27
|
+
from dbt_common.utils import cast_to_str
|
|
28
|
+
|
|
29
|
+
from dbt.adapters.base.query_headers import MacroQueryStringSetter
|
|
30
|
+
from dbt.adapters.contracts.connection import (
|
|
31
|
+
AdapterRequiredConfig,
|
|
32
|
+
AdapterResponse,
|
|
33
|
+
Connection,
|
|
34
|
+
ConnectionState,
|
|
35
|
+
Identifier,
|
|
36
|
+
LazyHandle,
|
|
37
|
+
)
|
|
38
|
+
from dbt.adapters.events.logging import AdapterLogger
|
|
39
|
+
from dbt.adapters.events.types import (
|
|
40
|
+
ConnectionClosed,
|
|
41
|
+
ConnectionClosedInCleanup,
|
|
42
|
+
ConnectionLeftOpen,
|
|
43
|
+
ConnectionLeftOpenInCleanup,
|
|
44
|
+
ConnectionReused,
|
|
45
|
+
NewConnection,
|
|
46
|
+
Rollback,
|
|
47
|
+
RollbackFailed,
|
|
48
|
+
)
|
|
49
|
+
from dbt.adapters.exceptions import FailedToConnectError, InvalidConnectionError
|
|
50
|
+
|
|
51
|
+
if TYPE_CHECKING:
|
|
52
|
+
import agate
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
SleepTime = Union[int, float] # As taken by time.sleep.
|
|
56
|
+
AdapterHandle = Any # Adapter connection handle objects can be any class.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|
60
|
+
"""Methods to implement:
|
|
61
|
+
- exception_handler
|
|
62
|
+
- cancel_open
|
|
63
|
+
- open
|
|
64
|
+
- begin
|
|
65
|
+
- commit
|
|
66
|
+
- clear_transaction
|
|
67
|
+
- execute
|
|
68
|
+
|
|
69
|
+
You must also set the 'TYPE' class attribute with a class-unique constant
|
|
70
|
+
string.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
TYPE: str = NotImplemented
|
|
74
|
+
|
|
75
|
+
def __init__(self, profile: AdapterRequiredConfig, mp_context: SpawnContext) -> None:
|
|
76
|
+
self.profile = profile
|
|
77
|
+
self.thread_connections: Dict[Hashable, Connection] = {}
|
|
78
|
+
self.lock: RLock = mp_context.RLock()
|
|
79
|
+
self.query_header: Optional[MacroQueryStringSetter] = None
|
|
80
|
+
|
|
81
|
+
def set_query_header(self, query_header_context: Dict[str, Any]) -> None:
|
|
82
|
+
self.query_header = MacroQueryStringSetter(self.profile, query_header_context)
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def get_thread_identifier() -> Hashable:
|
|
86
|
+
# note that get_ident() may be re-used, but we should never experience
|
|
87
|
+
# that within a single process
|
|
88
|
+
return os.getpid(), get_ident()
|
|
89
|
+
|
|
90
|
+
def get_thread_connection(self) -> Connection:
|
|
91
|
+
key = self.get_thread_identifier()
|
|
92
|
+
with self.lock:
|
|
93
|
+
if key not in self.thread_connections:
|
|
94
|
+
raise InvalidConnectionError(key, list(self.thread_connections))
|
|
95
|
+
return self.thread_connections[key]
|
|
96
|
+
|
|
97
|
+
def set_thread_connection(self, conn: Connection) -> None:
|
|
98
|
+
key = self.get_thread_identifier()
|
|
99
|
+
if key in self.thread_connections:
|
|
100
|
+
raise DbtInternalError("In set_thread_connection, existing connection exists for {}")
|
|
101
|
+
self.thread_connections[key] = conn
|
|
102
|
+
|
|
103
|
+
def get_if_exists(self) -> Optional[Connection]:
|
|
104
|
+
key = self.get_thread_identifier()
|
|
105
|
+
with self.lock:
|
|
106
|
+
return self.thread_connections.get(key)
|
|
107
|
+
|
|
108
|
+
def clear_thread_connection(self) -> None:
|
|
109
|
+
key = self.get_thread_identifier()
|
|
110
|
+
with self.lock:
|
|
111
|
+
if key in self.thread_connections:
|
|
112
|
+
del self.thread_connections[key]
|
|
113
|
+
|
|
114
|
+
def clear_transaction(self) -> None:
|
|
115
|
+
"""Clear any existing transactions."""
|
|
116
|
+
conn = self.get_thread_connection()
|
|
117
|
+
if conn is not None:
|
|
118
|
+
if conn.transaction_open:
|
|
119
|
+
self._rollback(conn)
|
|
120
|
+
self.begin()
|
|
121
|
+
self.commit()
|
|
122
|
+
|
|
123
|
+
def rollback_if_open(self) -> None:
|
|
124
|
+
conn = self.get_if_exists()
|
|
125
|
+
if conn is not None and conn.handle and conn.transaction_open:
|
|
126
|
+
self._rollback(conn)
|
|
127
|
+
|
|
128
|
+
@abc.abstractmethod
|
|
129
|
+
def exception_handler(self, sql: str) -> ContextManager:
|
|
130
|
+
"""Create a context manager that handles exceptions caused by database
|
|
131
|
+
interactions.
|
|
132
|
+
|
|
133
|
+
:param str sql: The SQL string that the block inside the context
|
|
134
|
+
manager is executing.
|
|
135
|
+
:return: A context manager that handles exceptions raised by the
|
|
136
|
+
underlying database.
|
|
137
|
+
"""
|
|
138
|
+
raise NotImplementedError("`exception_handler` is not implemented for this adapter!")
|
|
139
|
+
|
|
140
|
+
def set_connection_name(self, name: Optional[str] = None) -> Connection:
|
|
141
|
+
"""Called by 'acquire_connection' in BaseAdapter, which is called by
|
|
142
|
+
'connection_named'.
|
|
143
|
+
Creates a connection for this thread if one doesn't already
|
|
144
|
+
exist, and will rename an existing connection."""
|
|
145
|
+
|
|
146
|
+
conn_name: str = "master" if name is None else name
|
|
147
|
+
|
|
148
|
+
# Get a connection for this thread
|
|
149
|
+
conn = self.get_if_exists()
|
|
150
|
+
|
|
151
|
+
if conn and conn.name == conn_name and conn.state == "open":
|
|
152
|
+
# Found a connection and nothing to do, so just return it
|
|
153
|
+
return conn
|
|
154
|
+
|
|
155
|
+
if conn is None:
|
|
156
|
+
# Create a new connection
|
|
157
|
+
conn = Connection(
|
|
158
|
+
type=Identifier(self.TYPE),
|
|
159
|
+
name=conn_name,
|
|
160
|
+
state=ConnectionState.INIT, # type: ignore
|
|
161
|
+
transaction_open=False,
|
|
162
|
+
handle=None,
|
|
163
|
+
credentials=self.profile.credentials,
|
|
164
|
+
)
|
|
165
|
+
conn.handle = LazyHandle(self.open)
|
|
166
|
+
# Add the connection to thread_connections for this thread
|
|
167
|
+
self.set_thread_connection(conn)
|
|
168
|
+
fire_event(
|
|
169
|
+
NewConnection(conn_name=conn_name, conn_type=self.TYPE, node_info=get_node_info())
|
|
170
|
+
)
|
|
171
|
+
else: # existing connection either wasn't open or didn't have the right name
|
|
172
|
+
if conn.state != "open":
|
|
173
|
+
conn.handle = LazyHandle(self.open)
|
|
174
|
+
if conn.name != conn_name:
|
|
175
|
+
orig_conn_name: str = conn.name or ""
|
|
176
|
+
conn.name = conn_name
|
|
177
|
+
fire_event(ConnectionReused(orig_conn_name=orig_conn_name, conn_name=conn_name))
|
|
178
|
+
|
|
179
|
+
return conn
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def retry_connection(
|
|
183
|
+
cls,
|
|
184
|
+
connection: Connection,
|
|
185
|
+
connect: Callable[[], AdapterHandle],
|
|
186
|
+
logger: AdapterLogger,
|
|
187
|
+
retryable_exceptions: Iterable[Type[Exception]],
|
|
188
|
+
retry_limit: int = 1,
|
|
189
|
+
retry_timeout: Union[Callable[[int], SleepTime], SleepTime] = 1,
|
|
190
|
+
_attempts: int = 0,
|
|
191
|
+
) -> Connection:
|
|
192
|
+
"""Given a Connection, set its handle by calling connect.
|
|
193
|
+
|
|
194
|
+
The calls to connect will be retried up to retry_limit times to deal with transient
|
|
195
|
+
connection errors. By default, one retry will be attempted if retryable_exceptions is set.
|
|
196
|
+
|
|
197
|
+
:param Connection connection: An instance of a Connection that needs a handle to be set,
|
|
198
|
+
usually when attempting to open it.
|
|
199
|
+
:param connect: A callable that returns the appropiate connection handle for a
|
|
200
|
+
given adapter. This callable will be retried retry_limit times if a subclass of any
|
|
201
|
+
Exception in retryable_exceptions is raised by connect.
|
|
202
|
+
:type connect: Callable[[], AdapterHandle]
|
|
203
|
+
:param AdapterLogger logger: A logger to emit messages on retry attempts or errors. When
|
|
204
|
+
handling expected errors, we call debug, and call warning on unexpected errors or when
|
|
205
|
+
all retry attempts have been exhausted.
|
|
206
|
+
:param retryable_exceptions: An iterable of exception classes that if raised by
|
|
207
|
+
connect should trigger a retry.
|
|
208
|
+
:type retryable_exceptions: Iterable[Type[Exception]]
|
|
209
|
+
:param int retry_limit: How many times to retry the call to connect. If this limit
|
|
210
|
+
is exceeded before a successful call, a FailedToConnectError will be raised.
|
|
211
|
+
Must be non-negative.
|
|
212
|
+
:param retry_timeout: Time to wait between attempts to connect. Can also take a
|
|
213
|
+
Callable that takes the number of attempts so far, beginning at 0, and returns an int
|
|
214
|
+
or float to be passed to time.sleep.
|
|
215
|
+
:type retry_timeout: Union[Callable[[int], SleepTime], SleepTime] = 1
|
|
216
|
+
:param int _attempts: Parameter used to keep track of the number of attempts in calling the
|
|
217
|
+
connect function across recursive calls. Passed as an argument to retry_timeout if it
|
|
218
|
+
is a Callable. This parameter should not be set by the initial caller.
|
|
219
|
+
:raises dbt.adapters.exceptions.FailedToConnectError: Upon exhausting all retry attempts without
|
|
220
|
+
successfully acquiring a handle.
|
|
221
|
+
:return: The given connection with its appropriate state and handle attributes set
|
|
222
|
+
depending on whether we successfully acquired a handle or not.
|
|
223
|
+
"""
|
|
224
|
+
timeout = retry_timeout(_attempts) if callable(retry_timeout) else retry_timeout
|
|
225
|
+
if timeout < 0:
|
|
226
|
+
raise FailedToConnectError(
|
|
227
|
+
"retry_timeout cannot be negative or return a negative time."
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if retry_limit < 0 or retry_limit > sys.getrecursionlimit():
|
|
231
|
+
# This guard is not perfect others may add to the recursion limit (e.g. built-ins).
|
|
232
|
+
connection.handle = None
|
|
233
|
+
connection.state = ConnectionState.FAIL # type: ignore
|
|
234
|
+
raise FailedToConnectError("retry_limit cannot be negative")
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
connection.handle = connect()
|
|
238
|
+
connection.state = ConnectionState.OPEN # type: ignore
|
|
239
|
+
return connection
|
|
240
|
+
|
|
241
|
+
except tuple(retryable_exceptions) as e:
|
|
242
|
+
if retry_limit <= 0:
|
|
243
|
+
connection.handle = None
|
|
244
|
+
connection.state = ConnectionState.FAIL # type: ignore
|
|
245
|
+
raise FailedToConnectError(str(e))
|
|
246
|
+
|
|
247
|
+
logger.debug(
|
|
248
|
+
f"Got a retryable error when attempting to open a {cls.TYPE} connection.\n"
|
|
249
|
+
f"{retry_limit} attempts remaining. Retrying in {timeout} seconds.\n"
|
|
250
|
+
f"Error:\n{e}"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
sleep(timeout)
|
|
254
|
+
return cls.retry_connection(
|
|
255
|
+
connection=connection,
|
|
256
|
+
connect=connect,
|
|
257
|
+
logger=logger,
|
|
258
|
+
retry_limit=retry_limit - 1,
|
|
259
|
+
retry_timeout=retry_timeout,
|
|
260
|
+
retryable_exceptions=retryable_exceptions,
|
|
261
|
+
_attempts=_attempts + 1,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
except Exception as e:
|
|
265
|
+
connection.handle = None
|
|
266
|
+
connection.state = ConnectionState.FAIL # type: ignore
|
|
267
|
+
raise FailedToConnectError(str(e))
|
|
268
|
+
|
|
269
|
+
@abc.abstractmethod
|
|
270
|
+
def cancel_open(self) -> Optional[List[str]]:
|
|
271
|
+
"""Cancel all open connections on the adapter. (passable)"""
|
|
272
|
+
raise NotImplementedError("`cancel_open` is not implemented for this adapter!")
|
|
273
|
+
|
|
274
|
+
@classmethod
|
|
275
|
+
@abc.abstractmethod
|
|
276
|
+
def open(cls, connection: Connection) -> Connection:
|
|
277
|
+
"""Open the given connection on the adapter and return it.
|
|
278
|
+
|
|
279
|
+
This may mutate the given connection (in particular, its state and its
|
|
280
|
+
handle).
|
|
281
|
+
|
|
282
|
+
This should be thread-safe, or hold the lock if necessary. The given
|
|
283
|
+
connection should not be in either in_use or available.
|
|
284
|
+
"""
|
|
285
|
+
raise NotImplementedError("`open` is not implemented for this adapter!")
|
|
286
|
+
|
|
287
|
+
def release(self) -> None:
|
|
288
|
+
with self.lock:
|
|
289
|
+
conn = self.get_if_exists()
|
|
290
|
+
if conn is None:
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
# always close the connection. close() calls _rollback() if there
|
|
295
|
+
# is an open transaction
|
|
296
|
+
self.close(conn)
|
|
297
|
+
except Exception:
|
|
298
|
+
# if rollback or close failed, remove our busted connection
|
|
299
|
+
self.clear_thread_connection()
|
|
300
|
+
raise
|
|
301
|
+
|
|
302
|
+
def cleanup_all(self) -> None:
|
|
303
|
+
with self.lock:
|
|
304
|
+
for connection in self.thread_connections.values():
|
|
305
|
+
if connection.state not in {"closed", "init"}:
|
|
306
|
+
fire_event(ConnectionLeftOpenInCleanup(conn_name=cast_to_str(connection.name)))
|
|
307
|
+
else:
|
|
308
|
+
fire_event(ConnectionClosedInCleanup(conn_name=cast_to_str(connection.name)))
|
|
309
|
+
self.close(connection)
|
|
310
|
+
|
|
311
|
+
# garbage collect these connections
|
|
312
|
+
self.thread_connections.clear()
|
|
313
|
+
|
|
314
|
+
@abc.abstractmethod
|
|
315
|
+
def begin(self) -> None:
|
|
316
|
+
"""Begin a transaction. (passable)"""
|
|
317
|
+
raise NotImplementedError("`begin` is not implemented for this adapter!")
|
|
318
|
+
|
|
319
|
+
@abc.abstractmethod
|
|
320
|
+
def commit(self) -> None:
|
|
321
|
+
"""Commit a transaction. (passable)"""
|
|
322
|
+
raise NotImplementedError("`commit` is not implemented for this adapter!")
|
|
323
|
+
|
|
324
|
+
@classmethod
|
|
325
|
+
def _rollback_handle(cls, connection: Connection) -> None:
|
|
326
|
+
"""Perform the actual rollback operation."""
|
|
327
|
+
try:
|
|
328
|
+
connection.handle.rollback()
|
|
329
|
+
except Exception:
|
|
330
|
+
fire_event(
|
|
331
|
+
RollbackFailed(
|
|
332
|
+
conn_name=cast_to_str(connection.name),
|
|
333
|
+
exc_info=traceback.format_exc(),
|
|
334
|
+
node_info=get_node_info(),
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
@classmethod
|
|
339
|
+
def _close_handle(cls, connection: Connection) -> None:
|
|
340
|
+
"""Perform the actual close operation."""
|
|
341
|
+
# On windows, sometimes connection handles don't have a close() attr.
|
|
342
|
+
if hasattr(connection.handle, "close"):
|
|
343
|
+
fire_event(
|
|
344
|
+
ConnectionClosed(conn_name=cast_to_str(connection.name), node_info=get_node_info())
|
|
345
|
+
)
|
|
346
|
+
connection.handle.close()
|
|
347
|
+
else:
|
|
348
|
+
fire_event(
|
|
349
|
+
ConnectionLeftOpen(
|
|
350
|
+
conn_name=cast_to_str(connection.name), node_info=get_node_info()
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
@classmethod
|
|
355
|
+
def _rollback(cls, connection: Connection) -> None:
|
|
356
|
+
"""Roll back the given connection."""
|
|
357
|
+
if connection.transaction_open is False:
|
|
358
|
+
raise DbtInternalError(
|
|
359
|
+
f"Tried to rollback transaction on connection "
|
|
360
|
+
f'"{connection.name}", but it does not have one open!'
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
fire_event(Rollback(conn_name=cast_to_str(connection.name), node_info=get_node_info()))
|
|
364
|
+
cls._rollback_handle(connection)
|
|
365
|
+
|
|
366
|
+
connection.transaction_open = False
|
|
367
|
+
|
|
368
|
+
@classmethod
|
|
369
|
+
def close(cls, connection: Connection) -> Connection:
|
|
370
|
+
# if the connection is in closed or init, there's nothing to do
|
|
371
|
+
if connection.state in {ConnectionState.CLOSED, ConnectionState.INIT}:
|
|
372
|
+
return connection
|
|
373
|
+
|
|
374
|
+
if connection.transaction_open and connection.handle:
|
|
375
|
+
fire_event(Rollback(conn_name=cast_to_str(connection.name), node_info=get_node_info()))
|
|
376
|
+
cls._rollback_handle(connection)
|
|
377
|
+
connection.transaction_open = False
|
|
378
|
+
|
|
379
|
+
cls._close_handle(connection)
|
|
380
|
+
connection.state = ConnectionState.CLOSED # type: ignore
|
|
381
|
+
|
|
382
|
+
return connection
|
|
383
|
+
|
|
384
|
+
def commit_if_has_connection(self) -> None:
|
|
385
|
+
"""If the named connection exists, commit the current transaction."""
|
|
386
|
+
connection = self.get_if_exists()
|
|
387
|
+
if connection:
|
|
388
|
+
self.commit()
|
|
389
|
+
|
|
390
|
+
def _add_query_comment(self, sql: str) -> str:
|
|
391
|
+
if self.query_header is None:
|
|
392
|
+
return sql
|
|
393
|
+
return self.query_header.add(sql)
|
|
394
|
+
|
|
395
|
+
@abc.abstractmethod
|
|
396
|
+
def execute(
|
|
397
|
+
self,
|
|
398
|
+
sql: str,
|
|
399
|
+
auto_begin: bool = False,
|
|
400
|
+
fetch: bool = False,
|
|
401
|
+
limit: Optional[int] = None,
|
|
402
|
+
) -> Tuple[AdapterResponse, "agate.Table"]:
|
|
403
|
+
"""Execute the given SQL.
|
|
404
|
+
|
|
405
|
+
:param str sql: The sql to execute.
|
|
406
|
+
:param bool auto_begin: If set, and dbt is not currently inside a
|
|
407
|
+
transaction, automatically begin one.
|
|
408
|
+
:param bool fetch: If set, fetch results.
|
|
409
|
+
:param int limit: If set, limits the result set
|
|
410
|
+
:return: A tuple of the query status and results (empty if fetch=False).
|
|
411
|
+
:rtype: Tuple[AdapterResponse, agate.Table]
|
|
412
|
+
"""
|
|
413
|
+
raise NotImplementedError("`execute` is not implemented for this adapter!")
|
|
414
|
+
|
|
415
|
+
def add_select_query(self, sql: str) -> Tuple[Connection, Any]:
|
|
416
|
+
"""
|
|
417
|
+
This was added here because base.impl.BaseAdapter.get_column_schema_from_query expects it to be here.
|
|
418
|
+
That method wouldn't work unless the adapter used sql.impl.SQLAdapter, sql.connections.SQLConnectionManager
|
|
419
|
+
or defined this method on <Adapter>ConnectionManager before passing it in to <Adapter>Adapter.
|
|
420
|
+
|
|
421
|
+
See https://github.com/dbt-labs/dbt-core/issues/8396 for more information.
|
|
422
|
+
"""
|
|
423
|
+
raise NotImplementedError("`add_select_query` is not implemented for this adapter!")
|
|
424
|
+
|
|
425
|
+
@classmethod
|
|
426
|
+
def data_type_code_to_name(cls, type_code: Union[int, str]) -> str:
|
|
427
|
+
"""Get the string representation of the data type from the type_code."""
|
|
428
|
+
# https://peps.python.org/pep-0249/#type-objects
|
|
429
|
+
raise NotImplementedError("`data_type_code_to_name` is not implemented for this adapter!")
|