Skip to content

feat(sqlserver): consume clause-parameter pairs and declare measured probes - #7

Merged
vistart merged 10 commits into
release/v1.0.0.dev3from
feature/expression-schema-object-refactor
Oct 8, 2026
Merged

vistart merged 10 commits into
release/v1.0.0.dev3from
feature/expression-schema-object-refactor

Conversation

@vistart

@vistart vistart commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Description

This branch moves the SQL Server backend onto the expression/dialect separation that landed in core
(rhosocial/python-activerecord#125, merged as 5895bd3 on release/v1.0.0.dev30): an expression
collects parameters; the dialect decides spelling and support. It is the SQL Server repository of
the coordinated eleven-repository feature/expression-schema-object-refactor round.

Catalogue objects. Schema objects travel as objects, not parsed names (e6d44bd):

  • src/rhosocial/activerecord/backend/impl/sqlserver/expression/objects.py defines
    SQLServerTable (the # temporary prefix is a field settled at construction),
    PartitionFunction and PartitionScheme (each declaring a format_partition_*_object renderer).
  • src/rhosocial/activerecord/backend/impl/sqlserver/mixins/namespace.py implements core's
    NamespaceSupport for SQL Server: a catalog above the schema, both levels rendered, the
    128-character identifier cap at every level, and the documented bare-name format_object_id_name
    exception for OBJECT_ID (which does not strip brackets).
  • The dialect now declares core's split protocols — NamespaceSupport, one *ObjectSupport per
    catalogue kind, and Create*Support/Drop*Support/FulltextIndexSupport — and
    .claude/backend_development.md documents the mapping.

Clause pairs. Every two-spelling clause this dialect renders now carries one parameter per
spelling; "unspecified" is neither set, and setting both raises ValueError at construction. The
formatters consume each pair and refuse an explicitly requested spelling by name instead of
silently dropping it (802bed0, 16af634):

  • format_alter_sequence_statement (dialect.py): cycle/no_cycle, cache/no_cache (measured
    accepted on 2019/2022/2025); order/no_order refused by name (no ORDER option).
  • format_identity_clause (mixins/identity_column.py): cycle/no_cycle, cache/no_cache,
    order/no_order refused by name — IDENTITY(seed, increment) has no spelling for them; an
    unset pair renders the bare property.
  • format_set_operation_expression (mixins/set_operation.py): all_ rendered; distinct refused
    by name (T-SQL has no DISTINCT keyword in set operations; measured refused).
  • format_truncate_statement (dialect.py): cascade/restrict and
    restart_identity/continue_identity refused by name.
  • format_drop_view_statement (mixins/ddl_view.py) and format_drop_schema_statement
    (mixins/schema.py): cascade/restrict refused by name.
  • format_table_constraint, format_column_definition and _format_table_type_constraint
    (mixins/ddl_type.py): deferrable/not_deferrable, initially_deferred/initially_immediate,
    enforced/not_enforced gated on the existing probes and refused by name.
  • format_begin_transaction / format_set_transaction (mixins/transaction.py):
    deferrable/not_deferrable and wait/no_wait refused by name.

Two dialect-specific gaps closed (both were shape-C tri-states — one nullable bool meaning two
spellings):

  • SQLServerAlterColumn.not_null: Optional[bool] becomes not_null/nullable (NOT NULL /
    NULL); both set raises ValueError. Measured: unspecified ALTER COLUMN c INT is accepted and
    leaves the column nullable; both explicit spellings are accepted and take effect.
  • SQLServerColumnstoreIndexExpression.clustered: Optional[bool] becomes clustered/nonclustered;
    both set raises ValueError. Measured: the bare keyword requires a key list and the server records
    that form as NONCLUSTERED, so validate() allows columns when no keyword is set; the clustered
    form still forbids them.

Probes declared and corrected, each with a measured answer:

  • Sequence (4469d80, 7602a38): supports_sequence, supports_alter_sequence, the option
    probes (start/increment/minvalue/maxvalue/cycle/cache, 2012+) and the negatives (if_not_exists,
    order, owned_by) are now stated instead of inheriting core defaults;
    supports_sequence_if_exists is 2016+. supports_alter_sequence_start answers False: ALTER
    SEQUENCE rejects START WITH with error 11710 (RESTART WITH is the spelling). The local
    CREATE/DROP renderers were deleted — core's exact spelling once the probes answer — and the local
    DROP had gated IF EXISTS on 2012 rather than 2016. format_alter_sequence_statement stays
    (genuinely different grammar) and now consults its probes (8feb28a).
  • Identity (9cd15d0, c16c733): supports_identity_column true, generation_always false
    (ALWAYS is refused, never downgraded), start/increment true, minvalue/maxvalue/cycle/order/cache
    false, supports_auto_increment_column false.
  • Master probes (16af634): supports_truncate true, supports_with_data_clause false,
    supports_materialized_cte false, supports_transaction_wait false.
  • The BEGIN TRANSACTION WAIT trap: the server accepts BEGIN TRANSACTION WAIT — reading WAIT
    as the transaction name (sys.dm_tran_active_transactions.name returns WAIT) — so a naive
    probe would report support; NO WAIT, SET TRANSACTION WAIT and SET TRANSACTION NO WAIT are
    syntax errors. The probe answers False and both transaction formatters refuse a requested spelling
    by name.
  • New core restrict probes (supports_schema_restrict, supports_restrict_view,
    supports_drop_function_restrict, supports_materialized_view_restrict,
    supports_truncate_restrict): every candidate spelling was measured refused on 2019/2022/2025;
    the SQL Server overrides for DROP VIEW, DROP SCHEMA and TRUNCATE refuse an explicit spelling by
    name rather than silently dropping it.

Intended behaviour change: an explicit spelling whose probe is False now raises
UnsupportedFeatureError instead of rendering SQL that omitted the request; an unset pair renders
nothing where the old encoding spelled a negative form.

Dependencies. Core is already merged (PR #125 → 5895bd3), and 2bf5a42 repoints the workflow
from the core feature branch to release/v1.0.0.dev30; this PR does not wait on any unmerged core
branch and can be merged on its own. The testsuite install remains on its own
feature/expression-schema-object-refactor branch as part of the same round.

Adds # (no tracking issue; core's half of the round is rhosocial/python-activerecord#125)

Type of change

  • feat: A new feature
  • docs: Documentation only changes (if related to the new feature)
  • perf: A code change that improves performance (if part of the feature)
  • test: Adding missing tests or correcting existing tests (for the new feature)
  • chore: Changes to the build process or auxiliary tools and libraries suchs as documentation generation (if related to the new feature)
  • ci: Changes to our CI configuration files and scripts (if related to the new feature)
  • build: Changes that affect the build system or external dependencies (if related to the new feature)
  • revert: Reverts a previous commit

Breaking Change

  • Yes, for end-users (backward-incompatible API changes)
  • Yes, for backend developers (internal architectural changes that may affect custom implementations)
  • No breaking changes
    (If Yes, please describe the impact and migration path below):

Impact on End-Users:

Typical model/field/CRUD usage is unchanged — the round's before/after captures show every
unspecified state rendering byte-identically. Three narrow, end-user-visible breaks apply to code
that builds the SQL Server-specific expressions directly:

  1. SQLServerAlterColumn(..., not_null=False) used to spell NULL; it now means "unspecified".
    Migration: use nullable=True (the not_null=True form is unchanged).
  2. SQLServerColumnstoreIndexExpression(..., clustered=False) used to spell NONCLUSTERED; it now
    means "no keyword". Migration: use nonclustered=True.
  3. An explicitly requested spelling the server cannot produce (e.g. TRUNCATE ... CASCADE, identity
    ORDER/CACHE, transaction WAIT, UNION DISTINCT, constraint ENFORCED) now raises
    UnsupportedFeatureError instead of rendering SQL that silently omitted the request. Callers
    should remove the option, or switch to the spelling the dialect documents.

Both classes are exported from rhosocial.activerecord.backend.impl.sqlserver.expression (see its
__init__.py).

Impact on Backend Developers (Custom Implementations):

This backend follows core's protocol package split: the dialect is a NamespaceSupport and one
*ObjectSupport per catalogue kind; DDL protocols are one per statement
(CreateSchemaSupport/DropSchemaSupport, CreateIndexSupport/DropIndexSupport/
FulltextIndexSupport, CreateTypeSupport/DropTypeSupport, ...); schema objects render through
per-kind format_*_object methods. Its own protocol declarations follow: SQLServerTableSupport
derives from TableObjectSupport, SQLServerUserDefinedTypeSupport from CreateTypeSupport /
DropTypeSupport, and SQLServerSequenceSupport.format_next_value_for names
SQLServerNextValueForExpression (the old Any argument with the getattr/str() fallback is
gone). Custom subclasses that referenced the previous SchemaSupport/IndexSupport/TableSupport/
UserDefinedTypeSupport names, or relied on formatters silently dropping clause options, must be
updated to the new names and to the pair-consuming contracts.

Related Repositories/Packages

  • rhosocial/python-activerecord (core): PR #125, merged as 5895bd3 on release/v1.0.0.dev30.
    Required dependency; CI installs core from that line as of 2bf5a42.
  • rhosocial/python-activerecord-testsuite: installed from its
    feature/expression-schema-object-refactor branch in CI (same round; not yet on a release line).
  • Ten sibling backend repositories in the same feature/expression-schema-object-refactor round
    (eleven repositories total), all consuming the same core separation.

Testing

  • I have added new tests to cover my changes.
  • I have updated existing tests to reflect my changes.
  • All existing tests pass.
  • This change has been tested on [SQL Server 2017, 2019, 2022 and 2025].

Test Plan:

New guard files (run red first, green after):

  • tests/rhosocial/activerecord_sqlserver_test/feature/backend/test_clause_pair_guard_sqlserver.py
    — asserts the four states of every clause pair the dialect consumes are pairwise distinguishable.
    Red against the unmodified tree: 46 failed / 40 passed; after the change: 86 passed. Two mutations
    (a disabled no_cache branch, a disabled nullable branch) were each caught (1 failed / 85
    passed).
  • tests/rhosocial/activerecord_sqlserver_test/feature/backend/test_master_probes_guard_sqlserver.py
    — declaration completeness and a load-bearing walk for the four master probes, plus the four
    wait/no_wait states and dropping-formatter sentinels. Red: 20 failed / 21 passed; green:
    41 passed. Mutations (truncate gate removed; wait pair dropped) were each caught (2 failed / 1
    passed; 5 failed / 3 passed).
  • tests/rhosocial/activerecord_sqlserver_test/feature/backend/test_sequence_probes_sqlserver.py
    — walks every sequence option probe on CREATE/ALTER/DROP, flips each one in a subclass and asserts
    the behaviour flips with it; a self-test feeds the walk the pre-fix hard-coded shape and reports a
    decorative probe. It was red on ALTER order, NO ORDER and owned_by before the gates.

New / extended suites:

  • tests/rhosocial/activerecord_sqlserver_test/feature/backend/test_identity_clause_sqlserver.py
    and .../test_identity_execution_confirmation.py — rendering plus live execution verdicts, with
    sentinels that must come back REJECTED.
  • .../test_expression_roundtrip_all.py (+1238 lines), .../test_dialect_comprehensive.py,
    .../test_dialect_version_branches.py, .../test_sqlserver_protocol_conformance.py,
    .../test_schema_support.py, .../test_sqlserver_type_ddl.py — updated for catalogue objects,
    the renamed/split protocols, pair consumption and the probe answers.

Local pointed runs: the full pointed run recorded with the pair work (802bed0) reports
1348 passed, 9 skipped; the pointed re-run after the master-probe work (guard files plus the
roundtrip / dialect / protocol suites, 8 files) reports 1147 passed, 8 skipped. One async test
errors locally because aioodbc is not installed in the local venv (CI installs it).

CI: .github/workflows/test.yml runs nine jobs — Python 3.9/3.10/3.11/3.12/3.13/3.13t/3.14/3.14t
against SQL Server 2017, 2019, 2022 and 2025 containers. The workflow-dispatch run on this branch at
16af634 is green across all nine jobs; the final commit 2bf5a42 only repoints the core install
and has no fresh run recorded yet.

Live measurement: every probe answer and both dialect gaps were measured on SQL Server
2019/2022/2025 containers; the raw logs are kept in the round's (git-ignored) plan directory.

Checklist

  • My code follows the project's code style guidelines in .claude/code_style.md.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation (Docstrings, README.md, etc.).
  • My changes generate no new warnings.
  • I have added a Changelog fragment as described in .claude/version_control.md (changelog.d/<issue_number>.<type>.md).
  • I have verified that my changes do not introduce SQL injection vulnerabilities.
  • I have checked for potential performance regressions.

Additional Notes

  • Warning left unticked: the testsuite's expression roundtrip helper (and core's serializer)
    emits UserWarning: SQLServerTable.get_params(): cannot find attribute '_slots' or 'slots' for parameter 'slots' while round-tripping the new SQLServerTable. It is a helper introspection
    limitation, not a test failure; it is why the "no new warnings" box is unticked.
  • Changelog fragment not included: this branch adds no changelog.d/<issue>.<type>.md file, so
    that checkbox is unticked.
  • The behaviour-change inventories (which captured states move and which must not), the guard
    red/green runs and the mutation results live in the round's plan directory (git-ignored local
    evidence).
  • Reviewer entry points: git log --format='%h %s' origin/release/v1.0.0.dev3..HEAD; the three
    guard files above; and mixins/transaction.py::supports_transaction_wait for the
    WAIT-as-a-transaction-name trap.

…names them

Core's expression and dialect layers were reworked: a relation is split into the
object it names and the source that reads it, the namespace shape became a protocol
the dialect states once, and to_sql() now inspects what answers a format lookup.
Schema objects are rendered as objects rather than as parsed names, and the object
kind is checked where the name is spelled.

Thirty-two dispatch-failure pins move from AttributeError to UnsupportedFeatureError.
Their shared fragment moved with them, and the reason matters: a dialect that knows a
statement and refuses it also raises UnsupportedFeatureError in the same message
shape, and only the dispatch's suggestion offers a mixin. With the type no longer
separating the two causes, the fragment does.

Four shadowed formatters did not move. CreateFulltextIndexExpression, DropFunctionExpression,
CreateFunctionExpression and DropTriggerExpression still raise exactly what they raised
before, because the dispatch reaches them the same way: the cause is a signature
colliding with core's, not a missing formatter, and the pins still record it.

Of 303 distinct format_* names, none resolves to a Protocol and no non-protocol class
aliases a protocol stub. assert_sql_roundtrip_classified consulted its pinned tables
after catching UnsupportedFeatureError, so the retyped entries were unreachable on the
classification path; the tables come first now.

Four XML classes were pinned as unconstructible. Each wants a Sequence of its own item
type, and testsuite's constructor read that alias' name as the Sequence catalogue
object rather than as a list. They build now, and refuse at dispatch naming the
formatter this dialect does not compose.
… has

SQL Server 2012 introduced SEQUENCE, but SQLServerSequenceMixin declared only
supports_create_sequence and supports_drop_sequence and left supports_sequence()
and every option probe to core's defaults, which are False. Once core's 9dc18ab
gated the three sequence DDL formatters on those probes, the declaration and the
renderer disagreed: the dialect answered "no sequence object" and "no option"
for capabilities the engine has spelled since 2012.

Add supports_sequence(), supports_alter_sequence(),
supports_sequence_if_not_exists, supports_sequence_if_exists, the four new option
probes (start, increment, minvalue, maxvalue) and the remaining ones (cycle,
cache, order, owned_by), each version-gated to what the server accepts. Verified
against the CREATE/ALTER/DROP SEQUENCE (Transact-SQL) reference and against live
2019, 2022 and 2025 containers: START WITH, INCREMENT BY, MINVALUE, MAXVALUE,
CYCLE/NO CYCLE and CACHE are accepted; IF NOT EXISTS, ORDER/NOORDER and OWNED BY
are syntax errors; DROP SEQUENCE IF EXISTS is a 2016 (13.x) addition.

The local format_create_sequence_statement and format_drop_sequence_statement are
deleted: SQL Server's spelling is exactly what core's SequenceMixin emits once the
probes answer, so one renderer serves every dialect and the probes are the only
place that decides. The local create silently dropped an ORDER request, and the
local drop gated IF EXISTS on 2012 rather than 2016, so a 2012-2014 server would
have received a clause it rejects. The local format_alter_sequence_statement is
kept because SQL Server's ALTER grammar genuinely differs: it restarts with
RESTART WITH and rejects START WITH (error 11710, confirmed live), and has no
ORDER or OWNED BY. It now consults the probes and raises for the words the engine
cannot spell instead of dropping them.

format_next_value_for now takes the concrete SQLServerNextValueForExpression and
reads its sequence_name field, replacing the Any-typed argument, getattr guess and
str() fallback that could not name what it accepted. The protocol declaration
moves with it; the single call site passes the expression itself.
SQL Server's CREATE SEQUENCE accepts START WITH, but ALTER SEQUENCE does
not: it changes the start point with RESTART WITH, and the engine rejects
START WITH there outright with argument error 11710. The probe
supports_sequence_start describes the CREATE clause, so it cannot stand in
for the ALTER one.

Declare supports_alter_sequence_start on SQLServerSequenceMixin and answer
it False, then gate the start field of format_alter_sequence_statement on
that probe instead of refusing the clause as a bare statement-grammar
fact. The refusal is unchanged in effect, but it now names the option and
fails closed from a declared capability rather than an inline raise.

A probe answering True by default would let a dialect emit START WITH on
ALTER SEQUENCE and hand the server SQL it rejects, so the explicit False
is what keeps the two clauses honest.

Live check against SQL Server 2025: ALTER SEQUENCE ... START WITH 5 is
rejected with 11710, while RESTART WITH 5 and INCREMENT BY 5 are accepted.
…ctually has

The dialect's identity spelling is IDENTITY(seed, increment): it overrides
the standard GENERATED ... AS IDENTITY formatter, and SQL Server 2019 / 2022
/ 2025 all refuse the standard grammar (measured). Declare the probes core
now consults, and gate the formatter on them:

- supports_identity_column() = True -- IDENTITY(seed, increment) is this
  dialect's identity expression, measured to execute;
- supports_identity_generation_always() = False -- the identity property has
  no generation mode. ALWAYS was previously rendered byte-identical to
  BY DEFAULT ( IDENTITY(1, 1)); it is now refused by name;
- supports_identity_start() / _increment() = True -- the property's seed and
  increment, both measured to take effect;
- supports_identity_minvalue() / _maxvalue() / _cycle() = False -- every
  candidate spelling was measured refused on all three servers;
- supports_auto_increment_column() = False -- IDENTITY(seed, inc) is not
  AUTO_INCREMENT.

The formatter refuses a requested option by name rather than dropping it.
Execution confirmation covers the rendered DDL on all three servers, with
sentinels that must come back REJECTED.
…dropping them

The dialect overrides format_identity_clause to render its own
IDENTITY(seed, increment) property. That override gated minvalue / maxvalue
/ cycle and then jumped straight to seed / increment -- so a request for
order or cache, the two fields core added this round, rendered the bare
property ( IDENTITY(1, 1)) with the request silently dropped, while the
same method refused cycle. Two behaviours in one method.

Add the two gates. supports_identity_order() and supports_identity_cache()
answer False -- measured on 2019 / 2022 / 2025: ORDER, NO ORDER, CACHE <n>
and NO CACHE are syntax errors in the property -- so the refusal is by
name, IDENTITY ORDER / IDENTITY CACHE, never a downgrade.

Add a general walk test that reads every option field from
IdentityClause.__init__ itself and asserts the formatter renders each one or
refuses it by name, never renders it byte-identical to the bare clause.
Withdrawing a gate fails the walk with "the option was silently dropped",
and a self-test proves the walk rejects a formatter that drops options, so
the next field core adds cannot reopen this hole.

Execution confirmation runs the refusal verdict on all three servers, and
the raw candidate spellings join the measured-rejection list -- ORDER, NO
ORDER, CACHE <n> and NO CACHE are refused by 2019 / 2022 / 2025, with the
existing sentinels still proving the classifier can see a rejection.
…WNED BY

format_alter_sequence_statement refused ORDER and OWNED BY with a
hard-coded raise, while every neighbouring option in the same method
consulted its own probe. supports_sequence_order() and
supports_sequence_owned_by() answer False for SQL Server, so the output
was right by accident: flipping either probe in a subclass changed
nothing, because the formatter never asked.

Gate both options the way the neighbours are gated -- consult the probe,
refuse by name when it answers False, render the clause when it accepts.
The output is byte-identical: both probes still answer False, so a
request for either is still refused. Snapshots across 2012/2016/2022 and
live 2019/2022/2025 before and after differ by zero bytes; all live
verdicts and sentinels met on the three servers.

Add test_sequence_probes_sqlserver.py. Its
test_every_sequence_option_probe_is_load_bearing walks every sequence
option probe on CREATE/ALTER/DROP, flips each one in a subclass, and
asserts the behaviour flips with it; it was red on ALTER order, NO ORDER
and owned_by before the gates. A self-test feeds the walk the pre-fix
hard-coded shape and asserts it reports a decorative probe, and a
completeness check refuses to let a declared probe fall out of the walk.
… dialect gaps

Every two-spelling clause node now carries one parameter per spelling;
"unspecified" is neither set and setting both raises ValueError at
construction. The SQL Server formatters that read the changed fields consume
the pairs, and the overrides that replace core's gated formatters carry the
gates for the pairs their nodes grew.

Pairs consumed, per formatter:
- ALTER SEQUENCE override: cycle/no_cycle and cache/no_cache are spellable
  (measured accepted on 2019/2022/2025); order/no_order is refused by name
  because the engine has no ORDER option.
- Identity formatter: cycle/no_cycle, cache/no_cache, order/no_order have no
  spelling in IDENTITY(seed, increment); each explicit spelling is refused by
  name, and an unset pair renders the bare property.
- Set operation: all_ is rendered; distinct is refused by name (T-SQL has no
  DISTINCT keyword in set operations; measured refused).
- TRUNCATE / DROP VIEW / DROP SCHEMA overrides: cascade/restrict and
  restart_identity/continue_identity are refused by name instead of silently
  dropped.
- Constraint formatters (table, column, table-TYPE): deferrable/not_deferrable,
  initially_deferred/initially_immediate, enforced/not_enforced are gated on
  the existing probes and refused by name.
- Transaction formatters: deferrable/not_deferrable refused by name (BEGIN
  TRANSACTION DEFERRABLE parses as a transaction *name*, measured).
- The table-TYPE formatter's is-not-None field scan, which the bool defaults
  turned into an always-refuse, is replaced with per-spelling consumption.

Dialect-specific gaps:
- SQLServerAlterColumn: Optional[bool] not_null becomes not_null/nullable
  (NOT NULL / NULL); both set raises ValueError. Measured: unspecified
  ALTER COLUMN c INT is accepted and leaves the column NULLable; both explicit
  spellings are accepted and take effect.
- SQLServerColumnstoreIndexExpression: Optional[bool] clustered becomes
  clustered/nonclustered (CLUSTERED / NONCLUSTERED); both set raises
  ValueError. Measured: the bare keyword requires a key list and the server
  records that form as NONCLUSTERED, so validate() now allows columns when
  neither keyword is set; the clustered form still forbids them.

Probes: none of the new core probes (supports_schema_restrict,
supports_restrict_view, supports_drop_function_restrict,
supports_materialized_view_restrict, supports_truncate_restrict) is supported
by SQL Server; every candidate spelling was measured refused on 2019/2022/2025
and the probes stay False.

Behaviour change (intended): an explicit spelling whose probe is False now
raises instead of being dropped; an unset pair renders nothing where the old
encoding spelled a negative form.

Guard: test_clause_pair_guard_sqlserver.py asserts the four states of every
pair the dialect consumes are pairwise distinguishable (neither rendered /
A rendered or refused by name / B rendered or refused by name / both refused).
It was run red against the unmodified tree first (46 failing test ids) and is
green after the change (86 passed); two mutations (a disabled no_cache branch,
a disabled nullable branch) were each caught by the expected guard test.

Tests: the pointed files (roundtrip matrix, sequence/DDL, identity, protocol
conformance, sequence-probe guard, plus the formatter-touching files) pass:
1348 passed, 9 skipped; the one async test errors locally because aioodbc is
not installed in the local venv (CI installs it).
…pair

Core at c4d6adc now consults supports_materialized_cte (the CTE
MATERIALIZED hint), supports_truncate (TRUNCATE itself) and the new
supports_with_data_clause (WITH [NO] DATA on CTAS / materialized view
create / refresh), and added supports_transaction_wait for the
wait/no_wait pair on the two transaction expressions. A dialect that
leaves these probes undeclared inherits a core default; this declares
each one in this backend with its measured answer.

Measured (SQL Server 2019 / 2022 / 2025, ports 11433/11434/11435,
{SQL Server} driver; raw output in the round's plan directory):
- supports_truncate -> True: TRUNCATE TABLE accepted and empties the
  table; CASCADE rejected.
- supports_with_data_clause -> False: SELECT ... INTO accepted, but
  WITH DATA / WITH NO DATA are syntax errors near 'with'; CREATE TABLE
  ... AS is not SQL Server syntax either.
- supports_materialized_cte -> False: plain CTE accepted; AS MATERIALIZED
  and AS NOT MATERIALIZED are syntax errors.
- supports_transaction_wait -> False: BEGIN TRANSACTION WAIT parses the
  word as the transaction name (sys.dm_tran_active_transactions.name
  reads 'WAIT'); NO WAIT, SET TRANSACTION WAIT and SET TRANSACTION
  NO WAIT are syntax errors.

Formatter changes so each declaration is load-bearing:
- format_truncate_statement consults supports_truncate() and refuses by
  name; the stock output is unchanged (the probe is True).
- format_begin_transaction / format_set_transaction consume the
  wait/no_wait pair through _transaction_wait_clause: they refuse by name
  when the probe declines (the previous shape silently dropped the pair,
  e.g. no_wait rendered as a bare BEGIN TRANSACTION), and render the
  spelling when a subclass's probe accepts it.

Behaviour change (intended): an explicitly requested wait/no_wait on
SQL Server now raises instead of being dropped; every other captured
state renders byte-identically (before/after diff in the plan directory).

Guard: test_master_probes_guard_sqlserver.py walks all four probes for
load-bearing and declaration completeness, checks the four states of the
wait pair, and is able to fail (two dropping-formatter sentinels). It
was run red against the unmodified tree first (20 failing ids) and is
green after the change (41 passed); two mutations (truncate gate
removed, wait pair dropped) were each caught by the expected guard
tests.
The schema-object refactor these branches depend on landed on the core's
release/v1.0.0.dev30 line as PR #125, so the workflow no longer needs to install
the core from the feature branch. CORE_PKG now points at the release branch; the
testsuite dependency stays on its own feature branch, which is not merged yet.
@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.59350% with 125 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.97%. Comparing base (6fe9527) to head (b789a4a).

Files with missing lines Patch % Lines
...ial/activerecord/backend/impl/sqlserver/dialect.py 61.26% 25 Missing and 18 partials ⚠️
...erecord/backend/impl/sqlserver/mixins/namespace.py 70.00% 10 Missing and 8 partials ⚠️
...verecord/backend/impl/sqlserver/mixins/ddl_type.py 31.81% 9 Missing and 6 partials ⚠️
...iverecord/backend/impl/sqlserver/protocols/type.py 30.00% 6 Missing and 1 partial ⚠️
...cord/backend/impl/sqlserver/mixins/ddl_database.py 45.45% 3 Missing and 3 partials ⚠️
.../activerecord/backend/impl/sqlserver/mixins/dml.py 25.00% 3 Missing and 3 partials ⚠️
...d/backend/impl/sqlserver/mixins/identity_column.py 89.28% 0 Missing and 6 partials ⚠️
...iverecord/backend/impl/sqlserver/mixins/lateral.py 54.54% 3 Missing and 2 partials ⚠️
...cord/backend/impl/sqlserver/expression/temporal.py 63.63% 2 Missing and 2 partials ⚠️
...verecord/backend/impl/sqlserver/mixins/ddl_view.py 71.42% 2 Missing and 2 partials ⚠️
... and 6 more
Additional details and impacted files
@@                   Coverage Diff                   @@
##           release/v1.0.0.dev3       #7      +/-   ##
=======================================================
+ Coverage                73.36%   73.97%   +0.61%     
=======================================================
  Files                      132      135       +3     
  Lines                     7313     7605     +292     
  Branches                  1171     1261      +90     
=======================================================
+ Hits                      5365     5626     +261     
  Misses                    1540     1540              
- Partials                   408      439      +31     
Flag Coverage Δ
python-3.14 73.97% <74.59%> (+0.61%) ⬆️
sqlserver-2025 73.97% <74.59%> (+0.61%) ⬆️
unittests 73.97% <74.59%> (+0.61%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vistart vistart self-assigned this Oct 7, 2026
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@vistart vistart added the enhancement New feature or request label Oct 8, 2026
@vistart
vistart merged commit 610cd87 into release/v1.0.0.dev3 Oct 8, 2026
10 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant