dbt-sqlserver 1.11.0rc1__tar.gz → 1.11.1__tar.gz
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_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/PKG-INFO +16 -4
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/README.md +13 -2
- dbt_sqlserver-1.11.1/dbt/adapters/sqlserver/__version__.py +1 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_adapter.py +257 -18
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_connections.py +58 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_runtime.py +5 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/indexes.sql +94 -39
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/metadata.sql +2 -2
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/relation.sql +10 -4
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/hooks.sql +5 -1
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/incremental/incremental.sql +47 -6
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/table/table.sql +5 -3
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/view/view.sql +24 -4
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/relations/table/create.sql +81 -23
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt_sqlserver.egg-info/PKG-INFO +16 -4
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/pyproject.toml +3 -1
- dbt_sqlserver-1.11.0rc1/dbt/adapters/sqlserver/__version__.py +0 -1
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/LICENSE +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/__init__.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/__init__.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/py.typed +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/relation_configs/__init__.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/relation_configs/index.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/relation_configs/policies.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_auth.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_backend.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_column.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_configs.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_constants.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_credentials.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_helpers.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_mask.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_relation.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/__init__.py +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/dbt_project.yml +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/apply_grants.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/apply_masks.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/catalog.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/columns.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/persist_docs.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/schema.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/show.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/adapters/validate_sql.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/functions/helpers.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/functions/scalar.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/incremental/incremental_strategies.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/incremental/merge.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/table/columns_spec_ddl.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/unit_test/get_fixture_sql.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/unit_test/unit_test_create_table_as.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/models/view/create_view_as.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/snapshots/helpers.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/snapshots/snapshot.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/snapshots/snapshot_merge.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/snapshots/strategies.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/tests.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/materializations/unit_tests.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/relations/seeds/helpers.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/relations/table/clone.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/relations/views/create.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/any_value.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/array_construct.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/cast_bool_to_text.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/concat.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/date_trunc.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/dateadd.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/get_tables_by_pattern.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/hash.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/last_day.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/length.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/listagg.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/position.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/safe_cast.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/split_part.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/macros/utils/timestamps.sql +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/include/sqlserver/profile_template.yml +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt_sqlserver.egg-info/SOURCES.txt +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt_sqlserver.egg-info/dependency_links.txt +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt_sqlserver.egg-info/requires.txt +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt_sqlserver.egg-info/top_level.txt +0 -0
- {dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbt-sqlserver
|
|
3
|
-
Version: 1.11.
|
|
3
|
+
Version: 1.11.1
|
|
4
4
|
Summary: A Microsoft SQL Server adapter plugin for dbt
|
|
5
5
|
Author: Mikael Ene, Anders Swanson, Sam Debruyn, Cor Zuurmond, Cody Scott
|
|
6
6
|
License: MIT
|
|
@@ -14,10 +14,11 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
14
14
|
Classifier: Operating System :: Microsoft :: Windows
|
|
15
15
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
16
16
|
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.10
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
License-File: LICENSE
|
|
23
24
|
Requires-Dist: dbt-core<2.0,>=1.11.0
|
|
@@ -38,8 +39,19 @@ Dynamic: license-file
|
|
|
38
39
|
|
|
39
40
|
[dbt](https://www.getdbt.com) adapter for Microsoft SQL Server and Azure SQL services.
|
|
40
41
|
|
|
41
|
-
The adapter supports dbt-core 1.
|
|
42
|
-
E.g. version 1.
|
|
42
|
+
The adapter supports dbt-core 1.11 or newer and follows the same versioning scheme.
|
|
43
|
+
E.g. version 1.11.x of the adapter is compatible with dbt-core 1.11.x.
|
|
44
|
+
|
|
45
|
+
## Supported Python versions
|
|
46
|
+
|
|
47
|
+
The adapter is tested against:
|
|
48
|
+
|
|
49
|
+
| Python version | Status |
|
|
50
|
+
|---|---|
|
|
51
|
+
| 3.10 | Installable (not tested in CI) |
|
|
52
|
+
| 3.11 | Officially supported |
|
|
53
|
+
| 3.12 | Officially supported |
|
|
54
|
+
| 3.13 | Officially supported |
|
|
43
55
|
|
|
44
56
|
## Supported SQL Server versions
|
|
45
57
|
|
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[dbt](https://www.getdbt.com) adapter for Microsoft SQL Server and Azure SQL services.
|
|
4
4
|
|
|
5
|
-
The adapter supports dbt-core 1.
|
|
6
|
-
E.g. version 1.
|
|
5
|
+
The adapter supports dbt-core 1.11 or newer and follows the same versioning scheme.
|
|
6
|
+
E.g. version 1.11.x of the adapter is compatible with dbt-core 1.11.x.
|
|
7
|
+
|
|
8
|
+
## Supported Python versions
|
|
9
|
+
|
|
10
|
+
The adapter is tested against:
|
|
11
|
+
|
|
12
|
+
| Python version | Status |
|
|
13
|
+
|---|---|
|
|
14
|
+
| 3.10 | Installable (not tested in CI) |
|
|
15
|
+
| 3.11 | Officially supported |
|
|
16
|
+
| 3.12 | Officially supported |
|
|
17
|
+
| 3.13 | Officially supported |
|
|
7
18
|
|
|
8
19
|
## Supported SQL Server versions
|
|
9
20
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "1.11.1"
|
{dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_adapter.py
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from typing import Any, Dict, List, Optional
|
|
2
3
|
|
|
3
4
|
import agate
|
|
@@ -26,16 +27,118 @@ from dbt.adapters.sqlserver.relation_configs.index import (
|
|
|
26
27
|
index_config_changes,
|
|
27
28
|
normalize_drop_unmanaged,
|
|
28
29
|
)
|
|
30
|
+
from dbt.adapters.sqlserver.sqlserver_auth import is_mssql_python_backend
|
|
29
31
|
from dbt.adapters.sqlserver.sqlserver_column import SQLServerColumn, SQLServerColumnNative
|
|
30
32
|
from dbt.adapters.sqlserver.sqlserver_configs import SQLServerConfigs
|
|
31
|
-
from dbt.adapters.sqlserver.sqlserver_connections import
|
|
33
|
+
from dbt.adapters.sqlserver.sqlserver_connections import (
|
|
34
|
+
SQLServerConnectionManager,
|
|
35
|
+
_discard_pending_results,
|
|
36
|
+
)
|
|
32
37
|
from dbt.adapters.sqlserver.sqlserver_mask import ColumnMask
|
|
33
38
|
from dbt.adapters.sqlserver.sqlserver_mask import mask_changes as _mask_changes
|
|
34
39
|
from dbt.adapters.sqlserver.sqlserver_mask import resolve_masks as _resolve_masks
|
|
35
40
|
from dbt.adapters.sqlserver.sqlserver_relation import SQLServerRelation
|
|
41
|
+
from dbt.adapters.sqlserver.sqlserver_runtime import _get_pyodbc
|
|
36
42
|
|
|
37
43
|
logger = AdapterLogger("SQLServer")
|
|
38
44
|
|
|
45
|
+
# Mirrors sqlserver__select_starts_with_cte
|
|
46
|
+
# (dbt/include/sqlserver/macros/adapters/columns.sql): a query opening with a
|
|
47
|
+
# CTE cannot be neutered as ``select * from (...) where 1 = 0``, so it reaches
|
|
48
|
+
# get_column_schema_from_query unwrapped and would otherwise be executed in
|
|
49
|
+
# full just to read its column names.
|
|
50
|
+
_SQL_COMMENT = re.compile(r"(?s)/\*.*?\*/|--[^\n]*\n")
|
|
51
|
+
|
|
52
|
+
# sp_describe_first_result_set reports true SQL Server types; reading
|
|
53
|
+
# ``cursor.description`` reports Python classes, which collapse whole families
|
|
54
|
+
# (every integer width arrives as ``int``, every string type as ``varchar``).
|
|
55
|
+
# Contract comparison comes through this method either way, so the describe
|
|
56
|
+
# path is mapped back onto exactly the names the execute path yields via
|
|
57
|
+
# ``data_type_code_to_name``. TestCteProbeAvoidsExecution pins the two
|
|
58
|
+
# together; a type missing from this map falls back to executing rather than
|
|
59
|
+
# guessing.
|
|
60
|
+
#
|
|
61
|
+
# The names below are pyodbc's. The class a driver picks for a column is its
|
|
62
|
+
# own choice, not SQL Server's, and mssql-python decodes three of these
|
|
63
|
+
# differently -- see ``_MSSQL_PYTHON_TYPE_OVERRIDES``.
|
|
64
|
+
_SYSTEM_TYPE_TO_EXECUTED_NAME = {
|
|
65
|
+
"bigint": "int",
|
|
66
|
+
"int": "int",
|
|
67
|
+
"smallint": "int",
|
|
68
|
+
"tinyint": "int",
|
|
69
|
+
"bit": "bit",
|
|
70
|
+
"decimal": "decimal",
|
|
71
|
+
"numeric": "decimal",
|
|
72
|
+
"money": "decimal",
|
|
73
|
+
"smallmoney": "decimal",
|
|
74
|
+
"float": "float",
|
|
75
|
+
"real": "float",
|
|
76
|
+
"date": "date",
|
|
77
|
+
"time": "time",
|
|
78
|
+
"datetime": "datetime2(6)",
|
|
79
|
+
"smalldatetime": "datetime2(6)",
|
|
80
|
+
"datetime2": "datetime2(6)",
|
|
81
|
+
"char": "varchar",
|
|
82
|
+
"nchar": "varchar",
|
|
83
|
+
"varchar": "varchar",
|
|
84
|
+
"nvarchar": "varchar",
|
|
85
|
+
"text": "varchar",
|
|
86
|
+
"ntext": "varchar",
|
|
87
|
+
"xml": "varchar",
|
|
88
|
+
"uniqueidentifier": "varchar",
|
|
89
|
+
"binary": "varbinary",
|
|
90
|
+
"varbinary": "varbinary",
|
|
91
|
+
"image": "varbinary",
|
|
92
|
+
"timestamp": "varbinary",
|
|
93
|
+
"rowversion": "varbinary",
|
|
94
|
+
# datetimeoffset is deliberately absent *from this map*: add_query
|
|
95
|
+
# registers the -155 output converter after the execute that needed it,
|
|
96
|
+
# so pyodbc reports the column as bytearray on a connection's first query
|
|
97
|
+
# and as str on every one after. No fixed name mirrors that, so on pyodbc
|
|
98
|
+
# describing gives up and the query is executed -- which agrees with
|
|
99
|
+
# itself by construction. mssql-python needs no converter and is pinned
|
|
100
|
+
# in _MSSQL_PYTHON_TYPE_OVERRIDES.
|
|
101
|
+
"sql_variant": "varbinary",
|
|
102
|
+
"hierarchyid": "varbinary",
|
|
103
|
+
"geography": "varbinary",
|
|
104
|
+
"geometry": "varbinary",
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# mssql-python decodes three types into richer Python objects than pyodbc
|
|
108
|
+
# does: uniqueidentifier as ``uuid.UUID`` (pyodbc: ``str``), datetimeoffset
|
|
109
|
+
# as ``datetime`` and sql_variant as ``str`` (pyodbc: ``bytearray`` for
|
|
110
|
+
# both). Executing reports those classes, so describing has to agree.
|
|
111
|
+
_MSSQL_PYTHON_TYPE_OVERRIDES = {
|
|
112
|
+
"uniqueidentifier": "uniqueidentifier",
|
|
113
|
+
"datetimeoffset": "datetime2(6)",
|
|
114
|
+
"sql_variant": "varchar",
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _executed_name_for_system_type(base_type: str, backend: Any) -> Optional[str]:
|
|
119
|
+
"""The name the executed probe would report for a described column type.
|
|
120
|
+
|
|
121
|
+
None means "no confident answer" -- the caller then executes the query,
|
|
122
|
+
which is slower but cannot disagree with itself.
|
|
123
|
+
"""
|
|
124
|
+
if is_mssql_python_backend(backend):
|
|
125
|
+
overridden = _MSSQL_PYTHON_TYPE_OVERRIDES.get(base_type)
|
|
126
|
+
if overridden is not None:
|
|
127
|
+
return overridden
|
|
128
|
+
|
|
129
|
+
elif base_type == "uniqueidentifier":
|
|
130
|
+
# pyodbc yields uuid.UUID or str for a GUID depending on its
|
|
131
|
+
# module-level ``native_uuid`` flag -- process-global state anything
|
|
132
|
+
# in the process can flip, so read it rather than assume a default.
|
|
133
|
+
try:
|
|
134
|
+
native_uuid = bool(_get_pyodbc().native_uuid)
|
|
135
|
+
except Exception as e: # pragma: no cover - pyodbc is present if in use
|
|
136
|
+
logger.debug(f"Could not read pyodbc.native_uuid, executing the query: {e}")
|
|
137
|
+
return None
|
|
138
|
+
return "uniqueidentifier" if native_uuid else "varchar"
|
|
139
|
+
|
|
140
|
+
return _SYSTEM_TYPE_TO_EXECUTED_NAME.get(base_type)
|
|
141
|
+
|
|
39
142
|
|
|
40
143
|
class SQLServerAdapter(SQLAdapter):
|
|
41
144
|
"""
|
|
@@ -77,14 +180,6 @@ class SQLServerAdapter(SQLAdapter):
|
|
|
77
180
|
@property
|
|
78
181
|
def _behavior_flags(self) -> List[BehaviorFlag]:
|
|
79
182
|
return [
|
|
80
|
-
{
|
|
81
|
-
"name": "empty",
|
|
82
|
-
"default": False,
|
|
83
|
-
"description": (
|
|
84
|
-
"When enabled, table and view materializations will be created as empty "
|
|
85
|
-
"structures (no data)."
|
|
86
|
-
),
|
|
87
|
-
},
|
|
88
183
|
{
|
|
89
184
|
"name": "dbt_sqlserver_use_default_schema_concat",
|
|
90
185
|
"default": False,
|
|
@@ -143,18 +238,108 @@ class SQLServerAdapter(SQLAdapter):
|
|
|
143
238
|
|
|
144
239
|
@available.parse(lambda *a, **k: [])
|
|
145
240
|
def get_column_schema_from_query(self, sql: str) -> List[BaseColumn]:
|
|
146
|
-
"""Get a list of the Columns with names and data types from the given sql.
|
|
241
|
+
"""Get a list of the Columns with names and data types from the given sql.
|
|
242
|
+
|
|
243
|
+
Only the result *shape* is wanted, but the query still runs, so the
|
|
244
|
+
cursor comes back holding the whole result set. Usually that set is
|
|
245
|
+
empty: dbt-core's ``get_column_schema_from_query`` macro wraps the
|
|
246
|
+
query first, and ``sqlserver__get_empty_subquery_sql`` renders that as
|
|
247
|
+
``select * from (...) where 1 = 0``. A query that opens with a CTE
|
|
248
|
+
cannot be wrapped that way, though, and is passed through untouched
|
|
249
|
+
(dbt/include/sqlserver/macros/adapters/columns.sql), so snapshot
|
|
250
|
+
staging queries and CTE-headed contract models arrive here in full.
|
|
251
|
+
|
|
252
|
+
Either way the cursor must not be abandoned holding rows -- see
|
|
253
|
+
``_discard_pending_results`` for what that costs.
|
|
254
|
+
"""
|
|
255
|
+
if _SQL_COMMENT.sub("", sql).strip().lower().startswith("with"):
|
|
256
|
+
described = self._describe_result_set(sql)
|
|
257
|
+
if described is not None:
|
|
258
|
+
return described
|
|
259
|
+
|
|
147
260
|
_, cursor = self.connections.add_select_query(sql)
|
|
148
261
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
262
|
+
try:
|
|
263
|
+
columns = [
|
|
264
|
+
self.Column.create(
|
|
265
|
+
column_name, self.connections.data_type_code_to_name(column_type_code)
|
|
266
|
+
)
|
|
267
|
+
# https://peps.python.org/pep-0249/#description
|
|
268
|
+
for column_name, column_type_code, *_ in cursor.description
|
|
269
|
+
]
|
|
270
|
+
finally:
|
|
271
|
+
_discard_pending_results(cursor)
|
|
272
|
+
|
|
156
273
|
return columns
|
|
157
274
|
|
|
275
|
+
def _describe_result_set(self, sql: str) -> Optional[List[BaseColumn]]:
|
|
276
|
+
"""Read a query's column shape without running it, or None to fall back.
|
|
277
|
+
|
|
278
|
+
``sp_describe_first_result_set`` compiles the query and reports its
|
|
279
|
+
result shape, which is all this method ever wanted. It is already how
|
|
280
|
+
``sqlserver__get_columns_in_query`` handles CTEs (#698).
|
|
281
|
+
|
|
282
|
+
Returns None -- deliberately, rather than raising -- whenever the
|
|
283
|
+
describe cannot be trusted to match what executing would have reported:
|
|
284
|
+
a query it refuses to describe (it cannot see through ``#temp`` tables,
|
|
285
|
+
where executing works), or a type this backend's driver has no known
|
|
286
|
+
executed name for. The caller then executes as before, which is
|
|
287
|
+
slower but never disagrees with itself.
|
|
288
|
+
"""
|
|
289
|
+
credentials = self.connections.profile.credentials
|
|
290
|
+
|
|
291
|
+
# Inline rather than bound: mssql-python binds str as varchar and the
|
|
292
|
+
# procedure demands nvarchar(max). columns.sql:24 escapes it the same
|
|
293
|
+
# way for the same reason.
|
|
294
|
+
describe_sql = "exec sp_describe_first_result_set @tsql = N'{}'".format(
|
|
295
|
+
sql.replace("'", "''")
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
_, cursor = self.connections.add_select_query(describe_sql)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
logger.debug(f"Could not describe a CTE query, falling back to executing it: {e}")
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
fields = [description[0].lower() for description in cursor.description]
|
|
306
|
+
rows = cursor.fetchall()
|
|
307
|
+
except Exception as e:
|
|
308
|
+
logger.debug(f"Could not read a described result set, executing the query: {e}")
|
|
309
|
+
return None
|
|
310
|
+
finally:
|
|
311
|
+
_discard_pending_results(cursor)
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
hidden, name, type_name = (
|
|
315
|
+
fields.index("is_hidden"),
|
|
316
|
+
fields.index("name"),
|
|
317
|
+
fields.index("system_type_name"),
|
|
318
|
+
)
|
|
319
|
+
except ValueError: # pragma: no cover - shape is fixed by SQL Server
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
columns = []
|
|
323
|
+
for row in rows:
|
|
324
|
+
if row[hidden]:
|
|
325
|
+
continue
|
|
326
|
+
# "varchar(10)" / "decimal(10,2)" -> "varchar" / "decimal"
|
|
327
|
+
base_type = str(row[type_name]).split("(")[0].strip().lower()
|
|
328
|
+
executed_name = _executed_name_for_system_type(base_type, credentials.backend)
|
|
329
|
+
if executed_name is None or row[name] is None:
|
|
330
|
+
logger.debug(
|
|
331
|
+
f"Describing a CTE query reported {base_type!r}, which has no "
|
|
332
|
+
"equivalent in the executed path; executing it instead"
|
|
333
|
+
)
|
|
334
|
+
return None
|
|
335
|
+
columns.append(self.Column.create(row[name], executed_name))
|
|
336
|
+
|
|
337
|
+
# Every select has at least one column, so nothing described means
|
|
338
|
+
# sp_describe_first_result_set could not work the shape out. Returning
|
|
339
|
+
# an empty list would read as "this query has no columns" and surface
|
|
340
|
+
# as a baffling contract mismatch; execute instead.
|
|
341
|
+
return columns or None
|
|
342
|
+
|
|
158
343
|
@classmethod
|
|
159
344
|
def convert_boolean_type(cls, agate_table, col_idx):
|
|
160
345
|
return "bit"
|
|
@@ -332,7 +517,10 @@ class SQLServerAdapter(SQLAdapter):
|
|
|
332
517
|
"""Return the number of rows in the given relation."""
|
|
333
518
|
sql = f"SELECT COUNT_BIG(*) FROM {relation}"
|
|
334
519
|
_, cursor = self.connections.add_select_query(sql)
|
|
335
|
-
|
|
520
|
+
try:
|
|
521
|
+
row = cursor.fetchone()
|
|
522
|
+
finally:
|
|
523
|
+
_discard_pending_results(cursor)
|
|
336
524
|
return int(row[0]) if row else 0
|
|
337
525
|
|
|
338
526
|
def expand_column_types(self, goal, current, max_rows: int = 1000000):
|
|
@@ -427,6 +615,57 @@ class SQLServerAdapter(SQLAdapter):
|
|
|
427
615
|
def parse_index(self, raw_index: Any) -> Optional[SQLServerIndexConfig]:
|
|
428
616
|
return SQLServerIndexConfig.parse(raw_index)
|
|
429
617
|
|
|
618
|
+
@available
|
|
619
|
+
def index_needs_own_batch(self, raw_index: Any) -> bool:
|
|
620
|
+
"""True when raw_index's build_options (ONLINE / RESUMABLE) force its
|
|
621
|
+
CREATE INDEX to run outside any transaction: SQL Server rejects
|
|
622
|
+
RESUMABLE inside a user transaction (error 574), and an ONLINE build
|
|
623
|
+
wrapped in one holds its locks until commit, negating the point."""
|
|
624
|
+
parsed = self.parse_index(raw_index)
|
|
625
|
+
if not parsed:
|
|
626
|
+
return False
|
|
627
|
+
return create_needs_own_batch(parsed.build_options)
|
|
628
|
+
|
|
629
|
+
@available
|
|
630
|
+
def commit_if_open(self) -> None:
|
|
631
|
+
"""Commit the current transaction if one is open - a no-op otherwise.
|
|
632
|
+
|
|
633
|
+
dbt_sqlserver_use_dbt_transactions on (default) wraps a
|
|
634
|
+
materialization's whole build, from its first statement through its
|
|
635
|
+
own trailing ``adapter.commit()``, in one continuous ambient
|
|
636
|
+
transaction. Some statements must not share that transaction with
|
|
637
|
+
whatever runs after them: an ONLINE/RESUMABLE index build (SQL Server
|
|
638
|
+
rejects RESUMABLE inside a user transaction outright, and ONLINE
|
|
639
|
+
holds its locks until commit either way), or a full-refresh-in-
|
|
640
|
+
progress marker, which exists specifically to survive a later
|
|
641
|
+
failure and so must not roll back with it. This makes the prior
|
|
642
|
+
statement durable on its own; pair with begin_if_closed once the
|
|
643
|
+
statement(s) that must run outside a transaction are done, to leave
|
|
644
|
+
later code (more such statements, or the materialization's own
|
|
645
|
+
trailing ``adapter.commit()``, which raises if it finds nothing open)
|
|
646
|
+
working as if this call had never happened.
|
|
647
|
+
|
|
648
|
+
A no-op when no transaction is open: the caller's statement already
|
|
649
|
+
ran autocommitted on its own (e.g. via ``run_query``'s
|
|
650
|
+
``auto_begin=false``, before anything else began one), so there is
|
|
651
|
+
nothing to flush. Also a no-op, at the SQL level, whenever
|
|
652
|
+
dbt_sqlserver_use_dbt_transactions is off: begin/commit still flip
|
|
653
|
+
dbt-core's bookkeeping (see
|
|
654
|
+
SQLServerConnectionManager.add_begin_query/add_commit_query), but
|
|
655
|
+
emit no real T-SQL, matching the driver's own autocommit.
|
|
656
|
+
"""
|
|
657
|
+
connection = self.connections.get_thread_connection()
|
|
658
|
+
if connection is not None and connection.transaction_open:
|
|
659
|
+
self.connections.commit()
|
|
660
|
+
|
|
661
|
+
@available
|
|
662
|
+
def begin_if_closed(self) -> None:
|
|
663
|
+
"""Begin a transaction if none is open - a no-op otherwise. See
|
|
664
|
+
commit_if_open, which this pairs with."""
|
|
665
|
+
connection = self.connections.get_thread_connection()
|
|
666
|
+
if connection is not None and not connection.transaction_open:
|
|
667
|
+
self.connections.begin()
|
|
668
|
+
|
|
430
669
|
@available
|
|
431
670
|
def validate_indexes(
|
|
432
671
|
self, raw_indexes: Any, as_columnstore: Any = False, drop_unmanaged: Any = False
|
{dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_connections.py
RENAMED
|
@@ -61,6 +61,64 @@ from dbt.adapters.sqlserver.sqlserver_runtime import (
|
|
|
61
61
|
|
|
62
62
|
logger = AdapterLogger("sqlserver")
|
|
63
63
|
|
|
64
|
+
|
|
65
|
+
def _try_drain_nextset(cursor: Any) -> bool:
|
|
66
|
+
"""Drain one additional result set from *cursor*, if supported.
|
|
67
|
+
|
|
68
|
+
Returns ``True`` if a further result set was consumed, ``False`` if the
|
|
69
|
+
cursor has no more result sets.
|
|
70
|
+
"""
|
|
71
|
+
return bool(cursor.nextset())
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Rows per round trip when shedding a result set nobody asked for. Large
|
|
75
|
+
# enough that discarding even a big one costs a handful of round trips.
|
|
76
|
+
_DISCARD_CHUNK_SIZE = 10000
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _discard_pending_results(cursor: Any) -> None:
|
|
80
|
+
"""Consume and close *cursor*, leaving nothing for the driver to cancel.
|
|
81
|
+
|
|
82
|
+
Closing a cursor whose result set the server is still producing makes the
|
|
83
|
+
driver cancel the request, and the cancel reaches SQL Server as an
|
|
84
|
+
*attention*. Every connection opened here runs ``SET XACT_ABORT ON``
|
|
85
|
+
(``_apply_session_settings``, for dbt-msft/dbt-sqlserver#718), and SQL
|
|
86
|
+
Server answers an attention under ``XACT_ABORT ON`` by rolling back the
|
|
87
|
+
open transaction.
|
|
88
|
+
|
|
89
|
+
None of that raises -- an attention is not an error -- so a materialization
|
|
90
|
+
that abandons a cursor part-way through a build silently loses whatever it
|
|
91
|
+
had already created inside that transaction, then fails further on against
|
|
92
|
+
relations that no longer exist. Fetching the rows first makes the close an
|
|
93
|
+
ordinary one.
|
|
94
|
+
|
|
95
|
+
Failures while discarding are logged and swallowed: the caller already has
|
|
96
|
+
what it came for, and the connection is about to be reused for real work,
|
|
97
|
+
so trouble shedding rows nobody wanted must not become the error the user
|
|
98
|
+
sees.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
while True:
|
|
102
|
+
# ``description`` is None for statements that return no rows at
|
|
103
|
+
# all, where fetching would raise rather than yield nothing.
|
|
104
|
+
if cursor.description is not None:
|
|
105
|
+
while cursor.fetchmany(_DISCARD_CHUNK_SIZE):
|
|
106
|
+
pass
|
|
107
|
+
# nextset() only advances; whatever rows the set it lands on holds
|
|
108
|
+
# still have to be fetched, hence the outer loop.
|
|
109
|
+
if not _try_drain_nextset(cursor):
|
|
110
|
+
break
|
|
111
|
+
except Exception as e:
|
|
112
|
+
# AdapterLogger cannot serialize an exception as a log argument, so
|
|
113
|
+
# interpolate rather than passing ``e`` through.
|
|
114
|
+
logger.debug(f"Discarding a pending result set failed: {e}")
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
cursor.close()
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.debug(f"Closing a cursor failed: {e}")
|
|
120
|
+
|
|
121
|
+
|
|
64
122
|
# Attribute used to stash the in-flight pyodbc / mssql-python cursor on a
|
|
65
123
|
# Connection so cancel() can reach it from another thread. See cancel().
|
|
66
124
|
_IN_FLIGHT_CURSOR_ATTR = "_dbt_sqlserver_in_flight_cursor"
|
{dbt_sqlserver-1.11.0rc1 → dbt_sqlserver-1.11.1}/dbt/adapters/sqlserver/sqlserver_runtime.py
RENAMED
|
@@ -45,6 +45,11 @@ class PyodbcModuleProtocol(Protocol):
|
|
|
45
45
|
InterfaceError: type[Exception]
|
|
46
46
|
DatabaseError: type[Exception]
|
|
47
47
|
pooling: bool
|
|
48
|
+
# Whether pyodbc decodes a uniqueidentifier column as uuid.UUID rather
|
|
49
|
+
# than str. Read by the CTE probe, which has to report the same type
|
|
50
|
+
# executing the query would have. Module-global, so it can change under
|
|
51
|
+
# us; nothing here sets it.
|
|
52
|
+
native_uuid: bool
|
|
48
53
|
|
|
49
54
|
def connect(self, *args: Any, **kwargs: Any) -> Any: ...
|
|
50
55
|
|