Uh oh!
There was an error while loading. Please reload this page.
fix(driver-sql): boolean aggregands answer the ruled #11249 contract — PG lowering cast + all-dialect min/max JSON-boolean presentation - #11785
Draft
huangyiirene wants to merge 2 commits into
Conversation
…max as JSON booleans on every dialect Implements the #11249 ruling at the driver boundary: sum/avg over a declared boolean answer arithmetic (3 / 0.5 on the 3-true/3-false fixture) and min/max answer false/true in JSON — order statistics return a member of the input domain. - Postgres: the lowering now emits cast("flag" as int) for sum/avg/min/max over a declared boolean column (PG defines no arithmetic/order aggregates over boolean; SQLSTATE 42883, measured on PG 16.13). count/count_distinct are deliberately not cast. - Every dialect: min/max results over a declared boolean are tracked for boolean read-presentation, closing the MySQL gap where tinyint(1) answered 0/1 through the driver boundary (measured on MySQL 8.0.46). NULL (empty window) passes through untouched. - The #11455 suite's PG-only 42883 block is retired per its own retirement clause; the ruled answers are pinned on all three dialects by sql-driver-11635-boolean-aggregand-answers.test.ts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VK8rFDtg8eREaxBGX99Csn
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VK8rFDtg8eREaxBGX99Csn
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Aug 24, 2026
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 freeto 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.
Fixes#11635
The ruling this implements
#11249 (maintainer 2026-08-23, comment 5386670755, verbatim and untranslated: 「10950 不考虑存量,其他接受你的建议」):
min/maxover a boolean aggregand answerfalse/truein JSON — order statistics return a member of the input domain, SQL drivers convert at the driver boundary;sum/avganswer arithmetic (3/0.5on the 3-true/3-false fixture, the settled #11065 family shape). Cast, not refuse: a face that refuses cannot satisfy the ruled contract. This is declared = enforced restoration of an already-ruled contract, not a new capability.Premise re-measured on current
main(2a6122bd9d), before buildingBoth card measurements reproduced through
SqlDriver.aggregate()on the live servers this container runs at CI parity (PG 16.13timezone=Asia/Shanghai, MySQL 8.0.46time_zone=+08:00, processTZ=America/New_York):sum/avg/min/maxover a declared boolean all threwDATABASE_ERROR/500 withcause.code=42883(the post-driver-sql (PG): sum/avg/min/max over a boolean column throw the raw PostgreSQL 42883 with no ADR-0112 envelope (status undefined) #11455 envelope — driver-sql: an unresolvable WHERE column onaggregate()answers DATABASE_ERROR/500 wherefind()andcount()answer INVALID_FILTER/400 — the #8790 refusal never reached the third read door #11541's classifier leaves this fault to the terminal arm).min(flag)answered0,max(flag)answered1(JS numbers) — the presentation gap the card asked to verify is REAL, so the MySQL half needed a fix, not just a measurement.The change (2 seams in
sql-driver.ts+ tests)aggregate()'s statement builder:sum/avg/min/maxover a field inbooleanFieldsemitfunc(cast("col" as int))on Postgres only. The column stays a knex identifier binding.count/count_distinctare deliberately not cast (both lower tocount, defined over boolean everywhere; the acceptance keeps them unchanged). No new dialect predicate — the existingisPostgresgetter selects the branch (read-coupling with driver-sql: the isSqlite/isPostgres/isMysql getters recognise fewer knex client spellings than knex accepts, so a validclient: 'postgres'silently loses every dialect-specific behaviour #11550 noted below; its getters were not edited).'boolean'on every dialect, not only underreadPresentationKind's SQLite gate: MySQL'stinyint(1)and PG's castedintboth answer 1/0 from the backend and leave the driver as JSONfalse/true.null(empty window) passes through untouched —min/maxover no rows still answernull, matchingemptyGroupValueFor.sql-driver-11635-boolean-aggregand-answers.test.tspins the ruled answers on all three dialect cells (AGGREGATION_ROWS+ aflagboolean column, 3 true / 3 false, declaredtype: 'boolean';eastgroup all-true so groupedmin=truecatches whole-table/sticky presentation). The driver-sql (PG): sum/avg/min/max over a boolean column throw the raw PostgreSQL 42883 with no ADR-0112 envelope (status undefined) #11455 suite's PG-only 42883 block is retired per its own retirement clause ("if [finding]AGGREGATION_ROWShas no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152/[Decision]min/maxover a boolean aggregand: pin the cross-driver JSON answer —0/1(SQL) vsfalse/true(both in-memory faces) #11249 rule that the lowering should CAST, these cases stop failing and this block is RETIRED by that card") — the four 42883 pins, the fence case, and its log case are replaced by a tombstone naming the ruling; itscount/count_distinctcontrols live on in the new suite; the all-dialect terminal-envelope sweep (missing table) is untouched. No pin was deleted to make room — the retirement is the honest update the ruling forces, and this paragraph is its declaration.Acceptance numbers, measured through
SqlDriver.aggregate()sum(flag)"3"(Number 3)"3"(Number 3)3avg(flag)"0.50000000000000000000"(Number 0.5)"0.5000"(Number 0.5)0.5min(flag)false(JSON boolean)falsefalsemax(flag)true(JSON boolean)truetruecount(flag)"6"— unchanged6— unchanged6count_distinct(flag)"2"— unchanged2— unchanged2Contract surface (clause ②) — the answer set and its JSON types, named
sum/avg/min/maxover a declared boolean on PG now ANSWER (previouslyDATABASE_ERROR/500 wrapping SQLSTATE42883). No refusal is left behind on this route: the driver-sql (PG): sum/avg/min/max over a boolean column throw the raw PostgreSQL 42883 with no ADR-0112 envelope (status undefined) #11455 terminal envelope still owns every fault the driver-sql: an unresolvable WHERE column onaggregate()answers DATABASE_ERROR/500 wherefind()andcount()answer INVALID_FILTER/400 — the #8790 refusal never reached the third read door #11541 classifier does not claim (missing table etc.), unchanged.min/max→ JSON booleans on every dialect (strictly pinned).sum/avg/count/count_distinctarrive as the dialect client's exact-numeric wire shape — node-pg and mysql2 hand bigint/DECIMAL/numeric back as STRINGS ("3","0.5000"), for boolean and integer aggregands alike (the driver-sql (PG): sum/avg/min/max over a boolean column throw the raw PostgreSQL 42883 with no ADR-0112 envelope (status undefined) #11455 suite's control records the same convention). This PR deliberately does not touch that numeric convention: converting boolean sum/avg only would split boolean from integer aggregands on one dialect. The new suite pins the values throughNumber(...)and the booleans strictly.min/maxover a declared boolean on MySQL move from0/1(numbers) tofalse/true(booleans) — the ruled direction. The neighbouring row-read door (find()) still answers1/0on MySQL; filed as driver-sql (MySQL): find() answers 1/0 for a declared boolean field — the read coercion is gated to SQLite, so the storage form leaks on the row-read door #11782 (see below), not widened into this PR.Verification
sqlite+live postgres+live mysqlwithOS_EXPECT_LIVE_DIALECT_MATRIX=1(no cell skipped).@objectstack/driver-sqlsuite with both live servers: 133 files, 2696 passed | 1 skipped — verify-lock VERDICTcommand-exit 0.pnpm --filter @objectstack/driver-sql typecheckVERDICTcommand-exit 0.git restore --source=2a6122bd9dofsql-driver.ts, confirmed on disk by marker greps —castBooleanAggregandcount 0 and the pre-fix bare-lowering line back — with atrap ... EXIT INT TERMrestore): predicted 9 failed / 19 passed with PG reds by THROW and MySQL reds on VALUES; measured exactly that — PG: 6 red, allDATABASE_ERROR42883 (sum/avg/min/max/grouped/empty-window); MySQL: 3 red on values (expected +0 to be false,expected 1 to be true, grouped{lo:1,hi:1}); SQLite all green. Restore confirmed (marker count back to 2,git statusclean). The suite imports the driver by relative path, so vitest readssrc— no build stands between the mutation and the measurement.05c95a7b85(union re-run after the last commit): all derived + convention families green (exit 0 each):check:changeset-gate-self-tests,check:objectui-changeset,check:published-files,check:slot-lookup,check:test-source-alias,check:type-source-resolution,check:query-options-erasure,check:type-check-coverage,check:engine-double-contract,check:cross-package-test-inputs,check:where-matcher,check:nul-bytes,check-adr-0087-registration,check-changeset-no-major,check-empty-changeset,check-plugin-teardown-shape,check-affected-docs,release-rehearsal-clone --self-test,check:driver-conformance. Declared narrowing:check:type-check-debt's--re-measurehalf was not run locally (needs the full workspace build) — its population is the DEBT/TEST_DEBT ledgers incheck-type-check-coverage.mjs,@objectstack/driver-sqlis in none of them, driver-sql's own tsc program (src/**/*, tests included) covers every file this PR touches and exits 0, and the diff moves no export surface, so no ledgered package's program reads anything this PR changed; CI runs the full re-measure regardless.Sequencing
AGGREGATION_ROWShas no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 (itsBlocked-by:points at driver-sql: boolean aggregands need a lowering cast on PG (+ a MySQL min/max presentation check) — the ruledfalse/true+ arithmetic answers are unproducible on the PG face #11635) — the conformance column there is the cross-driver acceptance evidence once this lands.avg/sumover a boolean column tonull/0— the same cell as #11065, one driver over #11151, frozen under the [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 ruling) —driver-mongodbuntouched.client: 'postgres'silently loses every dialect-specific behaviour #11550: the PG branch is selected byisPostgres; that getter (and the other driver-sql: the isSqlite/isPostgres/isMysql getters recognise fewer knex client spellings than knex accepts, so a validclient: 'postgres'silently loses every dialect-specific behaviour #11550/driver-sql (MySQL): nothing sees the 65535-byte row budget, so a table with many wide declared bounds fails CREATE with an error naming no field #11565 regions) were not edited. Pins are written againstclient: 'pg'and stay green whichever lands first.Out of scope, filed
find()on MySQL answers1/0for a declared boolean field (thebooleanFieldsread coercion is SQLite-gated on the row-read door); measured while taking this card's readings.Generated by Claude Code