Skip to content

Commit 610cd87

Browse files
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 #7
1 parent 6fe9527 commit 610cd87

89 files changed

Lines changed: 6417 additions & 792 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.claude/backend_development.md‎

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -269,35 +269,72 @@ Every dialect inherits from:
269269

270270
##### Current Protocols and Mixins (Main Package)
271271

272+
Names below are the ones the core package actually defines, checked against
273+
`backend/dialect/protocols/` and `backend/dialect/mixins/`. A few of the
274+
protocols have no mixin of their own — their formatter lives in a broader one,
275+
and the column says which. `NamespaceSupport` is the naming protocol, not a DDL
276+
one: it answers whether a name may be *qualified* with a catalog or a schema,
277+
which is a different question from whether the engine has those objects at all
278+
(`CreateSchemaSupport` / `CreateDatabaseSupport`).
279+
272280
| Protocol | Mixin | Description |
273281
|----------|-------|-------------|
274282
| `WindowFunctionSupport` | `WindowFunctionMixin` | Window functions (OVER, PARTITION BY) |
275283
| `CTESupport` | `CTEMixin` | Common Table Expressions (WITH clause) |
276-
| `AdvancedGroupingSupport` | `AdvancedGroupingMixin` | ROLLUP, CUBE, GROUPING SETS |
277-
| `ReturningSupport` | `ReturningMixin` | RETURNING clause |
284+
| `AdvancedGroupingSupport` | `DQLMixin` | ROLLUP, CUBE, GROUPING SETS (`format_grouping_clause`) |
285+
| `DqlOrderSupport` | `DQLMixin` | ORDER BY in a query part |
286+
| `ReturningSupport` | `DMLMixin` | RETURNING clause (`format_returning_clause`) |
278287
| `UpsertSupport` | `UpsertMixin` | UPSERT operations (ON CONFLICT) |
279288
| `LateralJoinSupport` | `LateralJoinMixin` | LATERAL joins |
280289
| `ArraySupport` | `ArrayMixin` | Array types and operations |
281290
| `JSONSupport` | `JSONMixin` | JSON types and operations |
282291
| `ExplainSupport` | `ExplainMixin` | EXPLAIN statement |
283-
| `FilterClauseSupport` | `FilterClauseMixin` | FILTER clause for aggregates |
284-
| `OrderedSetAggregationSupport` | `OrderedSetAggregationMixin` | WITHIN GROUP (ORDER BY) |
292+
| `FilterClauseSupport` | `ExpressionMixin` | FILTER clause for aggregates |
293+
| `OrderedSetAggregationSupport` | `ExpressionMixin` | WITHIN GROUP (ORDER BY) |
285294
| `MergeSupport` | `MergeMixin` | MERGE statement |
286295
| `TemporalTableSupport` | `TemporalTableMixin` | FOR SYSTEM_TIME queries |
287-
| `QualifyClauseSupport` | `QualifyClauseMixin` | QUALIFY clause |
288-
| `LockingSupport` | `LockingMixin` | FOR UPDATE, SKIP LOCKED |
296+
| `QualifyClauseSupport` | `DQLMixin` | QUALIFY clause |
297+
| `LockingSupport` | `DQLMixin` | FOR UPDATE, SKIP LOCKED (`format_for_update_clause`) |
289298
| `GraphSupport` | `GraphMixin` | Graph queries (MATCH) |
299+
| `GraphTableSupport` | `GraphTableMixin` | GRAPH_TABLE row source (SQL/PGQ) |
290300
| `JoinSupport` | `JoinMixin` | JOIN operations |
301+
| `WildcardSupport` | `DQLMixin` | `*` and `table.*` expansion |
291302
| `SetOperationSupport` | `SetOperationMixin` | UNION, INTERSECT, EXCEPT |
292303
| `ILIKESupport` | `ILIKEMixin` | Case-insensitive LIKE |
293-
| `TableSupport` | `TableMixin` | CREATE/DROP/ALTER TABLE |
294-
| `ViewSupport` | `ViewMixin` | CREATE/DROP VIEW |
304+
| `CollationSupport` | `CollationMixin` | COLLATE |
305+
| `DateTimeSupport` | `DateTimeMixin` | Date and time parts |
306+
| `DataTypeSupport` | `DataTypeMixin` | Type spelling (`format_data_type_*`) |
307+
| `PivotSupport` | `PivotMixin` | PIVOT / UNPIVOT |
308+
| `PartitionSupport` | `PartitionMixin` | Table partitioning |
309+
| `ConstraintSupport` | `ConstraintMixin` / `DDLColumnMixin` | Column and table constraints |
310+
| `IntrospectionSupport` | `IntrospectionMixin` | Catalog introspection queries |
311+
| `TransactionControlSupport` | `TransactionControlMixin` | BEGIN / COMMIT / ROLLBACK |
312+
| `CommentSupport` | `CommentOnMixin` | COMMENT ON |
313+
| `GeneratedColumnSupport` | `GeneratedColumnMixin` | Generated (computed) columns |
314+
| `AutoIncrementSupport` | `AutoIncrementMixin` | IDENTITY / AUTO_INCREMENT |
315+
| `AlterTableModifierSupport` | `SQLServerAlterColumnModifierMixin` (backend) | Backend-only ALTER TABLE options |
316+
| `CreateTableSupport` / `DropTableSupport` / `AlterTableSupport` | `TableMixin` | CREATE / DROP / ALTER TABLE |
317+
| `CreateTableAsSupport` / `CreateTableLikeSupport` / `CreateTableCloneSupport` / `CreateTableUsingTemplateSupport` | `TableMixin` | The other CREATE TABLE forms |
318+
| `CreateViewSupport` / `DropViewSupport` | `ViewMixin` | CREATE / DROP VIEW |
319+
| `MaterializedViewSupport` | `ViewMixin` | CREATE / DROP / REFRESH MATERIALIZED VIEW |
295320
| `TruncateSupport` | `TruncateMixin` | TRUNCATE TABLE |
296-
| `SchemaSupport` | `SchemaMixin` | CREATE/DROP SCHEMA |
297-
| `IndexSupport` | `IndexMixin` | CREATE/DROP INDEX |
298-
| `SequenceSupport` | `SequenceMixin` | CREATE/DROP/ALTER SEQUENCE |
299-
| `TriggerSupport` | `TriggerMixin` | CREATE/DROP TRIGGER (SQL:1999) |
300-
| `FunctionSupport` | `FunctionMixin` | CREATE/DROP FUNCTION (SQL/PSM) |
321+
| `CreateSchemaSupport` / `DropSchemaSupport` | `SchemaMixin` | CREATE / DROP SCHEMA |
322+
| `CreateIndexSupport` / `DropIndexSupport` / `FulltextIndexSupport` | `IndexMixin` | CREATE / DROP INDEX, FULLTEXT INDEX |
323+
| `CreateSequenceSupport` / `DropSequenceSupport` / `AlterSequenceSupport` | `SequenceMixin` | CREATE / DROP / ALTER SEQUENCE |
324+
| `CreateTriggerSupport` / `DropTriggerSupport` | `TriggerMixin` | CREATE / DROP TRIGGER (SQL:1999) |
325+
| `CreateRoutineSupport` / `DropRoutineSupport` / `SQLFunctionSupport` | `FunctionMixin` | CREATE / DROP FUNCTION and PROCEDURE (SQL/PSM) |
326+
| `CreateTypeSupport` / `AlterTypeSupport` / `DropTypeSupport` | `UserDefinedTypeMixin` | CREATE / ALTER / DROP TYPE |
327+
| `CreateDomainSupport` / `AlterDomainSupport` / `DropDomainSupport` | `DomainMixin` | CREATE / ALTER / DROP DOMAIN |
328+
| `CreateDatabaseSupport` / `DropDatabaseSupport` / `AlterDatabaseSupport` | `DatabaseMixin` | CREATE / DROP / ALTER DATABASE |
329+
| `SQLXMLSupport` and the five `SQLXML*Support` protocols | `SQLXMLMixin` and its five siblings | SQL/XML and XQuery |
330+
| `TableObjectSupport`, `ViewObjectSupport`, `IndexObjectSupport`, `SequenceObjectSupport`, `TypeObjectSupport`, `RoutineObjectSupport`, `TriggerObjectSupport`, `SynonymObjectSupport`, `MaterializedViewObjectSupport`, `ForeignTableObjectSupport`, `PropertyGraphObjectSupport` | `TableNameMixin`, `ViewNameMixin`, … (one per kind) | Naming a catalogue object |
331+
| `NamespaceSupport` | `NamespaceMixin` | Which namespace levels a name may be qualified with |
332+
333+
The naming protocols are per object kind rather than one: each kind's
334+
`format_<kind>_object` lives in its own `*NameMixin`, and all of them are
335+
`NamespaceMixin` subclasses, which is where `validate_namespace` and
336+
`format_qualified_name` live. A dialect overrides the one kind it spells
337+
differently and inherits the rest.
301338

