Skip to content

feat: SQL Server SQL Graph support and statement-level NOSCAN option - #3

Merged
vistart merged 58 commits into
release/v1.0.0.dev3from
feature/parameter-inline
Sep 18, 2026
Merged

vistart merged 58 commits into
release/v1.0.0.dev3from
feature/parameter-inline

Conversation

@vistart

@vistart vistart commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR introduces significant improvements to the SQL Server backend:

  1. Parameter-inline Calling Convention: Migrated all dialect methods to (self, expr) signature pattern for consistency with core library
  2. DDL Enhancements: Added capability-gated DDL features including multi-action ALTER table, generated columns, and typed table options
  3. SQL Graph Support: Added SQL Server SQL Graph support with node/edge tables and MATCH predicate for graph queries
  4. Protocol Formalization: Formalized SQL Server type support and suggestion contracts with generic protocol conformance tests

Adds # (issue)

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

Impact on End-Users:
No backward-incompatible changes are expected for typical end-user applications.

Impact on Backend Developers (Custom Implementations):
The parameter-inline convention changes the signature of all format_* methods from (column, ...) to (self, expr). Backend developers with custom implementations will need to update their method signatures to match the new pattern.

Related Repositories/Packages

  • rhosocial-activerecord (core): Uses the same parameter-inline convention
  • python-activerecord-testsuite: May need protocol conformance test updates

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 2022/2025.
  • New test_execution_options.py covers sync/async NOSCAN regression (temporary solution, subject to change)

Test Plan:

PYTHONPATH=tests .venv3.14-ubuntu26.04/bin/pytest tests/ -v --tb=short

Checklist

  • My code follows the project's code style guidelines.
  • 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 (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

Key commits:

  • feat(graph): add SQL Server SQL Graph support (node/edge tables + MATCH predicate)
  • feat(dialect): declare GeneratedColumn protocol support
  • refactor(sqlserver): split dialect methods into 25 feature mixins
  • fix(sqlserver): correct pagination, database ownership and temporal formatting
  • test(protocol): add generic protocol conformance test

This is a substantial refactor that formalizes the SQL Server dialect architecture while adding new features. The parameter-inline convention is now consistently applied across all format methods.

…e format_method

- Backend types carry sqlserver_-prefixed generic names; dispatch by naming
  convention (format_data_type_<name>); @DDLTypeMixin.handles registration
  removed.
- Formatting functions take (self, expr); CAST is a CastExpression node.
- CI installs rhosocial-activerecord and the testsuite from their active
  branches.
- Renamed private format functions to remove backend name suffix.
- Remove extra filter_predicate param from format_function_call
- Update format_json_table_expression to accept single expr param
… method

- Updated import in dialect.py from JoinExpression to JoinClause
- Renamed format_grouping_expression to format_grouping_clause with correct
  signature (self, expr) instead of (self, operation, _expressions)
- Updated example code import in examples/query/join.py
- Updated test_expressions.py imports and class name
- Updated test_dialect_version_branches.py to use new method name and signature
Driver 17 is one generation too old for SQL Server 2025's protocol/TLS
defaults; parallel test workers failed connections at suite start.
…ion contracts

- 9 type classes declare dialect_options explicitly; unified _type_params
  based equality/hash
- 29 supports_data_type_* added (1:1 with format family)
- suggested_data_types(): uuid/interval/array/enum/jsonb/timestamptz/timetz
  -> honest cross-backend suggestions; keys disjoint from supported
- Precision validation: decimal 1-38/0-precision, float 1-53, temporal 0-7
- _register_type_formatters() copies supports methods + regex attrs
- New test_sqlserver_type_protocol.py (30 tests)
Update ColumnDefinition, DataType subclasses, and CreateTableExpression
to use dialect-first constructor signatures:
- ColumnDefinition(dialect, name, type, ...)
- IntegerType(dialect), VarCharType(dialect, length=...), etc.

This aligns tests with the parameter-inline refactor in core library
where expressions bind the dialect at construction time.

Fixes CI failures in:
- test_auto_increment_ddl (ColumnDefinition/DataType signatures)
- test_data_type_parsing (DataType dialect-first)
- test_dialect_comprehensive (CreateTableExpression)
- test_dialect_version_branches (DDL rendering snapshots)
- test_create_table_like (ColumnDefinition signatures)
- test_niladic_functions (DataType dialect-first)
- test_p2_coverage_features (DataType dialect-first)
- test_schema_diff, test_sqlserver_partition (providers/fixtures)
- test_connection_management, test_parallel_crud (providers/fixtures)
Root cause:
  _prepare_read_committed_snapshot was a session-scoped autouse fixture
  that executed ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON.
  This requires an exclusive database lock. With pytest-xdist -n 8,
  each of the 8 workers runs its own session and executes this fixture
  simultaneously — all trying to acquire the exclusive lock at once,
  causing a deadlock where every worker waits for the others.

Fix:
  1. Add tests/enable_read_committed_snapshot.py — a standalone script
     that enables READ_COMMITTED_SNAPSHOT using pyodbc, meant to run
     BEFORE pytest in the CI workflow step.
  2. In the workflow, insert 'python tests/enable_read_committed_snapshot.py'
     before every 'python -m pytest' invocation (10 jobs).
  3. In conftest.py, change the fixture to verify-only mode: it checks
     whether the option is already on (it should be, from step 1),
     and only attempts ALTER DATABASE as a lightweight fallback with
     a short 3s timeout and 2 retries (instead of 5s × 5 retries).
…ures

ColumnConstraint.__init__ requires dialect as first argument
(parameter-inline convention), but SQL Server fixtures were calling
ColumnConstraint(ColumnConstraintType.XXX) without dialect.

Fixed in: basic.py, events.py, mixins.py, query.py
…tion

col_def.data_type.to_sql(self) was passing dialect as an argument,
but parameter-inline refactored to_sql() to take no arguments
(dialect is bound at construction time). Change to to_sql().

Also addresses the locking.py to_sql() -> str question:
SQLServerTableHint is a standalone dataclass (not BaseExpression),
its to_sql() returning a hint name string is intentional.
Same parameter-inline migration as ClickHouse/Oracle: the method now
takes a single expr argument, extracting left/right/operation/all_/
alias/order_by_clause/limit_offset_clause from it.
Add the dialect first argument to all remaining ColumnDefinition,
ColumnConstraint, and DataType constructor calls across test files:
- test_alter_table_if_exists_backend.py
- test_auto_increment_ddl.py (_pk helper now takes dialect)
- test_create_table_like.py
- test_dialect_comprehensive.py
- test_dialect_version_branches.py (use local 'd' variable)
- test_niladic_functions.py
- test_p2_coverage_features.py (_create_table uses its 'd' param)
- test_schema_diff.py (add local SQLServerDialect)
- test_sqlserver_partition.py (use local 'd' variable)
…ative)

