Skip to content

Commit 1da1d79

Browse files
authored
Merge pull request #817 from axellpadilla/chore/1.12.0-rc3-release
chore: prepare 1.12.0rc3 release
2 parents be7e0f4 + 226119a commit 1da1d79

7 files changed

Lines changed: 28 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
- **Behavior change:** `dbt_sqlserver_use_default_schema_concat` now defaults to `True`: a custom schema is concatenated onto `target.schema` (dbt-core's standard `generate_schema_name` behavior) instead of being used directly. This also matches the behavior dbt-core v2 (Fusion) will ship unconditionally. The `False` (legacy, no prefix) behavior is deprecated and will be removed in a future release; set the flag explicitly to keep it during the deprecation window, or override `sqlserver__generate_schema_name` in your project for a permanent solution. [#800](https://github.com/dbt-msft/dbt-sqlserver/issues/800)
1212
- Add an experimental `adbc` backend (`backend: adbc`), an alternative to `pyodbc`/`mssql-python` built on [ADBC](https://arrow.apache.org/adbc/) that talks to SQL Server via `go-mssqldb` instead of an ODBC/DB-API bridge. Install with `dbt-sqlserver[adbc]` plus the separate `dbc` CLI-installed driver binary; supports SQL Server (user/password) authentication only for now. See [docs/adbc_backend.md](docs/adbc_backend.md). [#771](https://github.com/dbt-msft/dbt-sqlserver/issues/771)
1313
- Add a model-level `denies` config that re-applies object-level `DENY` permissions after each build, diffed against `sys.database_permissions`, so an object DENY survives dbt's drop-and-recreate. A SQL Server object DENY is the only way to carve an exception out of a schema-level GRANT, but it is stored against `object_id` and was silently discarded on every rebuild (every run for a view), leaving a fail-open posture. Shaped like `grants` (`{privilege: [principals]}`); covers `table`, `view`, `incremental` and `snapshot`; emits `DENY`/`REVOKE` only for what changed; warns-and-skips an absent principal; and is a no-op on other adapters. Mirrors the existing `masks` re-application. See the README for details.
14+
- Add the `sqlserver__openquery` macro for safely executing pass-through queries against linked servers, including remote-SQL quote escaping, carriage-return stripping and the SQL Server 8 KB query-length validation. Add a SQL Server best-practices guide covering when to use `OPENQUERY` instead of distributed four-part-name joins.
1415

1516
#### Bugfixes
1617

1718
- Fix a `view` model silently skipping a rebuild when text was removed from the *start* of its body (e.g. deleting a leading comment or CTE). The skip test compared the stored definition against the model with `endswith()`, so any edit whose new body was a tail of the old one looked unchanged: `dbt run` reported `PASS` but the change never reached the database, and `--full-refresh` did not fix it. The header (`CREATE [OR ALTER] VIEW <name> AS`) is now split off at its separating ` AS ` and the body compared exactly. The comparison also no longer lowercases or strips whitespace, both of which made genuinely different bodies (a string literal differing only in case, or any literal containing spaces) compare equal; where the definition cannot be parsed with certainty the view is rebuilt rather than skipped.
1819
- Fix snapshots failing on their second and later runs with `Invalid object name '..._dbt_tmp'`, and contract-enforced models silently losing their in-transaction `pre_hook` writes. `get_column_schema_from_query` reads a query's column shape by executing it, then returned without fetching the rows or closing the cursor. Closing a cursor whose result set the server is still producing makes the driver cancel the request, and SQL Server answers that cancel by rolling back the open transaction, since every connection runs `SET XACT_ABORT ON` (#718). Nothing is raised for any of it, so the snapshot lost the staging table it had just built and failed against it a statement later. The probe now drains and closes its cursor, as does the row-count probe in `expand_column_types`. Only queries opening with a CTE were affected - anything else is wrapped as `select * from (...) where 1 = 0` by `sqlserver__get_empty_subquery_sql` and returns no rows - which is why snapshot staging queries (`with snapshot_query as ...`, both `check` and `timestamp` strategies) and CTE-headed contract models were the ones that broke.
1920
- Fix models failing with `Incorrect syntax near '\'` when the schema name needs delimiters, such as a domain-qualified `domain\user`. The clustered columnstore index name embeds the schema and was emitted as a bare identifier, so the generated DDL did not parse. [#409](https://github.com/dbt-msft/dbt-sqlserver/issues/409)
2021
- Fix identifiers built inside string literals not being quoted, which broke schema names containing a `.` or a `"`. `OBJECT_ID('schema.table')` returns `NULL` rather than erroring for such a name, so the failures were silent: the drop-before-create guards in `create_table_as` treated an existing table as absent (then hit `Msg 2714`), and the mask introspection in `apply_masks` found no columns, so configured masks were never applied. `sp_rename` was affected too, failing the table rename-swap with `No item by the name of ...`. All now pass quoted, qualified names. [#785](https://github.com/dbt-msft/dbt-sqlserver/issues/785)
22+
- Fix the `sqlserver__openquery` macro to quote linked-server names through `adapter.quote()`, keeping its generated identifier style consistent with the rest of the adapter.
2123

2224
#### Under the hood
2325

README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
[dbt](https://www.getdbt.com) adapter for Microsoft SQL Server and Azure SQL services.
44

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.
77

88
## Supported Python versions
99

@@ -15,6 +15,7 @@ The adapter is tested against:
1515
| 3.11 | Officially supported |
1616
| 3.12 | Officially supported |
1717
| 3.13 | Officially supported |
18+
| 3.14 | Officially supported |
1819

1920
## Supported SQL Server versions
2021

@@ -178,19 +179,28 @@ Safe expansions are further gated by `column_type_expansion_max_rows` (default 1
178179

179180
### `dbt_sqlserver_use_dbt_transactions`
180181

181-
_(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.
182+
_(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.
182183

183-
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`.
184+
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.
184185

185186
The driver connection remains in autocommit mode (`autocommit=true`) in both modes.
186187

187-
This mode is opt-in and should be tested carefully with project-specific materializations and hooks.
188+
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.
188189

189190
```yaml
190191
# dbt_project.yml
191192
flags:
192193
dbt_sqlserver_enable_safe_type_expansion: true
193-
dbt_sqlserver_use_dbt_transactions: true # <-- opt-in; default is false
194+
dbt_sqlserver_use_dbt_transactions: true # default
195+
```
196+
197+
### `dbt_sqlserver_use_native_string_types`
198+
199+
*(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)`.
200+
201+
```yaml
202+
flags:
203+
dbt_sqlserver_use_native_string_types: false # deprecated legacy behavior
194204
```
195205

196206
### `xact_abort`
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = "1.12.0rc2"
1+
version = "1.12.0rc3"

docs/adbc_backend.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ pyodbc and mssql-python. ADBC (Arrow Database Connectivity) uses the
55
[Apache Arrow](https://arrow.apache.org/) columnar format natively, avoiding
66
the overhead of row-based ODBC/DB-API bridges.
77

8-
> **Experimental.** The ADBC backend is new and opt-in. It passes the full
9-
> dbt-sqlserver functional test suite (320 passed, 0 failed, as of v1.12.0-rc1)
10-
> and is covered by a dedicated CI job against the latest SQL Server. Please
8+
> **Experimental.** The ADBC backend is new and opt-in. It is covered by a
9+
> dedicated CI job against the latest SQL Server. Please
1110
> report issues at
1211
> [dbt-msft/dbt-sqlserver#771](https://github.com/dbt-msft/dbt-sqlserver/issues/771).
1312

docs/sqlserver-best-practices.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ select * from {{ sqlserver__openquery(
160160
The adapter ships
161161
[`sqlserver__openquery`](../dbt/include/sqlserver/macros/utils/openquery.sql) for
162162
this: it doubles single quotes in the remote SQL, strips carriage returns,
163-
brackets the server name, and fails compilation with a specific message if the
163+
quotes the server name through `adapter.quote()`, and fails compilation with a specific message if the
164164
escaped query exceeds the 8 KB limit or either argument is empty. Writing
165165
`OPENQUERY(...)` by hand means escaping every literal yourself — the date
166166
predicate above would need `''2026-01-01''`.

tests/functional/adapter/mssql/test_index_macros.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
# name (#409), the dot and the quote reach identifiers built inside string
125125
# literals. Raw string: what is written here is exactly what Jinja parses.
126126
backslash_schema_model = r"""
127-
{{ config(materialized='table', schema=target.schema ~ '_dom\\usr.x\"q') }}
127+
{{ config(materialized='table', schema='dom\\usr.x\"q') }}
128128
select 1 as id
129129
"""
130130

tests/functional/adapter/mssql/test_masks.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def select_as_unprivileged(project, table_name, columns):
7575
# A schema whose name needs delimiters: a dot made the bare OBJECT_ID() lookup
7676
# in the mask macros return NULL, so masks were silently never applied (#785).
7777
dotted_schema_model_sql = """
78-
{{ config(materialized="table", schema=target.schema ~ '.x') }}
78+
{{ config(materialized="table", schema='dotted.x') }}
7979
select cast('Smith' as varchar(50)) as surname
8080
"""
8181

@@ -119,7 +119,7 @@ def models(self):
119119
@pytest.fixture(scope="class", autouse=True)
120120
def drop_dotted_schema(self, project):
121121
yield
122-
schema = f"{project.test_schema}.x"
122+
schema = f"{project.test_schema}_dotted.x"
123123
with get_connection(project.adapter):
124124
project.adapter.execute(f'DROP TABLE IF EXISTS "{schema}".dotted_schema_model')
125125
project.adapter.execute(f'DROP SCHEMA IF EXISTS "{schema}"')
@@ -142,7 +142,9 @@ def test_masks_applied_in_a_schema_needing_delimiters(self, project):
142142
"""A dot in the schema made OBJECT_ID() return NULL, so the mask
143143
introspection found nothing and masks were silently skipped (#785)."""
144144
run_dbt(["run"])
145-
masks = masked_columns(project, "dotted_schema_model", schema=f"{project.test_schema}.x")
145+
masks = masked_columns(
146+
project, "dotted_schema_model", schema=f"{project.test_schema}_dotted.x"
147+
)
146148
assert masks.get("surname") == "default()"
147149

148150
def test_unprivileged_user_sees_masked_values(self, project):

0 commit comments

Comments
 (0)