302339
##### Principles for Adding New Protocols/Mixins
303340

‎.github/workflows/test.yml‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
- name: Install activerecord and testsuite from PyPI
6161
run: |
6262
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
63-
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
63+
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"
6464
6565
- name: Create test database
6666
run: |
@@ -150,7 +150,7 @@ jobs:
150150
- name: Install activerecord and testsuite from PyPI
151151
run: |
152152
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
153-
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
153+
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"
154154
155155
- name: Create test database
156156
run: |
@@ -240,7 +240,7 @@ jobs:
240240
- name: Install activerecord and testsuite from PyPI
241241
run: |
242242
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
243-
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
243+
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"
244244
245245
- name: Create test database
246246
run: |
@@ -330,7 +330,7 @@ jobs:
330330
- name: Install activerecord and testsuite from PyPI
331331
run: |
332332
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
333-
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
333+
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"
334334
335335
- name: Create test database
336336
run: |
@@ -420,7 +420,7 @@ jobs:
420420
- name: Install activerecord and testsuite from PyPI
421421
run: |
422422
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
423-
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
423+
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"
424424
425425
- name: Create test database
426426
run: |
@@ -510,7 +510,7 @@ jobs:
510510
- name: Install activerecord and testsuite from PyPI
511511
run: |
512512
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
513-
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
513+
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"
514514
515515
- name: Create test database
516516
run: |
@@ -601,7 +601,7 @@ jobs:
601601
- name: Install activerecord and testsuite from PyPI
602602
run: |
603603
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
604-
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
604+
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"
605605
606606
- name: Create test database
607607
run: |
@@ -691,7 +691,7 @@ jobs:
691691
- name: Install activerecord and testsuite from PyPI
692692
run: |
693693
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
694-
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
694+
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"
695695
696696
- name: Create test database
697697
run: |
@@ -790,7 +790,7 @@ jobs:
790790
- name: Install activerecord and testsuite from PyPI
791791
run: |
792792
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
793-
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
793+
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"
794794
795795
- name: Create test database
796796
run: |