New test_sqlserver_protocol_conformance.py complements the SQLServer-specific
test_protocol_support.py by covering the generic dialect protocols:
- SQLSERVER_PROTOCOLS (35 implemented generic protocols)
- SQLSERVER_NOT_IMPLEMENTED (10: SQL/XML*, GraphTable, ILIKE, FunctionSupport
  intentional; GeneratedColumnSupport known gap — naming mismatch, TODO)
- negative isinstance test + partition completeness test
SQL Server supports computed columns but only exposed the singular
supports_generated_column(). Add the plural supports_generated_columns()
(protocol-compliant) and promote GeneratedColumnSupport from
SQLSERVER_NOT_IMPLEMENTED to SQLSERVER_PROTOCOLS.
…tion

- SetOperation tests: construct SetOperationExpression instead of passing
  individual keyword args to format_set_operation_expression()
- MergeAction: add missing dialect= argument
- IndexDefinition/ForeignKeyConstraint/TableConstraint: add missing
  dialect positional argument in fixtures and test_p2_coverage_features.py
- test_dialect_version_branches.py: 3 MergeAction calls
- test_protocol_support.py: 2 MergeAction calls (also positional→keyword)
…ion-based signature

Core protocol changed from scatter args to (self, expr). Update backend
overrides and tests to construct expression objects.
SQL Server prohibits ORDER BY in CTEs unless TOP/OFFSET/FOR XML is
also specified. This is a known SQL Server limitation.
…ssion

TypeCastingMixin.cast() now returns a CastExpression AST node.
The old cast_types attribute no longer exists on JSONExpression.
This was causing AttributeError in derived field tests.
- Add backend reserved words and wire format_identifier(need_quote=...)
  through SQLDialectBase, warning when a reserved word is left unquoted
- Rename supports_table_like_syntax to supports_create_table_like
- Add identifier quoting tests
…methods

