dbt-sqlserver 1.12.0rc3__tar.gz → 1.12.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/PKG-INFO +136 -12
  2. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/README.md +135 -11
  3. dbt_sqlserver-1.12.1/dbt/adapters/sqlserver/__version__.py +1 -0
  4. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_adapter.py +395 -104
  5. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_configs.py +2 -1
  6. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/apply_denies.sql +3 -1
  7. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/apply_masks.sql +10 -2
  8. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/columns.sql +38 -5
  9. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/indexes.sql +21 -7
  10. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/metadata.sql +49 -0
  11. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/schema.sql +33 -8
  12. dbt_sqlserver-1.12.1/dbt/include/sqlserver/macros/materializations/hooks.sql +58 -0
  13. dbt_sqlserver-1.12.1/dbt/include/sqlserver/macros/materializations/models/incremental/incremental.sql +271 -0
  14. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/models/incremental/merge.sql +3 -3
  15. dbt_sqlserver-1.12.1/dbt/include/sqlserver/macros/materializations/models/table/columns_spec_ddl.sql +79 -0
  16. dbt_sqlserver-1.12.1/dbt/include/sqlserver/macros/materializations/models/table/table.sql +259 -0
  17. dbt_sqlserver-1.12.1/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql +200 -0
  18. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/models/unit_test/unit_test_create_table_as.sql +5 -1
  19. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/models/view/view.sql +9 -2
  20. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/snapshots/helpers.sql +32 -17
  21. dbt_sqlserver-1.12.1/dbt/include/sqlserver/macros/materializations/snapshots/snapshot.sql +207 -0
  22. dbt_sqlserver-1.12.1/dbt/include/sqlserver/macros/materializations/snapshots/strategies.sql +65 -0
  23. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/tests.sql +2 -5
  24. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/unit_tests.sql +11 -4
  25. dbt_sqlserver-1.12.1/dbt/include/sqlserver/macros/relations/table/create.sql +399 -0
  26. dbt_sqlserver-1.12.1/dbt/include/sqlserver/macros/utils/array_append.sql +3 -0
  27. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt_sqlserver.egg-info/PKG-INFO +136 -12
  28. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt_sqlserver.egg-info/SOURCES.txt +1 -0
  29. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/pyproject.toml +27 -7
  30. dbt_sqlserver-1.12.0rc3/dbt/adapters/sqlserver/__version__.py +0 -1
  31. dbt_sqlserver-1.12.0rc3/dbt/include/sqlserver/macros/materializations/hooks.sql +0 -23
  32. dbt_sqlserver-1.12.0rc3/dbt/include/sqlserver/macros/materializations/models/incremental/incremental.sql +0 -201
  33. dbt_sqlserver-1.12.0rc3/dbt/include/sqlserver/macros/materializations/models/table/columns_spec_ddl.sql +0 -30
  34. dbt_sqlserver-1.12.0rc3/dbt/include/sqlserver/macros/materializations/models/table/table.sql +0 -149
  35. dbt_sqlserver-1.12.0rc3/dbt/include/sqlserver/macros/materializations/models/table/table_dml_refresh.sql +0 -133
  36. dbt_sqlserver-1.12.0rc3/dbt/include/sqlserver/macros/materializations/snapshots/snapshot.sql +0 -139
  37. dbt_sqlserver-1.12.0rc3/dbt/include/sqlserver/macros/materializations/snapshots/strategies.sql +0 -5
  38. dbt_sqlserver-1.12.0rc3/dbt/include/sqlserver/macros/relations/table/create.sql +0 -264
  39. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/LICENSE +0 -0
  40. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/__init__.py +0 -0
  41. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/__init__.py +0 -0
  42. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/py.typed +0 -0
  43. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/relation_configs/__init__.py +0 -0
  44. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/relation_configs/index.py +0 -0
  45. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/relation_configs/policies.py +0 -0
  46. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_auth.py +0 -0
  47. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_backend.py +0 -0
  48. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_column.py +0 -0
  49. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_connections.py +0 -0
  50. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_constants.py +0 -0
  51. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_credentials.py +0 -0
  52. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_deny.py +0 -0
  53. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_helpers.py +0 -0
  54. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_mask.py +0 -0
  55. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_relation.py +0 -0
  56. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/adapters/sqlserver/sqlserver_runtime.py +0 -0
  57. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/__init__.py +0 -0
  58. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/dbt_project.yml +0 -0
  59. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/apply_grants.sql +0 -0
  60. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/catalog.sql +0 -0
  61. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/persist_docs.sql +0 -0
  62. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/relation.sql +0 -0
  63. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/show.sql +0 -0
  64. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/adapters/validate_sql.sql +0 -0
  65. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/functions/helpers.sql +0 -0
  66. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/functions/scalar.sql +0 -0
  67. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/models/incremental/incremental_strategies.sql +0 -0
  68. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/models/unit_test/get_fixture_sql.sql +0 -0
  69. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/models/view/create_view_as.sql +0 -0
  70. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/materializations/snapshots/snapshot_merge.sql +0 -0
  71. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/relations/seeds/helpers.sql +0 -0
  72. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/relations/table/clone.sql +0 -0
  73. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/relations/views/create.sql +0 -0
  74. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/any_value.sql +0 -0
  75. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/array_construct.sql +0 -0
  76. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/cast_bool_to_text.sql +0 -0
  77. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/concat.sql +0 -0
  78. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/date_trunc.sql +0 -0
  79. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/dateadd.sql +0 -0
  80. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/get_tables_by_pattern.sql +0 -0
  81. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/hash.sql +0 -0
  82. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/last_day.sql +0 -0
  83. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/length.sql +0 -0
  84. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/listagg.sql +0 -0
  85. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/openquery.sql +0 -0
  86. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/position.sql +0 -0
  87. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/safe_cast.sql +0 -0
  88. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/split_part.sql +0 -0
  89. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/macros/utils/timestamps.sql +0 -0
  90. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt/include/sqlserver/profile_template.yml +0 -0
  91. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt_sqlserver.egg-info/dependency_links.txt +0 -0
  92. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt_sqlserver.egg-info/requires.txt +0 -0
  93. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/dbt_sqlserver.egg-info/top_level.txt +0 -0
  94. {dbt_sqlserver-1.12.0rc3 → dbt_sqlserver-1.12.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbt-sqlserver
3
- Version: 1.12.0rc3
3
+ Version: 1.12.1
4
4
  Summary: A Microsoft SQL Server adapter plugin for dbt
5
5
  Author: Mikael Ene, Anders Swanson, Sam Debruyn, Cor Zuurmond, Cody Scott
6
6
  License: MIT
@@ -77,7 +77,7 @@ We've bundled all documentation on the dbt docs site:
77
77
  - [Profile setup & authentication](https://docs.getdbt.com/reference/warehouse-profiles/mssql-profile)
78
78
  - [Adapter documentation, usage and important notes](https://docs.getdbt.com/reference/resource-configs/mssql-configs)
79
79
 
80
- 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.
81
81
 
82
82
  ## Installation
83
83
 
@@ -166,11 +166,11 @@ An Arrow-native backend built on [ADBC](https://arrow.apache.org/adbc/), avoidin
166
166
  pip install -U "dbt-sqlserver[adbc]"
167
167
  ```
168
168
 
169
- The driver binary is not on PyPI and must be installed once via the `dbc` CLI. See [docs/adbc_backend.md](docs/adbc_backend.md) for the full setup, configuration, and known-differences guide.
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
170
 
171
171
  ## Changelog
172
172
 
173
- See [the changelog](CHANGELOG.md)
173
+ See [the changelog](https://github.com/dbt-msft/dbt-sqlserver/blob/master/CHANGELOG.md)
174
174
 
175
175
  ## Configuration
176
176
 
@@ -201,7 +201,7 @@ The same setting is also honoured via `vars:` for backwards compatibility; the b
201
201
 
202
202
  ### `backend`
203
203
 
204
- *(default: `pyodbc`)* Set to `mssql-python` or `adbc` (experimental, see [docs/adbc_backend.md](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.
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.
205
205
 
206
206
  ### `dbt_sqlserver_enable_safe_type_expansion`
207
207
 
@@ -215,6 +215,21 @@ The same setting is also honoured via `vars:` for backwards compatibility; the b
215
215
 
216
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.
217
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
+
218
233
  ### `dbt_sqlserver_use_dbt_transactions`
219
234
 
220
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.
@@ -225,11 +240,12 @@ The driver connection remains in autocommit mode (`autocommit=true`) in both mod
225
240
 
226
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.
227
242
 
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.
244
+
228
245
  ```yaml
229
246
  # dbt_project.yml
230
247
  flags:
231
- dbt_sqlserver_enable_safe_type_expansion: true
232
- dbt_sqlserver_use_dbt_transactions: true # default
248
+ dbt_sqlserver_use_dbt_transactions: false # legacy autocommit; deprecated
233
249
  ```
234
250
 
235
251
  ### `dbt_sqlserver_use_native_string_types`
@@ -272,7 +288,11 @@ your_profile:
272
288
 
273
289
  ### `prefer_single_alter_column`
274
290
 
275
- *(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.
276
296
 
277
297
  ```sql
278
298
  -- In an incremental model
@@ -280,8 +300,6 @@ your_profile:
280
300
  prefer_single_alter_column=true) }}
281
301
  ```
282
302
 
283
- **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.
284
-
285
303
  ### `as_columnstore`
286
304
 
287
305
  *(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.
@@ -305,6 +323,112 @@ You can also set it per model:
305
323
  {{ config(materialized="table", as_columnstore=false) }}
306
324
  ```
307
325
 
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`.
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
+
332
+ ### Constraints
333
+
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.
335
+
336
+ **Where a constraint lands depends on whether you name it.**
337
+
338
+ An unnamed constraint is rendered inline in the `CREATE TABLE` column list and SQL Server names it (`PK__my_model__3213E83F…`). It is validated as the table is built, so a violation fails before the new table is swapped in and the previous one is left untouched.
339
+
340
+ A *model-level* constraint carrying `name:` is applied by `ALTER TABLE … ADD CONSTRAINT` after the build swaps the new table into place and drops the old one. SQL Server scopes constraint names per schema, and a table is built alongside the one it replaces, so that is the first moment the name is free to reuse — naming a constraint inline would collide with the outgoing table (`Msg 2714`) on every rebuild after the first. The trade-off is that this runs after the model has committed: if the data violates the constraint, the model fails with the table already in place but unconstrained, and — as with any failure this late in a build — `post_hook`s declared with `transaction: false` do not run.
341
+
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.
343
+
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).
345
+
346
+ ```yaml
347
+ models:
348
+ - name: fact_sales
349
+ config:
350
+ contract:
351
+ enforced: true
352
+ constraints:
353
+ # named: applied by ALTER TABLE after the swap
354
+ - type: primary_key
355
+ name: PK_fact_sales
356
+ columns: [sale_id]
357
+ - type: foreign_key
358
+ name: FK_fact_sales_customer
359
+ columns: [customer_id]
360
+ to: ref('dim_customer')
361
+ to_columns: [customer_id]
362
+ # unnamed: rendered into the CREATE TABLE
363
+ - type: check
364
+ expression: amount >= 0
365
+ columns:
366
+ - name: sale_id
367
+ data_type: int
368
+ constraints:
369
+ - type: not_null
370
+ - name: customer_id
371
+ data_type: int
372
+ constraints:
373
+ - type: not_null
374
+ - name: amount
375
+ data_type: decimal(18,2)
376
+ ```
377
+
378
+ #### Clustering
379
+
380
+ `primary_key` and `unique` are emitted as `NONCLUSTERED` by default so they can coexist with the clustered columnstore index built for [`as_columnstore`](#as_columnstore). Use dbt's own `expression` field to ask for something else:
381
+
382
+ ```yaml
383
+ constraints:
384
+ - type: primary_key
385
+ name: PK_fact_sales
386
+ columns: [sale_id]
387
+ expression: clustered # requires as_columnstore: false
388
+ ```
389
+
390
+ `clustered` and `nonclustered` are the only values understood here. `expression` is free text that dbt splices between the keyword and the column list, which is the one place T-SQL accepts nothing else — index options such as `with (fillfactor = 90)` belong on a separate index, not on the constraint.
391
+
392
+ #### Foreign keys
393
+
394
+ Two things are worth knowing before adding them:
395
+
396
+ - **They are not free at build time.** Every load is validated against them; add them where you want the guarantee, not everywhere the relationship exists.
397
+ - **A foreign key pointing at a model blocks that model's rebuild.** The build renames the outgoing table to a backup and drops it, but the child's foreign key follows the renamed object, so the drop fails with `Msg 3726`. SQL Server has no `DROP TABLE … CASCADE`.
398
+
399
+ The adapter ships a macro for exactly this, meant as a `pre_hook` on the **referenced** (parent) model:
400
+
401
+ ```sql
402
+ {{ config(pre_hook="{{ drop_fk_constraints() }}") }}
403
+ ```
404
+
405
+ It drops the foreign keys in both directions — the inbound ones other tables hold against this model, and this model's own outbound ones — so the rebuild's backup drop succeeds. The trade-off is explicit and worth stating: **the child's foreign key does not exist between the parent's rebuild and the child's next build.** (dbt-postgres makes the same trade silently, by issuing every `drop table` with `cascade`.)
406
+
407
+ Add the hook *before* the first rebuild. Once a rebuild has failed with `Msg 3726`, the new table is already in place but without its own named constraints — those are applied after the backup drop that failed — and the child's key now points at `<model>__dbt_backup`, because a foreign key follows the object, not the name. A plain `dbt run` does not recover: the parent trips over the same backup and the child is skipped behind it. Rebuild the child alone — that run fails too, since the parent has no key to reference, but its swap drops the old child and the stale key with it — and a plain `dbt run` then rebuilds both in order. Dropping the child's key by hand (`ALTER TABLE <child> DROP CONSTRAINT <name>`) does the same.
408
+
409
+ `table_refresh_method: dml` is *not* a workaround. Its steady-state refresh issues `DELETE FROM <parent>`, which fails with `Msg 547` as soon as the child holds referencing rows, and its schema-change path falls back to the same rename-swap, hitting `Msg 3726` anyway.
410
+
411
+ - **SQL Server has no cross-database foreign keys.** `to: ref(...)` resolves to a fully qualified relation, database included, which SQL Server accepts as long as it names the current database. A target in another database fails with `references invalid table`.
412
+
413
+ A foreign key also does not order the build on its own: add an explicit `-- depends_on: {{ ref('dim_customer') }}` to the child model so the parent is built first.
414
+
415
+ #### Changing a constraint after the first build
416
+
417
+ Named constraints are applied by an `ALTER TABLE` whose `ADD` is guarded on the name already being present on the table, so:
418
+
419
+ - **Adding** a constraint to a model that already exists lands on its next run — no `--full-refresh` needed, on `table` and `incremental` alike.
420
+ - **Changing** an existing constraint's definition while keeping its name is *not* detected: a constraint name, unlike a `dbt_idx_` index name, says nothing about what the constraint does. Run `--full-refresh` to apply the new definition; that rebuilds the table, so the constraint is created fresh. (A `table` model rebuilds on every run and needs nothing special.)
421
+ - **Renaming** a constraint on a table that persists across runs adds the new name beside the old one. For a `check`, `unique` or `foreign_key` that means a duplicate; for a `primary_key` the run fails with `Msg 1779` (*table already has a primary key defined on it*) until the old one is dropped. Rename with `--full-refresh`.
422
+ - **Removing** a constraint from the yaml does not drop it from the database. Drop it yourself, or `--full-refresh`.
423
+
424
+ Every bullet above is about *named* constraints. Unnamed ones ride the `CREATE TABLE`, so they follow the table and change only when the table is rebuilt — on a materialization whose table persists across runs (`incremental` in its steady state, `table_refresh_method: dml`), adding an unnamed constraint to an existing model does nothing until a `--full-refresh`, and the run still reports success. Name it, or full-refresh.
425
+
426
+ #### Build-shape notes
427
+
428
+ An *unnamed* `primary_key` or `unique` constraint on a column that also carries a [data mask](#dynamic-data-masking-masked_with--masks) is rejected by the build. The constraint rides the `CREATE TABLE`, so its index already exists by the time `apply_masks` runs, and the adapter refuses to mask any column that an index has as a key: *is configured for masking but is also an index key column*. Declare that constraint at the model level **with a `name:`** instead — named constraints are applied by `ALTER TABLE` after the masks are in place, which the adapter allows.
429
+
430
+ A contract-enforced model is always loaded as `CREATE TABLE` followed by `INSERT … WITH (TABLOCK)`, on every build path. A `primary_key` or `unique` constraint — named or not — puts a nonclustered index on that table *before* the load, so the index is maintained row by row while the rows go in, and that part of the load is fully logged where an index-free heap would have been minimally logged. The cost lands on every rebuild of the model; it is most visible with `full_refresh_build: prebuilt`, whose point is a cheap bulk load. If a model's load time matters, weigh the key constraints against it; `check`, `not_null` and `foreign_key` do not create indexes and do not carry this cost.
431
+
308
432
  ### Dynamic Data Masking (`masked_with` / `masks`)
309
433
 
310
434
  The adapter can apply SQL Server [Dynamic Data Masking](https://learn.microsoft.com/en-us/sql/relational-databases/security/dynamic-data-masking) (DDM) to columns as part of the materialization, so masks are re-applied on every build and survive dbt's drop-and-recreate on a full refresh. A principal granted `SELECT` but not `UNMASK` then sees masked values instead of real data (dbt's own build principal, being `db_owner`, keeps `UNMASK` and reads real data). Requires **SQL Server 2016+**.
@@ -375,8 +499,8 @@ The `denies` config re-applies object-level DENYs after each build, diffed again
375
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)
376
500
 
377
501
  This adapter is community-maintained.
378
- You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in the Slack channel.
379
- If you're unsure how to get started, check out our [contributing guide](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).
380
504
 
381
505
  ## License
382
506
 
@@ -39,7 +39,7 @@ We've bundled all documentation on the dbt docs site:
39
39
  - [Profile setup & authentication](https://docs.getdbt.com/reference/warehouse-profiles/mssql-profile)
40
40
  - [Adapter documentation, usage and important notes](https://docs.getdbt.com/reference/resource-configs/mssql-configs)
41
41
 
42
- 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.
43
43
 
44
44
  ## Installation
45
45
 
@@ -128,11 +128,11 @@ An Arrow-native backend built on [ADBC](https://arrow.apache.org/adbc/), avoidin
128
128
  pip install -U "dbt-sqlserver[adbc]"
129
129
  ```
130
130
 
131
- The driver binary is not on PyPI and must be installed once via the `dbc` CLI. See [docs/adbc_backend.md](docs/adbc_backend.md) for the full setup, configuration, and known-differences guide.
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
132
 
133
133
  ## Changelog
134
134
 
135
- See [the changelog](CHANGELOG.md)
135
+ See [the changelog](https://github.com/dbt-msft/dbt-sqlserver/blob/master/CHANGELOG.md)
136
136
 
137
137
  ## Configuration
138
138
 
@@ -163,7 +163,7 @@ The same setting is also honoured via `vars:` for backwards compatibility; the b
163
163
 
164
164
  ### `backend`
165
165
 
166
- *(default: `pyodbc`)* Set to `mssql-python` or `adbc` (experimental, see [docs/adbc_backend.md](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.
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.
167
167
 
168
168
  ### `dbt_sqlserver_enable_safe_type_expansion`
169
169
 
@@ -177,6 +177,21 @@ The same setting is also honoured via `vars:` for backwards compatibility; the b
177
177
 
178
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.
179
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
+
180
195
  ### `dbt_sqlserver_use_dbt_transactions`
181
196
 
182
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.
@@ -187,11 +202,12 @@ The driver connection remains in autocommit mode (`autocommit=true`) in both mod
187
202
 
188
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.
189
204
 
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.
206
+
190
207
  ```yaml
191
208
  # dbt_project.yml
192
209
  flags:
193
- dbt_sqlserver_enable_safe_type_expansion: true
194
- dbt_sqlserver_use_dbt_transactions: true # default
210
+ dbt_sqlserver_use_dbt_transactions: false # legacy autocommit; deprecated
195
211
  ```
196
212
 
197
213
  ### `dbt_sqlserver_use_native_string_types`
@@ -234,7 +250,11 @@ your_profile:
234
250
 
235
251
  ### `prefer_single_alter_column`
236
252
 
237
- *(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.
238
258
 
239
259
  ```sql
240
260
  -- In an incremental model
@@ -242,8 +262,6 @@ your_profile:
242
262
  prefer_single_alter_column=true) }}
243
263
  ```
244
264
 
245
- **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.
246
-
247
265
  ### `as_columnstore`
248
266
 
249
267
  *(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.
@@ -267,6 +285,112 @@ You can also set it per model:
267
285
  {{ config(materialized="table", as_columnstore=false) }}
268
286
  ```
269
287
 
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`.
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
+
294
+ ### Constraints
295
+
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.
297
+
298
+ **Where a constraint lands depends on whether you name it.**
299
+
300
+ An unnamed constraint is rendered inline in the `CREATE TABLE` column list and SQL Server names it (`PK__my_model__3213E83F…`). It is validated as the table is built, so a violation fails before the new table is swapped in and the previous one is left untouched.
301
+
302
+ A *model-level* constraint carrying `name:` is applied by `ALTER TABLE … ADD CONSTRAINT` after the build swaps the new table into place and drops the old one. SQL Server scopes constraint names per schema, and a table is built alongside the one it replaces, so that is the first moment the name is free to reuse — naming a constraint inline would collide with the outgoing table (`Msg 2714`) on every rebuild after the first. The trade-off is that this runs after the model has committed: if the data violates the constraint, the model fails with the table already in place but unconstrained, and — as with any failure this late in a build — `post_hook`s declared with `transaction: false` do not run.
303
+
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.
305
+
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).
307
+
308
+ ```yaml
309
+ models:
310
+ - name: fact_sales
311
+ config:
312
+ contract:
313
+ enforced: true
314
+ constraints:
315
+ # named: applied by ALTER TABLE after the swap
316
+ - type: primary_key
317
+ name: PK_fact_sales
318
+ columns: [sale_id]
319
+ - type: foreign_key
320
+ name: FK_fact_sales_customer
321
+ columns: [customer_id]
322
+ to: ref('dim_customer')
323
+ to_columns: [customer_id]
324
+ # unnamed: rendered into the CREATE TABLE
325
+ - type: check
326
+ expression: amount >= 0
327
+ columns:
328
+ - name: sale_id
329
+ data_type: int
330
+ constraints:
331
+ - type: not_null
332
+ - name: customer_id
333
+ data_type: int
334
+ constraints:
335
+ - type: not_null
336
+ - name: amount
337
+ data_type: decimal(18,2)
338
+ ```
339
+
340
+ #### Clustering
341
+
342
+ `primary_key` and `unique` are emitted as `NONCLUSTERED` by default so they can coexist with the clustered columnstore index built for [`as_columnstore`](#as_columnstore). Use dbt's own `expression` field to ask for something else:
343
+
344
+ ```yaml
345
+ constraints:
346
+ - type: primary_key
347
+ name: PK_fact_sales
348
+ columns: [sale_id]
349
+ expression: clustered # requires as_columnstore: false
350
+ ```
351
+
352
+ `clustered` and `nonclustered` are the only values understood here. `expression` is free text that dbt splices between the keyword and the column list, which is the one place T-SQL accepts nothing else — index options such as `with (fillfactor = 90)` belong on a separate index, not on the constraint.
353
+
354
+ #### Foreign keys
355
+
356
+ Two things are worth knowing before adding them:
357
+
358
+ - **They are not free at build time.** Every load is validated against them; add them where you want the guarantee, not everywhere the relationship exists.
359
+ - **A foreign key pointing at a model blocks that model's rebuild.** The build renames the outgoing table to a backup and drops it, but the child's foreign key follows the renamed object, so the drop fails with `Msg 3726`. SQL Server has no `DROP TABLE … CASCADE`.
360
+
361
+ The adapter ships a macro for exactly this, meant as a `pre_hook` on the **referenced** (parent) model:
362
+
363
+ ```sql
364
+ {{ config(pre_hook="{{ drop_fk_constraints() }}") }}
365
+ ```
366
+
367
+ It drops the foreign keys in both directions — the inbound ones other tables hold against this model, and this model's own outbound ones — so the rebuild's backup drop succeeds. The trade-off is explicit and worth stating: **the child's foreign key does not exist between the parent's rebuild and the child's next build.** (dbt-postgres makes the same trade silently, by issuing every `drop table` with `cascade`.)
368
+
369
+ Add the hook *before* the first rebuild. Once a rebuild has failed with `Msg 3726`, the new table is already in place but without its own named constraints — those are applied after the backup drop that failed — and the child's key now points at `<model>__dbt_backup`, because a foreign key follows the object, not the name. A plain `dbt run` does not recover: the parent trips over the same backup and the child is skipped behind it. Rebuild the child alone — that run fails too, since the parent has no key to reference, but its swap drops the old child and the stale key with it — and a plain `dbt run` then rebuilds both in order. Dropping the child's key by hand (`ALTER TABLE <child> DROP CONSTRAINT <name>`) does the same.
370
+
371
+ `table_refresh_method: dml` is *not* a workaround. Its steady-state refresh issues `DELETE FROM <parent>`, which fails with `Msg 547` as soon as the child holds referencing rows, and its schema-change path falls back to the same rename-swap, hitting `Msg 3726` anyway.
372
+
373
+ - **SQL Server has no cross-database foreign keys.** `to: ref(...)` resolves to a fully qualified relation, database included, which SQL Server accepts as long as it names the current database. A target in another database fails with `references invalid table`.
374
+
375
+ A foreign key also does not order the build on its own: add an explicit `-- depends_on: {{ ref('dim_customer') }}` to the child model so the parent is built first.
376
+
377
+ #### Changing a constraint after the first build
378
+
379
+ Named constraints are applied by an `ALTER TABLE` whose `ADD` is guarded on the name already being present on the table, so:
380
+
381
+ - **Adding** a constraint to a model that already exists lands on its next run — no `--full-refresh` needed, on `table` and `incremental` alike.
382
+ - **Changing** an existing constraint's definition while keeping its name is *not* detected: a constraint name, unlike a `dbt_idx_` index name, says nothing about what the constraint does. Run `--full-refresh` to apply the new definition; that rebuilds the table, so the constraint is created fresh. (A `table` model rebuilds on every run and needs nothing special.)
383
+ - **Renaming** a constraint on a table that persists across runs adds the new name beside the old one. For a `check`, `unique` or `foreign_key` that means a duplicate; for a `primary_key` the run fails with `Msg 1779` (*table already has a primary key defined on it*) until the old one is dropped. Rename with `--full-refresh`.
384
+ - **Removing** a constraint from the yaml does not drop it from the database. Drop it yourself, or `--full-refresh`.
385
+
386
+ Every bullet above is about *named* constraints. Unnamed ones ride the `CREATE TABLE`, so they follow the table and change only when the table is rebuilt — on a materialization whose table persists across runs (`incremental` in its steady state, `table_refresh_method: dml`), adding an unnamed constraint to an existing model does nothing until a `--full-refresh`, and the run still reports success. Name it, or full-refresh.
387
+
388
+ #### Build-shape notes
389
+
390
+ An *unnamed* `primary_key` or `unique` constraint on a column that also carries a [data mask](#dynamic-data-masking-masked_with--masks) is rejected by the build. The constraint rides the `CREATE TABLE`, so its index already exists by the time `apply_masks` runs, and the adapter refuses to mask any column that an index has as a key: *is configured for masking but is also an index key column*. Declare that constraint at the model level **with a `name:`** instead — named constraints are applied by `ALTER TABLE` after the masks are in place, which the adapter allows.
391
+
392
+ A contract-enforced model is always loaded as `CREATE TABLE` followed by `INSERT … WITH (TABLOCK)`, on every build path. A `primary_key` or `unique` constraint — named or not — puts a nonclustered index on that table *before* the load, so the index is maintained row by row while the rows go in, and that part of the load is fully logged where an index-free heap would have been minimally logged. The cost lands on every rebuild of the model; it is most visible with `full_refresh_build: prebuilt`, whose point is a cheap bulk load. If a model's load time matters, weigh the key constraints against it; `check`, `not_null` and `foreign_key` do not create indexes and do not carry this cost.
393
+
270
394
  ### Dynamic Data Masking (`masked_with` / `masks`)
271
395
 
272
396
  The adapter can apply SQL Server [Dynamic Data Masking](https://learn.microsoft.com/en-us/sql/relational-databases/security/dynamic-data-masking) (DDM) to columns as part of the materialization, so masks are re-applied on every build and survive dbt's drop-and-recreate on a full refresh. A principal granted `SELECT` but not `UNMASK` then sees masked values instead of real data (dbt's own build principal, being `db_owner`, keeps `UNMASK` and reads real data). Requires **SQL Server 2016+**.
@@ -337,8 +461,8 @@ The `denies` config re-applies object-level DENYs after each build, diffed again
337
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)
338
462
 
339
463
  This adapter is community-maintained.
340
- You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in the Slack channel.
341
- If you're unsure how to get started, check out our [contributing guide](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).
342
466
 
343
467
  ## License
344
468
 
@@ -0,0 +1 @@
1
+ version = "1.12.1"