‎changelog.d/7.added.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add the SQL Server catalogue objects and namespace support: `SQLServerTable` — a table whose `#` temporary form is a construction-time field folded into its name and identity — plus `PartitionFunction` and `PartitionScheme` (rendered through the new `format_partition_function_object`/`format_partition_scheme_object` formatters) are exported from `rhosocial.activerecord.backend.impl.sqlserver.expression`, and the dialect implements core's `NamespaceSupport` with one object protocol per catalogue kind, so a schema object carries catalog, schema and name, every level is rendered and validated against SQL Server's 128-character identifier cap, and `format_object_id_name` provides the one deliberate bracket-free spelling `OBJECT_ID` needs.

‎changelog.d/7.changed.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Change how the SQL Server expressions encode two tri-state options and make every unsupported spelling fail closed: `SQLServerAlterColumn` now takes `not_null`/`nullable` (setting both raises `ValueError`), so `not_null=False` — which used to spell `NULL` — now means unspecified and callers wanting `NULL` must pass `nullable=True`; `SQLServerColumnstoreIndexExpression` now takes `clustered`/`nonclustered` (setting both raises `ValueError`), so `clustered=False` — which used to spell `NONCLUSTERED` — now means no keyword and callers wanting `NONCLUSTERED` must pass `nonclustered=True`, while `validate()` accepts key columns for the bare form the server records as NONCLUSTERED. The dialect consumes the two-spelling clause pairs (sequence `cycle`/`no_cycle` and `cache`/`no_cache`, identity `cycle`/`no_cycle`, `order`/`no_order` and `cache`/`no_cache`, set-operation `all_`/`distinct`, `TRUNCATE` and `DROP VIEW`/`DROP SCHEMA` `cascade`/`restrict` and `restart_identity`/`continue_identity`, constraint `deferrable`/`not_deferrable`, `initially_deferred`/`initially_immediate` and `enforced`/`not_enforced`, transaction `deferrable`/`not_deferrable` and `wait`/`no_wait`): an explicit spelling the dialect cannot express now raises `UnsupportedFeatureError` naming the requested spelling instead of rendering SQL that silently dropped it, and an unset pair renders nothing where the old encoding spelled a negative form. Formatters that name a catalogue object now check the object's kind, raising `TypeError` instead of rendering a well-formed statement over the wrong kind's name.

