Repository navigation
refactor(driver-sql): move the read exits' fault terminals out of sql-driver.ts into sql-driver-read-exit-fault.ts (#25521 cut 15) - #26490
Conversation
…-driver.ts into sql-driver-read-exit-fault.ts Move-only. backendStatementFault, unresolvableFilterColumnRefusal, aggregateBackendFault, distinctBackendFault and the aggregate and distinct column refusals move, unedited, into a new seam module behind ReadExitFaultHost, with the four module-level declarations only they read (backendStatementFaultError, unresolvableFilterColumnError, unresolvableColumnNameOf, unresolvableColumnProvenance) and the redactStatementFromMessage import with its comment. The class keeps every method as a stub with its docblock and signature. PRE_DDL_QUESTION_SCOPE, unsupportedFilterError and isFilterNode gain export in place. The compile-refusal seam test scans the new module too, and the unresolvable-where test imports unresolvableColumnNameOf from it. Claude-Session: https://claude.ai/code/session_01FEkQ5mL6cyUGiP87zTtR7y Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 77670858e8aff44fe7da5e2a5bc89cce3e2b847a && git checkout 77670858e8aff44fe7da5e2a5bc89cce3e2b847a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d0a7e748a933a7fb549c38c3105c1c49f5935dec 3b5318a22a8ca7614fcf4471689f14bf2acac744 && git checkout -B drift-repro d0a7e748a933a7fb549c38c3105c1c49f5935dec && git merge --no-ff 3b5318a22a8ca7614fcf4471689f14bf2acac744
node scripts/docs-audit/affected-docs.mjs --json d0a7e748a933a7fb549c38c3105c1c49f5935dec
|
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Part of #25521
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840
Ruling-ref: 6348891160
维护者速读
sql-driver.ts里「读操作被数据库拒绝时怎么回答」的整组代码(find/count的兜底故障信封、WHERE 列找不到时的拒绝、aggregate与distinct各自的分类器和列拒绝,以及只被它们用到的四个模块级函数)原样搬进新文件sql-driver-read-exit-fault.ts;类上的六个方法全部保留,只把方法体转交过去。src/sql-driver.ts(22,096 lines, +2,533 in 30 days) along itsSqlDriverseams, one move-only slice per PR #25521 拆分超大文件的第 15 刀;这组是剩余可搬区域里收益最大的一块,sql-driver.ts减少 445 行。sql-driver-read-exit-fault.ts的头注释与ReadExitFaultHost;sql-driver.ts中六个方法的转交桩;sql-driver-compile-refusal-seam.test.ts新增的那一个扫描文件。pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-backend-fault-envelope.test.ts src/sql-driver-compile-refusal-seam.test.ts src/sql-driver-unresolvable-where-column-refusal.test.ts,预期全部通过。What changes
Cut 15 of the
sql-driver.tssplit (#24586 ruling A, recorded in6270097840). The claim is6432488188. The cut opens with a fresh seam table (below), taken onorigin/maind1a2e71a3(cut 14's merge), and takes the area that pays best without breaking a series limit: the fault terminals of the read exits, what a read answers when the backend refused its statement. It moves unedited into a new seam module,packages/drivers/driver-sql/src/sql-driver-read-exit-fault.ts, through a new hostReadExitFaultHost:backendStatementFault, the A dotted WHERE key escapes as a raw dialect error with the bound literal inlined on Postgres and MySQL — pre-existing, and measured against live servers #8931 terminal thatfindandcountend in;unresolvableFilterColumnRefusal, driver-sql: one unresolvable WHERE column, two answers —find()silently returns [] whilecount()throws a raw dialect error with no ADR-0112 envelope #8790'sINVALID_FILTERfor a WHERE column the database could not resolve;aggregateBackendFault(driver-sql (PG): sum/avg/min/max over a boolean column throw the raw PostgreSQL 42883 with no ADR-0112 envelope (status undefined) #11455 / 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) anddistinctBackendFault(driver-sql: distinct() leaks the raw backend error with status undefined — the ADR-0112 envelope #11455 gave aggregate() never reached the third read door #17639 / driver-sql: an unresolvable column on distinct() answers DATABASE_ERROR/500 where find()/count() answer INVALID_FILTER/400 — the #8790 refusal never reached the distinct door #17857), the classifiers ofaggregateanddistinct, each of which ends in one of the two above or in its own column refusal;unresolvableAggregateColumnRefusalandunresolvableDistinctColumnRefusal, those two column refusals.With them go the four module-level declarations only they read: the #8931 envelope
backendStatementFaultError, #8790'sunresolvableFilterColumnError, the dialect-message readerunresolvableColumnNameOfand the #20020 attribution walkunresolvableColumnProvenance. The[#21385]import ofredactStatementFromMessagefollows its last reader into the module, with its own comment.The change is move-only, and the card stays open for later cuts. There is no
docs/adr/**edit, nopackages/spec/**edit, no anchor entry change, no gate edit and no new gate. Two tests change, and only as path readers (below). Branchclaude/project-thread-uliqpv(the platform assigned it to this card thread, as the claim records). The move is one commit,ef0640e10, ond1a2e71a3.Net line count (the seat's rule).
sql-driver.tsgoes from 19,794 lines to 19,349: -445.redactStatementFromMessageimport and its 6-line comment, 7 lines. Two names (declareTargetedTable,isMissingTableError) from the@objectstack/typesimport, 2 lines.Path readers migrated in the same commit (both tests, no assertion changed).
sql-driver-compile-refusal-seam.test.tsenumerates everyINVALID_FILTERbuilder by parsing a fixed list of source files, and assertsunresolvableFilterColumnErroris one of the three direct callers. That function now lives in the new module, so the list gains it (one line, plus its comment and the two docblocks that name the list). The direct-caller set it asserts is unchanged.sql-driver-unresolvable-where-column-refusal.test.tsimportsunresolvableColumnNameOffrom the new module instead of./sql-driver.js.Serial with the file's in-flight PRs. I read the file list of every open PR (23, by fetching each head and diffing it against its merge base with
origin/main) before the first edit. None touchessql-driver.ts,turso-driver.ts,sqlite-wasm-driver.ts,check-tenant-chokepoint.mjsor the driver-sql anchor entries. #26138 touchesdriver-sql/src/builtin-column-collision.tsonly; #26337 addsdriver-sql/src/sql-driver-external-unprovisioned-sort-anchor.test.tsonly, which reads none of the moved members. Re-measured beforepr_create(21 open PRs): #26451 edits the five drivers'package.jsononly, and #26337 is as above; neither touches this region.Seam table, fresh, on
d1a2e71a3sql-driver.tsis 19,794 lines. The 30-day column countsgit blamelines from commits on or after 2026-09-27T03:11:08Z, 30 days before the base commit. History reaches 2026-09-18.scripts/pm/git-history.mjs countproves 75 commits on the file in 30 days and 33 in the last 7. In all, 3,481 lines (17.6%) are from the last 30 days.SqlDriverspans lines 5149-19719 with 350 members: 243 instance methods, 13 accessors and 32 statics, all 32 private or protected.!andsatisfiesbefore it readsthis. Sections are the class's own banner comments.name…assertDialectEmitsconnect…disconnectfind…deletebulkCreate…countrawStatementFault…noteRuntimeIndexDdlbeginTransaction…rollbackTransactionaggregate…unresolvableAggregateColumnRefusaldistinct…unresolvableDistinctColumnRefusalfindWithWindowFunctions…findWithWindowFunctionsexplain…analyzeQuerysyncSchema…reclaimSpacerotationStateByTable…migrateMysqlTimeColumnssetDeferredDdl…flushDeferredSchemaDdldialectName…createNullSafeUniqueIndexlistPhysicalColumns…introspectSchemagetKnex…auditMissingTenanttableNameForBuilder…applyFalseConstantmapSortField…mapAggregateFuncbuildWindowFunction…buildWindowFunctionnowColumnDefault…applyDeclaredColumnDefaultsqliteFilename…isJsonFieldformatInput…formatOutputintrospectColumnOrder…introspectUniqueConstraintsPricing the areas
Eligibility is as cuts 11-14 defined it: an annotated return type or not public; no
getBuilder,this.FIELD.delete/insert/update(, private-static,super, bare-thisorthis-write site; notinsertOnlyUpsertColumns. 141 members are eligible on this base. Each area is priced as its stubs minus its bodies, then net of the module-level declarations it shares with members that stay (they stay in place, exported; ⛔ no second copy of a helper), counting only a declaration's own docblock (cut 14's correction: the move script refuses a declaration whose leading trivia is not exactly one JSDoc block directly above it, and none of the four had another comment).backendStatementFault,unresolvableFilterColumnRefusal,aggregateBackendFault,distinctBackendFault,unresolvableAggregateColumnRefusal,unresolvableDistinctColumnRefusal)backendStatementFaultalone (cut 14's priced next slice)backendStatementFaultError), +4 for the importaggregateBackendFault/distinctBackendFaultcall it.ensureRotation,registerExternalObject,warnBuiltinColumnCollisions)migrateMysqlDatetimeColumns/migrateMysqlTimeColumnsstay (cut 12 durability trap),ensureShardTableis named bycheck-object-def-param-keys.mjsandcomputeTenantFieldbypackages/spec/liveness/object.json.compileFilters-75,applyNormalizedComparison-60, …)applyFilterCondition, which an ADR-0139 pointer pins in place.Why
Why this area, as one area
findRowsandcounttounresolvableFilterColumnRefusalor thebackendStatementFaultterminal;aggregatetoaggregateBackendFault, which ends in its column refusal, the WHERE refusal or the terminal;distincttodistinctBackendFault, the same shape. The six bodies only classify an error the exit already caught and compose the envelope; none runs a statement. They sit under three banners (Bulk, Aggregation, Distinct) only because each sits next to the exit that calls it.findRows,count,aggregate,distinct) build throughgetBuilder, and stay.isUnresolvableColumnError(also read byfindRowsandcount),OWN_STATEMENT_FAULT(read bysql-driver-raw-execution.ts, which imports it fromsql-driver.ts),PRE_DDL_QUESTION_SCOPE(also run by the two media-arm resolvers),unsupportedFilterErrorandisFilterNode(each read by much of the filter compiler) are shared. The three that were not exported gain a leadingexportand nothing else, and the module imports all five back. The module reads them only inside function bodies, so the import cycle is evaluation-safe.index.tsnames its exports one by one; the export lists below prove none of them leaks.mapSortFieldandremoteColumnare read by members that stay too; the moved bodies reach them through the host.getBuilder, reads a static, has athis.FIELD.delete/insert/update(site, usessuperor a barethis, or writes tothis. Every one has an annotated return type and is protected.check:tenant-chokepoint's D8 predicates, measured with its ownnullArmsOf: 4 at base and 4 at head, all insql-driver.ts, 0 in the moved bodies (none reads a tenant field).catchclause (the word appears only in comments: one docblock heading and twocatch-allnotes).check:durability-log-level --listgives the same seams at both ends, line numbers aside; the gate reads 48 seams (7 propagating) and 82 read seams at both ends.this.logger.warn(/this.logger.debug?.(; the host carriesloggeras a getter, so the receiver is the driver's logger as before.check:logger-receiver-detach: 0 detaches at both ends.TursoDrivercallsaggregateBackendFaultanddistinctBackendFaulton its own instance;sql-driver-21385-refusal-log-line-redaction.test.tsoverrides four of the six in a subclass and callssuper. Every stub keeps its signature and staysprotected, and every member a body calls is a late-bound closure on the instance (probe below).sql-driver.ts#…pointer or asql-driver.ts::…key, and noscripts/adr-anchors/**record names them. Tests that readsql-driver.tsas text: the five cli generate pins (they readcreateColumn, which stays),logger-receiver-detach.test.ts(it scans for detach shapes, finds none either way), and the compile-refusal seam test (migrated above). Prose mentions elsewhere (turso-driver.ts,engine.ts, the runtime noise filters, the types predicates' tests, CHANGELOGs) name the functions, and each is still true.The shape (#23050 shape B, as cuts 1-14 apply it)
NAME.call(host, ...), marked[#25521 cut 15].logger,physicalTableByObject,deferredDdl,deferredSchemaObjects). Methods go as closures that call the method on the driver at call time (mapSortField,remoteColumn,backendStatementFault, and the three refusals). So a subclass override, an instance spy, or a field replaced after construction is still the one the body reaches.ReadExitFaultHostlists exactly those ten members, each typed as the class member's indexed access. There is no cast, and no member becomes public.StandardErrorCode(@objectstack/spec/api),type DriverQuery(@objectstack/spec/contracts),resolveFilterSubtreeProvenance(@objectstack/spec/data, still read insql-driver.tstoo),declareTargetedTableandisMissingTableError(@objectstack/types, whose last readers insql-driver.tsmoved, so they leave its import), the movedredactStatementFromMessageimport, and from./sql-driver.jsthe five shared declarations plustype SqlDriver.sql-driver.tsimport comment names cut 15 in 3 lines. The header cites noADR-NNNN Dn.Verification
ADR anchors
check:adr-anchorscounts 1876 decision-letter citations at based1a2e71a3(a clean detached worktree) and 1876 at head, over 91 anchored files at both ends. Both runs exit 0.ADR-…mention in them stays insql-driver.ts.sql-driver.tsentry's one paragraph (ADR-0053) describesformatOutputandpresentReadValue, neither of which moved.check:adr-symbol-anchors: 2664 resolve at both ends.The published
.d.tsdoes not changeThis is measured under the series bar (ruling
6348891160, item 1).pnpm turbo run build --filter=@objectstack/driver-sql --forceat each end, against the same dependency tree. The base leg writes basesql-driver.tsand the two base test files into this worktree and deletes the new module, under an EXIT/INT/TERM trap that restores HEAD. The restore is proved by the HEAD blob hashes (4aeac25e,dc34f18e, and both tests), an emptygit diff HEADand 0 status entries.dist/index.d.tsanddist/index.d.mtsis the same at base and at head,0f33bf3febaac3c7aa96f68a4226d752766b8203ab7acade523b69f9aaa44d74(unchanged since cut 8).protectedfrombackendStatementFaultin the headindex.d.ts. The result hashes to2e64636ebed0f4461e048dc1d3fa09a07c4172c537607d7024a2b36038eee409, with a 1-line diff andcmpexit 1.requireof the head dist gives 59 names (sha105757ce5ae); the ESM export statement of the base and headindex.mjslists the same 59 names (sha105757ce5aeboth). None of the three newly exported declarations, and nothing from the new module, is among them..call(hostsites inindex.mjsgo from 51 at base to 57 at head (six stubs).Move-only proof
The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base
d1a2e71a3against headef0640e10. It is a scratch script and is not committed.thisunresolvableFilterColumnRefusalbackendStatementFaultaggregateBackendFaultunresolvableAggregateColumnRefusaldistinctBackendFaultunresolvableDistinctColumnRefusalasync, at either end.sql-driver.ts. The three newly exported ones differ from base by a leadingexportand nothing else. TheredactStatementFromMessageimport moved whole with its comment (7 lines). Atsql-driver.ts's top level, 189 of 199 base statements are byte-identical; the rest are the four moved declarations, the three exports, the moved import, the@objectstack/typesimport (less two names) and the class. The one head-only statement besides those is the new import.PROOF OK, exit 0.scripts/ablation-replace.mjs, I changedtypeof declared === 'number'to'string'in the module'sbackendStatementFault(1 hit, checked on disk). The proof then reportsbackendStatementFaultwith its body tokens differing, printsPROOF FAILED, and exits 1. The restore was proved (blob equals HEAD, emptygit diff HEAD).Tests
Each end ran under one
os-verify-lockhold, after building that end's driver-sql dist. I compared the vitest JSON per test name and status, based1a2e71a3against headef0640e10. Every part's exit code was captured on its own (build, probe, three suites: all 0 at both ends).@objectstack/driver-sql@objectstack/driver-turso, against that end's built driver-sql dist@objectstack/driver-sqlite-wasmtsc --noEmit -p .in driver-sql exits 0.pnpm exec eslint --no-inline-config packages/drivers/driver-sqlexits 0.code,status,cause, own keys and symbol-keyed members (the declared target table), the log lines written, and the instance spies hit in order:backendStatementFaulton a declared error (returned as the same object), five undeclared ones, a federated object (the declared target followsphysicalTableByObject), and the three deferred-DDL cases (demoted todebug, warned, warned);unresolvableFilterColumnRefusalwith no filter, an author-marked, an unmarked and a policy-marked filter, and an unnamed column;aggregateBackendFaulton a groupBy column (string and structured), an aggregation column, both, a WHERE column, an unnamed column and another fault;distinctBackendFaulton the listed field, a filter column, an unnamed column and another fault;namedByshape;mapSortFieldandremoteColumn, each reached through the classifiers;logger,physicalTableByObject,deferredDdlanddeferredSchemaObjectsreplaced on the instance after construction (the new ones are the ones read);find/count/aggregate/distinctcalls end to end on SQLite, on a missing table and on a dropped column;_SqlDriver.X(orSub.X) at base andObject.Xat head. With that receiver name normalised, the two outputs are byte-identical. That is the host-literal receiver of shape B, the same at every earlier cut, and no test or consumer reads it.test:repo. After the merge oforigin/main(3b5318a22a), each under the lock after building its closure: therepoprojects of types (11 passed), core (83), objectql (5), spec (1293), rest (280 passed, 18 skipped) and runtime (947) all pass, exit 0.Gates
origin/main.3b5318a22amerges 9 commits that touch nopackages/drivers/**file and no gate this cut reads. After it,tsc --noEmit -p .in driver-sql exits 0 andcheck:adr-anchorsstill reads 1876.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 54 commands for the changed paths, the same 54 before and after the merge. On3b5318a22a, 53 exit 0.check:dual-build-cjs-loadsexits 3,PREREQUISITE NOT MET(no full build here): NOT MEASURED locally, CI runs it. The teardown self-test's pinned commit was fetched first, and objectql was built forcheck:lean-entry-closure.dispatch-gates --ran: 54 derived families accounted for, 53 run, 1 NOT-MEASURED (derived from the recorded exit 3), exit 0.d1a2e71a3) and at head (ef0640e10; the derived ones again on the merged head3b5318a22a), all exit 0:check:adr-anchorscheck:adr-symbol-anchorscheck:tenant-chokepointcheck:durability-log-level--listequal but for line numberscheck:driver-conformancecheck:keyed-text-boundscheck:object-def-param-keysmainbrought) / 545 / 7check:system-context-censuscheck-tenant-audit-censuscheck:logger-receiver-detachcheck:nul-bytespasses.check-platform-checklist --changed-from origin/main(a moved exported function) passes: 6 anchors judged, 0 lost. The pre-push hooks passed on push.Acceptance notes
OWN_STATEMENT_FAULThas no reader left insql-driver.ts. Its readers in the file were the three column refusals and the terminal, all moved;sql-driver-raw-execution.tsand the new module import it from there. It stays where it is: moving it would edit the raw-execution module's import for no line it pays, and one home is kept either way. The raw-execution header's phrase "OWN_STATEMENT_FAULThas readers that did not move" now means readers outside that module. Noted, not filed.check:tenant-chokepoint'sSCAN_FILESdoes not list the new module. The moved bodies hold 0 of the file's 4 tenant predicates and build no query. Noted, not filed: wideningSCAN_FILESwould be a gate change.ensureRotation,registerExternalObject,warnBuiltinColumnCollisions, about -118, with the durability-trap and path-reader members staying). The series' remaining yield outside the pinned methods (createColumn,applyFilterCondition,formatInput/formatOutput,initObjects) is small bodies of under -70 each.Human Minimum
packages/specchange.ReadExitFaultHost, follows the interface casing.eslint --no-inline-configpasses (repo-widepnpm lintis CI's run).main, one pull request.skip-changeset; both declaration lines above.packages/specnot touched.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5mL6cyUGiP87zTtR7y