Repository navigation
Commit a485b5a
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XWvG89mssHGAkmn1TLwURoV)_
Part of #25521
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840
Ruling-ref: 6348891160
## 维护者速读
- **改了什么**:把 `sql-driver.ts` 里过滤编译器「把一个已校验的比较写成
SQL」的那一层(规范化列比较、`$ne`/`$nin` 的空值安全包装、`$empty`、JSON 列上的 `$contains`
成员判断、`$contains`/`$startsWith`/`$endsWith`/`$icontains` 与
`$like`/`$ilike`),连同只被它们用到的十六个模块级声明(各方言的文本匹配构造、转义、ASCII 大小写表、`$like`
模式计划、`$empty` 的空列表判断与两个拒绝),原样搬进新文件
`sql-driver-predicate-emitters.ts`;类上的六个方法全部保留,只把方法体转交过去。顺带修好第 15 刀留下的四处
`{@link unresolvableColumnNameOf}` 失效链接(只加一个仅类型导入)。
- **为什么改**:#25521 拆分超大文件的第 16 刀(18.2.0
发布后重启);这一层是重新测量后收益最大的可搬区域,`sql-driver.ts` 减少 703 行。
- **风险与回滚**:纯搬移,无行为变化;三个 SQL 驱动的测试两端逐条一致,公开类型声明哈希不变,676 条探针读数两端一致(仅 2
条栈顶接收者名不同)。回滚即还原本 PR。
- **先看**:`sql-driver-predicate-emitters.ts` 的头注释与
`PredicateEmitterHost`;`sql-driver.ts` 中六个方法的转交桩和新的 import
行;`sql-driver-compile-refusal-seam.test.ts` 新增的扫描文件。
- **两分钟验证**:`pnpm --filter @objectstack/driver-sql exec vitest run
src/sql-driver-compile-refusal-seam.test.ts
src/sql-driver-like-pattern.test.ts
src/sql-driver-20446-empty-flip.test.ts
src/sql-driver-17590-json-column-membership.test.ts`,预期全部通过。
- **需要你做的**:无,席位落地。
## What changes
Cut 16 of the `sql-driver.ts` split (#24586 ruling A, recorded in
`6270097840`), the first after the 18.2.0 restart (`6439210093`). The
claim is `6439272472`. The cut opens with a fresh seam table (below),
taken on `origin/main` `f9867020c6`, and takes the area that pays best
without breaking a series limit: the **leaf predicate emitters of the
filter compiler**, the methods that turn one comparison the compiler has
already validated into SQL on the builder. They move unedited into a
**new** seam module,
`packages/drivers/driver-sql/src/sql-driver-predicate-emitters.ts`,
through a new host `PredicateEmitterHost`:
- `applyNormalizedComparison`, a value test against a storage-normalised
column expression;
- `applyNullSafeNegative`, the #5298 wrapper that lets a NULL column
satisfy a negative-polarity test;
- `applyEmptyOperator`, #20444's `$empty`;
- `applyJsonMembership`, the membership reading of `$contains` /
`$notContains` on a JSON column;
- `applyLike`, the `$contains` family and `$icontains`;
- `applyLikePattern`, #7536's `$like` / `$ilike`.
With them go the sixteen module-level declarations only they read:
- the text-match constructs `textMatchPredicate`,
`sqliteLengthAwareTextMatch` and `mysqlAsciiLowerBinary`, the comparand
escapes `escapeLikeComparand`, `escapeGlobComparand` and
`wrapTextMatchShape`, the ASCII case map `ASCII_UPPER_LETTERS` /
`ASCII_LOWER_LETTERS`, and `NUL_CHARACTER`;
- the `$like` pattern plan: `likePatternPredicate`,
`sqliteLikePatternPlan`, `sqliteLikePatternMatch` and the
`SqliteLikePatternPlan` shape;
- the `$empty` construct `emptyJsonListPredicate` and its two refusals,
`undeclaredEmptyOperatorFieldError` and
`emptyListUnsupportedDialectError`.
The `likePatternToGlobPattern` import loses its last reader in
`sql-driver.ts`, so that one name leaves the import there (the line and
its comment stay for the two `$like` gates beside it) and the module
imports it.
**Cut 15's follow-up, folded in.** Four links to `{@link
unresolvableColumnNameOf}`, in two staying docblocks
(`aggregateBackendFault`'s and `distinctBackendFault`'s), named a
function that cut 15 moved to `sql-driver-read-exit-fault.ts`. The
existing import of that module gains `type unresolvableColumnNameOf`, so
all four resolve again. No comment changes. The same spelling keeps this
cut from adding new broken links: five moved declarations that staying
docblocks link (`textMatchPredicate`, `likePatternPredicate`,
`emptyJsonListPredicate`, `undeclaredEmptyOperatorFieldError`,
`emptyListUnsupportedDialectError`) are exported from the module and
imported back as types. A link-only import is a type import, erased from
the build.
The change is move-only, and the card stays open for later cuts. There
is no `docs/adr/**` edit, no `packages/spec/**` edit, no anchor entry
change, no gate edit and no new gate. One test changes, and only as a
path reader (below). Branch `claude/project-thread-ulif5z` (the platform
assigned it to this card thread, as the claim records). The move is one
commit, `5ceec116b`, on `f9867020c6`, followed by one merge of
`origin/main`.
**Net line count (the seat's rule).** `sql-driver.ts` goes from 19,349
lines to 18,646: **-703**.
- **Out:** the six bodies, 131 lines (62 + 35 + 24 + 4 + 3 + 3). The
sixteen declarations, 592 lines with their own docblocks (ASCII map 13,
`NUL_CHARACTER` 2, `escapeLikeComparand` 11, `escapeGlobComparand` 21,
`wrapTextMatchShape` 6, `sqliteLengthAwareTextMatch` 85,
`mysqlAsciiLowerBinary` 27, `textMatchPredicate` 138,
`SqliteLikePatternPlan` 27, `sqliteLikePatternPlan` 32,
`sqliteLikePatternMatch` 62, `likePatternPredicate` 73,
`undeclaredEmptyOperatorFieldError` 29,
`emptyListUnsupportedDialectError` 19, `emptyJsonListPredicate` 47),
plus 15 separator lines.
- **In:** the six stub bodies, 31 lines (2 + 7 + 8 + 2 + 6 + 6), and a
4-line import of the module with its comment.
- The new module is 850 lines.
**Path readers migrated in the same commit.**
`sql-driver-compile-refusal-seam.test.ts` enumerates every
`INVALID_FILTER` builder by parsing a fixed list of source files, and
drives every caller of `withheldFilterError` through its table. The two
`$empty` refusals now live in the new module, so the list gains it (one
line, plus its comment and the two docblocks that name the list). No
assertion and no table row changes.
**Serial with the file's in-flight PRs.** I read the file list of every
open PR on `objectstack-ai/objectstack` at the start (19) and again
before opening this one (14). None touches `sql-driver.ts`,
`turso-driver.ts`, `sqlite-wasm-driver.ts`,
`check-tenant-chokepoint.mjs` or a driver-sql anchor entry. #26337 adds
`driver-sql/src/sql-driver-external-unprovisioned-sort-anchor.test.ts`,
which reads none of the moved members; #26556 and #26517 touch only
package manifests and changelogs. #26599 (it touched
`builtin-column-collision.ts`) merged into `main` during the cut and is
in this branch through the merge.
## Seam table, fresh, on `f9867020c6`
- **Size and churn.** `sql-driver.ts` is 19,349 lines. The 30-day column
counts `git blame` lines from commits on or after 2026-09-27T09:51:51Z,
30 days before the base commit. `scripts/pm/git-history.mjs count`
proves 75 commits on the file in 30 days and 32 in the last 7. In all,
3,432 lines (17.7%) are from the last 30 days.
- **Class shape.** `SqlDriver` spans lines 4847-19274 with 350 members:
243 instance methods, 13 accessors and 32 statics, all 32 private or
protected.
- **How it was measured.** The same TypeScript compiler AST script cuts
9-15 used, which strips casts, parentheses, `!` and `satisfies` before
it reads `this`. Sections are the class's own banner comments.
| Lines | Section | First … last member | Lines | 30-day | Methods
(public) | Inferred public | getBuilder sites | Statics
(private/protected) |
|:--|:--|:--|--:|--:|:--|--:|--:|:--|
| 1-4846 | Module preamble | - | 4,846 | 779 | - | - | - | - |
| 4847-6438 | Class head: fields, statics, constructor, capability
getters | `name` … `assertDialectEmits` | 1,592 | 290 | 8 (1) | 1 | 0 |
18 (18) |
| 6439-6870 | Lifecycle | `connect` … `disconnect` | 432 | 155 | 8 (3) |
0 | 0 | 6 (6) |
| 6871-9277 | CRUD — IDataDriver core | `find` … `delete` | 2,407 | 745
| 43 (7) | 0 | 12 | 0 (0) |
| 9278-9807 | Bulk & Batch Operations | `bulkCreate` … `count` | 530 |
70 | 9 (6) | 0 | 7 | 0 (0) |
| 9808-9904 | Raw Execution | `rawStatementFault` …
`noteRuntimeIndexDdl` | 97 | 28 | 3 (1) | 0 | 0 | 0 (0) |
| 9905-9997 | Transactions | `beginTransaction` … `rollbackTransaction`
| 93 | 46 | 7 (5) | 0 | 0 | 0 (0) |
| 9998-10494 | Aggregation | `aggregate` …
`unresolvableAggregateColumnRefusal` | 497 | 70 | 5 (1) | 0 | 1 | 0 (0)
|
| 10495-10756 | Distinct | `distinct` …
`unresolvableDistinctColumnRefusal` | 262 | 26 | 3 (1) | 0 | 1 | 0 (0) |
| 10757-10872 | Window Functions | `findWithWindowFunctions` …
`findWithWindowFunctions` | 116 | 4 | 1 (1) | 0 | 1 | 0 (0) |
| 10873-10971 | Query Plan Analysis | `explain` … `analyzeQuery` | 99 |
1 | 2 (2) | 0 | 1 | 0 (0) |
| 10972-11030 | Schema Sync (syncSchema / init) | `syncSchema` …
`reclaimSpace` | 59 | 34 | 3 (3) | 0 | 0 | 0 (0) |
| 11031-12579 | Schema Sync (syncSchema / init) › Data-lifecycle
rotation (ADR-0057 P2) | `rotationStateByTable` …
`migrateMysqlTimeColumns` | 1,549 | 158 | 26 (4) | 0 | 0 | 0 (0) |
| 12580-12836 | Schema Sync (syncSchema / init) › Deferred schema DDL
(#3917) | `setDeferredDdl` … `flushDeferredSchemaDdl` | 257 | 25 | 6 (4)
| 0 | 0 | 0 (0) |
| 12837-13651 | Schema Sync (syncSchema / init) › Managed-schema drift &
reconcile (#2186) | `dialectName` … `createNullSafeUniqueIndex` | 815 |
237 | 23 (3) | 0 | 0 | 0 (0) |
| 13652-13703 | Schema Introspection | `listPhysicalColumns` …
`introspectSchema` | 52 | 46 | 2 (2) | 0 | 0 | 0 (0) |
| 13704-13974 | Internal helpers | `getKnex` … `auditMissingTenant` |
271 | 73 | 8 (1) | 0 | 1 | 0 (0) |
| 13975-16124 | Internal helpers › Filter helpers |
`tableNameForBuilder` … `applyFalseConstant` | 2,150 | 349 | 44 (2) | 0
| 0 | 0 (0) |
| 16125-16300 | Internal helpers › Field mapping | `mapSortField` …
`mapAggregateFunc` | 176 | 2 | 6 (0) | 0 | 0 | 0 (0) |
| 16301-16348 | Internal helpers › Window function builder |
`buildWindowFunction` … `buildWindowFunction` | 48 | 26 | 1 (0) | 0 | 0
| 0 (0) |
| 16349-18295 | Internal helpers › Column creation helper |
`nowColumnDefault` … `applyDeclaredColumnDefault` | 1,947 | 92 | 19 (0)
| 0 | 0 | 8 (8) |
| 18296-18593 | Internal helpers › Database helpers | `sqliteFilename` …
`isJsonField` | 298 | 76 | 9 (2) | 0 | 0 | 0 (0) |
| 18594-19070 | Internal helpers › SQLite serialisation | `formatInput`
… `formatOutput` | 477 | 27 | 2 (0) | 0 | 0 | 0 (0) |
| 19071-19274 | Internal helpers › Introspection internals |
`introspectColumnOrder` … `introspectUniqueConstraints` | 204 | 73 | 5
(0) | 0 | 0 | 0 (0) |
| 19275-19349 | After the class | - | 75 | 0 | - | - | - | - |
### Pricing the areas
Eligibility is as cuts 11-15 defined it: an annotated return type or not
public; no `getBuilder`, `this.FIELD.delete/insert/update(`,
private-static, `super`, bare-`this` or `this`-write site; not
`insertOnlyUpsertColumns`. Each area is priced as its stubs minus its
bodies, then net of the module-level declarations only it reads (they
move along, byte-identical) and the ones it shares with members that
stay (they stay in place, exported; ⛔ no second copy of a helper).
| Area | Bodies | Bodies net | Declarations that move along |
Disposition |
|:--|--:|--:|--:|:--|
| **Leaf predicate emitters** (`applyNormalizedComparison`,
`applyNullSafeNegative`, `applyEmptyOperator`, `applyJsonMembership`,
`applyLike`, `applyLikePattern`) | 6 | -100 | -607 (sixteen declarations
with their docblocks and separators), +4 for the import | **Moves (this
cut).** -703 measured. |
| Rotation sub-banner (`ensureRotation`, `registerExternalObject`,
`warnBuiltinColumnCollisions`, `rotationShardKey`, `rotationColumnList`)
| 5 | -124 (dry run) | 0 | Not taken: about -120 with its import, a
sixth of this cut. Priced as the next slice (below). |
| `compileFilters` with `filterArrayReachedDriverError` | 1 | about -115
| included | Not taken: `compileFilters` has an inferred return type, so
the move script refuses it (series limit). |
| Deferred-DDL previews | - | about -80 | 0 | Not taken: smaller. |
| Withheld-filter log family | - | about -40 | 0 | Not taken: smaller. |
## Why
### Why this area, as one area
- **One mechanism.** The filter compiler (`applyFilters` →
`compileFilters` → `applyFilterCondition`) validates a condition,
resolves its column, and then hands ONE comparison to a leaf emitter,
which only writes the predicate onto the builder. The six emitters run
no statement and read no schema beyond the two predicates the host
carries. They sat inside the 2,150-line Filter helpers sub-banner only
because the compiler calls them.
- **It pays the most of any area that moves cleanly**, nearly six times
the next one, because sixteen module-level declarations (the dialect
text-match constructs, the `$like` pattern plan, the `$empty` construct
and its two refusals) have no other reader and move with them.
- **What stays, and why.**
- The compiler itself stays; `applyFilterCondition` is pinned in place
by its ADR-0139 pointer, and `compileFilters` has an inferred return
type.
- `applyContainsLike`, `applyTextOperatorOverNonTextColumn` and
`applyFalseConstant` are leaf emitters too, but each reads none of the
moving declarations and its stub would cost as many lines as its body.
- `LIKE_ESCAPE_CHARACTER` is also bound by `scanMaxNumericTail`'s prefix
scan, and `TextMatchShape` is named by `applyLike`'s signature on the
class. Each gains a leading `export` and nothing else, and the module
imports both back. `withheldFilterError`, the #7929 seam both `$empty`
refusals build through, was already exported. The module reads these
only inside function bodies, so the import cycle is evaluation-safe.
`index.ts` names its exports one by one; the export lists below prove
none of them leaks.
- `declaredValueShape`, `isJsonColumn` and `applyNullSafeNegative` are
read by members that stay too; the moved bodies reach them through the
host.
- **No series limit is touched.** None of the six builds through
`getBuilder`, reads a static, has a `this.FIELD.delete/insert/update(`
site, uses `super` or a bare `this`, or writes to `this`. Every one has
an annotated return type and is private. `check:tenant-chokepoint`'s D8
predicates, measured with its own `nullArmsOf`: **4 at base and 4 at
head**, all in `sql-driver.ts`, 0 in the moved code (none reads a tenant
field).
- **Durability gate.** The moved code has no `catch` clause.
`check:durability-log-level --list` gives the same seams at both ends,
line numbers aside; the gate reads 48 seams (7 propagating) and 82 read
seams at head.
- **Logger.** No moved body writes to a logger.
`check:logger-receiver-detach`: 0 detaches (3779 non-test files at
head).
- **Subclasses.** `TursoDriver` and `SqliteWasmDriver` extend
`SqlDriver` and override none of the six. Every stub keeps its signature
and modifier, and every member a body calls is a late-bound closure on
the instance (probe below).
- **Path readers, screened.** No script, test, doc or ADR names any of
the six, or the sixteen moved declarations, by a `sql-driver.ts#…`
pointer or a `sql-driver.ts::…` key, and no `scripts/adr-anchors/**`
record names them. Of the tests that read `sql-driver.ts` as text, only
the compile-refusal seam test reads a moved declaration (migrated
above). Prose mentions elsewhere (`service-analytics`'s text-match and
`$like` mirrors, `@objectstack/core`'s JSON membership helper, the
spec's text conformance table, the driver tests) name the functions, and
each is still true.
## The shape (#23050 shape B, as cuts 1-15 apply it)
- Each class method keeps its docblock and signature, byte-identical.
Its body is now one host literal plus `NAME.call(host, ...)`, marked
`[#25521 cut 16]`. `applyNormalizedComparison` and
`applyNullSafeNegative` read no member of the driver, so their methods
call the function directly.
- `dialectName` goes as a GETTER that reads the driver at call time.
`declaredValueShape`, `isJsonColumn` and `applyNullSafeNegative` go as
closures that call the method on the driver at call time. So a subclass
override, an instance spy, or a `dialectName` replaced after
construction is still the one the body reaches.
- `PredicateEmitterHost` lists exactly those four members, each typed as
the class member's indexed access. There is no cast, and no member
becomes public.
- **Module imports.** `jsonMembershipPredicate` (`@objectstack/core`),
`expandEmptyOperator` and `likePatternToGlobPattern`
(`@objectstack/spec/data`), `type SqlDialectName` (`./schema-drift.js`),
and from `./sql-driver.js` `LIKE_ESCAPE_CHARACTER`,
`withheldFilterError`, `type TextMatchShape`, `type isMultiValuedColumn`
(named by a moved docblock) and `type SqlDriver`.
- **Comments.** The module header says what moved, what stayed and why.
The `sql-driver.ts` import comment names cut 16 in 3 lines. The header
cites no `ADR-NNNN Dn`.
- **How the move was made.** A script produced it from the source text;
nothing was retyped. Each body line is re-indented two columns; no body
line starts inside a template literal.
## Verification
### ADR anchors
- **The count.** `check:adr-anchors` counts **1881** decision-letter
citations at base `f9867020c6` (a clean detached worktree) and **1881**
at head, over 91 anchored files at both ends. Both runs exit 0.
- **Why equal.** No comment inside the moved bodies, declarations or
import cites a decision letter. The stubs keep their docblocks, so every
`ADR-…` mention in them stays in `sql-driver.ts`.
- **No entry needs a new home.** No anchor entry names a moved member or
declaration. `check:adr-symbol-anchors`: 2683 resolve at both ends.
- **After the merge.** `main` moved under the cut, so I measured its tip
`dbf7e04adf` against the merge head `048467ad98`: **1872** letters at
both (91 anchored files, exit 0), and 2687 symbol anchors resolve at
both. The drop from 1881 comes from `main`'s own commits, not from this
diff.
### The published `.d.ts` does not change
This is measured under the series bar (ruling `6348891160`, item 1).
- **How each end was built.** `pnpm turbo run build
--filter=@objectstack/driver-sql --force` at each end, against the same
dependency tree. The base leg writes base `sql-driver.ts` and the base
seam test 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, an empty `git diff HEAD` and 0 status entries.
- **The hashes.** The raw sha256 of `dist/index.d.ts` and
`dist/index.d.mts` is the same at base and at head,
`0f33bf3febaac3c7aa96f68a4226d752766b8203ab7acade523b69f9aaa44d74`
(unchanged since cut 8), on the move commit and again on the merge head.
These two are the dist's only declaration files (no chunk), so nothing
was re-ordered and the declaration-reorder bar of ruling `6441032442`
does not come into play.
- **Positive control.** I dropped `private` from `applyLike` in the head
`index.d.ts`. The result hashes to
`4450f3699c74b340a3c946f037a353be89d3c21fe80163eed2040a9a86dcb1ca`, with
`cmp` exit 1.
- **Export lists.** CJS `require` of the head dist gives 59 names (sha1
`05757ce5ae`); the ESM export statement of the base and head `index.mjs`
lists the same 59 names (sha1 `05757ce5ae` both). None of the seven
newly exported declarations, and nothing from the new module, is among
them.
- **The head dist carries the move.** `.call(host` sites in `index.mjs`
go from 57 at base to 61 at head (four host stubs; the two direct-call
stubs have none).
### Move-only proof
The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes
skipped), base `f9867020c6` against head `5ceec116b`. It is a scratch
script and is not committed.
| Moved body | Body tokens | Comments | Parameter tokens less `this` |
Return type tokens | Body lines |
|:--|--:|--:|--:|--:|--:|
| `applyNormalizedComparison` | 432 | 5 | 27 | 1 | 62 |
| `applyEmptyOperator` | 259 | 3 | 27 | 1 | 35 |
| `applyJsonMembership` | 172 | 3 | 25 | 1 | 24 |
| `applyNullSafeNegative` | 33 | 0 | 18 | 1 | 4 |
| `applyLike` | 50 | 0 | 21 | 1 | 3 |
| `applyLikePattern` | 46 | 0 | 15 | 1 | 3 |
- **Each moved body.** In every row, 0 tokens differ and every comment
is identical. Each body line is the base line less two leading spaces.
None is `async`, at either end.
- **The six stubs.** Leading trivia (the docblock), modifiers and the
signature are identical to base.
- **The class.** It has 350 members at both ends, in the same order, and
the 344 unmoved members are byte-identical, trivia included.
- **Module-level declarations.** The sixteen that moved are
byte-identical in the module from their own docblock on, and gone from
`sql-driver.ts`; five of them carry a leading `export` and nothing else.
`LIKE_ESCAPE_CHARACTER` and `TextMatchShape` differ from base by a
leading `export` and nothing else. At `sql-driver.ts`'s top level, 174
of 195 base statements are byte-identical; the rest are the sixteen
moved declarations, the two exports, the two edited imports and the
class. The one head-only statement besides those is the new import.
- **Result.** `PROOF OK`, exit 0.
- **Negative control.** Through `scripts/ablation-replace.mjs`, I
changed `'[$&]'` to `'[$&]]'` in the module's `escapeGlobComparand` (1
hit, checked on disk). The proof then reports `escapeGlobComparand` as
differing, prints `PROOF FAILED`, and exits 1. The restore was proved
(blob equals HEAD, empty `git diff HEAD`).
### Tests
Each end ran under one `os-verify-lock` hold, after building that end's
driver-sql dist. I compared the vitest JSON per test name and status,
base `f9867020c6` against head `5ceec116b`, and then again on the merge
head `048467ad98`. Every part's exit code was captured on its own
(build, probe, three suites: all 0 at every leg).
| Suite | Base | Head | Lost / changed / added |
|:--|:--|:--|:--|
| `@objectstack/driver-sql` | 4060 (3846 passed, 214 skipped; 248 files)
| same | 0 / 0 / 0 |
| `@objectstack/driver-turso`, against that end's built driver-sql dist
| 2526 (2492 passed, 34 skipped; 95 files) | same | 0 / 0 / 0 |
| `@objectstack/driver-sqlite-wasm` | 746 (746 passed; 39 files) | same
| 0 / 0 / 0 |
On the merge head (it brings in `main` up to `dbf7e04adf`), the three
suites give the same totals and the same per-test statuses as base, 0 /
0 / 0 in each.
- **Type check and lint.** `tsc --noEmit -p .` in driver-sql exits 0 on
both heads. `pnpm exec eslint --no-inline-config
packages/drivers/driver-sql` exits 0.
- **Late-binding probe.** A scratch script, not committed. It runs node
against each end's built dist and prints 676 readings, each with the
result or the envelope's message, `code`, `status`, own keys and
symbol-keyed members, the SQL and bindings left on the builder, and the
log lines written:
1. `applyNormalizedComparison` over every operator on both joins, with
null comparands and empty or malformed lists, on SQLite, Postgres, MySQL
and an unknown dialect;
2. `applyNullSafeNegative` on both methods;
3. `applyEmptyOperator` on three row shapes, both polarities and both
joins, an undeclared field and an unknown dialect (both refusals);
4. `applyJsonMembership` on a JSON and a scalar column, both polarities,
scalar and list values;
5. `applyLike` on the three shapes, negated, folded, with metacharacters
and a NUL;
6. `applyLikePattern` plain, with wildcards, escapes, a NUL, a GLOB
metacharacter, and folded;
7. a SUBCLASS overriding `declaredValueShape`, `isJsonColumn` and
`applyNullSafeNegative`, instance spies on each, and `dialectName`
replaced on the instance after construction (the new ones are the ones
reached);
8. `find`/`count` end to end on SQLite through the compiler, reaching
each emitter;
9. bad arguments.
- **Probe result.** 674 of the 676 readings are identical, SQL, bindings
and envelopes included. The other 2 differ only in the top stack frame's
receiver name, where a null builder throws inside a moved body:
`_SqlDriver.X` at base and `Object.X` at head. That is the host-literal
receiver of shape B, the same at every earlier cut, and no test or
consumer reads it. On the merge head the probe output is byte-identical
to the move commit's.
- **Not run.** No ablation, because no tested behaviour changed.
- **`test:repo`.** Not run locally; it is CI's run on this PR.
### Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives **54** families for this diff, the
same list on the move commit and on the merge head. I ran each one on
its own and captured its exit code before any pipe.
- **Merge head `048467ad98`: 54 run, all 54 exit 0.** `--ran`
reconciles: `54 derived famil(ies) accounted for — 54 run, 0
NOT-MEASURED`, exit 0.
- **Move commit `5ceec116b`:** 53 exited 0; `check:dual-build-cjs-loads`
exited 3 there (its prerequisite, a full workspace build, was missing).
After building the driver-sql and objectql closures for the merge head
it ran and passed (112 require entry points across 69 packages load).
- **Hand gates on the merge head, all exit 0:** `check-adr-anchors`,
`check-adr-symbol-anchors`, `check-tenant-audit-census`,
`check-platform-checklist --changed-from origin/main`,
`check-commit-card-trailers --range origin/main..HEAD`, `eslint
--no-inline-config packages/drivers/driver-sql`,
`check-durability-degradation-log-level --list`.
- **No gate silently empty.** Each derived gate's log prints a non-zero
population (for example `check:logger-receiver-detach` walks 3779 files
and fires on its control corpus; `check:tenant-chokepoint` reads 24
`getBuilder()` bindings across 3 files).
The 54 derived commands: `node
scripts/check-closing-keyword-parity.mjs`, `node
scripts/check-closing-keyword-parity.mjs --self-test`, `node
scripts/check-comment-mask-adoption.mjs`, `node
scripts/check-comment-mask-adoption.mjs --self-test`, `node
scripts/check-comment-mask-corpus.mjs`, `node
scripts/check-dts-emitted.mjs --self-test`, `node
scripts/check-issue-citations.mjs`, `node
scripts/check-plugin-teardown-shape.mjs`, `node
scripts/check-plugin-teardown-shape.mjs --self-test`, `node
scripts/ci/assert-turbo-remote-cache.mjs --self-test`, `node
scripts/ci/plan-test-core-shards.mjs --self-test`, `node
scripts/ci/prune-turbo-cache.mjs --self-test`, `node
scripts/docs-audit/check-affected-docs.mjs`, `node
scripts/docs-audit/check-drift-comment.mjs`, `pnpm --filter
@objectstack/spec run check:duration-unit-keys`, `pnpm
check:ci-filter-parity`, `pnpm check:closed-ceremony-text`, `pnpm
check:cross-package-test-inputs`, `pnpm
check:dispatcher-error-vocabulary`, `pnpm check:doc-authoring`, `pnpm
check:driver-conformance`, `pnpm check:driver-memory-census`, `pnpm
check:dts-closure`, `pnpm check:dual-build-cjs-loads`, `pnpm
check:durability-log-level`, `pnpm check:engine-double-contract`, `pnpm
check:error-status-conformance`, `pnpm check:gitlink-declared`, `pnpm
check:issue-citations`, `pnpm check:keyed-text-bounds`, `pnpm
check:lean-entry-closure`, `pnpm check:logger-receiver-detach`, `pnpm
check:nul-bytes`, `pnpm check:object-def-param-keys`, `pnpm
check:objectql-double-limit`, `pnpm check:org-identifier`, `pnpm
check:page-declaration-shape`, `pnpm
check:platform-object-tenancy-census`, `pnpm check:published-files`,
`pnpm check:query-options-erasure`, `pnpm check:refd-timer-probe`, `pnpm
check:registry-log-declared`, `pnpm check:rest-log-spy-declared`, `pnpm
check:slot-lookup`, `pnpm check:sourcemap-no-sources-content`, `pnpm
check:system-context-census`, `pnpm check:tenant-chokepoint`, `pnpm
check:test-source-alias`, `pnpm check:tier-file-adoption`, `pnpm
check:type-check-coverage`, `pnpm check:type-check-debt`, `pnpm
check:undeclared-dep-imports`, `pnpm check:watch-hint-literal`, `pnpm
check:where-matcher`.
## Acceptance notes
- **Other stale links from cut 15 are left.** Cut 15 also moved
`unresolvableFilterColumnError` (3 links), `backendStatementFaultError`
(3) and `unresolvableColumnProvenance` (2), which staying docblocks in
`sql-driver.ts` still name. The order folded in only the
`unresolvableColumnNameOf` links, so those stay as they were. On a scan
of `{@link X}` names that are neither a top-level binding nor a
`SqlDriver` member, `sql-driver.ts` goes from 34 at base to 30 at head
(the four fixed); this cut adds none. In the new module, one comment
inside the moved `applyNormalizedComparison` body names the class member
`filterColumnExpr`, byte-identical to base. Noted, not filed: the same
type-import repair would close them in one later line.
- **`check:tenant-chokepoint`'s `SCAN_FILES` does not list the new
module.** The moved bodies hold 0 of the file's 4 tenant predicates and
build no query. Noted, not filed: widening `SCAN_FILES` would be a gate
change.
- **Next slice, priced on this base.** The rotation sub-banner is now
the largest clean area (`ensureRotation`, `registerExternalObject`,
`warnBuiltinColumnCollisions`, `rotationShardKey`, `rotationColumnList`,
about -120), with
`migrateMysqlDatetimeColumns`/`migrateMysqlTimeColumns` (durability
trap), `ensureShardTable` (named by `check-object-def-param-keys.mjs`)
and `computeTenantField` (named by `packages/spec/liveness/object.json`)
staying. The series' remaining yield outside the pinned methods
(`createColumn`, `applyFilterCondition`, `formatInput`/`formatOutput`,
`initObjects`) is small bodies of under -120 each.
## Human Minimum
- [x] 1. No `packages/spec` change.
- [x] 2. The one new name, `PredicateEmitterHost`, follows the interface
casing.
- [x] 3. No formatter ran; scoped `eslint --no-inline-config` passes
(repo-wide `pnpm lint` is CI's run).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. No publishable behaviour change: `skip-changeset`; both
declaration lines above.
- [x] 6. `packages/spec` not touched.
- [x] 7. No hand edits to generated docs, changelogs or release notes.
- [x] 8. Title, body and comments are in English (the maintainer brief
excepted).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5ncyQ2HYfpr45hwrPU)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5ncyQ2HYfpr45hwrPU
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 74db43e commit a485b5a
3 files changed
Lines changed: 898 additions & 747 deletions
File tree
- packages/drivers/driver-sql/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | | - | |
33 | | - | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| |||
66 | 67 | | |
67 | 68 | | |
68 | 69 | | |
69 | | - | |
| 70 | + | |
70 | 71 | | |
71 | 72 | | |
72 | 73 | | |
| |||
273 | 274 | | |
274 | 275 | | |
275 | 276 | | |
276 | | - | |
277 | | - | |
| 277 | + | |
| 278 | + | |
278 | 279 | | |
279 | 280 | | |
280 | 281 | | |
| |||
287 | 288 | | |
288 | 289 | | |
289 | 290 | | |
| 291 | + | |
| 292 | + | |
290 | 293 | | |
291 | 294 | | |
292 | 295 | | |
293 | 296 | | |
| 297 | + | |
294 | 298 | | |
295 | 299 | | |
296 | 300 | | |
| |||
0 commit comments