Add FilterClauseMixin, TransactionControlMixin, AutoIncrementMixin,
GeneratedColumnMixin, TriggerMixin, PartitionMixin, ILIKEMixin,
FunctionMixin to inheritance chain (P0 fix: these were missing,
causing support methods to return None via Protocol stubs).

Remove 29 supports_* methods that are now redundant with the newly
added mixins or existing global mixin defaults:
- ArrayMixin, GraphMixin, QualifyClauseMixin, OrderedSetAggregationMixin
- ViewMixin, SchemaMixin, IndexMixin, SequenceMixin, TableMixin
- ConstraintMixin, DDLColumnMixin, SetOperationMixin

dialect.py: 1970 → 1900 lines (-70)
The dialect now composes FunctionMixin/ILIKEMixin, so it satisfies
FunctionSupport/ILikesupport; the negative-contract list was stale and
failed. Note their semantics remain backend-specific.
Move 90 methods from dialect.py into 25 mixin files:
- datetime.py, collation.py, cte.py, returning.py, constraint.py,
  window.py, json.py, grouping.py, locking.py, merge.py, temporal.py,
  upsert.py, lateral.py, explain.py, dql.py, dml.py, view.py,
  schema.py, index.py, generated_column.py, set_operation.py,
  table.py, identifier.py, transaction.py, function.py (new)
- Extended: trigger.py, sequence.py
- New shared constants: version_constants.py

dialect.py: 1900 → 1062 lines.
Expression nodes for format_* signature compliance (mirrors the MySQL/
MariaDB expression-object convention).
Drop the redundant _support suffix from the protocol module name.
SQLDialectBase precedes the feature mixins in the MRO, so the bracket
formatter in SQLServerIdentifierMixin was shadowed and identifiers were
rendered with double quotes. Re-declare it on the dialect.
Tests were calling dialect formatters with raw strings/lists, bypassing
the expression layer. Construct the corresponding expression classes and
call to_sql() so the tests exercise the real expression<->formatter
integration:

- NEXT VALUE FOR -> SQLServerNextValueForExpression
- CONTAINS / FREETEXT -> SQLServerContainsPredicate / SQLServerFreetextPredicate
- TRY_CAST / TRY_CONVERT -> SQLServerTryCastExpression / SQLServerTryConvertExpression
- table hints -> SQLServerTableHintClause
- OPENJSON -> SQLServerOpenJsonExpression
- temporal period -> SQLServerTemporalPeriodDefinition
- FULLTEXT CATALOG -> SQLServerCreateFullTextCatalogExpression
- OPTION hints -> SQLServerOptionHintClause
CollationMixin was imported but not in the bases, so format_collate_
expression resolved to the CollationSupport protocol stub (None).
…sion

- remove the inline dialect_options['like_table'] guard from
  format_create_table_statement; SQL Server inherits
  supports_create_table_like() == False, so the gated generic
  format_create_table_like_statement raises UnsupportedFeatureError
- tests migrate to CreateTableLikeExpression and assert the raise

BREAKING CHANGE: CREATE TABLE ... LIKE is no longer expressed through
CreateTableExpression(dialect_options={'like_table': ...}); use
CreateTableLikeExpression, which raises UnsupportedFeatureError on
SQL Server.
Renderer now reads memory_optimized/durability from typed
CreateTableOptions fields first, falling back to dialect_options.
- Add supports_drop_table_cascade() → False
- Add supports_drop_table_restrict() → False
- Remove format_drop_table_statement override (use generic impl)
SQL Server requires one action per ALTER TABLE statement.
- Add SQLServerDatabaseMixin for CREATE/DROP/ALTER DATABASE support
- Add SQLServerViewMixin with capability gating
- Add test_sqlserver_ddl_improvements.py with 5 tests
- Rename view.py -> ddl_view.py
- Per-expression virtual-table prefix (Column(table='DELETED'|'INSERTED'))
- ReturningClause.output_into renders OUTPUT ... INTO <target>
- Raise UnsupportedFeatureError for non-column expressions, wildcards, aliases
- supports_returning_alias/wildcard False, supports_returning_into True
- Fix stale .mixins.view import (renamed to ddl_view)
- Add OUTPUT tests
- format_limit_offset_clause: FETCH NEXT ... ROWS WITH TIES
- supports_fetch_with_ties True; supports_nulls_first_last False
- locking: gate strength (no silent ignore)
- set_operation: raise on unsupported FOR UPDATE
…CH predicate)

SQL Server SQL Graph is independent from SQL/PGQ (different data model and
query shape), so it lives in the SQL Server backend, not the core GraphMixin.

