dbt-sqlserver 1.11.2rc1__tar.gz → 1.11.3__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.
Files changed (84) hide show
  1. {dbt_sqlserver-1.11.2rc1/dbt_sqlserver.egg-info → dbt_sqlserver-1.11.3}/PKG-INFO +7 -8
  2. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/README.md +6 -7
  3. dbt_sqlserver-1.11.3/dbt/adapters/sqlserver/__version__.py +1 -0
  4. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_adapter.py +83 -33
  5. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/columns.sql +15 -1
  6. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/metadata.sql +49 -0
  7. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/schema.sql +34 -9
  8. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/view/view.sql +8 -15
  9. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/snapshots/helpers.sql +1 -1
  10. dbt_sqlserver-1.11.3/dbt/include/sqlserver/macros/materializations/snapshots/strategies.sql +65 -0
  11. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/tests.sql +3 -6
  12. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/unit_tests.sql +2 -5
  13. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/relations/table/create.sql +22 -8
  14. dbt_sqlserver-1.11.3/dbt/include/sqlserver/macros/utils/array_append.sql +3 -0
  15. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3/dbt_sqlserver.egg-info}/PKG-INFO +7 -8
  16. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt_sqlserver.egg-info/SOURCES.txt +1 -0
  17. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/pyproject.toml +27 -0
  18. dbt_sqlserver-1.11.2rc1/dbt/adapters/sqlserver/__version__.py +0 -1
  19. dbt_sqlserver-1.11.2rc1/dbt/include/sqlserver/macros/materializations/snapshots/strategies.sql +0 -5
  20. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/LICENSE +0 -0
  21. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/__init__.py +0 -0
  22. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/__init__.py +0 -0
  23. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/py.typed +0 -0
  24. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/relation_configs/__init__.py +0 -0
  25. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/relation_configs/index.py +0 -0
  26. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/relation_configs/policies.py +0 -0
  27. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_auth.py +0 -0
  28. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_backend.py +0 -0
  29. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_column.py +0 -0
  30. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_configs.py +0 -0
  31. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_connections.py +0 -0
  32. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_constants.py +0 -0
  33. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_credentials.py +0 -0
  34. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_helpers.py +0 -0
  35. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_mask.py +0 -0
  36. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_relation.py +0 -0
  37. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/adapters/sqlserver/sqlserver_runtime.py +0 -0
  38. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/__init__.py +0 -0
  39. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/dbt_project.yml +0 -0
  40. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/apply_grants.sql +0 -0
  41. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/apply_masks.sql +0 -0
  42. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/catalog.sql +0 -0
  43. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/indexes.sql +0 -0
  44. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/persist_docs.sql +0 -0
  45. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/relation.sql +0 -0
  46. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/show.sql +0 -0
  47. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/adapters/validate_sql.sql +0 -0
  48. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/functions/helpers.sql +0 -0
  49. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/functions/scalar.sql +0 -0
  50. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/hooks.sql +0 -0
  51. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/incremental/incremental.sql +0 -0
  52. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/incremental/incremental_strategies.sql +0 -0
  53. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/incremental/merge.sql +0 -0
  54. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/table/columns_spec_ddl.sql +0 -0
  55. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/table/table.sql +0 -0
  56. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql +0 -0
  57. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/unit_test/get_fixture_sql.sql +0 -0
  58. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/unit_test/unit_test_create_table_as.sql +0 -0
  59. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/models/view/create_view_as.sql +0 -0
  60. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/snapshots/snapshot.sql +0 -0
  61. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/materializations/snapshots/snapshot_merge.sql +0 -0
  62. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/relations/seeds/helpers.sql +0 -0
  63. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/relations/table/clone.sql +0 -0
  64. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/relations/views/create.sql +0 -0
  65. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/any_value.sql +0 -0
  66. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/array_construct.sql +0 -0
  67. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/cast_bool_to_text.sql +0 -0
  68. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/concat.sql +0 -0
  69. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/date_trunc.sql +0 -0
  70. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/dateadd.sql +0 -0
  71. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/get_tables_by_pattern.sql +0 -0
  72. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/hash.sql +0 -0
  73. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/last_day.sql +0 -0
  74. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/length.sql +0 -0
  75. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/listagg.sql +0 -0
  76. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/position.sql +0 -0
  77. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/safe_cast.sql +0 -0
  78. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/split_part.sql +0 -0
  79. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/macros/utils/timestamps.sql +0 -0
  80. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt/include/sqlserver/profile_template.yml +0 -0
  81. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt_sqlserver.egg-info/dependency_links.txt +0 -0
  82. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt_sqlserver.egg-info/requires.txt +0 -0
  83. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/dbt_sqlserver.egg-info/top_level.txt +0 -0
  84. {dbt_sqlserver-1.11.2rc1 → dbt_sqlserver-1.11.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbt-sqlserver
3
- Version: 1.11.2rc1
3
+ Version: 1.11.3
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
@@ -157,7 +157,7 @@ your_profile:
157
157
 
158
158
  ## Changelog
159
159
 
160
- See [the changelog](CHANGELOG.md)
160
+ See [the changelog](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/CHANGELOG.md)
161
161
 
162
162
  ## Configuration
163
163
 
@@ -204,7 +204,7 @@ Safe expansions are further gated by `column_type_expansion_max_rows` (default 1
204
204
 
205
205
  ### `dbt_sqlserver_use_dbt_transactions`
206
206
 
207
- _(default: `false`)_ When enabled, makes dbt's transaction hooks real at the SQL Server level by emitting `BEGIN TRANSACTION` / `COMMIT TRANSACTION` through the adapter's `add_begin_query` and `add_commit_query` methods.
207
+ _(default: `false`)_ When enabled, makes dbt's transaction hooks real at the SQL Server level by emitting `BEGIN TRANSACTION` / `COMMIT TRANSACTION` through the adapter's `add_begin_query` and `add_commit_query` methods.
208
208
 
209
209
  The default is `false`, preserving existing behavior where `begin`/`commit` hooks are logical no-ops and the ODBC driver auto-commits each statement. When `dbt_sqlserver_use_dbt_transactions: true`, the adapter emits real T-SQL transaction statements, and rollback uses `IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION`.
210
210
 
@@ -212,10 +212,11 @@ The driver connection remains in autocommit mode (`autocommit=true`) in both mod
212
212
 
213
213
  This mode is opt-in and should be tested carefully with project-specific materializations and hooks.
214
214
 
215
+ **Compatibility notes:** Enabling `dbt_sqlserver_use_dbt_transactions: true` may expose transaction-state assumptions hidden by autocommit-only mode. Explicit transaction macros may interact with dbt-managed transactions, and cleanup after failed DDL/DML may differ. Review pre/post hooks for in-transaction vs out-of-transaction semantics.
216
+
215
217
  ```yaml
216
218
  # dbt_project.yml
217
219
  flags:
218
- dbt_sqlserver_enable_safe_type_expansion: true
219
220
  dbt_sqlserver_use_dbt_transactions: true # <-- opt-in; default is false
220
221
  ```
221
222
 
@@ -258,8 +259,6 @@ your_profile:
258
259
  prefer_single_alter_column=true) }}