‎changelog.d/7.fixed.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Declare the SQL Server capability probes from measurement rather than inheriting core's defaults, and make them load-bearing. The sequence probes answer 2012+ for `supports_sequence`, `supports_alter_sequence` and every option the engine spells, with `supports_sequence_if_exists` at 2016+ (the deleted local `DROP SEQUENCE` renderer had gated `IF EXISTS` on 2012) and `supports_alter_sequence_start` answering `False` from its own ALTER-side question — `ALTER SEQUENCE ... START WITH` is rejected with error 11710 and `RESTART WITH` is the engine's spelling, while `ORDER` and `OWNED BY` have no spelling at all. The identity probes declare `IDENTITY(seed, increment)` (`supports_identity_column` and start/increment `True`) and answer `False` for `ALWAYS`, `MINVALUE`/`MAXVALUE`/`CYCLE`/`ORDER`/`CACHE` — `ALWAYS` had been silently downgraded to the bare property and the options dropped — with `supports_auto_increment_column` `False`. The four master probes are measured too: `supports_truncate` `True`, `supports_with_data_clause` `False`, `supports_materialized_cte` `False` and `supports_transaction_wait` `False`, whose probing trap is that `BEGIN TRANSACTION WAIT` is accepted only because the server reads `WAIT` as the transaction name.

‎changelog.d/7.internal.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Follow core's protocol package split: the dialect now declares `NamespaceSupport` plus one `*ObjectSupport` protocol per catalogue kind and one support protocol per DDL statement (`CreateTableSupport`/`DropTableSupport`/`AlterTableSupport`, `CreateSchemaSupport`/`DropSchemaSupport`, `CreateIndexSupport`/`DropIndexSupport`/`FulltextIndexSupport`, `CreateTypeSupport`/`DropTypeSupport`), with `SQLServerTableSupport` deriving from `TableObjectSupport` and `SQLServerUserDefinedTypeSupport` from `CreateTypeSupport`/`DropTypeSupport`; this backend's protocols and its CLI introspection have moved off the old `TableSupport`/`SchemaSupport`/`IndexSupport`/`FunctionSupport`/`TriggerSupport`/`SequenceSupport`/`ViewSupport` names, so custom subclasses that referenced them must move to the new ones. The local `CREATE SEQUENCE`/`DROP SEQUENCE` renderer overrides are deleted in favour of core's shared probe-gated renderers and `format_next_value_for` now takes `SQLServerNextValueForExpression` instead of `Any`; `.claude/backend_development.md` records the protocol-to-mixin mapping, and CI keeps core pinned to `release/v1.0.0.dev30` while the testsuite installs from its round branch.