- expression/graph.py: SQLServerGraphDirection, SQLServerGraphPseudoColumn,
  SQLServerGraphNodeRef, SQLServerGraphEdgeRef, SQLServerGraphPattern,
  SQLServerMatchPredicate, SQLServerShortestPathExpression,
  SQLServerGraphPathAggregate, SQLServerForPathTable
- expression/ddl/graph.py: SQLServerGraphTableKind,
  SQLServerAsGraphTableExpression, SQLServerEdgeConstraint
- protocols/graph.py: SQLServerGraphSupport (6 switches + 9 formatters)
- mixins/graph.py: SQLServerGraphMixin with version gating
  (SQL Graph 2017+, SHORTEST_PATH / path aggregates 2019+)
- dialect: compose mixin + protocol; integrate AS NODE/AS EDGE and
  CONNECTION edge constraints into format_create_table_statement
- version_constants: add SQL_SERVER_2025
- keep core SQL/PGQ MatchClause rejection; clarify suggestion text
- tests: 30 cases (switches, pattern/MATCH/SHORTEST_PATH/path aggregate,
  pseudo-column validation, DDL, version-gating negatives)
Store 'pattern'/'extra_patterns' so the introspection-based get_params()
serialization round-trip can reconstruct the expression; expose 'patterns'
as a derived property.
@sonarqubecloud

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

@codecov

codecov Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.83274% with 222 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.98%. Comparing base (f25368f) to head (b03d009).

Files with missing lines Patch % Lines
...verecord/backend/impl/sqlserver/mixins/datetime.py 27.58% 20 Missing and 1 partial ⚠️
...cord/backend/impl/sqlserver/mixins/ddl_database.py 55.55% 18 Missing and 2 partials ⚠️
...ial/activerecord/backend/impl/sqlserver/dialect.py 85.83% 9 Missing and 8 partials ⚠️
...ord/backend/impl/sqlserver/mixins/set_operation.py 68.18% 9 Missing and 5 partials ⚠️
...record/backend/impl/sqlserver/protocols/spatial.py 55.17% 13 Missing ⚠️
.../activerecord/backend/impl/sqlserver/mixins/dml.py 85.71% 6 Missing and 6 partials ⚠️
...verecord/backend/impl/sqlserver/mixins/ddl_view.py 73.17% 7 Missing and 4 partials ⚠️
...ctiverecord/backend/impl/sqlserver/mixins/graph.py 90.99% 6 Missing and 4 partials ⚠️
...ecord/backend/impl/sqlserver/mixins/transaction.py 70.58% 6 Missing and 4 partials ⚠️
...iverecord/backend/impl/sqlserver/protocols/json.py 28.57% 9 Missing and 1 partial ⚠️
... and 25 more
Additional details and impacted files
@@                   Coverage Diff                   @@
##           release/v1.0.0.dev3       #3      +/-   ##
=======================================================
+ Coverage                72.14%   73.98%   +1.84%     
=======================================================
  Files                       86      124      +38     
  Lines                     5783     6715     +932     
  Branches                   934     1019      +85     
=======================================================
+ Hits                      4172     4968     +796     
- Misses                    1325     1432     +107     
- Partials                   286      315      +29     
Flag Coverage Δ
python-3.14 73.98% <86.83%> (+1.84%) ⬆️
sqlserver-2025 73.98% <86.83%> (+1.84%) ⬆️
unittests 73.98% <86.83%> (+1.84%) ⬆️

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 changed the base branch from main to release/v1.0.0.dev3 September 18, 2026 05:21
@vistart vistart self-assigned this Sep 18, 2026
@vistart vistart added the enhancement New feature or request label Sep 18, 2026
@vistart
vistart merged commit 8e12401 into release/v1.0.0.dev3 Sep 18, 2026
12 of 13 checks passed
vistart added a commit that referenced this pull request Sep 24, 2026
)

* feat(sqlserver): adapt to node-based expression system and declarative format_method

- Backend types carry sqlserver_-prefixed generic names; dispatch by naming
  convention (format_data_type_<name>); @DDLTypeMixin.handles registration
  removed.
- Formatting functions take (self, expr); CAST is a CastExpression node.
- CI installs rhosocial-activerecord and the testsuite from their active
  branches.
- Renamed private format functions to remove backend name suffix.

* refactor: enforce (self, expr) signature on format_* methods

