Skip to content

feat(sqlserver): Part 4 — relation quoting and factory wiring - #14

Merged
axellpadilla merged 1 commit into
sqlserver-v2-portfrom
part-4-sqlserver-relation
Aug 3, 2026
Merged

feat(sqlserver): Part 4 — relation quoting and factory wiring#14
axellpadilla merged 1 commit into
sqlserver-v2-portfrom
part-4-sqlserver-relation

Conversation

@axellpadilla

@axellpadilla axellpadilla commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Part 4 of the SQL Server Fusion adapter series. Part of dbt-labs#15714.

Stacked on #13 (Part 3). Base is part-3-sqlserver-auth, so this diff shows only Part 4's three files. Merge #11#12#13 first; this retargets automatically as each lands.

Two exhaustive matches stopped compiling when Part 1 added the variant, and relation_impl.rs has none — every arm it needs is a silent-correctness one that shows up as wrong SQL rather than a build failure, so this was worked from the list in the plan rather than from cargo build output.

backend_of — not claimed by any issue

adapter_factory.rs backend_of maps AdapterType::SqlServer to Backend::SQLServer, the same ADBC backend Fabric rides. It is one of the 45 E0004s and no issue in the series names it; #4 is the closest home, since without it no adapter can be constructed at all. Flagging it so the series checklist can be corrected.

relation_impl.rs

get_database joins the Databricks | Fabric | Postgres | … group that raises InvalidConfig when database is unset. The _ arm returns an empty string, so without the arm a relation missing a database renders as .schema.table rather than erroring.

get_canonical_fqn joins Fabric | Bigquery, which pass an unquoted path part through verbatim rather than case-folding it. SQL Server stores an identifier with the case it was given.

normalize_component is deliberately left alone. It models how the server resolves an unquoted name, which is a different question — folding to lower case is the correct canonicalization for a case-insensitive collation, and that is the bucket Fabric already sits in. Passing through here would make MyTable and mytable distinct semantic FQNs on a server that treats them as one object.

quoted now doubles an embedded delimiter for SQL Server. The shared default in dbt-schemas is:

fn quoted(&self, s: &str) -> String {
    format!("{}{}{}", self.quote_character(), s, self.quote_character())
}

so x"q rendered as "x"q" — unparseable, and v2 would reject names v1 accepts. dbt-sqlserver v1 fixed exactly this in dbt-msft/dbt-sqlserver#795, and the escaping is part of the quoting decision this port is built on.

Scoped to SqlServer rather than fixed in the shared default on purpose: quoted() is inherited by every adapter with a quote character, and widening it here would change rendering for Snowflake, Postgres, Redshift and Fabric in a SQL Server PR. Worth raising separately — the shared default looks wrong for all of them — but not worth burying in this diff.

new_sqlserver mirrors new_fabric. Its callers (metadata/get_relation.rs, the metadata module) arrive in Part 5, which is why it has none yet.

include_policy needs nothing. Its _ => Policy::trues() is already correct for 3-part naming; every explicit arm there is an adapter that drops a path part. #4's checklist lists it as work — it isn't.

Verification