259
260
  ```
260
261
 
261
- **Compatibility notes:** Enabling `dbt_sqlserver_use_dbt_transactions: true` may expose transaction-state assumptions hidden by autocommit-only mode. Explicit transaction macros may interact with dbt-managed transactions, and cleanup after failed DDL/DML may differ. Review pre/post hooks for in-transaction vs out-of-transaction semantics.
262
-
263
262
  ### `as_columnstore`
264
263
 
265
264
  *(default: `true`)* When building a table, the adapter creates a [clustered columnstore index](https://learn.microsoft.com/en-us/sql/relational-databases/indexes/columnstore-indexes-overview) (CCI) on it. Set `as_columnstore: false` to build a plain rowstore table instead.
@@ -297,7 +296,7 @@ A *model-level* constraint carrying `name:` is applied by `ALTER TABLE … ADD C
297
296
 
298
297
  Name a constraint when you want it stable across environments (schema-comparison tools report the generated names as differences) or need to reference it later. A `name:` on a *column-level* constraint is ignored with a warning — declare it under the model's `constraints:` key instead.
299
298
 
300
- The full set of rules, with the test that verifies each, is in [docs/constraints.md](docs/constraints.md).
299
+ The full set of rules, with the test that verifies each, is in [docs/constraints.md](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/docs/constraints.md).
301
300
 
302
301
  ```yaml
303
302
  models:
@@ -439,7 +438,7 @@ Behaviour:
439
438
 
440
439
  This adapter is community-maintained.
441
440
  You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in the Slack channel.
442
- If you're unsure how to get started, check out our [contributing guide](CONTRIBUTING.md).
441
+ If you're unsure how to get started, check out our [contributing guide](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/CONTRIBUTING.md).
443
442
 
444
443
  ## License
445
444
 
@@ -120,7 +120,7 @@ your_profile:
120
120
 
121
121
  ## Changelog
122
122
 
123
- See [the changelog](CHANGELOG.md)
123
+ See [the changelog](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/CHANGELOG.md)
124
124
 
125
125
  ## Configuration
126
126
 
@@ -167,7 +167,7 @@ Safe expansions are further gated by `column_type_expansion_max_rows` (default 1
167
167
 
168
168
  ### `dbt_sqlserver_use_dbt_transactions`
169
169
 
170
- _(default: `false`)_ When enabled, makes dbt's transaction hooks real at the SQL Server level by emitting `BEGIN TRANSACTION` / `COMMIT TRANSACTION` through the adapter's `add_begin_query` and `add_commit_query` methods.
170
+ _(default: `false`)_ When enabled, makes dbt's transaction hooks real at the SQL Server level by emitting `BEGIN TRANSACTION` / `COMMIT TRANSACTION` through the adapter's `add_begin_query` and `add_commit_query` methods.
171
171
 
172
172
  The default is `false`, preserving existing behavior where `begin`/`commit` hooks are logical no-ops and the ODBC driver auto-commits each statement. When `dbt_sqlserver_use_dbt_transactions: true`, the adapter emits real T-SQL transaction statements, and rollback uses `IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION`.
173
173
 
@@ -175,10 +175,11 @@ The driver connection remains in autocommit mode (`autocommit=true`) in both mod
175
175
 
176
176
  This mode is opt-in and should be tested carefully with project-specific materializations and hooks.
177
177
 
178
+ **Compatibility notes:** Enabling `dbt_sqlserver_use_dbt_transactions: true` may expose transaction-state assumptions hidden by autocommit-only mode. Explicit transaction macros may interact with dbt-managed transactions, and cleanup after failed DDL/DML may differ. Review pre/post hooks for in-transaction vs out-of-transaction semantics.
179
+
178
180
  ```yaml
179
181
  # dbt_project.yml
180
182
  flags:
181
- dbt_sqlserver_enable_safe_type_expansion: true
182
183
  dbt_sqlserver_use_dbt_transactions: true # <-- opt-in; default is false
183
184
  ```
184
185
 
@@ -221,8 +222,6 @@ your_profile:
221
222
  prefer_single_alter_column=true) }}
222
223
  ```
223
224
 
224
- **Compatibility notes:** Enabling `dbt_sqlserver_use_dbt_transactions: true` may expose transaction-state assumptions hidden by autocommit-only mode. Explicit transaction macros may interact with dbt-managed transactions, and cleanup after failed DDL/DML may differ. Review pre/post hooks for in-transaction vs out-of-transaction semantics.
225
-
226
225
  ### `as_columnstore`
227
226
 
