dbt-sqlserver 1.11.2rc2__tar.gz → 1.12.0__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 (93) hide show
  1. {dbt_sqlserver-1.11.2rc2/dbt_sqlserver.egg-info → dbt_sqlserver-1.12.0}/PKG-INFO +89 -27
  2. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/README.md +82 -21
  3. dbt_sqlserver-1.12.0/dbt/adapters/sqlserver/__version__.py +1 -0
  4. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/relation_configs/index.py +8 -2
  5. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_adapter.py +271 -68
  6. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_auth.py +8 -0
  7. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_backend.py +95 -1
  8. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_configs.py +10 -1
  9. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_connections.py +345 -8
  10. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_constants.py +5 -1
  11. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_credentials.py +2 -0
  12. dbt_sqlserver-1.12.0/dbt/adapters/sqlserver/sqlserver_deny.py +141 -0
  13. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_helpers.py +36 -0
  14. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_relation.py +10 -1
  15. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_runtime.py +78 -0
  16. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/adapters/apply_denies.sql +118 -0
  17. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/apply_grants.sql +3 -3
  18. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/apply_masks.sql +13 -5
  19. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/columns.sql +23 -4
  20. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/indexes.sql +49 -24
  21. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/metadata.sql +1 -1
  22. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/relation.sql +2 -1
  23. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/schema.sql +13 -7
  24. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/materializations/hooks.sql +58 -0
  25. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/materializations/models/incremental/incremental.sql +271 -0
  26. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/materializations/models/table/table.sql +259 -0
  27. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql +200 -0
  28. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/models/unit_test/unit_test_create_table_as.sql +1 -1
  29. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/models/view/view.sql +7 -0
  30. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/snapshots/helpers.sql +31 -16
  31. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/materializations/snapshots/snapshot.sql +207 -0
  32. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/materializations/snapshots/strategies.sql +65 -0
  33. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/tests.sql +2 -1
  34. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/unit_tests.sql +14 -2
  35. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/relations/table/create.sql +395 -0
  36. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/relations/views/create.sql +9 -1
  37. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/utils/array_append.sql +3 -0
  38. dbt_sqlserver-1.12.0/dbt/include/sqlserver/macros/utils/openquery.sql +21 -0
  39. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0/dbt_sqlserver.egg-info}/PKG-INFO +89 -27
  40. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt_sqlserver.egg-info/SOURCES.txt +4 -0
  41. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt_sqlserver.egg-info/requires.txt +6 -5
  42. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/pyproject.toml +29 -12
  43. dbt_sqlserver-1.11.2rc2/dbt/adapters/sqlserver/__version__.py +0 -1
  44. dbt_sqlserver-1.11.2rc2/dbt/include/sqlserver/macros/materializations/hooks.sql +0 -23
  45. dbt_sqlserver-1.11.2rc2/dbt/include/sqlserver/macros/materializations/models/incremental/incremental.sql +0 -200
  46. dbt_sqlserver-1.11.2rc2/dbt/include/sqlserver/macros/materializations/models/table/table.sql +0 -136
  47. dbt_sqlserver-1.11.2rc2/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql +0 -167
  48. dbt_sqlserver-1.11.2rc2/dbt/include/sqlserver/macros/materializations/snapshots/snapshot.sql +0 -135
  49. dbt_sqlserver-1.11.2rc2/dbt/include/sqlserver/macros/materializations/snapshots/strategies.sql +0 -5
  50. dbt_sqlserver-1.11.2rc2/dbt/include/sqlserver/macros/relations/table/create.sql +0 -278
  51. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/LICENSE +0 -0
  52. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/__init__.py +0 -0
  53. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/__init__.py +0 -0
  54. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/py.typed +0 -0
  55. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/relation_configs/__init__.py +0 -0
  56. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/relation_configs/policies.py +0 -0
  57. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_column.py +0 -0
  58. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/adapters/sqlserver/sqlserver_mask.py +0 -0
  59. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/__init__.py +0 -0
  60. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/dbt_project.yml +0 -0
  61. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/catalog.sql +0 -0
  62. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/persist_docs.sql +0 -0
  63. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/show.sql +0 -0
  64. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/adapters/validate_sql.sql +0 -0
  65. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/functions/helpers.sql +0 -0
  66. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/functions/scalar.sql +0 -0
  67. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/models/incremental/incremental_strategies.sql +0 -0
  68. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/models/incremental/merge.sql +0 -0
  69. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/models/table/columns_spec_ddl.sql +0 -0
  70. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/models/unit_test/get_fixture_sql.sql +0 -0
  71. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/models/view/create_view_as.sql +0 -0
  72. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/materializations/snapshots/snapshot_merge.sql +0 -0
  73. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/relations/seeds/helpers.sql +0 -0
  74. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/relations/table/clone.sql +0 -0
  75. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/any_value.sql +0 -0
  76. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/array_construct.sql +0 -0
  77. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/cast_bool_to_text.sql +0 -0
  78. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/concat.sql +0 -0
  79. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/date_trunc.sql +0 -0
  80. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/dateadd.sql +0 -0
  81. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/get_tables_by_pattern.sql +0 -0
  82. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/hash.sql +0 -0
  83. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/last_day.sql +0 -0
  84. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/length.sql +0 -0
  85. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/listagg.sql +0 -0
  86. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/position.sql +0 -0
  87. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/safe_cast.sql +0 -0
  88. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/split_part.sql +0 -0
  89. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/macros/utils/timestamps.sql +0 -0
  90. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt/include/sqlserver/profile_template.yml +0 -0
  91. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt_sqlserver.egg-info/dependency_links.txt +0 -0
  92. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/dbt_sqlserver.egg-info/top_level.txt +0 -0
  93. {dbt_sqlserver-1.11.2rc2 → dbt_sqlserver-1.12.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbt-sqlserver
3
- Version: 1.11.2rc2
3
+ Version: 1.12.0
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
@@ -18,29 +18,30 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Requires-Python: >=3.10
22
23
  Description-Content-Type: text/markdown
23
24
  License-File: LICENSE
24
- Requires-Dist: dbt-core<2.0,>=1.11.0
25
+ Requires-Dist: dbt-core<2.0,>=1.12.0
25
26
  Requires-Dist: dbt-common<2.0,>=1.22.0
26
- Requires-Dist: dbt-adapters<2.0,>=1.24.1
27
- Requires-Dist: pyodbc>=5.2.0
27
+ Requires-Dist: dbt-adapters<2.0,>=1.24.5
28
28
  Provides-Extra: azure
29
29
  Requires-Dist: azure-identity>=1.12.0; extra == "azure"
30
30
  Provides-Extra: pyodbc
31
31
  Requires-Dist: pyodbc>=5.2.0; extra == "pyodbc"
32
32
  Provides-Extra: mssql
33
33
  Requires-Dist: mssql-python>=1.7.1; extra == "mssql"
34
- Requires-Dist: azure-identity>=1.12.0; extra == "mssql"
35
- Requires-Dist: azure-core>=1.0.0; extra == "mssql"
34
+ Provides-Extra: adbc
35
+ Requires-Dist: adbc-driver-manager>=1.0.0; extra == "adbc"
36
+ Requires-Dist: pyarrow>=14.0.0; extra == "adbc"
36
37
  Dynamic: license-file
37
38
 
38
39
  # dbt-sqlserver
39
40
 
40
41
  [dbt](https://www.getdbt.com) adapter for Microsoft SQL Server and Azure SQL services.
41
42
 
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.
43
+ The adapter supports dbt-core 1.12 or newer and follows the same versioning scheme.
44
+ E.g. version 1.12.x of the adapter is compatible with dbt-core 1.12.x.
44
45
 
45
46
  ## Supported Python versions
46
47
 
@@ -52,6 +53,7 @@ The adapter is tested against:
52
53
  | 3.11 | Officially supported |
53
54
  | 3.12 | Officially supported |
54
55
  | 3.13 | Officially supported |
56
+ | 3.14 | Officially supported |
55
57
 
56
58
  ## Supported SQL Server versions
57
59
 
@@ -75,7 +77,7 @@ We've bundled all documentation on the dbt docs site:
75
77
  - [Profile setup & authentication](https://docs.getdbt.com/reference/warehouse-profiles/mssql-profile)
76
78
  - [Adapter documentation, usage and important notes](https://docs.getdbt.com/reference/resource-configs/mssql-configs)
77
79
 
78
- Join us on the [dbt Slack](https://getdbt.slack.com/archives/CMRMDDQ9W) to ask questions, get help, or to discuss the project.
80
+ Ask questions and get help in [GitHub Discussions](https://github.com/dbt-msft/dbt-sqlserver/discussions/categories/q-a). The [dbt Slack](https://getdbt.slack.com/archives/CMRMDDQ9W) channel is there for chatting with the community.
79
81
 
80
82
  ## Installation
81
83
 
@@ -91,6 +93,7 @@ Latest pre-release: ![GitHub tag (latest SemVer pre-release)](https://img.shield
91
93
  |---|---|---|
92
94
  | `pyodbc` | `dbt-sqlserver[pyodbc]` or `pyodbc` | `unixodbc-dev` plus the Microsoft ODBC Driver for SQL Server |
93
95
  | `mssql-python` | `dbt-sqlserver[mssql]` or `mssql-python` | `libltdl7`, `libkrb5-3`, `libgssapi-krb5-2` |
96
+ | `adbc` *(experimental)* | `dbt-sqlserver[adbc]` | none (driver binary installed separately via the `dbc` CLI) |
94
97
 
95
98
 
96
99
  ### `pyodbc` backend
@@ -155,31 +158,41 @@ your_profile:
155
158
  backend: mssql-python # <-- enables this backend
156
159
  ```
157
160
 
161
+ ### `adbc` backend *(experimental)*
162
+
163
+ An Arrow-native backend built on [ADBC](https://arrow.apache.org/adbc/), avoiding the row-based ODBC/DB-API bridge entirely. SQL Server authentication only (no Azure AD / Windows auth yet).
164
+
165
+ ```shell
166
+ pip install -U "dbt-sqlserver[adbc]"
167
+ ```
168
+
169
+ The driver binary is not on PyPI and must be installed once via the `dbc` CLI. See [docs/adbc_backend.md](https://github.com/dbt-msft/dbt-sqlserver/blob/master/docs/adbc_backend.md) for the full setup, configuration, and known-differences guide.
170
+
158
171
  ## Changelog
159
172
 
160
- See [the changelog](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/CHANGELOG.md)
173
+ See [the changelog](https://github.com/dbt-msft/dbt-sqlserver/blob/master/CHANGELOG.md)
161
174
 
162
175
  ## Configuration
163
176
 
164
177
  ### `dbt_sqlserver_use_default_schema_concat`
165
178
 
166
- *(default: `false`)* Controls schema name generation when a [custom schema](https://docs.getdbt.com/docs/build/custom-schemas) is set on a model.
179
+ *(default: `true`)* Controls schema name generation when a [custom schema](https://docs.getdbt.com/docs/build/custom-schemas) is set on a model.
167
180
 
168
181
  | Flag value | `custom_schema_name` | Result |
169
182
  |---|---|---|
170
- | `false` (default, legacy) | *(none)* | `target.schema` |
171
- | `false` (default, legacy) | `"reporting"` | `reporting` |
172
- | `true` (dbt-core standard) | *(none)* | `target.schema` |
173
- | `true` (dbt-core standard) | `"reporting"` | `target.schema_reporting` |
183
+ | `true` (default, dbt-core standard) | *(none)* | `target.schema` |
184
+ | `true` (default, dbt-core standard) | `"reporting"` | `target.schema_reporting` |
185
+ | `false` (legacy, deprecated) | *(none)* | `target.schema` |
186
+ | `false` (legacy, deprecated) | `"reporting"` | `reporting` |
174
187
 
175
- When `false` (the default), the adapter uses its legacy behaviour: `custom_schema_name` is used **as-is** without being prefixed by `target.schema`.
176
- When `true`, the adapter delegates to dbt-core's `default__generate_schema_name`, which concatenates `target.schema` + `_` + `custom_schema_name`.
188
+ When `true` (the default), the adapter delegates to dbt-core's `default__generate_schema_name`, which concatenates `target.schema` + `_` + `custom_schema_name`. This also matches the behavior dbt-core v2 (Fusion) ships unconditionally.
189
+ When `false`, the adapter uses its legacy behaviour: `custom_schema_name` is used **as-is** without being prefixed by `target.schema`. This legacy behavior is deprecated and will be removed in a future release.
177
190
 
178
191
  **Example usage in `dbt_project.yml`:**
179
192
 
180
193
  ```yaml
181
194
  flags:
182
- dbt_sqlserver_use_default_schema_concat: true # Enable standard schema concatenation
195
+ dbt_sqlserver_use_default_schema_concat: false # Opt back into the deprecated legacy behavior
183
196
  ```
184
197
 
185
198
  The same setting is also honoured via `vars:` for backwards compatibility; the behavior flag under `flags:` takes precedence when both are set.
@@ -188,7 +201,7 @@ The same setting is also honoured via `vars:` for backwards compatibility; the b
188
201
 
189
202
  ### `backend`
190
203
 
191
- *(default: `pyodbc`)* Set to `mssql-python` in a profile target to use the `mssql-python` backend instead of `pyodbc`. The adapter fails if the required backend package (Python dependency), such as `pyodbc` or `mssql-python`, is not installed.
204
+ *(default: `pyodbc`)* Set to `mssql-python` or `adbc` (experimental, see [docs/adbc_backend.md](https://github.com/dbt-msft/dbt-sqlserver/blob/master/docs/adbc_backend.md)) in a profile target to use that backend instead of `pyodbc`. The adapter fails if the required backend package (Python dependency), such as `pyodbc`, `mssql-python`, or `adbc-driver-manager`, is not installed.
192
205
 
193
206
  ### `dbt_sqlserver_enable_safe_type_expansion`
194
207
 
@@ -202,22 +215,46 @@ The same setting is also honoured via `vars:` for backwards compatibility; the b
202
215
 
203
216
  Safe expansions are further gated by `column_type_expansion_max_rows` (default 1,000,000 rows) to avoid long-running operations on large tables.
204
217
 
218
+ ### `pre_hook_transaction_scope`
219
+
220
+ _(default: `load`)_ Where a `table`, `incremental` or `snapshot` build resolves its schema
221
+ (the tmp view and the empty `CREATE`) relative to its in-transaction pre-hooks.
222
+ `load` stages it before them, so the new table's `Sch-M` lock is released in an
223
+ instant and the load blocks no metadata reader in other sessions; a
224
+ `transaction: true` pre-hook still rolls back with a failed load, except on the
225
+ two paths that commit a full-refresh marker before the load
226
+ (`full_refresh_build: prebuilt`, and an incremental `--full-refresh` of an
227
+ existing table), where neither scope can roll it back. `build` stages
228
+ it inside the hook's transaction, for the one case `load` cannot serve: a
229
+ `transaction: true` pre-hook that creates an object the model reads. See
230
+ [docs/transaction_scope.md](https://github.com/dbt-msft/dbt-sqlserver/blob/master/docs/transaction_scope.md) for the full flow and
231
+ the post-hook ordering change.
232
+
205
233
  ### `dbt_sqlserver_use_dbt_transactions`
206
234
 
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.
235
+ _(default: `true`)_ 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
236
 
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`.
237
+ The default is `true`, so dbt-managed transaction hooks emit real T-SQL transaction statements and rollback uses `IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION`. Set it to `false` to opt back into the deprecated legacy behavior where `begin`/`commit` hooks are logical no-ops and the driver auto-commits each statement.
210
238
 
211
239
  The driver connection remains in autocommit mode (`autocommit=true`) in both modes.
212
240
 
213
- This mode is opt-in and should be tested carefully with project-specific materializations and hooks.
241
+ This is now the default and should be tested carefully with project-specific materializations and hooks. Projects that depend on autocommit-only behavior should set the flag to `false` during migration.
214
242
 
215
243
  **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
244
 
217
245
  ```yaml
218
246
  # dbt_project.yml
219
247
  flags:
220
- dbt_sqlserver_use_dbt_transactions: true # <-- opt-in; default is false
248
+ dbt_sqlserver_use_dbt_transactions: false # legacy autocommit; deprecated
249
+ ```
250
+
251
+ ### `dbt_sqlserver_use_native_string_types`
252
+
253
+ *(default: `true`)* Controls the SQL Server-native mappings used for dbt string types. With the default enabled, `STRING` maps to `VARCHAR(MAX)`, `NCHAR` maps to `NCHAR(1)`, and `NVARCHAR` maps to `NVARCHAR(4000)`. Set it to `false` to opt back into the deprecated legacy mappings: `STRING` and `NVARCHAR` map to `VARCHAR(8000)`, while `NCHAR` maps to `CHAR(1)`.
254
+
255
+ ```yaml
256
+ flags:
257
+ dbt_sqlserver_use_native_string_types: false # deprecated legacy behavior
221
258
  ```
222
259
 
223
260
  ### `xact_abort`
@@ -251,7 +288,11 @@ your_profile:
251
288
 
252
289
  ### `prefer_single_alter_column`
253
290
 
254
- *(default: `false`)* Model-level config that controls how `alter_column_type` changes column types on tables. When `false` (default), the adapter uses the safer approach: add a temporary column, copy data, drop the original, and rename. When `true`, the adapter uses a single `ALTER COLUMN` statement, which is faster on small, medium tables and instant on safe type expansions but may fail for types that cannot be implicitly converted.
291
+ *(default: unset)* Model-level config that controls how `alter_column_type` changes column types on tables:
292
+
293
+ * `true`: a single `ALTER COLUMN` statement. It is atomic, instant for a longer `varchar`/`nvarchar`, and keeps the column's indexes, default constraint and position. It fails for types that cannot be implicitly converted, and changing `varchar` to `nvarchar` on a large clustered columnstore table can fail with Msg 35357 (dictionary size limit).
294
+ * `false`: add a temporary column, copy the data, drop the original and rename. It fails when an index, default constraint or statistics object depends on the column, and it moves the column to the end of the table. A failed attempt no longer blocks the next run.
295
+ * unset (default): `true` when column type expansion widens a column within its type (a longer `varchar`, or `varchar(max)`), `false` for every other change.
255
296
 
256
297
  ```sql
257
298
  -- In an incremental model
@@ -284,6 +325,10 @@ You can also set it per model:
284
325
 
285
326
  With `table_refresh_method: dml`, a schema change makes the refresh fall back to a rename-swap. On that run — and only that run — the scratch table is rebuilt the way this adapter builds every other table, so it carries the model's columnstore index, and under an enforced contract its `NOT NULL`s and inline constraints, into the swap. That run therefore executes the model's SQL twice — once for the `SELECT … INTO` that probes for the schema change, once for the rebuild — and builds the columnstore index once. Steady-state refreshes are unaffected and keep the single `SELECT … INTO`. A table that lost its columnstore index to this bug before you upgraded is not repaired automatically: its schema still matches, so it stays on the cheap path. To rebuild it, temporarily set `full_refresh_build: prebuilt` and run with `--full-refresh`.
286
327
 
328
+ ### Ephemeral models
329
+
330
+ Ephemeral models are supported, with one limit: an ephemeral model can't start with its own `WITH`. dbt inlines it as a CTE, and T-SQL can't nest a `WITH` inside one (still true on SQL Server 2025). Write it with derived tables instead, or materialize it as a `view`, which SQL Server expands into the calling query the same way.
331
+
287
332
  ### Constraints
288
333
 
289
334
  Constraints declared in a model's yaml are applied when — and only when — the model's [contract](https://docs.getdbt.com/reference/resource-configs/contract) is enforced, which is what every dbt adapter does and keeps their cost opt-in. (dbt-core does not raise if you declare constraints with the contract off — they are simply never emitted.) `not_null`, `check`, `unique`, `primary_key` and `foreign_key` are all supported.
@@ -296,7 +341,7 @@ A *model-level* constraint carrying `name:` is applied by `ALTER TABLE … ADD C
296
341
 
297
342
  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.
298
343
 
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).
344
+ The full set of rules, with the test that verifies each, is in [docs/constraints.md](https://github.com/dbt-msft/dbt-sqlserver/blob/master/docs/constraints.md).
300
345
 
301
346
  ```yaml
302
347
  models:
@@ -431,14 +476,31 @@ Behaviour:
431
476
 
432
477
  **Version notes.** All masking DDL the adapter emits (`ADD MASKED`, `MASKED WITH`, `DROP MASKED`) and the functions `default()`, `email()`, `random(a,b)` and `partial(...)` work on 2016+. The `datetime()` partial-date function and granular column/schema/table-scoped `UNMASK` are SQL Server 2022+ only; the adapter never emits them, but mask-function strings are passed through verbatim, so using a 2022-only function on an older server will be rejected by SQL Server.
433
478
 
479
+ ### Object-level DENY (`denies`)
480
+
481
+ In SQL Server, an object-level `DENY` is the only way to carve an exception out of a schema-level `GRANT` — grant a principal `SELECT` on a whole schema, then `DENY SELECT` on the individual PII-bearing models. But a `DENY` is stored against the object's `object_id`, so dbt destroys it every time it drops and recreates the relation (which is *every* run for a view). The schema grant survives; its exceptions silently evaporate, leaving a **fail-open** posture that no run reports.
482
+
483
+ The `denies` config re-applies object-level DENYs after each build, diffed against `sys.database_permissions`, the same way `masks` re-applies Dynamic Data Masking. Its shape mirrors `grants` — a `{privilege: [principals]}` map — and it is settable in the in-file `{{ config() }}`, the model's `.yml` `config:` block, or a directory-wide default in `dbt_project.yml` (merging key-wise across those levels, like `masks`):
484
+
485
+ ```sql
486
+ {{ config(denies={'select': ['Restricted_Read_Only']}) }}
487
+ ```
488
+
489
+ - **Survives rebuilds:** the DENY is present after `dbt run` and *still present after the next run*, for `table`, `view`, `incremental` (append and full-refresh) and `snapshot`. Unlike masks, this **includes views** — a view is a valid securable and is recreated on every run, which is where a DENY is lost most often.
490
+ - **Reconciled:** the adapter diffs the config against the live DENY state and emits only what changed — `DENY` for configured-not-present, `REVOKE` for present-not-configured (a `REVOKE` removes a `DENY`). Removing a principal from `denies` revokes its DENY on the next run; a converged, persisted relation issues no DDL.
491
+ - **Scope:** object-level DENY for the table privileges `select`, `insert`, `update`, `delete`, `references`. An unsupported privilege is warned and skipped (the warning surfaces the likely typo without taking down the build). Column-, database- and server-scoped permissions are out of scope (the latter survive a rebuild already).
492
+ - **Absent principal:** a `DENY` to a non-existent database principal is warned-and-skipped (the run still succeeds), so one config runs unchanged across dev, CI and prod where the principal set differs.
493
+ - **Ordering vs `grants`:** grants are applied first, then denies, so the final state is unambiguous. A `(privilege, principal)` pair appearing in both `grants` and `denies` is warned as a likely mistake (a DENY overrides a GRANT in SQL Server).
494
+ - **Other adapters:** `denies` is a no-op on non-SQL-Server adapters, not an error. This is SQL-Server-specific by design — `DENY` is absent from the SQL standard and does not port.
495
+
434
496
  ## Contributing
435
497
 
436
498
  [![Unit tests](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/unit-tests.yml)
437
499
  [![Integration tests on SQL Server](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/integration-tests-sqlserver.yml/badge.svg)](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/integration-tests-sqlserver.yml)
438
500
 
439
501
  This adapter is community-maintained.
440
- You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in the Slack channel.
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).
502
+ You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in [Discussions](https://github.com/dbt-msft/dbt-sqlserver/discussions) and on the [dbt Slack](https://getdbt.slack.com/archives/CMRMDDQ9W).
503
+ If you're unsure how to get started, check out our [contributing guide](https://github.com/dbt-msft/dbt-sqlserver/blob/master/CONTRIBUTING.md).
442
504
 
443
505
  ## License
444
506
 
@@ -2,8 +2,8 @@
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.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.
5
+ The adapter supports dbt-core 1.12 or newer and follows the same versioning scheme.
6
+ E.g. version 1.12.x of the adapter is compatible with dbt-core 1.12.x.
7
7
 
8
8
  ## Supported Python versions
9
9
 
@@ -15,6 +15,7 @@ The adapter is tested against:
15
15
  | 3.11 | Officially supported |
16
16
  | 3.12 | Officially supported |
17
17
  | 3.13 | Officially supported |
18
+ | 3.14 | Officially supported |
18
19
 
19
20
  ## Supported SQL Server versions
20
21
 
@@ -38,7 +39,7 @@ We've bundled all documentation on the dbt docs site:
38
39
  - [Profile setup & authentication](https://docs.getdbt.com/reference/warehouse-profiles/mssql-profile)
39
40
  - [Adapter documentation, usage and important notes](https://docs.getdbt.com/reference/resource-configs/mssql-configs)
40
41
 
41
- Join us on the [dbt Slack](https://getdbt.slack.com/archives/CMRMDDQ9W) to ask questions, get help, or to discuss the project.
42
+ Ask questions and get help in [GitHub Discussions](https://github.com/dbt-msft/dbt-sqlserver/discussions/categories/q-a). The [dbt Slack](https://getdbt.slack.com/archives/CMRMDDQ9W) channel is there for chatting with the community.
42
43
 
43
44
  ## Installation
44
45
 
@@ -54,6 +55,7 @@ Latest pre-release: ![GitHub tag (latest SemVer pre-release)](https://img.shield
54
55
  |---|---|---|
55
56
  | `pyodbc` | `dbt-sqlserver[pyodbc]` or `pyodbc` | `unixodbc-dev` plus the Microsoft ODBC Driver for SQL Server |
56
57
  | `mssql-python` | `dbt-sqlserver[mssql]` or `mssql-python` | `libltdl7`, `libkrb5-3`, `libgssapi-krb5-2` |
58
+ | `adbc` *(experimental)* | `dbt-sqlserver[adbc]` | none (driver binary installed separately via the `dbc` CLI) |
57
59
 
58
60
 
59
61
  ### `pyodbc` backend
@@ -118,31 +120,41 @@ your_profile:
118
120
  backend: mssql-python # <-- enables this backend
119
121
  ```
120
122
 
123
+ ### `adbc` backend *(experimental)*
124
+
125
+ An Arrow-native backend built on [ADBC](https://arrow.apache.org/adbc/), avoiding the row-based ODBC/DB-API bridge entirely. SQL Server authentication only (no Azure AD / Windows auth yet).
126
+
127
+ ```shell
128
+ pip install -U "dbt-sqlserver[adbc]"
129
+ ```
130
+
131
+ The driver binary is not on PyPI and must be installed once via the `dbc` CLI. See [docs/adbc_backend.md](https://github.com/dbt-msft/dbt-sqlserver/blob/master/docs/adbc_backend.md) for the full setup, configuration, and known-differences guide.
132
+
121
133
  ## Changelog
122
134
 
123
- See [the changelog](https://github.com/dbt-msft/dbt-sqlserver/blob/release/v1.11/CHANGELOG.md)
135
+ See [the changelog](https://github.com/dbt-msft/dbt-sqlserver/blob/master/CHANGELOG.md)
124
136
 
125
137
  ## Configuration
126
138
 
127
139
  ### `dbt_sqlserver_use_default_schema_concat`
128
140
 
129
- *(default: `false`)* Controls schema name generation when a [custom schema](https://docs.getdbt.com/docs/build/custom-schemas) is set on a model.
141
+ *(default: `true`)* Controls schema name generation when a [custom schema](https://docs.getdbt.com/docs/build/custom-schemas) is set on a model.
130
142
 
131
143
  | Flag value | `custom_schema_name` | Result |
132
144
  |---|---|---|
133
- | `false` (default, legacy) | *(none)* | `target.schema` |
134
- | `false` (default, legacy) | `"reporting"` | `reporting` |
135
- | `true` (dbt-core standard) | *(none)* | `target.schema` |
136
- | `true` (dbt-core standard) | `"reporting"` | `target.schema_reporting` |
145
+ | `true` (default, dbt-core standard) | *(none)* | `target.schema` |
146
+ | `true` (default, dbt-core standard) | `"reporting"` | `target.schema_reporting` |
147
+ | `false` (legacy, deprecated) | *(none)* | `target.schema` |
148
+ | `false` (legacy, deprecated) | `"reporting"` | `reporting` |
137
149
 
138
- When `false` (the default), the adapter uses its legacy behaviour: `custom_schema_name` is used **as-is** without being prefixed by `target.schema`.
139
- When `true`, the adapter delegates to dbt-core's `default__generate_schema_name`, which concatenates `target.schema` + `_` + `custom_schema_name`.
150
+ When `true` (the default), the adapter delegates to dbt-core's `default__generate_schema_name`, which concatenates `target.schema` + `_` + `custom_schema_name`. This also matches the behavior dbt-core v2 (Fusion) ships unconditionally.
151
+ When `false`, the adapter uses its legacy behaviour: `custom_schema_name` is used **as-is** without being prefixed by `target.schema`. This legacy behavior is deprecated and will be removed in a future release.
140
152
 
141
153
  **Example usage in `dbt_project.yml`:**
142
154
 
143
155
  ```yaml
144
156
  flags:
145
- dbt_sqlserver_use_default_schema_concat: true # Enable standard schema concatenation
157
+ dbt_sqlserver_use_default_schema_concat: false # Opt back into the deprecated legacy behavior
146
158
  ```
147
159
 
148
160
  The same setting is also honoured via `vars:` for backwards compatibility; the behavior flag under `flags:` takes precedence when both are set.
@@ -151,7 +163,7 @@ The same setting is also honoured via `vars:` for backwards compatibility; the b
151
163
 
152
164
  ### `backend`
153
165
 
154
- *(default: `pyodbc`)* Set to `mssql-python` in a profile target to use the `mssql-python` backend instead of `pyodbc`. The adapter fails if the required backend package (Python dependency), such as `pyodbc` or `mssql-python`, is not installed.
166
+ *(default: `pyodbc`)* Set to `mssql-python` or `adbc` (experimental, see [docs/adbc_backend.md](https://github.com/dbt-msft/dbt-sqlserver/blob/master/docs/adbc_backend.md)) in a profile target to use that backend instead of `pyodbc`. The adapter fails if the required backend package (Python dependency), such as `pyodbc`, `mssql-python`, or `adbc-driver-manager`, is not installed.
155
167
 
156
168
  ### `dbt_sqlserver_enable_safe_type_expansion`
157
169
 
@@ -165,22 +177,46 @@ The same setting is also honoured via `vars:` for backwards compatibility; the b
165
177
 
166
178
  Safe expansions are further gated by `column_type_expansion_max_rows` (default 1,000,000 rows) to avoid long-running operations on large tables.
167
179
 
180
+ ### `pre_hook_transaction_scope`
181
+
182
+ _(default: `load`)_ Where a `table`, `incremental` or `snapshot` build resolves its schema
183
+ (the tmp view and the empty `CREATE`) relative to its in-transaction pre-hooks.
184
+ `load` stages it before them, so the new table's `Sch-M` lock is released in an
185
+ instant and the load blocks no metadata reader in other sessions; a
186
+ `transaction: true` pre-hook still rolls back with a failed load, except on the
187
+ two paths that commit a full-refresh marker before the load
188
+ (`full_refresh_build: prebuilt`, and an incremental `--full-refresh` of an
189
+ existing table), where neither scope can roll it back. `build` stages
190
+ it inside the hook's transaction, for the one case `load` cannot serve: a
191
+ `transaction: true` pre-hook that creates an object the model reads. See
192
+ [docs/transaction_scope.md](https://github.com/dbt-msft/dbt-sqlserver/blob/master/docs/transaction_scope.md) for the full flow and
193
+ the post-hook ordering change.
194
+
168
195
  ### `dbt_sqlserver_use_dbt_transactions`
169
196
 
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.
197
+ _(default: `true`)_ 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
198
 
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`.
199
+ The default is `true`, so dbt-managed transaction hooks emit real T-SQL transaction statements and rollback uses `IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION`. Set it to `false` to opt back into the deprecated legacy behavior where `begin`/`commit` hooks are logical no-ops and the driver auto-commits each statement.
173
200
 
174
201
  The driver connection remains in autocommit mode (`autocommit=true`) in both modes.
175
202
 
176
- This mode is opt-in and should be tested carefully with project-specific materializations and hooks.
203
+ This is now the default and should be tested carefully with project-specific materializations and hooks. Projects that depend on autocommit-only behavior should set the flag to `false` during migration.
177
204
 
178
205
  **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
206
 
180
207
  ```yaml
181
208
  # dbt_project.yml
182
209
  flags:
183
- dbt_sqlserver_use_dbt_transactions: true # <-- opt-in; default is false
210
+ dbt_sqlserver_use_dbt_transactions: false # legacy autocommit; deprecated
211
+ ```
212
+
213
+ ### `dbt_sqlserver_use_native_string_types`
214
+
215
+ *(default: `true`)* Controls the SQL Server-native mappings used for dbt string types. With the default enabled, `STRING` maps to `VARCHAR(MAX)`, `NCHAR` maps to `NCHAR(1)`, and `NVARCHAR` maps to `NVARCHAR(4000)`. Set it to `false` to opt back into the deprecated legacy mappings: `STRING` and `NVARCHAR` map to `VARCHAR(8000)`, while `NCHAR` maps to `CHAR(1)`.
216
+
217
+ ```yaml
218
+ flags:
219
+ dbt_sqlserver_use_native_string_types: false # deprecated legacy behavior
184
220
  ```
185
221
 
186
222
  ### `xact_abort`
@@ -214,7 +250,11 @@ your_profile:
214
250
 
215
251
  ### `prefer_single_alter_column`
216
252
 
217
- *(default: `false`)* Model-level config that controls how `alter_column_type` changes column types on tables. When `false` (default), the adapter uses the safer approach: add a temporary column, copy data, drop the original, and rename. When `true`, the adapter uses a single `ALTER COLUMN` statement, which is faster on small, medium tables and instant on safe type expansions but may fail for types that cannot be implicitly converted.
253
+ *(default: unset)* Model-level config that controls how `alter_column_type` changes column types on tables:
254
+
255
+ * `true`: a single `ALTER COLUMN` statement. It is atomic, instant for a longer `varchar`/`nvarchar`, and keeps the column's indexes, default constraint and position. It fails for types that cannot be implicitly converted, and changing `varchar` to `nvarchar` on a large clustered columnstore table can fail with Msg 35357 (dictionary size limit).
256
+ * `false`: add a temporary column, copy the data, drop the original and rename. It fails when an index, default constraint or statistics object depends on the column, and it moves the column to the end of the table. A failed attempt no longer blocks the next run.
257
+ * unset (default): `true` when column type expansion widens a column within its type (a longer `varchar`, or `varchar(max)`), `false` for every other change.
218
258
 
219
259
  ```sql
220
260
  -- In an incremental model
@@ -247,6 +287,10 @@ You can also set it per model:
247
287
 
248
288
  With `table_refresh_method: dml`, a schema change makes the refresh fall back to a rename-swap. On that run — and only that run — the scratch table is rebuilt the way this adapter builds every other table, so it carries the model's columnstore index, and under an enforced contract its `NOT NULL`s and inline constraints, into the swap. That run therefore executes the model's SQL twice — once for the `SELECT … INTO` that probes for the schema change, once for the rebuild — and builds the columnstore index once. Steady-state refreshes are unaffected and keep the single `SELECT … INTO`. A table that lost its columnstore index to this bug before you upgraded is not repaired automatically: its schema still matches, so it stays on the cheap path. To rebuild it, temporarily set `full_refresh_build: prebuilt` and run with `--full-refresh`.
249
289
 
290
+ ### Ephemeral models
291
+
292
+ Ephemeral models are supported, with one limit: an ephemeral model can't start with its own `WITH`. dbt inlines it as a CTE, and T-SQL can't nest a `WITH` inside one (still true on SQL Server 2025). Write it with derived tables instead, or materialize it as a `view`, which SQL Server expands into the calling query the same way.
293
+
250
294
  ### Constraints
251
295
 
252
296
  Constraints declared in a model's yaml are applied when — and only when — the model's [contract](https://docs.getdbt.com/reference/resource-configs/contract) is enforced, which is what every dbt adapter does and keeps their cost opt-in. (dbt-core does not raise if you declare constraints with the contract off — they are simply never emitted.) `not_null`, `check`, `unique`, `primary_key` and `foreign_key` are all supported.
@@ -259,7 +303,7 @@ A *model-level* constraint carrying `name:` is applied by `ALTER TABLE … ADD C
259
303
 
260
304
  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.
261
305
 
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).
306
+ The full set of rules, with the test that verifies each, is in [docs/constraints.md](https://github.com/dbt-msft/dbt-sqlserver/blob/master/docs/constraints.md).
263
307
 
264
308
  ```yaml
265
309
  models:
@@ -394,14 +438,31 @@ Behaviour:
394
438
 
395
439
  **Version notes.** All masking DDL the adapter emits (`ADD MASKED`, `MASKED WITH`, `DROP MASKED`) and the functions `default()`, `email()`, `random(a,b)` and `partial(...)` work on 2016+. The `datetime()` partial-date function and granular column/schema/table-scoped `UNMASK` are SQL Server 2022+ only; the adapter never emits them, but mask-function strings are passed through verbatim, so using a 2022-only function on an older server will be rejected by SQL Server.
396
440
 
441
+ ### Object-level DENY (`denies`)
442
+
443
+ In SQL Server, an object-level `DENY` is the only way to carve an exception out of a schema-level `GRANT` — grant a principal `SELECT` on a whole schema, then `DENY SELECT` on the individual PII-bearing models. But a `DENY` is stored against the object's `object_id`, so dbt destroys it every time it drops and recreates the relation (which is *every* run for a view). The schema grant survives; its exceptions silently evaporate, leaving a **fail-open** posture that no run reports.
444
+
445
+ The `denies` config re-applies object-level DENYs after each build, diffed against `sys.database_permissions`, the same way `masks` re-applies Dynamic Data Masking. Its shape mirrors `grants` — a `{privilege: [principals]}` map — and it is settable in the in-file `{{ config() }}`, the model's `.yml` `config:` block, or a directory-wide default in `dbt_project.yml` (merging key-wise across those levels, like `masks`):
446
+
447
+ ```sql
448
+ {{ config(denies={'select': ['Restricted_Read_Only']}) }}
449
+ ```
450
+
451
+ - **Survives rebuilds:** the DENY is present after `dbt run` and *still present after the next run*, for `table`, `view`, `incremental` (append and full-refresh) and `snapshot`. Unlike masks, this **includes views** — a view is a valid securable and is recreated on every run, which is where a DENY is lost most often.
452
+ - **Reconciled:** the adapter diffs the config against the live DENY state and emits only what changed — `DENY` for configured-not-present, `REVOKE` for present-not-configured (a `REVOKE` removes a `DENY`). Removing a principal from `denies` revokes its DENY on the next run; a converged, persisted relation issues no DDL.
453
+ - **Scope:** object-level DENY for the table privileges `select`, `insert`, `update`, `delete`, `references`. An unsupported privilege is warned and skipped (the warning surfaces the likely typo without taking down the build). Column-, database- and server-scoped permissions are out of scope (the latter survive a rebuild already).
454
+ - **Absent principal:** a `DENY` to a non-existent database principal is warned-and-skipped (the run still succeeds), so one config runs unchanged across dev, CI and prod where the principal set differs.
455
+ - **Ordering vs `grants`:** grants are applied first, then denies, so the final state is unambiguous. A `(privilege, principal)` pair appearing in both `grants` and `denies` is warned as a likely mistake (a DENY overrides a GRANT in SQL Server).
456
+ - **Other adapters:** `denies` is a no-op on non-SQL-Server adapters, not an error. This is SQL-Server-specific by design — `DENY` is absent from the SQL standard and does not port.
457
+
397
458
  ## Contributing
398
459
 
399
460
  [![Unit tests](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/unit-tests.yml)
400
461
  [![Integration tests on SQL Server](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/integration-tests-sqlserver.yml/badge.svg)](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/integration-tests-sqlserver.yml)
401
462
 
402
463
  This adapter is community-maintained.
403
- You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in the Slack channel.
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).
464
+ You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in [Discussions](https://github.com/dbt-msft/dbt-sqlserver/discussions) and on the [dbt Slack](https://getdbt.slack.com/archives/CMRMDDQ9W).
465
+ If you're unsure how to get started, check out our [contributing guide](https://github.com/dbt-msft/dbt-sqlserver/blob/master/CONTRIBUTING.md).
405
466
 
406
467
  ## License
407
468
 
@@ -0,0 +1 @@
1
+ version = "1.12.0"
@@ -593,13 +593,19 @@ def index_config_changes(
593
593
  SQLServerIndexConfig.parse_relation_results(rows_by_name[name])
594
594
  )
595
595
  changes.append(
596
- SQLServerIndexConfigChange(action=RelationConfigChangeAction.drop, context=context)
596
+ SQLServerIndexConfigChange(
597
+ # `action` is inherited from RelationConfigChange, a dataclass a
598
+ # type checker cannot see; see [tool.ty.rules] in pyproject.toml.
599
+ action=RelationConfigChangeAction.drop, # ty: ignore[unknown-argument]
600
+ context=context,
601
+ )
597
602
  )
598
603
  for name, config in expected_by_name.items():
599
604
  if name not in existing_names:
600
605
  changes.append(
601
606
  SQLServerIndexConfigChange(
602
- action=RelationConfigChangeAction.create, context=config
607
+ action=RelationConfigChangeAction.create, # ty: ignore[unknown-argument]
608
+ context=config,
603
609
  )
604
610
  )
605
611
  return changes, warnings