Skip to content

Commit 5e52aaf

Browse files
committed
feat(sqlserver): add optional mssql-python connection backend and docs (#681)
Add support for selecting either the existing pyodbc backend or the new mssql-python backend for SQL Server connections. Keep pyodbc as the default backend for backward compatibility, while allowing users to opt into mssql-python through the new backend configuration and optional dependency extra. Changes include: - Add backend selection using a SQL Server backend enum - Add lazy imports for optional backend dependencies - Keep pyodbc as the default backend - Add optional mssql-python dependency support - Update pyodbc validation for missing, empty, and whitespace-only driver values - Add authentication normalization and validation for SQL Server profile fields - Add mssql-python connection-string handling for MSI and integrated authentication - Add support for Active Directory access-token authentication - Improve connection-string sanitization for logging - Validate connection requirements and query timeout settings - Expand unit tests for backend selection, authentication handling, and connection behavior - Refactor integration tests to cover both pyodbc and mssql-python backends - Add a dedicated mssql CI image and backend requirements table - Update README and contributing docs for backend installation and development setup - Add workflow_dispatch support for CI/CD Docker publishing
1 parent b118945 commit 5e52aaf

32 files changed

Lines changed: 4285 additions & 624 deletions

.devcontainer/setup_env.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ set -euo pipefail
22

33
cp test.env.sample test.env
44

5+
sudo apt-get update
6+
sudo apt-get install -y libltdl7 libkrb5-3 libgssapi-krb5-2
7+
58
docker compose build
69
docker compose up -d
710

@@ -12,5 +15,6 @@ command -v uv >/dev/null 2>&1 || pip install uv
1215
[ -d .venv ] || uv venv
1316
source .venv/bin/activate
1417

15-
uv sync --group dev --extra pyodbc
18+
# Install both backend extras so the devcontainer can exercise either connection path.
19+
uv sync --group dev --extra pyodbc --extra mssql
1620
pre-commit install

.github/workflows/integration-tests-sqlserver.yml

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,72 @@ on: # yamllint disable-line rule:truthy
3535

3636
jobs:
3737
integration-tests-sql-server:
38-
name: Regular
38+
name: ${{ matrix.backend }} / Py${{ matrix.python_version }} / SQL${{ matrix.sqlserver_version }} / ODBC${{ matrix.msodbc_version }} / ${{ matrix.collation }}
3939
if: github.actor != 'dependabot[bot]'
4040
strategy:
41+
# Smaller matrix allows this to check if a failure is specific.
42+
fail-fast: false
4143
matrix:
4244
python_version: ["3.10", "3.11", "3.12", "3.13"]
43-
msodbc_version: ["17", "18"]
44-
sqlserver_version: ["2017", "2019", "2022"]
45-
collation: ["SQL_Latin1_General_CP1_CS_AS", "SQL_Latin1_General_CP1_CI_AS"]
45+
backend: [pyodbc, mssql-python]
46+
# Baseline on 2022
47+
sqlserver_version: ["2022"]
48+
msodbc_version: ["18"]
49+
collation: [SQL_Latin1_General_CP1_CI_AS]
50+
exclude:
51+
- backend: mssql-python
52+
python_version: "3.10"
53+
sqlserver_version: "2022"
54+
- backend: mssql-python
55+
python_version: "3.11"
56+
sqlserver_version: "2022"
57+
- backend: mssql-python
58+
python_version: "3.12"
59+
sqlserver_version: "2022"
60+
include:
61+
# Keep pyodbc on every supported Python version, but retain
62+
# SQL Server ODBC 17 coverage for the oldest and newest Python.
63+
- backend: pyodbc
64+
python_version: "3.10"
65+
sqlserver_version: "2022"
66+
msodbc_version: "17"
67+
collation: SQL_Latin1_General_CP1_CI_AS
68+
- backend: pyodbc
69+
python_version: "3.13"
70+
sqlserver_version: "2022"
71+
msodbc_version: "17"
72+
collation: SQL_Latin1_General_CP1_CI_AS
73+
# Older SQL Server versions stay on pyodbc only, with a single
74+
# collation so the matrix stays small and readable.
75+
- backend: pyodbc
76+
python_version: "3.10"
77+
sqlserver_version: "2017"
78+
msodbc_version: "17"
79+
collation: SQL_Latin1_General_CP1_CI_AS
80+
- backend: pyodbc
81+
python_version: "3.13"
82+
sqlserver_version: "2019"
83+
msodbc_version: "17"
84+
collation: SQL_Latin1_General_CP1_CI_AS
85+
# Add the case-sensitive collation only on the latest SQL Server
86+
# and latest Python/backend rows.
87+
- backend: pyodbc
88+
python_version: "3.13"
89+
sqlserver_version: "2022"
90+
msodbc_version: "17"
91+
collation: SQL_Latin1_General_CP1_CS_AS
92+
# mssql-python stays on the latest Python only.
93+
- backend: mssql-python
94+
python_version: "3.13"
95+
sqlserver_version: "2022"
96+
msodbc_version: "18"
97+
collation: SQL_Latin1_General_CP1_CS_AS
4698
runs-on: ubuntu-latest
4799
container:
48-
image: ghcr.io/${{ github.repository }}:CI-${{ matrix.python_version }}-msodbc${{ matrix.msodbc_version }}
100+
image: ghcr.io/${{ github.repository }}:CI-${{ matrix.python_version }}-${{ matrix.backend == 'pyodbc' && format('msodbc{0}', matrix.msodbc_version) || 'mssql' }}
101+
credentials:
102+
username: ${{ github.actor }}
103+
password: ${{ secrets.GITHUB_TOKEN }}
49104
services:
50105
sqlserver:
51106
image: ghcr.io/${{ github.repository }}:server-${{ matrix.sqlserver_version }}
@@ -63,12 +118,15 @@ jobs:
63118
run: pip install uv
64119

65120
- name: Install dependencies
66-
run: uv pip install --system -e ".[pyodbc]" --group dev
121+
env:
122+
INSTALL_EXTRA: ${{ matrix.backend == 'pyodbc' && 'pyodbc' || 'mssql' }}
123+
run: uv pip install --system -e ".[$INSTALL_EXTRA]" --group dev
67124

68125
- name: Run functional tests
69126
run: pytest -ra -v tests/functional --profile "ci_sql_server"
70127
env:
71128
DBT_TEST_USER_1: DBT_TEST_USER_1
72129
DBT_TEST_USER_2: DBT_TEST_USER_2
73130
DBT_TEST_USER_3: DBT_TEST_USER_3
74-
SQLSERVER_TEST_DRIVER: 'ODBC Driver ${{ matrix.msodbc_version }} for SQL Server'
131+
SQLSERVER_TEST_DRIVER: "ODBC Driver ${{ matrix.msodbc_version }} for SQL Server"
132+
SQLSERVER_TEST_BACKEND: ${{ matrix.backend }}

.github/workflows/publish-docker.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
name: Publish Docker images for CI/CD
33
on: # yamllint disable-line rule:truthy
4+
workflow_dispatch:
45
push:
56
paths:
67
- 'devops/**'
@@ -13,7 +14,7 @@ jobs:
1314
strategy:
1415
matrix:
1516
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
16-
docker_target: ["msodbc17", "msodbc18"]
17+
docker_target: ["msodbc17", "msodbc18", "mssql"]
1718
runs-on: ubuntu-latest
1819
permissions:
1920
contents: read

CONTRIBUTING.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,52 @@ The functional tests require a running SQL Server instance. You can easily spin
3232
make server
3333
```
3434

35+
### Backend requirements at a glance
36+
37+
| Backend | Python package | Debian/Ubuntu system packages |
38+
|---|---|---|
39+
| `pyodbc` | `dbt-sqlserver[pyodbc]` or `pyodbc` | `unixodbc-dev` plus the Microsoft ODBC Driver for SQL Server |
40+
| `mssql-python` | `dbt-sqlserver[mssql]` or `mssql-python` | `libltdl7`, `libkrb5-3`, `libgssapi-krb5-2` |
41+
42+
The default development flow uses the ODBC-based path, but the ODBC driver itself is now an optional dependency. If you want to develop or test that backend, install either the adapter extra or the driver itself before running tests.
43+
44+
```shell
45+
pip install -U "dbt-sqlserver[pyodbc]"
46+
# or
47+
pip install -U pyodbc
48+
```
49+
50+
If you want to develop or test the optional `mssql-python` backend instead, install either the adapter extra or the driver itself before running tests.
51+
52+
```shell
53+
pip install -U "dbt-sqlserver[mssql]"
54+
# or
55+
pip install -U mssql-python
56+
```
57+
58+
On Debian/Ubuntu-based environments, `mssql-python` may also require these system libraries:
59+
60+
```shell
61+
sudo apt-get install -y libltdl7 libkrb5-3 libgssapi-krb5-2
62+
```
63+
3564
This will use Docker Compose to spin up a local instance of SQL Server. Docker Compose is now bundled with Docker, so make sure to [install the latest version of Docker](https://docs.docker.com/get-docker/).
3665

3766
Next, tell our tests how they should connect to the local instance by creating a file called `test.env` in the root of the project.
3867
You can use the provided `test.env.sample` as a base and if you started the server with `make server`, then this matches the instance running on your local machine.
3968

69+
If you are testing the optional `mssql-python` backend, also enable its backend setting in `test.env` so the adapter selects that implementation instead of the legacy driver-based one.
70+
4071
```shell
4172
cp test.env.sample test.env
4273
```
4374

75+
When using the optional `mssql-python` backend, update `test.env` with:
76+
77+
```shell
78+
SQLSERVER_TEST_BACKEND=mssql-python
79+
```
80+
4481
You can tweak the contents of this file to test against a different database.
4582

4683
Note that we need 3 users to be able to run tests related to the grants.
@@ -57,6 +94,8 @@ make unit
5794
make functional
5895
```
5996

97+
This remains the documented test procedure for both connection backends. When the `pyodbc` path is enabled, run the same commands after installing `dbt-sqlserver[pyodbc]` or `pyodbc`. When the `mssql-python` backend is enabled, run the same commands after installing `dbt-sqlserver[mssql]` or `mssql-python` and setting `SQLSERVER_TEST_BACKEND=mssql-python` in `test.env`.
98+
6099
## CI/CD
61100

62101
We use Docker images that have all the things we need to test the adapter in the CI/CD workflows.

README.md

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,87 @@ E.g. version 1.1.x of the adapter will be compatible with dbt-core 1.1.x.
99

1010
We've bundled all documentation on the dbt docs site:
1111

12-
* [Profile setup & authentication](https://docs.getdbt.com/reference/warehouse-profiles/mssql-profile)
13-
* [Adapter documentation, usage and important notes](https://docs.getdbt.com/reference/resource-configs/mssql-configs)
12+
- [Profile setup & authentication](https://docs.getdbt.com/reference/warehouse-profiles/mssql-profile)
13+
- [Adapter documentation, usage and important notes](https://docs.getdbt.com/reference/resource-configs/mssql-configs)
1414

1515
Join us on the [dbt Slack](https://getdbt.slack.com/archives/CMRMDDQ9W) to ask questions, get help, or to discuss the project.
1616

1717
## Installation
1818

19-
This adapter requires the Microsoft ODBC driver to be installed:
19+
The default install uses the `pyodbc` backend and includes the `pyodbc` dependency. If you want the optional `mssql-python` backend instead, install the `mssql` extra.
20+
21+
Latest version: ![PyPI](https://img.shields.io/pypi/v/dbt-sqlserver?label=latest%20stable&logo=pypi)
22+
Latest pre-release: ![GitHub tag (latest SemVer pre-release)](https://img.shields.io/github/v/tag/dbt-msft/dbt-sqlserver?include_prereleases&label=latest%20pre-release&logo=pypi)
23+
24+
25+
### Backend requirements at a glance
26+
27+
| Backend | Python package | Debian/Ubuntu system packages |
28+
|---|---|---|
29+
| `pyodbc` | `dbt-sqlserver[pyodbc]` or `pyodbc` | `unixodbc-dev` plus the Microsoft ODBC Driver for SQL Server |
30+
| `mssql-python` | `dbt-sqlserver[mssql]` or `mssql-python` | `libltdl7`, `libkrb5-3`, `libgssapi-krb5-2` |
31+
32+
33+
### `pyodbc` backend
34+
35+
The legacy and currently default ODBC path uses `pyodbc` and the Microsoft ODBC driver.
36+
37+
```shell
38+
pip install -U dbt-sqlserver
39+
```
40+
41+
You should migrate to using an explicit extra in preparation for deprecation; the following is equivalent:
42+
43+
```shell
44+
pip install -U "dbt-sqlserver[pyodbc]"
45+
```
46+
47+
You also need the Microsoft ODBC driver for SQL Server installed on your system:
2048
[Windows](https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver16#download-for-windows) |
2149
[macOS](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver16) |
22-
[Linux](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver16)
50+
[Linux](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-sql-server?view=sql-server-ver16)
2351

2452
<details><summary>Debian/Ubuntu</summary>
25-
<p>
2653

27-
Make sure to install the ODBC headers as well as the driver linked above:
54+
Install the ODBC headers as well as the driver linked above:
2855

2956
```shell
3057
sudo apt-get install -y unixodbc-dev
3158
```
3259

33-
</p>
3460
</details>
3561

36-
Latest version: ![PyPI](https://img.shields.io/pypi/v/dbt-sqlserver?label=latest%20stable&logo=pypi)
62+
### `mssql-python` backend
63+
64+
An alternative backend that does not require the ODBC driver.
3765

3866
```shell
39-
pip install -U dbt-sqlserver
67+
pip install -U "dbt-sqlserver[mssql]"
4068
```
4169

42-
Latest pre-release: ![GitHub tag (latest SemVer pre-release)](https://img.shields.io/github/v/tag/dbt-msft/dbt-sqlserver?include_prereleases&label=latest%20pre-release&logo=pypi)
70+
On Debian/Ubuntu-based systems, `mssql-python` requires these system libraries:
4371

4472
```shell
45-
pip install -U --pre dbt-sqlserver
73+
sudo apt-get install -y libltdl7 libkrb5-3 libgssapi-krb5-2
74+
```
75+
76+
Enable it per target in your `profiles.yml`:
77+
78+
```yaml
79+
your_profile:
80+
target: dev
81+
outputs:
82+
dev:
83+
type: sqlserver
84+
host: your-server
85+
port: 1433
86+
database: your-database
87+
schema: dbo
88+
user: your-user
89+
password: your-password
90+
encrypt: true
91+
trust_cert: false
92+
backend: mssql-python # <-- enables this backend
4693
```
4794
4895
## Changelog
@@ -51,8 +98,6 @@ See [the changelog](CHANGELOG.md)
5198
5299
## Configuration
53100
54-
### Flags
55-
56101
- `dbt_sqlserver_use_default_schema_concat`: *(default: `false`)* Controls schema name generation when a [custom schema](https://docs.getdbt.com/docs/build/custom-schemas) is set on a model.
57102

58103
| Flag value | `custom_schema_name` | Result |
@@ -77,6 +122,31 @@ See [the changelog](CHANGELOG.md)
77122
> **Note:** If you want to permanently customise schema generation and avoid any future changes, override the `sqlserver__generate_schema_name` macro directly in your project instead.
78123

79124

125+
### `dbt_sqlserver_use_default_schema_concat`
126+
127+
*(default: `false`)* Controls schema name generation when a [custom schema](https://docs.getdbt.com/docs/build/custom-schemas) is set on a model.
128+
129+
| Value | `custom_schema_name` | Result |
130+
|---|---|---|
131+
| `false` (default) | *(none)* | `target.schema` |
132+
| `false` (default) | `"reporting"` | `reporting` |
133+
| `true` | *(none)* | `target.schema` |
134+
| `true` | `"reporting"` | `target.schema_reporting` |
135+
136+
When `false`, `custom_schema_name` is used as-is without being prefixed by `target.schema`.
137+
When `true`, the adapter delegates to dbt-core's `default__generate_schema_name`.
138+
139+
```yaml
140+
# dbt_project.yml
141+
vars:
142+
dbt_sqlserver_use_default_schema_concat: true
143+
```
144+
145+
> **Note:** To permanently customise schema generation without a flag dependency, override the `sqlserver__generate_schema_name` macro directly in your project.
146+
147+
### `backend`
148+
149+
*(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.
80150

81151
## Contributing
82152

@@ -85,7 +155,7 @@ See [the changelog](CHANGELOG.md)
85155
[![Integration tests on Azure](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/integration-tests-azure.yml/badge.svg)](https://github.com/dbt-msft/dbt-sqlserver/actions/workflows/integration-tests-azure.yml)
86156

87157
This adapter is community-maintained.
88-
You are welcome to contribute by creating issues, opening or reviewing pull requests or helping other users in Slack channel.
158+
You are welcome to contribute by creating issues, opening or reviewing pull requests, or helping other users in the Slack channel.
89159
If you're unsure how to get started, check out our [contributing guide](CONTRIBUTING.md).
90160

91161
## License

0 commit comments

Comments
 (0)