- Remove extra filter_predicate param from format_function_call
- Update format_json_table_expression to accept single expr param

* fix: Rename JoinExpression→JoinClause, fix format_grouping_expression method

- Updated import in dialect.py from JoinExpression to JoinClause
- Renamed format_grouping_expression to format_grouping_clause with correct
  signature (self, expr) instead of (self, operation, _expressions)
- Updated example code import in examples/query/join.py
- Updated test_expressions.py imports and class name
- Updated test_dialect_version_branches.py to use new method name and signature

* ci: upgrade to ODBC Driver 18 for SQL Server 2025 jobs

Driver 17 is one generation too old for SQL Server 2025's protocol/TLS
defaults; parallel test workers failed connections at suite start.

* feat(dialect-protocol): formalize SQL Server type support and suggestion contracts

- 9 type classes declare dialect_options explicitly; unified _type_params
  based equality/hash
- 29 supports_data_type_* added (1:1 with format family)
- suggested_data_types(): uuid/interval/array/enum/jsonb/timestamptz/timetz
  -> honest cross-backend suggestions; keys disjoint from supported
- Precision validation: decimal 1-38/0-precision, float 1-53, temporal 0-7
- _register_type_formatters() copies supports methods + regex attrs
- New test_sqlserver_type_protocol.py (30 tests)

* fix(test): adapt all tests to dialect-first parameter-inline convention

Update ColumnDefinition, DataType subclasses, and CreateTableExpression
to use dialect-first constructor signatures:
- ColumnDefinition(dialect, name, type, ...)
- IntegerType(dialect), VarCharType(dialect, length=...), etc.

This aligns tests with the parameter-inline refactor in core library
where expressions bind the dialect at construction time.

Fixes CI failures in:
- test_auto_increment_ddl (ColumnDefinition/DataType signatures)
- test_data_type_parsing (DataType dialect-first)
- test_dialect_comprehensive (CreateTableExpression)
- test_dialect_version_branches (DDL rendering snapshots)
- test_create_table_like (ColumnDefinition signatures)
- test_niladic_functions (DataType dialect-first)
- test_p2_coverage_features (DataType dialect-first)
- test_schema_diff, test_sqlserver_partition (providers/fixtures)
- test_connection_management, test_parallel_crud (providers/fixtures)

* fix(ci): prevent xdist worker deadlock on READ_COMMITTED_SNAPSHOT

Root cause:
  _prepare_read_committed_snapshot was a session-scoped autouse fixture
  that executed ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON.
  This requires an exclusive database lock. With pytest-xdist -n 8,
  each of the 8 workers runs its own session and executes this fixture
  simultaneously — all trying to acquire the exclusive lock at once,
  causing a deadlock where every worker waits for the others.

Fix:
  1. Add tests/enable_read_committed_snapshot.py — a standalone script
     that enables READ_COMMITTED_SNAPSHOT using pyodbc, meant to run
     BEFORE pytest in the CI workflow step.
  2. In the workflow, insert 'python tests/enable_read_committed_snapshot.py'
     before every 'python -m pytest' invocation (10 jobs).
  3. In conftest.py, change the fixture to verify-only mode: it checks
     whether the option is already on (it should be, from step 1),
     and only attempts ALTER DATABASE as a lightweight fallback with
     a short 3s timeout and 2 retries (instead of 5s × 5 retries).

* fix(fixtures): add dialect to ColumnConstraint in all SQL Server fixtures

ColumnConstraint.__init__ requires dialect as first argument
(parameter-inline convention), but SQL Server fixtures were calling
ColumnConstraint(ColumnConstraintType.XXX) without dialect.

Fixed in: basic.py, events.py, mixins.py, query.py

* fix(dialect): use to_sql() without arguments in _format_column_definition

col_def.data_type.to_sql(self) was passing dialect as an argument,
but parameter-inline refactored to_sql() to take no arguments
(dialect is bound at construction time). Change to to_sql().

Also addresses the locking.py to_sql() -> str question:
SQLServerTableHint is a standalone dataclass (not BaseExpression),
its to_sql() returning a hint name string is intentional.

* fix(dialect): migrate format_set_operation_expression to (self, expr)

Same parameter-inline migration as ClickHouse/Oracle: the method now
takes a single expr argument, extracting left/right/operation/all_/
alias/order_by_clause/limit_offset_clause from it.

* fix(test): complete dialect-first migration in SQL Server tests

