Repository navigation
feat(sqlserver): adopt the typed-columns column layer - ColumnTypeSupport table, TimestampColumn rename, and the T-SQL TRIM spellings - #8
Merged
Conversation
… column_data_type)
…; lpad/rpad/repeat stay refused)
…as merged While these branches were in development, CI installed the core from `@feature/typed-columns`, because the release branch did not carry the rebuilt column layer yet. That branch has now merged into `release/v1.0.0.dev30` (PR #126), so the feature reference is both stale and about to disappear with branch deletion. Point the core dependency at `release/v1.0.0.dev30` instead — the same branch these backends are merging into, which is what the release-side CI already used. The testsuite reference is deliberately left alone: that branch (`test/typed-column-contracts`) has not merged into its release yet, and pointing at a release that lacks the new contracts would break every backend at once. It moves when the testsuite merges. ClickHouse expresses the dependency as a `ref:` on `actions/checkout` rather than as a pip requirement; that one is switched the same way.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## release/v1.0.0.dev3 #8 +/- ##
=======================================================
+ Coverage 73.97% 74.57% +0.59%
=======================================================
Files 135 139 +4
Lines 7605 7783 +178
Branches 1261 1298 +37
=======================================================
+ Hits 5626 5804 +178
- Misses 1540 1541 +1
+ Partials 439 438 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Description
This backend follows the core's column-layer rebuild (
python-activerecord#126, merged intorelease/v1.0.0.dev30): the core now decides which operations a column offers by its class rather than by a universal base, routes every operation through oneformat_*hook, turnstrim/lpad/rpad/repeatinto dedicated nodes, renamesDateTimeColumntoTimestampColumn, and rebuilds the DDL-source accessors. This branch makes the SQL Server dialect answer each of the new questions the rebuild asks, in this server's own terms.The column half of
ColumnTypeSupport. A newSQLServerColumnTypeMixincarriesSQLSERVER_COLUMN_TYPES, an eighteen-entry table answering every one of the framework's common Python types with a column class —str→StringColumn,list/dict/tuple/set/frozenset→JSONColumn(SQL Server has no array type on any version, and a document is text inNVARCHAR(MAX)that the 2016 function family reads),uuid.UUID→UUIDColumn,enum.Enum→StringColumn,datetime.timedelta→NumericColumn(a span here is aDATEDIFFcount), and so on. It is composed into the dialect ahead of core's genericColumnTypeMixin, so its answer is the one a model layer reaches; core supplies no table of its own, so an unanswered type fails at resolution time, by name, rather than being filled in with a guess about a server core has never seen. Every deviation in the table is recorded with its measured evidence (a live-server sweep across 2019 / 2022 / 2025), and the two famous facts that deliberately stay out of the table are stated in the module docstring instead, so their absence reads as a decision: JSON is not a storage type here (the functions make a document), anduniqueidentifiersorts by internal byte layout, which is a rendering obligation (CONVERT(varchar(36), u)restores text order) rather than a missing operation. The dialect now also declares the newSQLServerTypeSupportprotocol — the stated shape of thesqlserver_-prefixed type family (isxmlreal here, isBITa boolean, istinyintsigned) — positioned in the MRO next to the mixin that carries the behaviour, andColumnTypeSupportjoinsSQLSERVER_PROTOCOLSin the conformance test.The storage half.
suggested_data_types()is reworked and documented as "concepts SQL Server cannot spell, and what it stores instead": the keys are exactly the core types this dialect does not render (binary/varbinary→BlobType,interval→BigIntType,array→JsonType,enum→VarCharType,jsonb→JsonType,timetz→TimeType,uuid→ the backend's ownSQLServerUniqueIdentifierType, because T-SQL writesUNIQUEIDENTIFIERand the dispatch key has to be a word this backend can produce), and the docstring records what is deliberately not there:xmlandtimestamptz, both of which have native types here. A load-order defect surfaced while wiring this up —_type_class_for(name)resolves a name by walking the liveDataTypesubclass tree, so asqlserver_-prefixed name whose class nothing had imported yet resolved toNoneand dropped out of the mapping silently; the registration path now imports the backend's own DataType classes first.The rename.
DateTimeColumn→TimestampColumnthroughout (core renamed it: the column layer models values, anddatetimeis a vendor spelling, not a different value), and the type-protocol tests follow the core's rename ofDateTimeSupporttoTimestampSupport. A newSQLServerUUIDMixinlets this backend's UUID spellings (NEWID,UNIQUEIDENTIFIER) win over core's single fixed spelling, andSQLServerProtocolSupportMixinnow declaresSTRING_CONCATENATION = "CONCAT", because T-SQL reads||as logical OR.SQLServerTrimMixin, written new. SQL Server'sTRIMhas no ANSITRIM(BOTH … FROM …)form at every compatibility level: up to 2019 — and in Azure Synapse Analytics — the only spelling isTRIM([characters FROM] string), which always trims both ends and puts the character set before the operand; 2022 added theLEADING/TRAILING/BOTHkeywords, but only at compatibility level 160, so the ANSI default form the core renderer emits is not portable even on the newest server. The mixin keeps the operand order SQL Server had first and encodes the direction in the name:BOTH→TRIM(chars FROM x)/TRIM(x),LEADING→LTRIM(x)/LTRIM(x, chars),TRAILING→RTRIM(x)/RTRIM(x, chars). It deliberately does not version-gate: the dialect has no mechanism for a per-expression capability switch, and the rendering and its limits are recorded in the docstring instead.The deliberate refusals. SQL Server has no
LPAD,RPADorREPEATfunctions at all (pad is spelledREPLICATEwithRIGHT/LEFT; repeat isREPLICATE). With those nodes now dedicated core classes, their dispatch reaches formatters no base in this dialect provides — and before this branch, that refusal read as "SQL Server lacks the feature" and passed, indistinguishable from a probe genuinely declining something. The round-trip matrix now pinsUNMODELLED_FORMATTERSin both directions, so every dispatch refusal is a row with a stated reason (also covering the SQL/XML family, DOMAIN, core's generic PIVOT and theTableSourceroot) and a method nobody listed fails the run instead of passing as "unsupported".The round-trip enumeration. The expression matrix test enumerates every core node class, walking the package deterministically instead of the process-global
ExpressionRegistry— whose contents depended on which sibling modules pytest happened to import first — so a missing formatter cannot stay silent, and a class that becomes unconstructible must be a row in the pinnedUNCONSTRUCTIBLEtuple rather than an unexamined skip.Tests follow the core's rebuilt semantics. The DDL-source tests move to the accessor migration (
column_type()/columns_type()→column_data_type()/columns_data_type()), the DDL check/index conditions and the predicates previously expressed through the bareColumn's operator sugar are built explicitly as nodes, and one parametrisation stops instantiatingColumnBase— now an ABC — substituting a concrete typed column. A new table-type definition round-trip test covers the reworkedsuggested_data_types()answers.CI and dependencies. The core install line now points at
@release/v1.0.0.dev30now that typed-columns has merged there; the testsuite install line points at@test/typed-column-contracts. A Python 3.15 leg is deliberately not added:pyodbc5.3.0 ships no cp315 wheel (source tarball only, noPython :: 3.15classifier), so a 3.15 leg would fail at install while pip compiles the driver — the README records this, and adding the leg awaits apyodbcrelease rather than anything in this repository. For Python 3.13 / 3.13t,requirements-dev.txtpinspydantic 2.12.2/pydantic-core 2.41.4, mirroring the core's pins: this file is installed before the core package, so an unpinnedpydanticthere picks apydantic-corewhose only cp313t distribution is an sdist that fails to build on the free-threaded jobs.Type of change
feat: A new featuredocsperftest: Adding missing tests and correcting existing tests for the new featurechoreci: Changes to CI configuration files and scripts (core install line now on its release branch; testsuite line)build: Pydantic pinning for Python 3.13 / 3.13trevertBreaking Change
(If Yes, please describe the impact and migration path below):
Impact on End-Users:
The large breaking surface is the core's (see python-activerecord #126); what is visible through this backend's API is the following, and nothing else:
DateTimeColumnrenamedTimestampColumnColumnlost its operators (core)StringColumn,NumericColumn, …) or build the node explicitly (ComparisonPredicate,InPredicate,BinaryArithmeticExpression)DDLSource.column_type()is now a declaration accessor (core)column_data_type()/columns_data_type();columns_typeis gonetrim/lpad/rpad/repeatreturn nodes, not function calls (core)UnsupportedFeatureError, or target a server that has the functionsupports_data_type_*probe family is retired (core)supports_data_types(), which now answers from formatter presenceImpact on Backend Developers (Custom Implementations):
TrimMixin/LpadMixin/RepeatMixin/RpadMixin) or writes aformat_*override where its spelling differs —SQLServerTrimMixinhere is the worked example, including a non-ANSI operand order that the shared default does not produce.ColumnTypeSupportis a new protocol a backend must implement; the conformance surface (SQLSERVER_PROTOCOLS) must classify it, and the backend must declare the shape of its own type family (SQLServerTypeSupporthere).suggested_data_types()) must answer only with classes the dialect really renders, and must not name a key it can produce under its own word (uuid→SQLServerUniqueIdentifierTypehere).Related Repositories/Packages
python-activerecordrelease/v1.0.0.dev30python-activerecord-testsuitetest/typed-column-contractspython-activerecord-{postgres,mysql,mariadb,oracle,firebird,clickhouse,snowflake,bigquery}feature/typed-columnsNote on the install references: this branch's release CI installs the core from
@release/v1.0.0.dev30(re-pointed in the last commit now that #126 has merged) and the testsuite from@test/typed-column-contracts. Once the testsuite branch lands on its release branch, that line has to be re-pointed too — otherwise the release branch keeps depending on a branch that will be deleted.Testing
Test Plan:
Full-suite verification is left to CI — the project forbids local full-suite runs, so no
pytest tests/was executed locally.Checklist
.claude/code_style.md.README.md— the 3.15-leg rationale is recorded there).dictper call; no per-render work is added).Additional Notes
Deliberately out of scope for this branch (recorded so reviewers do not expect it):
pyodbc; the README records why.LTRIM's two-argument form (2022 only, at compatibility level 160). The dialect has no mechanism for a per-expression capability switch, so the mixin records the rendering and its limits instead of gating — a future per-expression capability hook would let it narrow.jsontype. Not modelled yet: the catalogue answersjsonwithCHARACTER_MAXIMUM_LENGTH = -1and the parser returnsCustomType('json')whileformat_data_type_jsonstill rendersNVARCHAR(MAX). The column answer is unaffected (a document carries the same operations wherever it is kept), and this is documented in the column-type module docstring.format_*annotation sweep org-wide, and the rest of the DataType four-table rework, are follow-up work.Verified before drafting: the release tip is an ancestor of the branch (merge-base = release tip, 0 behind), so no rebase is needed on merge.