feat(sqlserver): Part 3 — SQL login auth and TLS connection args - #13
Open
axellpadilla wants to merge 1 commit into
Open
feat(sqlserver): Part 3 — SQL login auth and TLS connection args#13axellpadilla wants to merge 1 commit into
axellpadilla wants to merge 1 commit into
Conversation
`SQLServerAuth` reached the driver with Entra flows only, and built a URI carrying just host, port and database. A native SQL Server login -- the default in every dbt-sqlserver v1 profile -- had no path through it, and `encrypt` / `trust_cert` / `login_timeout` were parsed into the profile struct and then dropped. `SqlLogin` is a new top-level `SQLServerAuthIR` variant rather than a refinement of an existing one: a server-local login is a different authentication contract from a federated Entra token, not a subtype of it. It sets `user id` and `password` with no `fedauth`, reading `UID` and `PWD` the same way `ActiveDirectoryPassword` does. `authentication: sql` selects it, case-insensitively, alongside the existing `serviceprincipal` normalization. `apply_connection_args` now emits `encrypt`, `TrustServerCertificate` and `connection timeout`. Values and defaults come from v1's `build_adbc_connection_uri`, which builds the same query string against the same driver: `encrypt=true`, `trust_cert=false`, and `login_timeout` omitted when it is not positive. Both flags accept a YAML boolean or its string spelling, and an unrecognized value falls back to the default rather than erroring -- in both cases the default is the safe direction. Measured against a live SQL Server 2022 through the same go-mssqldb ADBC driver v1.6.0 that dbt installs, using the URI shape this produces: - the SQL login connects, and a `!` in the password survives the query-pair encoding - `connection timeout=30` is accepted - `encrypt=true` with `TrustServerCertificate=false` fails the handshake against a self-signed certificate, which is what these parameters exist to let an on-prem profile opt out of Ten unit tests cover the new paths. `DEFAULT_AUTH` is unchanged: it is shared with Fabric, which maps to the same backend. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
axellpadilla
added a commit
to dbt-sqlserver-next/dbt-sqlserver-v2-roadmap
that referenced
this pull request
Aug 2, 2026
…pter Part 3 (dbt-sqlserver-next/dbt-core#13) landed SQL login auth and the `encrypt` / `TrustServerCertificate` / `connection timeout` params. Two of §5.4's remaining items turned out to be stated wrongly, both checkable: `src/init.rs` cannot live in `dbt-auth`. dbt-core has no per-connection init hook — `AdbcEngine::new_connection_with_config` goes straight to `connection::Builder::default().build()` — and the one precedent, `apply_duckdb_init_sql`, runs once per database on a throwaway connection, which cannot carry a SQL Server session setting. The file would have no call site. It belongs in dbt-adapter next to the hook, so §5.4 and `05` #1 both now say so. And it should not be about `QUOTED_IDENTIFIER`. Measured through the same go-mssqldb ADBC driver v1.6.0 dbt installs, against `make server`: QUOTED_IDENTIFIER, ANSI_NULLS and CONCAT_NULL_YIELDS_NULL are all on, and `@@options & 16384 = 0` — XACT_ABORT is off. That reproduces #795's finding and identifies the setting that actually differs from v1, whose macro bodies say in comments that they rely on it. Two smaller corrections: the plan told the implementer to write SQL auth into the URI userinfo, which would have skipped the percent-encoding the existing query-pair form gets for free; and it did not mention that `DEFAULT_AUTH` can't be changed to v1's `sql` default without changing Fabric, which shares the module through `Backend::SQLServer`. Named instances are recorded as still broken rather than fixed: the `url` crate rejects a backslash in a host, and nothing here can confirm go-mssqldb decodes `%5C` back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 3 of the SQL Server Fusion adapter series. Part of dbt-labs#15714.
SQLServerAuthreached the driver with Entra flows only, and built a URI carrying just host, port and database. A native SQL Server login — the default in every dbt-sqlserver v1 profile — had no path through it, andencrypt/trust_cert/login_timeoutwere parsed intoSqlServerDbConfigin Part 2 and then dropped.SqlLoginA new top-level
SQLServerAuthIRvariant rather than a refinement of an existing one: a server-local login is a different authentication contract from a federated Entra token, not a subtype of it (this crate'sAGENTS.md, invariant 5 — horizontal growth). It setsuser idandpasswordwith nofedauth, readingUID/PWDexactly asActiveDirectoryPassworddoes.authentication: sqlselects it, case-insensitively, alongside the existingserviceprincipalnormalization.Query pairs rather than URI userinfo, which is how v1's ADBC backend writes it. Both work with go-mssqldb, and the pair form matches the three variants already in the file — and
append_pairpercent-encodes, which closes the user/password encoding gap v1 handles by hand.Connection args
encrypt,TrustServerCertificateandconnection timeoutnow reach the URI. Values and defaults are ported from v1'sbuild_adbc_connection_uri(sqlserver_backend.py), which builds the same query string against the same driver, per dbt-msft/dbt-sqlserver#783:encrypt=true,trust_cert=false,login_timeoutomitted when it is not positive.Both flags accept a YAML boolean or its string spelling, following the
securehandling inclickhouse/mod.rs. An unrecognized value falls back to the default rather than erroring; in both cases the default is the safe direction (encrypted, certificate verified).Live measurement
Against SQL Server 2022 in the project's own container, through the same go-mssqldb ADBC driver v1.6.0 dbt installs, driving it with the URI shape this PR produces:
authentication: sql!in the password survives the encodingconnection timeout=30encrypt=true,TrustServerCertificate=falseTLS Handshake failed: … doesn't contain any IP SANsThat last row is the point of the parameters: without them an on-prem instance with a self-signed certificate cannot be reached, and there is no way to say so in the profile.
The same session also re-measured what dbt-msft/dbt-sqlserver#795 found, and one thing it didn't:
Not in this PR
SET QUOTED_IDENTIFIER ONconnection-init SQL (the third item on #3). Two findings move it out of scope here:SET XACT_ABORT ON(sqlserver_connections.py_apply_session_settings, EvaluateSET XACT_ABORT ONfor SQL Server DML refresh transactions dbt-msft/dbt-sqlserver#718), and that one is measurably off by default. Several v1 macros are written assuming it is on —create.sql,table_dml_refresh.sql,indexes.sqlall say so in comments.AdbcEngine::new_connection_with_configgoes straight toconnection::Builder::default().build(). The one precedent,apply_duckdb_init_sql, runs once per database on a throwaway connection — which cannot carry a SQL Server session setting, since every connection is its own session.So a
dbt-auth/src/sqlserver/init.rslanded here would be dead code with no call site. It belongs with thedbt-adapterwork in Parts 4–7, alongside the hook that executes it, and it should probably be aboutXACT_ABORTrather thanQUOTED_IDENTIFIER. Filed as a follow-up rather than stubbed.Named instances.
host: myserver\SQLEXPRESSfails at URI parse —invalid domain character— because theurlcrate rejects a backslash in the host. v1 handles this by omitting the port and letting the SQL Server Browser resolve it. Percent-encoding to%5Cparses on our side, but whether go-mssqldb decodes it back is not something this checkout can verify, and shipping an unverified host rewrite is worse than the loud error we have today. Left as aTODOat the parse site.DEFAULT_AUTHis unchanged. v1 defaultsauthenticationtosql; this crate defaults it toActiveDirectoryServicePrincipal. Changing it would also change Fabric, which maps to the same backend (AdapterType::Fabric => Backend::SQLServer) and shares this module — andauth_for_backendreceives only aBackend, so the two cannot be told apart here. Until that is resolved, a ported v1 profile that omitsauthenticationgets a "client_id is required" error rather than a SQL login. Loud, but not what a migrating user expects.Tests
Ten new tests in
sqlserver::tests: SQL login and its case-insensitive spelling, missing-credential rejection, password encoding, TLS defaults, TLS flags from YAML booleans and from strings, andlogin_timeoutapplied and omitted.cargo nextest run -p dbt-authis 287 passed / 0 failed;cargo fmt --checkandcargo clippy --all-targetsare clean.crates/dbt-auth/AGENTS.mdasks for a specific report on changes to this crate:SqlLoginholds&'a str; the only allocation islogin_timeout.to_string()at the URI boundary.get_str/get_stringbehavior: no existing call changed. New reads userequire_strforUID/PWD(matchingActiveDirectoryPassword) andget_stringforencrypt/trust_cert/login_timeout, so native YAML and string forms both work.authentication: sqlpreviously errored; no previously-valid value now fails.It also points at live smoke tests in
crates/dbt-auth-tests, which does not exist in this checkout — the live measurements above are the substitute.Closes #3