Add the dialect first argument to all remaining ColumnDefinition,
ColumnConstraint, and DataType constructor calls across test files:
- test_alter_table_if_exists_backend.py
- test_auto_increment_ddl.py (_pk helper now takes dialect)
- test_create_table_like.py
- test_dialect_comprehensive.py
- test_dialect_version_branches.py (use local 'd' variable)
- test_niladic_functions.py
- test_p2_coverage_features.py (_create_table uses its 'd' param)
- test_schema_diff.py (add local SQLServerDialect)
- test_sqlserver_partition.py (use local 'd' variable)

* test(protocol): add generic protocol conformance test (positive + negative)

New test_sqlserver_protocol_conformance.py complements the SQLServer-specific
test_protocol_support.py by covering the generic dialect protocols:
- SQLSERVER_PROTOCOLS (35 implemented generic protocols)
- SQLSERVER_NOT_IMPLEMENTED (10: SQL/XML*, GraphTable, ILIKE, FunctionSupport
  intentional; GeneratedColumnSupport known gap — naming mismatch, TODO)
- negative isinstance test + partition completeness test

* feat(dialect): declare GeneratedColumn protocol support

SQL Server supports computed columns but only exposed the singular
supports_generated_column(). Add the plural supports_generated_columns()
(protocol-compliant) and promote GeneratedColumnSupport from
SQLSERVER_NOT_IMPLEMENTED to SQLSERVER_PROTOCOLS.

* fix(test): migrate remaining tests to parameter-inline calling convention

- SetOperation tests: construct SetOperationExpression instead of passing
  individual keyword args to format_set_operation_expression()
- MergeAction: add missing dialect= argument
- IndexDefinition/ForeignKeyConstraint/TableConstraint: add missing
  dialect positional argument in fixtures and test_p2_coverage_features.py

* fix(test): add missing dialect= to MergeAction in remaining test files

- test_dialect_version_branches.py: 3 MergeAction calls
- test_protocol_support.py: 2 MergeAction calls (also positional→keyword)

* fix(dialect): adapt format_fulltext_match/array_expression to expression-based signature

Core protocol changed from scatter args to (self, expr). Update backend
overrides and tests to construct expression objects.

* feat(protocol): override supports_cte_order_by to False

SQL Server prohibits ORDER BY in CTEs unless TOP/OFFSET/FOR XML is
also specified. This is a known SQL Server limitation.

* refactor(protocol): rename supports_cte_order_by to supports_unconditional_cte_order_by

* fix(dialect): remove stale cast_types handling from format_json_expression

TypeCastingMixin.cast() now returns a CastExpression AST node.
The old cast_types attribute no longer exists on JSONExpression.
This was causing AttributeError in derived field tests.

* feat(identifier): add reserved words and opt-out identifier quoting

- Add backend reserved words and wire format_identifier(need_quote=...)
  through SQLDialectBase, warning when a reserved word is left unquoted
- Rename supports_table_like_syntax to supports_create_table_like
- Add identifier quoting tests

* refactor(identifier): remove IdentifierMixin and fix method signatures

* refactor(sqlserver): add missing global mixins + remove 29 duplicate methods

Add FilterClauseMixin, TransactionControlMixin, AutoIncrementMixin,
GeneratedColumnMixin, TriggerMixin, PartitionMixin, ILIKEMixin,
FunctionMixin to inheritance chain (P0 fix: these were missing,
causing support methods to return None via Protocol stubs).

Remove 29 supports_* methods that are now redundant with the newly
added mixins or existing global mixin defaults:
- ArrayMixin, GraphMixin, QualifyClauseMixin, OrderedSetAggregationMixin
- ViewMixin, SchemaMixin, IndexMixin, SequenceMixin, TableMixin
- ConstraintMixin, DDLColumnMixin, SetOperationMixin

dialect.py: 1970 → 1900 lines (-70)

* test(protocol): move Function/ILIKE to positive protocol list

The dialect now composes FunctionMixin/ILIKEMixin, so it satisfies
FunctionSupport/ILikesupport; the negative-contract list was stale and
failed. Note their semantics remain backend-specific.

* refactor(sqlserver): split dialect methods into 25 feature mixins

Move 90 methods from dialect.py into 25 mixin files:
- datetime.py, collation.py, cte.py, returning.py, constraint.py,
  window.py, json.py, grouping.py, locking.py, merge.py, temporal.py,
  upsert.py, lateral.py, explain.py, dql.py, dml.py, view.py,
  schema.py, index.py, generated_column.py, set_operation.py,
  table.py, identifier.py, transaction.py, function.py (new)
