Repository navigation
Commit 610cd87
authored
feat(sqlserver): consume clause-parameter pairs and declare measured probes (#7)
* refactor(sqlserver): carry catalogue objects and check the kind that 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.
* fix(sqlserver): declare the sequence capabilities the engine actually 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.
* fix(sqlserver): refuse ALTER SEQUENCE START through its own probe
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.
* fix(sqlserver): declare the identity-column capabilities the engine actually 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.
* fix(sqlserver): gate the identity ORDER and CACHE options instead of 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.
* fix(sqlserver): ask the sequence probes before refusing ALTER ORDER/OWNED 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.
* feat(sqlserver): consume the clause-parameter pairs and close the two 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).
* feat(sqlserver): declare the four master probes and consume the wait 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.
* ci: pin the core dependency to the merged release line
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.
* chore: add changelog fragment for PR #71 parent 6fe9527 commit 610cd87
89 files changed
Lines changed: 6417 additions & 792 deletions
File tree
- .claude
- .github/workflows
- changelog.d
- src/rhosocial/activerecord/backend/impl/sqlserver
- backend
- cli
- examples
- connection
- ddl
- functions
- insert
- named_expressions
- query
- schema_diff
- transaction
- types
- expression
- ddl
- mixins
- protocols
- tests
- providers
- fixtures
- rhosocial/activerecord_sqlserver_test/feature/backend
- dialect
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
269 | 269 | | |
270 | 270 | | |
271 | 271 | | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
272 | 280 | | |
273 | 281 | | |
274 | 282 | | |
275 | 283 | | |
276 | | - | |
277 | | - | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
278 | 287 | | |
279 | 288 | | |
280 | 289 | | |
281 | 290 | | |
282 | 291 | | |
283 | | - | |
284 | | - | |
| 292 | + | |
| 293 | + | |
285 | 294 | | |
286 | 295 | | |
287 | | - | |
288 | | - | |
| 296 | + | |
| 297 | + | |
289 | 298 | | |
| 299 | + | |
290 | 300 | | |
| 301 | + | |
291 | 302 | | |
292 | 303 | | |
293 | | - | |
294 | | - | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
295 | 320 | | |
296 | | - | |
297 | | - | |
298 | | - | |
299 | | - | |
300 | | - | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
301 | 338 | | |
302 | 339 | | |
303 | 340 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | | - | |
| 63 | + | |
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
153 | | - | |
| 153 | + | |
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
| |||
240 | 240 | | |
241 | 241 | | |
242 | 242 | | |
243 | | - | |
| 243 | + | |
244 | 244 | | |
245 | 245 | | |
246 | 246 | | |
| |||
330 | 330 | | |
331 | 331 | | |
332 | 332 | | |
333 | | - | |
| 333 | + | |
334 | 334 | | |
335 | 335 | | |
336 | 336 | | |
| |||
420 | 420 | | |
421 | 421 | | |
422 | 422 | | |
423 | | - | |
| 423 | + | |
424 | 424 | | |
425 | 425 | | |
426 | 426 | | |
| |||
510 | 510 | | |
511 | 511 | | |
512 | 512 | | |
513 | | - | |
| 513 | + | |
514 | 514 | | |
515 | 515 | | |
516 | 516 | | |
| |||
601 | 601 | | |
602 | 602 | | |
603 | 603 | | |
604 | | - | |
| 604 | + | |
605 | 605 | | |
606 | 606 | | |
607 | 607 | | |
| |||
691 | 691 | | |
692 | 692 | | |
693 | 693 | | |
694 | | - | |
| 694 | + | |
695 | 695 | | |
696 | 696 | | |
697 | 697 | | |
| |||
790 | 790 | | |
791 | 791 | | |
792 | 792 | | |
793 | | - | |
| 793 | + | |
794 | 794 | | |
795 | 795 | | |
796 | 796 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
125 | | - | |
126 | | - | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
127 | 128 | | |
128 | 129 | | |
129 | 130 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
262 | 262 | | |
263 | 263 | | |
264 | 264 | | |
265 | | - | |
266 | | - | |
| 265 | + | |
267 | 266 | | |
268 | 267 | | |
269 | 268 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
28 | | - | |
| 27 | + | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
32 | | - | |
| 31 | + | |
| 32 | + | |
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
| 36 | + | |
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
42 | | - | |
| 41 | + | |
| 42 | + | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
| |||
0 commit comments