228
227
  *(default: `true`)* When building a table, the adapter creates a [clustered columnstore index](https://learn.microsoft.com/en-us/sql/relational-databases/indexes/columnstore-indexes-overview) (CCI) on it. Set `as_columnstore: false` to build a plain rowstore table instead.
@@ -260,7 +259,7 @@ A *model-level* constraint carrying `name:` is applied by `ALTER TABLE … ADD C
260
259
 
261
260
  Name a constraint when you want it stable across environments (schema-comparison tools report the generated names as differences) or need to reference it later. A `name:` on a *column-level* constraint is ignored with a warning — declare it under the model's `constraints:` key instead.
262
261
 
263
- The full set of rules, with the test that verifies each, is in [docs/constraints.md](docs/constraints.md).
262
+ The full set of rules, with the test that verifies each, is in [docs/constraints.md](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/docs/constraints.md).
264
263
 
265
264
  ```yaml
266
265
  models:
@@ -402,7 +401,7 @@ Behaviour:
402
401
 
403
402
  This adapter is community-maintained.
404
403
  You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in the Slack channel.
405
- If you're unsure how to get started, check out our [contributing guide](CONTRIBUTING.md).
404
+ If you're unsure how to get started, check out our [contributing guide](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/CONTRIBUTING.md).
406
405
 
407
406
  ## License
408
407
 
@@ -0,0 +1 @@
1
+ version = "1.11.3"
@@ -1,5 +1,6 @@
1
1
  import re
2
- from typing import Any, Dict, List, Optional
2
+ from contextlib import contextmanager
3
+ from typing import Any, Dict, Iterator, List, Optional
3
4
 
4
5
  import agate
5
6
  import dbt_common.exceptions
@@ -56,7 +57,7 @@ _KEYED_CONSTRAINTS = frozenset(
56
57
  # full just to read its column names.
57
58
  _SQL_COMMENT = re.compile(r"(?s)/\*.*?\*/|--[^\n]*\n")
58
59
 
59
- # sp_describe_first_result_set reports true SQL Server types; reading
60
+ # The describe reports true SQL Server types; reading
60
61
  # ``cursor.description`` reports Python classes, which collapse whole families
61
62
  # (every integer width arrives as ``int``, every string type as ``varchar``).
62
63
  # Contract comparison comes through this method either way, so the describe
@@ -184,6 +185,13 @@ class SQLServerAdapter(SQLAdapter):
184
185
  SQLServerConnectionManager._dbt_sqlserver_use_dbt_transactions = use_dbt_transactions
185
186
  self.connections._dbt_sqlserver_use_dbt_transactions = use_dbt_transactions
186
187
 
188
+ @SQLAdapter.behavior.setter
189
+ def behavior(self, flags: List[BehaviorFlag]) -> None:
190
+ # The base setter extends `flags` in place, and it is passed the module-level
191
+ # DEFAULT_BASE_BEHAVIOR_FLAGS, which then grows on every adapter init (#855).
192
+ # Drop once dbt-adapters stops mutating it.
193
+ SQLAdapter.behavior.fset(self, list(flags))
194
+
187
195
  @property
188
196
  def _behavior_flags(self) -> List[BehaviorFlag]:
189
197
  return [
@@ -282,34 +290,42 @@ class SQLServerAdapter(SQLAdapter):
282
290
  def _describe_result_set(self, sql: str) -> Optional[List[BaseColumn]]:
283
291
  """Read a query's column shape without running it, or None to fall back.
284
292
 
285
- ``sp_describe_first_result_set`` compiles the query and reports its
286
- result shape, which is all this method ever wanted. It is already how
287
- ``sqlserver__get_columns_in_query`` handles CTEs (#698).
293
+ ``sys.dm_exec_describe_first_result_set`` compiles the query and
294
+ reports its result shape, which is all this method ever wanted. It is
295
+ the table-valued form of the ``sp_describe_first_result_set`` that
296
+ ``sqlserver__get_columns_in_query`` uses for CTEs (#698).
288
297
 
289
298
  Returns None -- deliberately, rather than raising -- whenever the
290
299
  describe cannot be trusted to match what executing would have reported:
291
- a query it refuses to describe (it cannot see through ``#temp`` tables,
292
- where executing works), or a type this backend's driver has no known
293
- executed name for. The caller then executes as before, which is
300
+ a query the describe fails on, or a type this backend's driver has no
301
+ known executed name for. The caller then executes as before, which is
294
302
  slower but never disagrees with itself.
303
+
304
+ A query that fails to compile is no exception: executing it fails at
305
+ compile too, costing nothing, and raises the query's own error --
306
+ Msg 207 naming the missing column -- through the normal path.
295
307
  """
296
308
  credentials = self.connections.profile.credentials
297
309
 
310
+ # The function rather than ``exec sp_describe_first_result_set``: the
311
+ # procedure raises whatever stops it describing, and handling a raised
312
+ # error closes the connection, so the fallback executed on a closed
313
+ # handle and reported that instead. The function hands the same
314
+ # failures back as rows with an error_number, which leaves the
315
+ # connection alone and needs no parsing of driver message text (which
316
+ # mssql-python strips of the error number anyway).
317
+ #
298
318
  # Inline rather than bound: mssql-python binds str as varchar and the
299
- # procedure demands nvarchar(max). columns.sql:24 escapes it the same
319
+ # function demands nvarchar(max). columns.sql:24 escapes it the same
300
320
  # way for the same reason.
301
- describe_sql = "exec sp_describe_first_result_set @tsql = N'{}'".format(
302
- sql.replace("'", "''")
303
- )
321
+ describe_sql = (
322
+ "select is_hidden, name, system_type_name, error_number, error_message"
323
+ " from sys.dm_exec_describe_first_result_set(N'{}', null, 0)"
324
+ " order by column_ordinal"
325
+ ).format(sql.replace("'", "''"))
304
326
 
327
+ _, cursor = self.connections.add_select_query(describe_sql)
305
328
  try:
306
- _, cursor = self.connections.add_select_query(describe_sql)
307
- except Exception as e:
308
- logger.debug(f"Could not describe a CTE query, falling back to executing it: {e}")
309
- return None
310
-
311
- try:
312
- fields = [description[0].lower() for description in cursor.description]
313
329
  rows = cursor.fetchall()
314
330
  except Exception as e:
315
331
  logger.debug(f"Could not read a described result set, executing the query: {e}")
@@ -317,32 +333,35 @@ class SQLServerAdapter(SQLAdapter):
317
333
  finally:
318
334
  _discard_pending_results(cursor)
319
335
 
320
- try:
321
- hidden, name, type_name = (
322
- fields.index("is_hidden"),
323
- fields.index("name"),
324
- fields.index("system_type_name"),
336
+ errors = [
337
+ f"Msg {error_number}: {message}" for *_, error_number, message in rows if error_number
338
+ ]
339
+ if errors:
340
+ # Nothing to tell apart here. A query the describe declines -- one
341
+ # under SET STATISTICS XML, say -- executes fine; one that does
342
+ # not compile fails executing too, with its own message.
343
+ logger.debug(
344
+ f"Could not describe a CTE query, executing it instead: {'; '.join(errors)}"
325
345
  )
326
- except ValueError: # pragma: no cover - shape is fixed by SQL Server
327
346
  return None
328
347
 
329
348
  columns = []
330
- for row in rows:
331
- if row[hidden]:
349
+ for hidden, name, type_name, *_ in rows:
350
+ if hidden:
332
351
  continue
333
352
  # "varchar(10)" / "decimal(10,2)" -> "varchar" / "decimal"
334
- base_type = str(row[type_name]).split("(")[0].strip().lower()
353
+ base_type = str(type_name).split("(")[0].strip().lower()
335
354
  executed_name = _executed_name_for_system_type(base_type, credentials.backend)
336
- if executed_name is None or row[name] is None:
355
+ if executed_name is None or name is None:
337
356
  logger.debug(
338
357
  f"Describing a CTE query reported {base_type!r}, which has no "
339
358
  "equivalent in the executed path; executing it instead"
340
359
  )
341
360
  return None
342
- columns.append(self.Column.create(row[name], executed_name))
361
+ columns.append(self.Column.create(name, executed_name))
343
362
 
344
363
  # Every select has at least one column, so nothing described means
345
- # sp_describe_first_result_set could not work the shape out. Returning
364
+ # the describe could not work the shape out. Returning
346
365
  # an empty list would read as "this query has no columns" and surface
347
366
  # as a baffling contract mismatch; execute instead.
348
367
  return columns or None
@@ -357,8 +376,15 @@ class SQLServerAdapter(SQLAdapter):
357
376
 
358
377
  @classmethod
359
378
  def convert_number_type(cls, agate_table, col_idx):
360
- decimals = agate_table.aggregate(agate.MaxPrecision(col_idx))
361
- return "float" if decimals else "int"
379
+ if agate_table.aggregate(agate.MaxPrecision(col_idx)):
380
+ return "float"
381
+ values = agate_table.columns[col_idx].values_without_nulls()
382
+ low, high = (min(values), max(values)) if values else (0, 0)
383
+ if -(2**31) <= low and high < 2**31:
384
+ return "int"
385
+ if -(2**63) <= low and high < 2**63:
386
+ return "bigint"
387
+ return "numeric(38,0)"
362
388
 
363
389
  def create_schema(self, relation: BaseRelation) -> None:
364
390
  relation = relation.without_identifier()
@@ -381,6 +407,8 @@ class SQLServerAdapter(SQLAdapter):
381
407
  # see https://github.com/fishtown-analytics/dbt/pull/2255
382
408
  lens = [len(d.encode("utf-8")) for d in column.values_without_nulls()]
383
409
  max_len = max(lens) if lens else 64
410
+ if max_len > 8000:
411
+ return "varchar(max)"
384
412
  length = max_len if max_len > 16 else 16
385
413
  return "varchar({})".format(length)
386
414
 
@@ -804,6 +832,28 @@ class SQLServerAdapter(SQLAdapter):
804
832
  return False
805
833
  return create_needs_own_batch(parsed.build_options)
806
834
 
835
+ # dbt-core's run-operation runs on connections with these names.
836
+ _RUN_OPERATION_CONNECTION_PREFIXES = ("macro_", "inline_query")
837
+
838
+ @contextmanager
839
+ def connection_named(
840
+ self, name: str, query_header_context: Any = None, should_release_connection: bool = True
841
+ ) -> Iterator[None]:
842
+ """Commit a run-operation's open transaction when it finishes cleanly.
843
+
844
+ Stopgap for dbt-labs/dbt#16434, remove once dbt-core commits after a
845
+ run-operation: dbt-core never does, so with dbt-managed transactions
846
+ on, a write through ``statement()`` (auto_begin) is rolled back when
847
+ the connection closes. The commit sits after the ``yield`` so it runs
848
+ only when the macro did not raise; a failed macro still rolls back.
849
+ Matching on the connection name is a heuristic, since dbt-core marks
850
+ run-operation connections no other way.
851
+ """
852
+ with super().connection_named(name, query_header_context, should_release_connection):
853
+ yield
854
+ if name.startswith(self._RUN_OPERATION_CONNECTION_PREFIXES):
855
+ self.commit_if_open()
856
+
807
857
  @available
808
858
  def commit_if_open(self) -> None:
809
859
  """Commit the current transaction if one is open - a no-op otherwise.
@@ -40,10 +40,17 @@
40
40
 
41
41
  {% set prefer_single = config.get('prefer_single_alter_column', false) %}
42
42
 
43
+ {#-- ALTER COLUMN without NOT NULL makes the column nullable --#}
44
+ {% set nullable_sql %}
45
+ {{ get_use_database_sql(relation.database) }}
46
+ select columnproperty(object_id('{{ escape_single_quotes(relation) }}'), '{{ escape_single_quotes(column_name) }}', 'AllowsNull')
47
+ {%- endset %}
48
+ {%- set not_null = run_query(nullable_sql).columns[0].values()[0] == 0 -%}
49
+
43
50
  {% if prefer_single and relation.type == 'table' %}
44
51
  {% set alter_sql %}
45
52
  alter {{ relation.type }} {{ relation }}
46
- alter column "{{ column_name }}" {{ new_column_type }};
53
+ alter column "{{ column_name }}" {{ new_column_type }}{{ ' not null' if not_null }};
47
54
  {%- endset %}
48
55
  {% do run_query(alter_sql) %}
49
56
 
@@ -64,11 +71,18 @@
64
71
  {% set rename_column %}
65
72
  exec sp_rename '{{ relation | replace('"', '') }}.{{ tmp_column }}', '{{ column_name }}', 'column'
66
73
  {%- endset %}
74
+ {% set alter_sql_not_null %}
75
+ alter {{ relation.type }} {{ relation }}
76
+ alter column "{{ column_name }}" {{ new_column_type }} not null;
77
+ {%- endset %}
67
78
 
68
79
  {% do run_query(add_column) %}
69
80
  {% do run_query(update_column) %}
70
81
  {% do run_query(drop_column) %}
71
82
  {% do run_query(rename_column) %}
83
+ {% if not_null %}
84
+ {% do run_query(alter_sql_not_null) %}
85
+ {% endif %}
72
86
  {% endif %}
73
87
 
74
88
  {% endmacro %}
@@ -187,6 +187,55 @@
187
187
  where object_id({{ object_name }}, 'V') is not null
188
188
  {% endmacro %}
189
189
 
190
+ {% macro get_view_skip_check_sql(relation, compiled_sql) %}
191
+ {{ return(adapter.dispatch('get_view_skip_check_sql')(relation, compiled_sql)) }}
192
+ {% endmacro %}
193
+
194
+ {#- `definition` for the skip decision, and `needs_refresh` when the cached column
195
+ metadata no longer matches what the body resolves to. Refreshing unconditionally
196
+ would advance modify_date on every unchanged view.
197
+ - dm_exec_describe_first_result_set is referenced once: each reference compiles the body.
198
+ - Its error row is kept, so a body it cannot describe is refreshed.
199
+ - COLLATE DATABASE_DEFAULT, or the join fails where catalog and database collations differ.
200
+ - The body is inlined: the function needs nvarchar and mssql-python binds str as varchar. -#}
201
+ {% macro sqlserver__get_view_skip_check_sql(relation, compiled_sql) -%}
202
+ {%- set object_name = "quotename('" ~ relation.schema ~ "') + '.' + quotename('" ~ relation.identifier ~ "')" -%}
203
+ {{ get_use_database_sql(relation.database) }}
204
+ declare @dbt_sqlserver_skip_check_body nvarchar(max) = N'{{ escape_single_quotes(compiled_sql) }}';
205
+ select
206
+ object_definition(object_id({{ object_name }}, 'V')) as definition,
207
+ case when exists (
208
+ select 1
209
+ from (
210
+ select column_ordinal, name, system_type_id, user_type_id,
211
+ max_length, precision, scale, collation_name
212
+ from sys.dm_exec_describe_first_result_set(
213
+ @dbt_sqlserver_skip_check_body, null, 0)
214
+ where is_hidden = 0 or error_number is not null
215
+ ) as described
216
+ full outer join (
217
+ select row_number() over (order by column_id) as column_ordinal,
218
+ name, system_type_id, user_type_id,
219
+ max_length, precision, scale, collation_name
220
+ from sys.columns {{ information_schema_hints() }}
221
+ where object_id = object_id({{ object_name }}, 'V')
222
+ ) as cached
223
+ on described.column_ordinal = cached.column_ordinal
224
+ where described.column_ordinal is null
225
+ or cached.column_ordinal is null
226
+ or described.name collate database_default
227
+ <> cached.name collate database_default
228
+ or described.system_type_id <> cached.system_type_id
229
+ or described.user_type_id <> cached.user_type_id
230
+ or described.max_length <> cached.max_length
231
+ or described.precision <> cached.precision
232
+ or described.scale <> cached.scale
233
+ or isnull(described.collation_name, '') collate database_default
234
+ <> isnull(cached.collation_name, '') collate database_default
235
+ ) then 1 else 0 end as needs_refresh
236
+ where object_id({{ object_name }}, 'V') is not null
237
+ {% endmacro %}
238
+
190
239
  {% macro sqlserver__get_relation_last_modified(information_schema, relations) -%}
191
240
  {%- call statement('last_modified', fetch_result=True) -%}
192
241
  select
@@ -1,20 +1,45 @@
1
- {% macro sqlserver__create_schema(relation) -%}
2
- {% call statement('create_schema') -%}
3
- USE [{{ relation.database }}];
4
- IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{{ relation.schema }}')
1
+ {#
2
+ SQL Server has no `CREATE SCHEMA IF NOT EXISTS`, and check-then-act races under
3
+ concurrency (Msg 2714), so an applock serializes the check and the create.
4
+ The lock is session-scoped: a transaction-scoped one would be held until dbt
5
+ commits, after the test query in `get_test_sql`. `XACT_ABORT` skips a trailing
6
+ release when the create fails, so the `CATCH` releases and rethrows. A timed-out
7
+ request falls through to the bare check.
8
+ #}
9
+ {% macro create_schema_if_not_exists(schema, authorization=none) -%}
10
+ {%- set lock_resource = 'dbt_create_schema_' ~ schema -%}
11
+ DECLARE @dbt_schema_lock int;
12
+ EXEC @dbt_schema_lock = sp_getapplock
13
+ @Resource = '{{ escape_single_quotes(lock_resource) }}',
14
+ @LockMode = 'Exclusive',
15
+ @LockOwner = 'Session',
16
+ @LockTimeout = 30000;
17
+ BEGIN TRY
18
+ IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{{ escape_single_quotes(schema) }}')
5
19
  BEGIN
6
- EXEC('CREATE SCHEMA [{{ relation.schema }}]')
20
+ EXEC('CREATE SCHEMA {{ escape_single_quotes(adapter.quote(schema)) }}{% if authorization is not none %} AUTHORIZATION {{ escape_single_quotes(adapter.quote(authorization)) }}{% endif %}')
7
21
  END
22
+ END TRY
23
+ BEGIN CATCH
24
+ IF @dbt_schema_lock >= 0
25
+ EXEC sp_releaseapplock @Resource = '{{ escape_single_quotes(lock_resource) }}', @LockOwner = 'Session';
26
+ THROW;
27
+ END CATCH
28
+ IF @dbt_schema_lock >= 0
29
+ EXEC sp_releaseapplock @Resource = '{{ escape_single_quotes(lock_resource) }}', @LockOwner = 'Session';
30
+ {%- endmacro %}
31
+
32
+ {% macro sqlserver__create_schema(relation) -%}
33
+ {% call statement('create_schema') -%}
34
+ {{ get_use_database_sql(relation.database) }}
35
+ {{ create_schema_if_not_exists(relation.schema) }}
8
36
  {% endcall %}
9
37
  {% endmacro %}
10
38
 
11
39
  {% macro sqlserver__create_schema_with_authorization(relation, schema_authorization) -%}
12
40
  {% call statement('create_schema') -%}
13
41
  {{ get_use_database_sql(relation.database) }}
14
- IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{{ relation.schema }}')
15
- BEGIN
16
- EXEC('CREATE SCHEMA [{{ relation.schema }}] AUTHORIZATION [{{ schema_authorization }}]')
17
- END
42
+ {{ create_schema_if_not_exists(relation.schema, schema_authorization) }}
18
43
  {% endcall %}
19
44
  {% endmacro %}
20
45
 
@@ -41,7 +41,8 @@
41
41
  {% endfor %}
42
42
  {% set build_sql = get_create_view_as_sql(intermediate_relation, sql) %}
43
43
  {% elif existing_relation is not none and existing_relation.type == 'view' %}
44
- {% set current_view_definition_table = run_query(get_view_definition_sql(existing_relation)) %}
44
+ {% set current_view_definition_table = run_query(get_view_skip_check_sql(existing_relation, sql)) %}
45
+ {% set view_metadata_is_stale = false %}
45
46
  {% if current_view_definition_table is not none and current_view_definition_table.rows | length > 0 %}
46
47
  {#- Compare the view *body* exactly, not by suffix. The stored definition is
47
48
  the whole statement (CREATE [OR ALTER] VIEW <name> AS <body>); the model is
@@ -67,23 +68,15 @@
67
68
  {% set model_body = (model_body[:-1] if model_body.endswith(';') else model_body) | trim %}
68
69
  {% set should_skip_view_update = stored_body == model_body %}
69
70
  {% endif %}
71
+ {% set view_metadata_is_stale = current_view_definition_table.rows[0][1] == 1 %}
70
72
  {% endif %}
71
- {% if should_skip_view_update %}
72
- {#- The view's SQL text is unchanged, so the CREATE/ALTER is skipped -
73
- but a *referenced* table can still have changed shape (columns
74
- added, dropped, or reordered) since this view was last built. SQL
75
- Server resolves an unqualified `select *` at CREATE/ALTER time and
76
- caches the result; skipping that statement here means the cached
77
- column list silently goes stale relative to the underlying table,
78
- even though this view's own definition never changed. sp_refreshview
79
- re-derives that cached metadata from the table's current shape
80
- without re-running the CREATE, so a skip stays a skip (no DDL, no
81
- grant/deny churn) while the view keeps reporting the right columns. -#}
82
- {#- sp_refreshview resolves its argument in the *current* database, so this needs
83
- the same USE prefix every other name-resolving statement here carries -
84
- without it a cross-database view model would refresh nothing and error. -#}
73
+ {% if should_skip_view_update and view_metadata_is_stale %}
74
+ {#- A source changed shape under an unchanged body (e.g. a `select *` expansion).
75
+ sp_refreshview resolves its argument in the current database, hence the USE. -#}
85
76
  {% set object_name = "quotename('" ~ target_relation.schema ~ "') + '.' + quotename('" ~ target_relation.identifier ~ "')" %}
86
77
  {% set build_sql = get_use_database_sql(target_relation.database) ~ " declare @dbt_sqlserver_refresh_target nvarchar(max) = " ~ object_name ~ "; exec sp_refreshview @dbt_sqlserver_refresh_target;" %}
78
+ {% elif should_skip_view_update %}
79
+ {% set build_sql = 'declare @dbt_sqlserver_noop int;' %}
87
80
  {% else %}
88
81
  {% set build_sql = get_create_view_as_sql(target_relation, sql) %}
89
82
  {% endif %}
@@ -1,7 +1,7 @@
1
1
  {% macro sqlserver__create_columns(relation, columns) %}
2
2
  {% set column_list %}
3
3
  {% for column_entry in columns %}
4
- {{column_entry.name}} {{column_entry.data_type}}{{ ", " if not loop.last }}
4
+ {{ adapter.quote(column_entry.name) }} {{column_entry.data_type}}{{ ", " if not loop.last }}
5
5
  {% endfor %}
6
6
  {% endset %}
7
7
 
@@ -0,0 +1,65 @@
1
+ {% macro sqlserver__snapshot_hash_arguments(args) %}
2
+ CONVERT(VARCHAR(32), HashBytes('MD5', {% for arg in args %}
3
+ coalesce(cast({{ arg }} as varchar(8000)), '') {% if not loop.last %} + '|' + {% endif %}
4
+ {% endfor %}), 2)
5
+ {% endmacro %}
6
+
7
+
8
+ {#
9
+ dbt-adapters does not dispatch "snapshot_check_all_get_existing_columns", so
10
+ this shadows the global project's macro; adapter package macros take
11
+ precedence. Keep in sync with dbt-adapters when upgrading. When upstream
12
+ dispatches it, delete the public wrapper and keep the sqlserver__ handler.
13
+
14
+ Change from upstream: for a check_cols list, upstream reads the columns'
15
+ casing from "select <check_cols> from (<sql>) subq", and T-SQL can't nest a
16
+ WITH in a subquery. This reads the query's columns unwrapped, as the 'all'
17
+ branch does, and picks check_cols from them case-insensitively.
18
+ #}
19
+
20
+ {% macro snapshot_check_all_get_existing_columns(node, target_exists, check_cols_config) -%}
21
+ {{ return(sqlserver__snapshot_check_all_get_existing_columns(node, target_exists, check_cols_config)) }}
22
+ {%- endmacro %}
23
+
24
+
25
+ {% macro sqlserver__snapshot_check_all_get_existing_columns(node, target_exists, check_cols_config) -%}
26
+ {%- if not target_exists -%}
27
+ {#-- no table yet -> return whatever the query does --#}
28
+ {{ return((false, query_columns)) }}
29
+ {%- endif -%}
30
+
31
+ {#-- handle any schema changes --#}
32
+ {%- set target_relation = adapter.get_relation(database=node.database, schema=node.schema, identifier=node.alias) -%}
33
+
34
+ {% if check_cols_config == 'all' %}
35
+ {%- set query_columns = get_columns_in_query(node['compiled_code']) -%}
36
+
37
+ {% elif check_cols_config is iterable and (check_cols_config | length) > 0 %}
38
+ {%- set all_columns = get_columns_in_query(node['compiled_code']) -%}
39
+ {%- set all_columns_lower = all_columns | map('lower') | list -%}
40
+ {%- set query_columns = [] -%}
41
+ {%- for check_col in check_cols_config -%}
42
+ {%- if (check_col | lower) not in all_columns_lower -%}
43
+ {% do exceptions.raise_compiler_error("check_cols column '" ~ check_col ~ "' is not in the snapshot query") %}
44
+ {%- endif -%}
45
+ {%- do query_columns.append(all_columns[all_columns_lower.index(check_col | lower)]) -%}
46
+ {%- endfor -%}
47
+
48
+ {% else %}
49
+ {% do exceptions.raise_compiler_error("Invalid value for 'check_cols': " ~ check_cols_config) %}
50
+ {% endif %}
51
+
52
+ {%- set existing_cols = adapter.get_columns_in_relation(target_relation) | map(attribute = 'name') | list -%}
53
+ {%- set ns = namespace() -%} {#-- handle for-loop scoping with a namespace --#}
54
+ {%- set ns.column_added = false -%}
55
+
56
+ {%- set intersection = [] -%}
57
+ {%- for col in query_columns -%}
58
+ {%- if col in existing_cols -%}
59
+ {%- do intersection.append(adapter.quote(col)) -%}
60
+ {%- else -%}
61
+ {% set ns.column_added = true %}
62
+ {%- endif -%}
63
+ {%- endfor -%}
64
+ {{ return((ns.column_added, intersection)) }}
65
+ {%- endmacro %}
@@ -1,11 +1,8 @@
1
1
  {% macro sqlserver__get_test_sql(main_sql, fail_calc, warn_if, error_if, limit) -%}
2
2
 
3
- -- Create target schema if it does not
4
- USE [{{ target.database }}];
5
- IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{{ target.schema }}')
6
- BEGIN
7
- EXEC('CREATE SCHEMA [{{ target.schema }}]')
8
- END
3
+ -- Create target schema if it does not exist
4
+ {{ get_use_database_sql(target.database) }}
5
+ {{ create_schema_if_not_exists(target.schema) }}
9
6
 
10
7
  {% set testview %}
11
8
  [{{ target.schema }}].[testview_{{ local_md5(main_sql) }}_{{ range(1300, 19000) | random }}]
@@ -1,10 +1,7 @@
1
1
  {% macro sqlserver__get_unit_test_sql(main_sql, expected_fixture_sql, expected_column_names) -%}
2
2
 
3
- USE [{{ target.database }}];
4
- IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{{ target.schema }}')
5
- BEGIN
6
- EXEC('CREATE SCHEMA [{{ target.schema }}]')
7
- END
3
+ {{ get_use_database_sql(target.database) }}
4
+ {{ create_schema_if_not_exists(target.schema) }}
8
5
 
9
6
  {% set test_view %}
10
7
  [{{ target.schema }}].[testview_{{ local_md5(main_sql) }}_{{ range(1300, 19000) | random }}]
@@ -209,13 +209,21 @@
209
209
  {#- mark a table as having a full refresh in flight; the marker rides the
210
210
  object, so a successful swap (old table dropped) or in-place rebuild
211
211
  (table dropped) clears it naturally -#}
212
+ {#- USE persists for the session, so without it both halves resolve
213
+ against wherever the last statement left the connection - and there
214
+ they disagree: OBJECT_ID returns NULL, so the guard runs the EXEC even
215
+ when the marker is present, and the proc rejects the same name as
216
+ absent (Msg 15135). -#}
212
217
  {% do run_query(
213
- "if not exists (select 1 from sys.extended_properties where major_id = OBJECT_ID('"
214
- ~ relation.schema ~ "." ~ relation.identifier
215
- ~ "') and name = 'dbt_full_refresh_incomplete')"
218
+ get_use_database_sql(relation.database)
219
+ ~ " if not exists (select 1 from sys.extended_properties"
220
+ ~ " where class = 1 and minor_id = 0"
221
+ ~ " and name = 'dbt_full_refresh_incomplete' and major_id = OBJECT_ID('"
222
+ ~ escape_single_quotes(relation.include(database=False))
223
+ ~ "'))"
216
224
  ~ " EXEC sp_addextendedproperty @name = N'dbt_full_refresh_incomplete', @value = '1',"
217
- ~ " @level0type = N'SCHEMA', @level0name = N'" ~ relation.schema ~ "',"
218
- ~ " @level1type = N'TABLE', @level1name = N'" ~ relation.identifier ~ "'"
225
+ ~ " @level0type = N'SCHEMA', @level0name = N'" ~ escape_single_quotes(relation.schema) ~ "',"
226
+ ~ " @level1type = N'TABLE', @level1name = N'" ~ escape_single_quotes(relation.identifier) ~ "'"
219
227
  ) %}
220
228
  {#- This marker exists to survive a failed rebuild, so it must not ride on
221
229
  the same transaction as the rebuild it's guarding: if a prior
@@ -234,10 +242,16 @@
234
242
 
235
243
 
236
244
  {% macro sqlserver__assert_no_incomplete_full_refresh(relation) -%}
245
+ {#- Needs the USE more than the marker does: off-database this returns 0
246
+ rather than erroring, which reads as "no marker" and lets the append
247
+ through. -#}
237
248
  {%- set marker = run_query(
238
- "select count(*) as marker_count from sys.extended_properties where major_id = OBJECT_ID('"
239
- ~ relation.schema ~ "." ~ relation.identifier
240
- ~ "') and name = 'dbt_full_refresh_incomplete'"
249
+ get_use_database_sql(relation.database)
250
+ ~ " select count(*) as marker_count from sys.extended_properties"
251
+ ~ " where class = 1 and minor_id = 0"
252
+ ~ " and name = 'dbt_full_refresh_incomplete' and major_id = OBJECT_ID('"
253
+ ~ escape_single_quotes(relation.include(database=False))
254
+ ~ "')"
241
255
  ) -%}
242
256
  {%- if marker.rows[0][0] > 0 -%}
243
257
  {{ exceptions.raise_compiler_error(
@@ -0,0 +1,3 @@
1
+ {% macro sqlserver__array_append(array, new_element) -%}
2
+ JSON_MODIFY({{ array }}, 'append $', {{ new_element }})
3
+ {%- endmacro %}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbt-sqlserver
3
- Version: 1.11.2rc1
3
+ Version: 1.11.3
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
@@ -157,7 +157,7 @@ your_profile:
157
157
 
158
158
  ## Changelog
159
159
 
160
- See [the changelog](CHANGELOG.md)
160
+ See [the changelog](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/CHANGELOG.md)
161
161
 
162
162
  ## Configuration
163
163
 
@@ -204,7 +204,7 @@ Safe expansions are further gated by `column_type_expansion_max_rows` (default 1
204
204
 
205
205
  ### `dbt_sqlserver_use_dbt_transactions`
206
206
 
207
- _(default: `false`)_ When enabled, makes dbt's transaction hooks real at the SQL Server level by emitting `BEGIN TRANSACTION` / `COMMIT TRANSACTION` through the adapter's `add_begin_query` and `add_commit_query` methods.
207
+ _(default: `false`)_ When enabled, makes dbt's transaction hooks real at the SQL Server level by emitting `BEGIN TRANSACTION` / `COMMIT TRANSACTION` through the adapter's `add_begin_query` and `add_commit_query` methods.
208
208
 
209
209
  The default is `false`, preserving existing behavior where `begin`/`commit` hooks are logical no-ops and the ODBC driver auto-commits each statement. When `dbt_sqlserver_use_dbt_transactions: true`, the adapter emits real T-SQL transaction statements, and rollback uses `IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION`.
210
210
 
@@ -212,10 +212,11 @@ The driver connection remains in autocommit mode (`autocommit=true`) in both mod
212
212
 
213
213
  This mode is opt-in and should be tested carefully with project-specific materializations and hooks.
214
214
 
215
+ **Compatibility notes:** Enabling `dbt_sqlserver_use_dbt_transactions: true` may expose transaction-state assumptions hidden by autocommit-only mode. Explicit transaction macros may interact with dbt-managed transactions, and cleanup after failed DDL/DML may differ. Review pre/post hooks for in-transaction vs out-of-transaction semantics.
216
+
215
217
  ```yaml
216
218
  # dbt_project.yml
217
219
  flags:
218
- dbt_sqlserver_enable_safe_type_expansion: true
219
220
  dbt_sqlserver_use_dbt_transactions: true # <-- opt-in; default is false
220
221
  ```
221
222
 
@@ -258,8 +259,6 @@ your_profile:
258
259
  prefer_single_alter_column=true) }}
259
260
  ```
260
261
 
261
- **Compatibility notes:** Enabling `dbt_sqlserver_use_dbt_transactions: true` may expose transaction-state assumptions hidden by autocommit-only mode. Explicit transaction macros may interact with dbt-managed transactions, and cleanup after failed DDL/DML may differ. Review pre/post hooks for in-transaction vs out-of-transaction semantics.
262
-
263
262
  ### `as_columnstore`
264
263
 
265
264
  *(default: `true`)* When building a table, the adapter creates a [clustered columnstore index](https://learn.microsoft.com/en-us/sql/relational-databases/indexes/columnstore-indexes-overview) (CCI) on it. Set `as_columnstore: false` to build a plain rowstore table instead.
@@ -297,7 +296,7 @@ A *model-level* constraint carrying `name:` is applied by `ALTER TABLE … ADD C
297
296
 
298
297
  Name a constraint when you want it stable across environments (schema-comparison tools report the generated names as differences) or need to reference it later. A `name:` on a *column-level* constraint is ignored with a warning — declare it under the model's `constraints:` key instead.
299
298
 
300
- The full set of rules, with the test that verifies each, is in [docs/constraints.md](docs/constraints.md).
299
+ The full set of rules, with the test that verifies each, is in [docs/constraints.md](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/docs/constraints.md).
301
300
 
302
301
  ```yaml
303
302
  models:
@@ -439,7 +438,7 @@ Behaviour:
439
438
 
440
439
  This adapter is community-maintained.
441
440
  You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in the Slack channel.
442
- If you're unsure how to get started, check out our [contributing guide](CONTRIBUTING.md).
441
+ If you're unsure how to get started, check out our [contributing guide](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/CONTRIBUTING.md).
443
442
 
444
443
  ## License
445
444
 
@@ -58,6 +58,7 @@ dbt/include/sqlserver/macros/relations/table/clone.sql
58
58
  dbt/include/sqlserver/macros/relations/table/create.sql
59
59
  dbt/include/sqlserver/macros/relations/views/create.sql
60
60
  dbt/include/sqlserver/macros/utils/any_value.sql
61
+ dbt/include/sqlserver/macros/utils/array_append.sql
61
62
  dbt/include/sqlserver/macros/utils/array_construct.sql
62
63
  dbt/include/sqlserver/macros/utils/cast_bool_to_text.sql
63
64
  dbt/include/sqlserver/macros/utils/concat.sql
@@ -68,6 +68,7 @@ dev = [
68
68
  "pytz",
69
69
  "ruff",
70
70
  "tox>=3.13",
71
+ "towncrier>=26.9.0",
71
72
  "twine",
72
73
  ]
73
74
 
@@ -94,6 +95,32 @@ link-mode = "copy"
94
95
  environments = "python_version >= '3.11'"
95
96
  override-dependencies = ["pathspec>=0.9,<1.1"] # Remove on dbt-core v1.12 or migration to ty by astral
96
97
 
98
+ [tool.towncrier]
99
+ directory = "changes"
100
+ filename = "CHANGELOG.md"
101
+ title_format = "### v{version}"
102
+ issue_format = "[#{issue}](https://github.com/dbt-msft/dbt-sqlserver/issues/{issue})"
103
+
104
+ [[tool.towncrier.type]]
105
+ directory = "behavior"
106
+ name = "Behavior changes"
107
+ showcontent = true
108
+
109
+ [[tool.towncrier.type]]
110
+ directory = "feature"
111
+ name = "Features"
112
+ showcontent = true
113
+
114
+ [[tool.towncrier.type]]
115
+ directory = "bugfix"
116
+ name = "Bugfixes"
117
+ showcontent = true
118
+
119
+ [[tool.towncrier.type]]
120
+ directory = "misc"
121
+ name = "Under the hood"
122
+ showcontent = true
123
+
97
124
  [tool.ruff]
98
125
  line-length = 99
99
126
  target-version = "py311"
@@ -1 +0,0 @@
1
- version = "1.11.2rc1"
@@ -1,5 +0,0 @@
1
- {% macro sqlserver__snapshot_hash_arguments(args) %}
2
- CONVERT(VARCHAR(32), HashBytes('MD5', {% for arg in args %}
3
- coalesce(cast({{ arg }} as varchar(8000)), '') {% if not loop.last %} + '|' + {% endif %}
4
- {% endfor %}), 2)
5
- {% endmacro %}