- Extended: trigger.py, sequence.py
- New shared constants: version_constants.py

dialect.py: 1900 → 1062 lines.

* feat(sqlserver): add JSON/SET/spatial expression classes

Expression nodes for format_* signature compliance (mirrors the MySQL/
MariaDB expression-object convention).

* refactor(sqlserver): rename protocols/json_support.py to json.py

Drop the redundant _support suffix from the protocol module name.

* fix(sqlserver): declare format_identifier on the dialect

SQLDialectBase precedes the feature mixins in the MRO, so the bracket
formatter in SQLServerIdentifierMixin was shadowed and identifiers were
rendered with double quotes. Re-declare it on the dialect.

* test(sqlserver): route direct formatter calls through expression nodes

Tests were calling dialect formatters with raw strings/lists, bypassing
the expression layer. Construct the corresponding expression classes and
call to_sql() so the tests exercise the real expression<->formatter
integration:

- NEXT VALUE FOR -> SQLServerNextValueForExpression
- CONTAINS / FREETEXT -> SQLServerContainsPredicate / SQLServerFreetextPredicate
- TRY_CAST / TRY_CONVERT -> SQLServerTryCastExpression / SQLServerTryConvertExpression
- table hints -> SQLServerTableHintClause
- OPENJSON -> SQLServerOpenJsonExpression
- temporal period -> SQLServerTemporalPeriodDefinition
- FULLTEXT CATALOG -> SQLServerCreateFullTextCatalogExpression
- OPTION hints -> SQLServerOptionHintClause

* fix(sqlserver): compose CollationMixin for format_collate_expression

CollationMixin was imported but not in the bases, so format_collate_
expression resolved to the CollationSupport protocol stub (None).

* refactor(sqlserver): emit bind placeholders via dialect accessor

* fix: replace hardcoded ? placeholder with dialect.p() in async_backend

* refactor: normalize format_* return types to Tuple[str, tuple] and eliminate isinstance

- Global: Tuple[str, Tuple] → Tuple[str, tuple] across all format_* methods
- format_auto_increment: str → Tuple[str, tuple]
- format_top_n_clause: str → Tuple[str, tuple]
- format_inline_index: str → Tuple[str, tuple]
- format_limit_offset: Tuple[Optional[str], List[Any]] → Tuple[str, tuple]
- format_next_value_for: str → Tuple[str, tuple]
- format_set_identity_insert: str → Tuple[str, tuple]
- format_temporal_period_definition: str → Tuple[str, tuple]
- format_table_hint / format_table_hint_locking: str → Tuple[str, tuple]
- format_tablesample_clause: str → Tuple[str, tuple]
- format_*_fulltext_*: str → Tuple[str, tuple]
- Eliminated isinstance checks:
  - format_column_definition: data_type always DataType, default_value uses inline_sql_literal
  - format_create_index_statement: columns unified to expression objects
  - format_fulltext_match: columns unified to expression objects
  - format_json_function_expression: column unified to expression objects
  - format_sqlserver_partition_function: values use inline_sql_literal
  - format_openjson_schema: schema unified to expression objects
- Added type hints to ~40% of methods previously lacking them
- Updated all protocol definitions to match new signatures

* chore: fix .gitignore for .claude/plan*/

* fix: update tests and dialect for signature normalization

- format_limit_offset returns (str, tuple) not (None, []); fix test assertions
- format_top_n_clause returns Tuple[str, tuple] not str; fix test assertions
- format_table_hint_locking/format_set_identity_insert return Tuple[str, tuple]
- format_create_index_statement: handle string columns via isinstance check
- Import ToSQLProtocol from expression.bases for column type checking

* fix: update tests to unpack (sql, params) tuple from format_tablesample_clause

* fix: handle BaseExpression default values in format_column_definition

When DEFAULT value is a FunctionCall or other BaseExpression, call
to_sql() instead of inline_sql_literal() which cannot handle expression
objects.

* refactor(ddl)!: SQL Server LIKE unsupported via CreateTableLikeExpression

- remove the inline dialect_options['like_table'] guard from
  format_create_table_statement; SQL Server inherits
  supports_create_table_like() == False, so the gated generic
  format_create_table_like_statement raises UnsupportedFeatureError
- tests migrate to CreateTableLikeExpression and assert the raise