‎src/rhosocial/activerecord/backend/impl/sqlserver/alter_table_modifier.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ def format_alter_column_type_action(self, action: Any) -> Tuple[str, tuple]:
122122
if collate:
123123
parts.append(f"COLLATE {collate}")
124124

125-
not_null = getattr(action, "not_null", None)
126-
if not_null is not None:
125+
not_null = getattr(action, "not_null", False)
126+
nullable = getattr(action, "nullable", False)
127+
if not_null or nullable:
127128
parts.append("NOT NULL" if not_null else "NULL")
128129

129130
if getattr(action, "sparse", False):

‎src/rhosocial/activerecord/backend/impl/sqlserver/backend/async_backend.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,7 @@ async def _get_scope_identity_async(self) -> Optional[int]:
262262

263263
async def set_identity_insert(self, table: str, on: bool = True) -> None:
264264
"""Enable or disable IDENTITY_INSERT for a table (async)."""
265-
state = "ON" if on else "OFF"
266-
sql = f"SET IDENTITY_INSERT {self.dialect.format_identifier(table)} {state}"
265+
sql, _params = self.dialect.format_set_identity_insert(table, on)
267266
async with await self._get_cursor() as cursor:
268267
await cursor.execute(sql)
269268

‎src/rhosocial/activerecord/backend/impl/sqlserver/cli/info.py‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,22 @@
2424
ExplainSupport,
2525
GraphSupport,
2626
SetOperationSupport,
27-
ViewSupport,
28-
TableSupport,
27+
ViewObjectSupport,
28+
TableObjectSupport,
2929
TruncateSupport,
3030
GeneratedColumnSupport,
31-
TriggerSupport,
32-
FunctionSupport,
31+
TriggerObjectSupport,
32+
RoutineObjectSupport,
3333
AdvancedGroupingSupport,
3434
ArraySupport,
3535
ILIKESupport,
36-
IndexSupport,
36+
IndexObjectSupport,
3737
LockingSupport,
3838
MergeSupport,
3939
OrderedSetAggregationSupport,
4040
QualifyClauseSupport,
41-
SchemaSupport,
42-
SequenceSupport,
41+
NamespaceSupport,
42+
SequenceObjectSupport,
4343
TemporalTableSupport,
4444
)
4545

@@ -77,10 +77,10 @@
7777
],
7878
"Transaction & Locking": [LockingSupport, TemporalTableSupport],
7979
"Query Analysis": [ExplainSupport, GraphSupport, QualifyClauseSupport],
80-
"DDL - Table": [TableSupport, TruncateSupport, GeneratedColumnSupport],
81-
"DDL - View": [ViewSupport],
82-
"DDL - Schema & Index": [SchemaSupport, IndexSupport],
83-
"DDL - Sequence & Trigger": [SequenceSupport, TriggerSupport, FunctionSupport],
80+
"DDL - Table": [TableObjectSupport, TruncateSupport, GeneratedColumnSupport],
81+
"DDL - View": [ViewObjectSupport],
82+
"DDL - Schema & Index": [NamespaceSupport, IndexObjectSupport],
83+
"DDL - Sequence & Trigger": [SequenceObjectSupport, TriggerObjectSupport, RoutineObjectSupport],
8484
"String Matching": [ILIKESupport],
8585
"SQL Server Native": [
8686
SQLServerIndexedViewSupport,

0 commit comments

Comments
 (0)