Five tests in a new sqlserver module: three-part rendering, the missing-database error, delimiter doubling (dom\usr + x"q"my_db"."dom\usr"."x""q"), case preservation through get_canonical_fqn, and construction through RelationStatic.

dbt-adapter still has 43 E0004s (Parts 5–7), so cargo test -p dbt-adapter cannot run on this branch as-is. To actually run these rather than just write them, I stubbed the remaining 43 arms locally with todo!(), ran the suite, and reverted the stubs before committing:

  • 881 passed / 0 failed, the full dbt-adapter suite
  • all 5 new tests pass
  • the pre-existing generic::test_normalized_fqn_* tests still pass, which is what pins the quoted override to SQL Server only

The workspace count drops 46 → 44 here: relation/factory.rs and adapter/adapter_factory.rs. The rest are adapter_impl.rs (35), sql_types.rs (5), column_builder.rs (2), metadata/get_relation.rs (1) — and one outside this crate, dbt-df-providers seed_io.rs infer_seed_column_name_strategy, which no issue in the series claims. Earlier PRs in this stack said 45, all in dbt-adapter; that came from per-crate checks that never reached dbt-df-providers.

Collation

normalize_component is left on the lowercase arm above, which is correct under SQL Server's default _CI_ collation and wrong under a _CS_ one, where MODEL and model are two objects that fold to one semantic_fqn. Reachable only when a project turns quoting off — DEFAULT_RESOLVED_QUOTING is all-true, and the quoted path skips the fold entirely.

dbt-sqlserver v1 has the same defect and knows it: TestCachingUppercaseModel is @pytest.mark.skipped with "Fails because of case sensitivity. MODEL is coereced to model which fails the test as it sees conflicting naming." So this matches v1 rather than regressing against it, but nothing in dbt-core models a collation at all, and SQL Server is the only adapter where case behavior is configurable per database and per column. Marked TODO at the arm.

Closes #4

axellpadilla added a commit to dbt-sqlserver-next/dbt-sqlserver-v2-roadmap that referenced this pull request Aug 2, 2026
§5.5's relation section asked whether the shared rendering path escapes an
embedded delimiter, and left where to fix it open. It does not:
`BaseRelation::quoted` in dbt-schemas interpolates verbatim, so `x"q`
rendered `"x"q"`. Fixed with a SqlServer arm in `Relation`'s override
rather than in the shared default, because `quoted()` is inherited by every
adapter with a quote character and the wider fix would change Snowflake,
Postgres, Redshift and Fabric rendering inside a SQL Server PR.

Two additions the section didn't have. `normalize_component` needs to stay
on the lowercase arm even though `get_canonical_fqn` moves to the
pass-through one — they model different things, and passing through there
would split one object into two `semantic_fqn`s under a case-insensitive
collation. And `adapter_factory.rs` `backend_of` is an E0004 that no issue
in the series claims; it landed with Part 4 because nothing can construct
an adapter without it.

dbt-sqlserver-next/dbt-core#14.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two exhaustive matches stopped compiling when Part 1 added the variant --
`create_static_relation` and `backend_of` -- and `relation_impl.rs` has
none, so every arm it needs is a silent-correctness one that shows up as
wrong SQL rather than a build failure.

`backend_of` maps `SqlServer` to `Backend::SQLServer`, the same ADBC
backend Fabric rides. No adapter can be constructed without it, and no
issue in the series claimed it.

In `relation_impl.rs`:

- `get_database` joins the group that raises `InvalidConfig` when
  `database` is unset. The `_` arm returns an empty string, so without this
  a relation missing a database renders as `.schema.table`.
- `get_canonical_fqn` joins `Fabric | Bigquery`, which pass an unquoted
  path part through verbatim. SQL Server stores the case it was given.
  `normalize_component` is deliberately left alone: it models how the
  server resolves an unquoted name, and folding to lower case is the right
  model for a case-insensitive collation, which is where Fabric already
  sits.
- `quoted` doubles an embedded delimiter for `SqlServer`. The shared
  default interpolates verbatim, so `x"q` rendered `"x"q"` -- unparseable,
  and v2 would have rejected names v1 accepts. The override is scoped to
  this adapter rather than fixed in `dbt-schemas`, where it would change
  rendering for every adapter that quotes.
- `new_sqlserver` mirrors `new_fabric`; its callers arrive with the
  metadata module.

`include_policy` needs nothing: `_ => Policy::trues()` is already right for
3-part naming, and the explicit arms there are all adapters that drop a
path part.

Five tests in a `sqlserver` module: three-part rendering, the missing
database error, delimiter doubling, case preservation through
`get_canonical_fqn`, and construction through `RelationStatic`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@axellpadilla
axellpadilla force-pushed the part-4-sqlserver-relation branch from d76021e to 9d83076 Compare August 2, 2026 17:28
axellpadilla added a commit to dbt-sqlserver-next/dbt-sqlserver-v2-roadmap that referenced this pull request Aug 2, 2026
Every case decision in the Rust is a per-AdapterType constant, and SQL
Server is the only supported adapter whose case behavior is configurable
per database, per column and per comparison. `05`'s risk list now names all
four sites that bake the assumption in, with what each one gets wrong on a
_CS_ server: `normalize_component` (folds, merging MODEL and model — v1
has the same defect and skips the test that catches it),
`get_canonical_fqn` (passes through, safe either way),
`infer_seed_column_name_strategy`, and the Part 5 catalog queries, where v1
forces the issue with `collate database_default`. The first is marked TODO
in the code as of dbt-sqlserver-next/dbt-core#14.

The census in §5.3 said the remaining 45 all belong to dbt-adapter. It was
46, and one is in dbt-df-providers — `infer_seed_column_name_strategy`,
which no issue in the Part series claims and which is itself a collation
decision. Measured with `cargo check --workspace` rather than per-crate,
which is what missed it the first time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@axellpadilla
axellpadilla changed the base branch from part-3-sqlserver-auth to sqlserver-v2-port August 3, 2026 01:18
@axellpadilla
axellpadilla merged commit 2830179 into sqlserver-v2-port Aug 3, 2026
@axellpadilla axellpadilla linked an issue Aug 3, 2026 that may be closed by this pull request
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(sqlserver): Part 4 — relation quoting and factory wiring

1 participant