BREAKING CHANGE: CREATE TABLE ... LIKE is no longer expressed through
CreateTableExpression(dialect_options={'like_table': ...}); use
CreateTableLikeExpression, which raises UnsupportedFeatureError on
SQL Server.

* feat(ddl): prefer typed table_options for memory_optimized/durability

Renderer now reads memory_optimized/durability from typed
CreateTableOptions fields first, falling back to dialect_options.

* fix(ddl): fix DROP TABLE CASCADE/RESTRICT and remove override

- Add supports_drop_table_cascade() → False
- Add supports_drop_table_restrict() → False
- Remove format_drop_table_statement override (use generic impl)

* feat(ddl): add supports_multi_action_alter_table(False)

SQL Server requires one action per ALTER TABLE statement.

* refactor(ddl): rename table.py → ddl_table.py in mixins

* feat: add SQL Server DDL improvements with capability gating

- Add SQLServerDatabaseMixin for CREATE/DROP/ALTER DATABASE support
- Add SQLServerViewMixin with capability gating
- Add test_sqlserver_ddl_improvements.py with 5 tests
- Rename view.py -> ddl_view.py

* refactor: remove ReturningMixin import (merged into DMLMixin in core)

* feat: improve OUTPUT clause (per-expression prefix, INTO, validation)

- Per-expression virtual-table prefix (Column(table='DELETED'|'INSERTED'))
- ReturningClause.output_into renders OUTPUT ... INTO <target>
- Raise UnsupportedFeatureError for non-column expressions, wildcards, aliases
- supports_returning_alias/wildcard False, supports_returning_into True
- Fix stale .mixins.view import (renamed to ddl_view)
- Add OUTPUT tests

* test: classify DatabaseSupport in SQL Server protocol conformance

* test: pass SQL Server version in DDL capability tests to satisfy version gating

* feat(dql): WITH TIES + typed lock strength + set-op FOR UPDATE gate

- format_limit_offset_clause: FETCH NEXT ... ROWS WITH TIES
- supports_fetch_with_ties True; supports_nulls_first_last False
- locking: gate strength (no silent ignore)
- set_operation: raise on unsupported FOR UPDATE

* refactor: remove core DQL/Expression mixin references (merged into DQLMixin/ExpressionMixin)

* feat(ddl): render generated columns via core GeneratedColumnExpression

* chore: add scripts/preflight.sh and optional git hook installer

* feat(graph): add SQL Server SQL Graph support (node/edge tables + MATCH predicate)

SQL Server SQL Graph is independent from SQL/PGQ (different data model and
query shape), so it lives in the SQL Server backend, not the core GraphMixin.

- expression/graph.py: SQLServerGraphDirection, SQLServerGraphPseudoColumn,
  SQLServerGraphNodeRef, SQLServerGraphEdgeRef, SQLServerGraphPattern,
  SQLServerMatchPredicate, SQLServerShortestPathExpression,
  SQLServerGraphPathAggregate, SQLServerForPathTable
- expression/ddl/graph.py: SQLServerGraphTableKind,
  SQLServerAsGraphTableExpression, SQLServerEdgeConstraint
- protocols/graph.py: SQLServerGraphSupport (6 switches + 9 formatters)
- mixins/graph.py: SQLServerGraphMixin with version gating
  (SQL Graph 2017+, SHORTEST_PATH / path aggregates 2019+)
- dialect: compose mixin + protocol; integrate AS NODE/AS EDGE and
  CONNECTION edge constraints into format_create_table_statement
- version_constants: add SQL_SERVER_2025
- keep core SQL/PGQ MatchClause rejection; clarify suggestion text
- tests: 30 cases (switches, pattern/MATCH/SHORTEST_PATH/path aggregate,
  pseudo-column validation, DDL, version-gating negatives)

* fix(graph): align SQLServerShortestPathExpression attrs with ctor params

Store 'pattern'/'extra_patterns' so the introspection-based get_params()
serialization round-trip can reconstruct the expression; expose 'patterns'
as a derived property.

* chore(ci): update rhosocial-activerecord install source to release/v1.0.0.dev30

* fix(sqlserver): correct pagination, database ownership and temporal formatting

* fix(graph): correct shortest path grouping and edge constraint gating

* test(graph): reuse scenario fixtures and protocol capability checks

* feat(sqlserver): add statement-level NOSCAN execution option

* chore: add changelog fragment for graph support and NOSCAN option
@vistart
vistart deleted the feature/parameter-inline branch October 1, 2026 03:52
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