Skip to content

Commit d7f76b6

Browse files
refactor(driver-sql): move the declared column DEFAULT emission out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 19) (#26960)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XCTPWahtfVNDqaZ7ZjxvNs5)_ Part of #25521 Clause-②: no Metadata-protocol: none Ruling-ref: 6270097840 Ruling-ref: 6348891160 ## 维护者速读 - **改了什么**:把 `sql-driver.ts` 里「字段默认值在建表语句里写成什么」这一个机制的两个方法体(`nowColumnDefault`:`defaultValue: 'NOW()'` 在各数据库方言下的列默认值;`applyDeclaredColumnDefault`:判断一个字段的 `defaultValue` 该不该、以什么形式成为列默认值)原样搬进已有的 `sql-driver-schema-sync.ts`;类上两个方法全部保留,只把方法体转交过去,`SchemaSyncHost` 一个成员都不用加。 - **为什么改**:#25521 拆分超大文件的第 19 刀;第 18 刀合入后重新测量,这是仍成「一个机制」、收益最大且不碰任何系列限制的区域,`sql-driver.ts` 减少 41 行。 - **风险与回滚**:纯搬移,无行为变化;三个 SQL 驱动的测试两端逐条一致,公开类型声明哈希不变,149 条探针读数两端一致(仅 1 条栈顶接收者名不同)。回滚即还原本 PR。 - **先看**:`sql-driver-schema-sync.ts` 头注释新增的第 19 刀段落与文件末尾两个函数;`sql-driver.ts` 中两个方法的转交桩;`sql-driver.ts` 里 `MYSQL_DATETIME_PRECISION`、`isNowDefaultValue` 两处就地加 `export`。 - **两分钟验证**:`pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-user-datetime-default-format.test.ts src/sql-driver-option-default-ddl.test.ts src/sql-driver-21241-mysql-now-default-precision.test.ts src/sql-driver-11550-dialect-client-spellings.test.ts`,预期全部通过。 - **需要你做的**:无,席位落地。 ## What changes Cut 19 of the `sql-driver.ts` split (#24586 ruling A, recorded in `6270097840`). The claim is `6454724955`. Cut 18 (#26894) merged first, as `452325eed9`; this cut opens with a fresh seam table taken on `origin/main` `4049efa345` (below) and takes the largest area that is one mechanism and moves cleanly: the **declared column DEFAULT emission**, from the class's `Internal helpers › Column creation helper` sub-banner. Its two bodies move unedited into the **existing** seam module `packages/drivers/driver-sql/src/sql-driver-schema-sync.ts`, through its host `SchemaSyncHost`: - `nowColumnDefault`, the driver-native column DEFAULT for a `defaultValue: 'NOW()'` field, per dialect and per `date` / `time` / `datetime` type (the UTC expression defaults of #3994 and #4022, the MySQL `DATETIME(3)` precision of #21241, the canonical SQLite `strftime` forms); - `applyDeclaredColumnDefault`, the one place that decides what a field's `defaultValue` becomes in DDL (`NOW()` through `nowColumnDefault`; a runtime token such as `current_user` emits nothing, #4560; an expression envelope emits nothing; a literal is emitted verbatim). The SQLite table rebuild that cut 6 moved into this module (`rebuildSqliteTablePatched`) already reaches both through `SchemaSyncHost`, which already lists both members and every member their bodies read, so **the host does not grow**. `createColumn` (pinned in place) and `createAuditTimestampColumn`, the other callers, stay in the class and still call the two methods on the driver. **Module-level declarations.** None moves. The bodies read three: - `MYSQL_DATETIME_PRECISION` and `isNowDefaultValue` are declared in `sql-driver.ts`, which still reads both (`createAuditTimestampColumn`, `createColumn`, the MySQL widenings). Each gains `export` in place, and the module imports them from `./sql-driver.js` through its existing import line. `index.ts` names its re-exports one by one and names neither, so the published surface does not move (measured below). - `isRuntimeDefaultToken` comes from `@objectstack/spec/data`. Its only reader in `sql-driver.ts` moved, so it leaves that import line (`isNowDefaultToken` stays there), and the module adds it to its existing `@objectstack/spec/data` import. - The moved signatures name `Knex.Raw` and `Knex.ColumnBuilder`, so the module gains `import type { Knex } from 'knex';` (erased; `sql-driver-autonumber.ts` has the same line). 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, no test edit and no new gate. Branch `claude/project-thread-1nvx81` (the platform assigned it to this card thread, as the claim records). The move is one commit, `fe7fb7ec5`, on `4049efa345`, followed by one merge of `origin/main` (`aa00362fe5`, 12 commits, none in `packages/drivers/**`); the head is `79ca88b71b`. **Net line count (the seat's rule).** `sql-driver.ts` goes from 18,447 lines to 18,406: **-41**. - **Out:** the two bodies, 60 lines plus their closing braces (51 + 9). - **In:** the two stub bodies, 19 lines with their closing braces (13 + 6), and 2 comment lines above the existing `sql-driver-schema-sync.js` import, whose one line gains the two names. The `@objectstack/spec/data` token import and the two `export` additions change lines in place. - `sql-driver-schema-sync.ts` goes from 2,066 to 2,148 lines (+82): the two functions with their one-line docblocks, a 13-line header paragraph and the `knex` type import. `SchemaSyncHost` is unchanged (80 members). **Path readers.** No test, script, doc, ADR or anchor entry reads either body by path, so none is migrated (screen below). **Serial with the file's in-flight PRs.** I read the file list of every open PR on `objectstack-ai/objectstack` at the start (39) and again before opening this one (30). None touches `sql-driver.ts`, `sql-driver-schema-sync.ts`, `turso-driver.ts`, `sqlite-wasm-driver.ts`, `check-tenant-chokepoint.mjs` or a driver-sql anchor entry; #26556 (dependabot) touches only the three drivers' `package.json`. ## Seam table, fresh, on `4049efa345` - **Size and churn.** `sql-driver.ts` is 18,447 lines. The 30-day column counts `git blame` lines from commits on or after 2026-09-27T21:44:05Z, 30 days before the base commit. `scripts/pm/git-history.mjs count` proves 32 commits on the file in 30 days and 4 in the last 7 (first-parent `origin/main`, history deepened to cover the window). In all, 3,412 lines (18.5%) are from the last 30 days. - **Class shape.** `SqlDriver` spans lines 4245-18372 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-18 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-4244 | Module preamble | - | 4,244 | 695 | - | - | - | - | | 4245-5836 | Class head: fields, statics, constructor, capability getters | `name` … `assertDialectEmits` | 1,592 | 290 | 8 (1) | 1 | 0 | 18 (18) | | 5837-6268 | Lifecycle | `connect` … `disconnect` | 432 | 155 | 8 (3) | 0 | 0 | 6 (6) | | 6269-8675 | CRUD — IDataDriver core | `find` … `delete` | 2,407 | 745 | 43 (7) | 0 | 12 | 0 (0) | | 8676-9205 | Bulk & Batch Operations | `bulkCreate` … `count` | 530 | 70 | 9 (6) | 0 | 7 | 0 (0) | | 9206-9302 | Raw Execution | `rawStatementFault` … `noteRuntimeIndexDdl` | 97 | 28 | 3 (1) | 0 | 0 | 0 (0) | | 9303-9395 | Transactions | `beginTransaction` … `rollbackTransaction` | 93 | 46 | 7 (5) | 0 | 0 | 0 (0) | | 9396-9892 | Aggregation | `aggregate` … `unresolvableAggregateColumnRefusal` | 497 | 70 | 5 (1) | 0 | 1 | 0 (0) | | 9893-10154 | Distinct | `distinct` … `unresolvableDistinctColumnRefusal` | 262 | 26 | 3 (1) | 0 | 1 | 0 (0) | | 10155-10270 | Window Functions | `findWithWindowFunctions` … `findWithWindowFunctions` | 116 | 4 | 1 (1) | 0 | 1 | 0 (0) | | 10271-10369 | Query Plan Analysis | `explain` … `analyzeQuery` | 99 | 1 | 2 (2) | 0 | 1 | 0 (0) | | 10370-10428 | Schema Sync (syncSchema / init) | `syncSchema` … `reclaimSpace` | 59 | 34 | 3 (3) | 0 | 0 | 0 (0) | | 10429-11853 | Schema Sync (syncSchema / init) › Data-lifecycle rotation (ADR-0057 P2) | `rotationStateByTable` … `migrateMysqlTimeColumns` | 1,425 | 219 | 26 (4) | 0 | 0 | 0 (0) | | 11854-12034 | Schema Sync (syncSchema / init) › Deferred schema DDL (#3917) | `setDeferredDdl` … `flushDeferredSchemaDdl` | 181 | 46 | 6 (4) | 0 | 0 | 0 (0) | | 12035-12849 | Schema Sync (syncSchema / init) › Managed-schema drift & reconcile (#2186) | `dialectName` … `createNullSafeUniqueIndex` | 815 | 237 | 23 (3) | 0 | 0 | 0 (0) | | 12850-12901 | Schema Introspection | `listPhysicalColumns` … `introspectSchema` | 52 | 46 | 2 (2) | 0 | 0 | 0 (0) | | 12902-13172 | Internal helpers | `getKnex` … `auditMissingTenant` | 271 | 73 | 8 (1) | 0 | 1 | 0 (0) | | 13173-15222 | Internal helpers › Filter helpers | `tableNameForBuilder` … `applyFalseConstant` | 2,050 | 331 | 44 (2) | 0 | 0 | 0 (0) | | 15223-15398 | Internal helpers › Field mapping | `mapSortField` … `mapAggregateFunc` | 176 | 2 | 6 (0) | 0 | 0 | 0 (0) | | 15399-15446 | Internal helpers › Window function builder | `buildWindowFunction` … `buildWindowFunction` | 48 | 26 | 1 (0) | 0 | 0 | 0 (0) | | 15447-17393 | Internal helpers › Column creation helper | `nowColumnDefault` … `applyDeclaredColumnDefault` | 1,947 | 92 | 19 (0) | 0 | 0 | 8 (8) | | 17394-17691 | Internal helpers › Database helpers | `sqliteFilename` … `isJsonField` | 298 | 76 | 9 (2) | 0 | 0 | 0 (0) | | 17692-18168 | Internal helpers › SQLite serialisation | `formatInput` … `formatOutput` | 477 | 27 | 2 (0) | 0 | 0 | 0 (0) | | 18169-18372 | Internal helpers › Introspection internals | `introspectColumnOrder` … `introspectUniqueConstraints` | 204 | 73 | 5 (0) | 0 | 0 | 0 (0) | | 18373-18447 | After the class | - | 75 | 0 | - | - | - | - | ### Pricing the areas Eligibility is as cuts 11-18 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`; not a member pinned in place (`createColumn`, `applyFilterCondition`, `formatInput`/`formatOutput`, `initObjects`, `ensureShardTable`, `computeTenantField`, `migrateMysqlDatetimeColumns`/`migrateMysqlTimeColumns`, `compileFilters`). Each area is priced as its stubs minus its bodies, then net of the module-level declarations only it reads and of its import edits. The two leading rows were measured by running the move script on a scratch copy of the base file; the rest are the stub estimate (a stub costs 4 lines, +2 per field read, +1 per method read). Cut 18 priced the largest clean single area at about -62, naming two candidates: the class head's date-bucket/query-timing pair and the column-creation remainder. Re-measured, neither is one mechanism: each -62 is the sum of a sub-banner's movable members, the same kind of sum as the filter-helper remainder this series does not stitch. So I priced mechanism by mechanism. | Area | Bodies | Bodies net | Declarations and imports | Disposition | |:--|--:|--:|--:|:--| | **Declared column DEFAULT emission** (`nowColumnDefault` -39, `applyDeclaredColumnDefault` -4) | 2 | -43 (measured) | 0 declarations move (2 gain `export` in place); +2 for the import comment | **Moves (this cut).** -41 measured. | | Date bucketing (`buildDateBucketExpr`) | 1 | -42 (measured) | No existing seam module fits it: a new module costs an import line plus its comment, +3 | **Runner-up**, about -39. Not taken: smaller. It reads the `dateGranularityCapabilities` accessor and `sqliteTemporalArg` / `temporalFieldKind`, all of which stay. | | Presumed-timestamp update (`updateWithPresumedTimestamp` -31, `resolveUpdatedAtColumnPresence` -6) | 2 | -37 | +2 or more for the import comment | Not taken: smaller (about -35). `attemptWithoutPoisoning` is shared with the autonumber module and is a separate mechanism. | | Column creation remainder as a whole (the two above, plus `schemaBytesPerChar` -9, `rethrowWithRowSizeExplanation` -2, `decorateHashShadowDuplicate` -8) | 5 | -62 | | Not taken whole: three mechanisms (column DEFAULT, the row-size refusal's explanation, the hash-shadow duplicate's explanation). The other two pay -11 and -8. The other 13 members of that sub-banner read private statics or are pinned. | | Class head pair (`buildDateBucketExpr` -42, `installQueryTiming` -20) | 2 | -62 | 0 | Not taken whole: two unrelated mechanisms; date bucketing alone is the runner-up above. | | Filter helpers remainder (14 small members, largest `presentReadValue` -29) | 14 | -80 | | Not taken: four mechanisms of -29 or less each, which the order says not to stitch. `compileFilters` (-75) is refused (inferred return type). | | `Internal helpers` tenant members (`auditMissingTenant` -26, `applyTenantScope` -25, `getBuilder` -5, `injectTenantOnInsert` -2) | 4 | -58 | | Not taken: `applyTenantScope` holds 2 of the file's 4 D8 predicates and `getBuilder` is the builder itself, which `check:tenant-chokepoint` reads only in `sql-driver.ts` (series limit). | | Window function builder (`buildWindowFunction`), field mapping (`orderKeysFor`), database helpers (`ensureDatabaseExists`, `sqliteFilename`) | 1 / 1 / 2 | -28 / -27 / -27 | | Below -30 each. | ## Why ### Why this area, as one area - **One mechanism.** The two answer one question, "what does a field's declared `defaultValue` become in the DDL of a fresh or rebuilt column", and `applyDeclaredColumnDefault` is how a declared field's `NOW()` reaches `nowColumnDefault`. They go beside the SQLite rebuild that already calls both through the host. The other callers of `nowColumnDefault` (the audit-column DDL, the MySQL datetime widening, the rebuild's audit columns) still call the method on the driver. - **It pays the most of any single mechanism that moves cleanly.** Date bucketing (-42 in bodies) is within one line of it in bodies, but needs a new module and its import line; this area joins a module whose import line and host already exist. - **What stays, and why.** `createAuditTimestampColumn` (its stub would cost more than its body) and the rest of the `Column creation helper` sub-banner: the hash-shadow and varchar-width members read private statics, `createColumn` is pinned, and the row-size and hash-shadow explanations are other mechanisms. - **No series limit is touched.** Neither body builds through `getBuilder`, reads a static, has a `this.FIELD.delete/insert/update(` site, uses `super` or a bare `this`, or assigns to `this`. Both are protected with annotated returns (`Knex.Raw`, `void`). `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 bodies. - **Durability gate.** Neither body has a `try` or calls `logDurabilityFailure`. `check:durability-log-level --list` prints 164 lines at both ends, identical except that two `runWideningAlters` seams in `sql-driver.ts` move 2 lines down (the import comment). - **Subclasses.** `TursoDriver` and `SqliteWasmDriver` override neither method; `remote-transport.ts` names `applyDeclaredColumnDefault` in prose, which is still true. Tests that widen the class (the CLI's `generate-declared-column-default.pin.test.ts` oracle and four driver-sql suites) call `this.nowColumnDefault(type)` on a subclass instance, which still reaches the same body. - **Path readers, screened.** No script, test, doc or ADR names either member by a `sql-driver.ts#…` pointer or a `sql-driver.ts::…` key, and no `scripts/adr-anchors/**` record names them. ADR-0053 names `nowColumnDefault` in prose, still true. ## The shape (#23050 shape B, as cuts 1-18 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 19]`. Both methods are synchronous at both ends, so a failure is still a synchronous throw (probe reading 7). - The fields `nowColumnDefault` reads (`isMysql`, `isPostgres`, `isSqlite`, `knex`) go as GETTERS that read the driver at call time. The method `applyDeclaredColumnDefault` calls (`nowColumnDefault`) goes as a closure that calls the method on the driver at call time. So a subclass override, an instance spy, or a field replaced after construction is still the one the body reaches (probe readings 2, 5, 6). - **Comments.** The module header gains one `[#25521 cut 19]` paragraph saying what moved, what stayed and where each read declaration comes from. The `sql-driver.ts` import comment names cut 19 in 2 lines. Neither cites an `ADR-NNNN` letter. Both stubs keep their docblocks (which mention ADR-0074 and #4560/#7246) in `sql-driver.ts`. - **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 **1897** decision-letter citations at base `4049efa345` (a clean detached worktree) and **1897** at the move commit, over 91 anchored files at both ends; the two outputs are byte-identical. Both runs exit 0. - **Why equal.** No comment inside the moved bodies or the new header paragraph cites a decision letter. - **No entry needs a new home.** No anchor entry names a moved member. `check:adr-symbol-anchors`: 2687 resolve at both ends (output byte-identical). - **After the merge.** `main` moved under the cut, and its own commits changed the count, so I measured its tip `aa00362fe5` (a clean detached worktree) against the merge head `79ca88b71b`: **1896** letters at both, exit 0 both, and 2687 symbol anchors resolve at both. ### 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 base `sql-driver-schema-sync.ts` into this worktree 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 (`RESTORE-OK 1084637… 8b345d7…`). - **The hashes.** The raw sha256 of `dist/index.d.ts` and `dist/index.d.mts` is the same at base, at the move commit and at the merge head: `0f33bf3febaac3c7aa96f68a4226d752766b8203ab7acade523b69f9aaa44d74` (unchanged since cut 8). These two are the dist's only declaration files (no chunk), so the declaration-reorder bar of ruling `6441032442` does not come into play. - **Positive control.** I dropped `protected` from `nowColumnDefault` in a copy of the head `index.d.ts` (1 hit, then 0). The copy hashes to `ae5be4b2d6abbda6d50b0c513af286406f61d5d9e9a97a53c8da4d4d0a7b3359`, with `cmp` exit 1. - **Export lists.** CJS `require` of each dist gives 59 names (sha1 `05757ce5ae` at both ends); the ESM namespace of `index.mjs` gives the same 59 at both ends (sha1 `05757ce5ae`). Neither moved function, neither newly exported declaration and not `SchemaSyncHost` is among them. - **The head dist carries the move.** `.call(host` sites in `index.mjs` go from 67 at base to 69 at head (the two stubs). ### Move-only proof The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base `4049efa345` against the move commit `fe7fb7ec5`. It is a scratch script and is not committed. | Moved body | Body tokens | Comments | Parameter tokens less `this` | Return type tokens | Body lines | |:--|--:|--:|--:|--:|--:| | `nowColumnDefault` | 172 | 31 | 3 | 3 | 51 | | `applyDeclaredColumnDefault` | 71 | 0 | 11 | 1 | 9 | - **Each moved body.** In both rows, 0 tokens differ and every comment is identical. Each body line is the base line less two leading spaces. Neither is `async` at either end. - **The two 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 348 unmoved members are byte-identical, trivia included. - **Top level of `sql-driver.ts`.** 175 of 180 base statements are byte-identical; the other five are the class, the two edited imports (the spec token import loses `isRuntimeDefaultToken`; the schema-sync import gains two names and its 2-line comment), and `MYSQL_DATETIME_PRECISION` and `isNowDefaultValue`, each checked to gain a leading `export` and nothing else. - **The module.** Its 45 base statements are all in the head module byte-identical except three: its first import (whose leading trivia is the header, which gained the cut 19 paragraph), the `@objectstack/spec/data` import (`isRuntimeDefaultToken` added) and the `./sql-driver.js` import (`isNowDefaultValue` and `MYSQL_DATETIME_PRECISION` added). The head adds the `knex` type import and the two functions. `SchemaSyncHost` is byte-identical. - **Result.** `PROOF OK`, exit 0; on the merge head its output is byte-identical. - **Negative control.** Through `scripts/ablation-replace.mjs`, I changed `(cast(utc_timestamp() as date))` to `(cast(utc_timestamp() as datez))` in the module's `nowColumnDefault` (anchor 1 hit, then 0, checked on disk). The proof then reports `nowColumnDefault` as differing, prints `PROOF FAILED`, and exits 1. The restore was proved (blob `8b345d7a5c` 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 `4049efa345` against the move commit `fe7fb7ec5`, and then the merge head `79ca88b71b` against the same base. 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` | 4061 (3847 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 `aa00362fe5`: 12 commits, none in `packages/drivers/**`), the three suites give the same totals and the same per-test statuses as base, 0 / 0 / 0 in each. Since then `main` has moved 11 more commits to `40264beec4`, none in `packages/drivers/**`; `git merge-tree --write-tree` of the head with it exits 0, so the tree is not stale. - **Type check and lint.** `tsc --noEmit -p .` in driver-sql exits 0 on the move commit and on the merge head. `pnpm exec eslint --no-inline-config packages/drivers/driver-sql` exits 0 (driver-sql has no `lint` script of its own). - **Late-binding probe.** A scratch script, not committed. It runs node against each end's built dist and prints 149 readings, each with the result or the error's class, message and top stack frame: 1. `nowColumnDefault` on SQLite, PostgreSQL and MySQL drivers (never connected) for `date`, `time`, `datetime` and two non-temporal types; 2. a SUBCLASS overriding the `isSqlite` and `isPostgres` getters (the overrides are the ones read); 3. `knex` replaced on the instance after construction, then restored; 4. `applyDeclaredColumnDefault` on all three dialects over 13 `defaultValue` shapes (absent, `null`, two `NOW()` spellings and a bare `now`, `current_user` in two cases, an expression envelope, a string, `0`, `false`, a decimal, an array) for three types, with a recording column builder, and with no field at all; 5. an instance spy on `nowColumnDefault` (it is the one `applyDeclaredColumnDefault` reaches); 6. a subclass override of `nowColumnDefault` (likewise); 7. a failure inside the body (`knex` unset): a synchronous `TypeError`; 8. real SQLite DDL through `initObjects`: the stored column defaults (`PRAGMA table_info`) and a defaulted row's shapes for declared `NOW()` datetime/date/time fields, literals, `current_user`, `false`, an option-level `default`, and the audit columns; 9. the SQLite rebuild path (`rebuildSqliteTablePatched`) after a re-`initObjects` that adds a literal default and a `NOW()` column; 10. both methods' shape on the prototype (type, arity). - **Probe result.** 148 of the 149 readings are identical. The other one is reading 7, which differs only in the top stack frame's receiver name: `_SqlDriver.nowColumnDefault` at base and `Object.nowColumnDefault` at head. That is shape B's receiver, 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 **48** families for this diff, the same list on the move commit and on the merge head (and the same 48 cuts 17 and 18 derived). I ran each one on its own and captured its exit code before any pipe, after a full `pnpm build` of that tree (116 of 116 tasks). - **Move commit `fe7fb7ec5`: 48 run, all 48 exit 0.** `--ran` reconciles: `48 derived famil(ies) accounted for — 48 run, 0 NOT-MEASURED`, exit 0. (I fetched the pinned fixture commit of `check-plugin-teardown-shape.mjs --self-test` into this shallow clone first.) - **Merge head `79ca88b71b`: 48 run, all 48 exit 0** (after a second full `pnpm build`, 116 of 116). `--ran` reconciles the same way, exit 0. - **Hand gates on both heads, all exit 0:** `check-adr-anchors`, `check-adr-symbol-anchors`, `check-tenant-audit-census` (244 write call sites), `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`. In a clean detached worktree at base `4049efa345` and on the move commit, `check-adr-anchors`, `check-adr-symbol-anchors`, `check-tenant-audit-census` and `check-object-def-param-keys` print byte-identical output, and `check-durability-degradation-log-level --list` prints the same seams, line numbers aside. - **No gate silently empty.** Each derived gate's log prints a non-zero population (for example `check:object-def-param-keys` compares 7 override parameter positions over 3375 files with every control firing; `check:tenant-chokepoint` reads 24 `getBuilder()` bindings across 3 files; `check:durability-log-level` reads 48 durability-critical catch seams and 88 read seams; `check:dual-build-cjs-loads` loads 112 entry points across 69 packages; `check:driver-conformance` scores 55 covered cells). The 48 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: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:org-identifier`, `pnpm check:page-declaration-shape`, `pnpm check:platform-object-tenancy-census`, `pnpm check:published-files`, `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:undeclared-dep-imports`, `pnpm check:watch-hint-literal`. ## Acceptance notes - **`check:tenant-chokepoint`'s `SCAN_FILES` does not list `sql-driver-schema-sync.ts`.** The moved bodies hold 0 of the file's 4 tenant predicates and build no query (they return a `knex.raw` / `knex.fn.now()` expression or call `defaultTo` on a column builder). Noted, not filed: widening `SCAN_FILES` would be a gate change. - **Stale links.** `sql-driver.ts` has the same 30 `{@link X}` sites (21 names) that are neither a top-level binding nor a `SqlDriver` member at base and at head (the name set is identical). One moved line comment in `nowColumnDefault` says `{@link canonicalTimeOfDay}`; in the module that name is not a binding, so the module's own count of such sites goes from 10 to 11. It is a `//` comment, which no tool resolves, and it moved byte-identical. - **The series' remaining yield, priced on this base.** After this cut, two single mechanisms still pay more than -30: date bucketing (`buildDateBucketExpr`, about -39 with a new module's import) and the presumed-timestamp update (`updateWithPresumedTimestamp` with `resolveUpdatedAtColumnPresence`, about -35). Everything else is -28 or less per mechanism (window function builder -28, `orderKeysFor` -27, database helpers -27, the four filter-helper mechanisms -29 or less), or blocked by a series limit (the tenant members). ## Human Minimum - [x] 1. No `packages/spec` change. - [x] 2. No new name; `SchemaSyncHost` is unchanged, and two module-level constants gain `export` (not re-exported by `index.ts`). - [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_01FEkQ5m67V6oBcEqg8yLDKL)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01FEkQ5m67V6oBcEqg8yLDKL Co-authored-by: Claude <noreply@anthropic.com>
1 parent a27f889 commit d7f76b6

2 files changed

Lines changed: 107 additions & 66 deletions

File tree

‎packages/drivers/driver-sql/src/sql-driver-schema-sync.ts‎

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,19 @@
119119
* reference counts from `sql-driver.ts`, as before. `sql-driver.ts` no longer
120120
* reads `fieldHasColumn` as a value.
121121
*
122+
* [#25521 cut 19] The declared column DEFAULT emission joined them the same
123+
* way: `nowColumnDefault`, the driver-native DEFAULT for a `defaultValue:
124+
* 'NOW()'` field on each dialect, and `applyDeclaredColumnDefault`, which
125+
* decides what a field's `defaultValue` becomes in DDL and calls it. The SQLite
126+
* rebuild above already reached both through the host; `createColumn` and
127+
* `createAuditTimestampColumn`, which call them too, stay in the class, and so
128+
* do both methods. `SchemaSyncHost` already listed every member these bodies
129+
* read. The module-level declarations they read are imported from where they
130+
* are declared: `MYSQL_DATETIME_PRECISION` and `isNowDefaultValue` (which now
131+
* carry `export`) from `sql-driver.ts`, which still reads both, and
132+
* `isRuntimeDefaultToken` from `@objectstack/spec/data`, which `sql-driver.ts`
133+
* no longer reads.
134+
*
122135
* A MOVE, not a redesign. Each function below is the body of the `SqlDriver`
123136
* method of the same name as it stood, re-indented one level and otherwise
124137
* unedited: the same dialect statements, the same rebuild procedure, the same
@@ -149,15 +162,16 @@
149162
*/
150163

151164
import { isUniqueViolationError } from '@objectstack/types';
165+
import type { Knex } from 'knex';
152166
import { StorageNameMapping } from '@objectstack/spec/system';
153167
import { carriesNullSafeTenantKeyPart, ceremonyOwnedDropFinding, describeMissingIndexColumns, diffManagedIndexes, diffManagedTable, diffUnbuildableIndexes, driftKey, expectedIndexes, fieldHasColumn, GLOBAL_TENANT, indexedKeyColumns, isCeremonyOwnedColumnDrop, isIndexDriftOp, legacyUniqueReplacements, normalizeDeclaredIndex, type DeclaredIndexInput, type DriftOp, type ManagedDriftEntry, type PendingSchemaWork, type PhysicalColumn } from './schema-drift.js';
154168
import { isJsonColumnType, mediaColumnMoveDialect, mediaColumnMovePlan, type MediaColumnMovePlan, type MediaColumnMoveRefusal, type MediaColumnMoveScan } from './media-column-move.js';
155169
import { StandardErrorCode } from '@objectstack/spec/api';
156170
import { recoverUnencodedJsonText } from './unencoded-json-text.js';
157171
import { aggregandColumnClass } from '@objectstack/core';
158-
import { FILE_REFERENCE_TYPES, NUMERIC_VALUE_TYPES, parseAutonumberFormat, resolveAutonumberFormat, type AutonumberToken } from '@objectstack/spec/data';
172+
import { FILE_REFERENCE_TYPES, isRuntimeDefaultToken, NUMERIC_VALUE_TYPES, parseAutonumberFormat, resolveAutonumberFormat, type AutonumberToken } from '@objectstack/spec/data';
159173
import { formatAttribute, undeliveredStorageAttributes, type UndeliveredAttribute } from './builtin-column-collision.js';
160-
import { AUDIT_TIMESTAMP_COLUMNS, declaredValueShapes, type IntrospectedColumn, isMultiValuedColumn, LOCK_WAIT_REFUSAL, NUMERIC_SCALAR_TYPES, type SqlDriver, SQLITE_NON_TEMPORAL_TEXT_REFS, SQLITE_TIME_EXPR_REFS } from './sql-driver.js';
174+
import { AUDIT_TIMESTAMP_COLUMNS, declaredValueShapes, type IntrospectedColumn, isMultiValuedColumn, isNowDefaultValue, LOCK_WAIT_REFUSAL, MYSQL_DATETIME_PRECISION, NUMERIC_SCALAR_TYPES, type SqlDriver, SQLITE_NON_TEMPORAL_TEXT_REFS, SQLITE_TIME_EXPR_REFS } from './sql-driver.js';
161175

162176
/**
163177
* The `SqlDriver` members the functions below read — every one of them, and
@@ -2064,3 +2078,71 @@ export async function previewTimeConvergence(this: Pick<SchemaSyncHost, 'isMysql
20642078
return [];
20652079
}
20662080
}
2081+
2082+
/** The body of `SqlDriver#nowColumnDefault`; the method's docblock there is its contract. */
2083+
export function nowColumnDefault(this: Pick<SchemaSyncHost, 'isMysql' | 'isPostgres' | 'isSqlite' | 'knex'>, type: string): Knex.Raw {
2084+
if (!this.isSqlite) {
2085+
// A `time` column deserves the same reasoning on the native dialects
2086+
// (#3994): `knex.fn.now()` compiles to CURRENT_TIMESTAMP, which a TIME
2087+
// column resolves in the SERVER's timezone on Postgres and the INSERTING
2088+
// session's timezone on MySQL — measured as three different wall clocks
2089+
// for one instant across the three dialects. Pin the default to the UTC
2090+
// time-of-day, matching what the driver's own writes and the SQLite
2091+
// branch below produce. (MySQL 8.0 additionally REJECTS a plain
2092+
// CURRENT_TIMESTAMP default on a TIME column — only the parenthesised
2093+
// expression form, MySQL 8.0.13+/MariaDB 10.2+, is legal there at all.)
2094+
if (type === 'time') {
2095+
if (this.isMysql) return this.knex.raw('(cast(utc_timestamp(3) as time(3)))');
2096+
if (this.isPostgres) return this.knex.raw("(timezone('utc', now())::time(3))");
2097+
}
2098+
// `date` has the same two problems one type over (#4022): a bare
2099+
// CURRENT_TIMESTAMP default resolves the calendar day in the SERVER's
2100+
// timezone on Postgres (measured: a UTC-12 server records YESTERDAY),
2101+
// and MySQL 8.0 rejects it on a DATE column outright (MariaDB is merely
2102+
// permissive; the driver's UTC-pinned session masked the semantic half
2103+
// there). Same fix as `time`: an expression default reading the UTC
2104+
// clock, which is also what `formatInput`'s NOW() safety-net and the
2105+
// SQLite branch below store.
2106+
if (type === 'date') {
2107+
if (this.isMysql) return this.knex.raw('(cast(utc_timestamp() as date))');
2108+
if (this.isPostgres) return this.knex.raw("(timezone('utc', now())::date)");
2109+
}
2110+
// `datetime` on MySQL (#21241): the column is `DATETIME(n)` (see
2111+
// `createColumn`), and MySQL REFUSES a `CURRENT_TIMESTAMP` default whose
2112+
// precision differs from its column's — the whole `CREATE TABLE` fails
2113+
// with "Invalid default value". The bare `knex.fn.now()` below is
2114+
// precision 0, so the default carries the column's precision from the
2115+
// one source both read. Postgres keeps the bare form: its `timestamptz`
2116+
// takes `CURRENT_TIMESTAMP` at microsecond precision, measured accepted.
2117+
if (type === 'datetime' && this.isMysql) return this.knex.fn.now(MYSQL_DATETIME_PRECISION);
2118+
return this.knex.fn.now();
2119+
}
2120+
switch (type) {
2121+
case 'date': return this.knex.raw("(strftime('%Y-%m-%d', 'now'))");
2122+
// The CASE trims a zero-millisecond `.000` so a defaulted row is
2123+
// byte-canonical ({@link canonicalTimeOfDay}) — `%f` alone would store
2124+
// `'01:55:08.000'` once in a thousand inserts, and that row would then
2125+
// miss an equality filter against the canonical `'01:55:08'`. SQLite
2126+
// fixes `'now'` per statement, so the three calls cannot straddle a
2127+
// millisecond boundary.
2128+
case 'time': return this.knex.raw(
2129+
"(case when strftime('%H:%M:%f', 'now') like '%.000' " +
2130+
"then strftime('%H:%M:%S', 'now') else strftime('%H:%M:%f', 'now') end)",
2131+
);
2132+
// datetime (and any non-temporal field that opts into NOW()): canonical instant.
2133+
default: return this.knex.raw("(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))");
2134+
}
2135+
}
2136+
2137+
/** The body of `SqlDriver#applyDeclaredColumnDefault`; the method's docblock there is its contract. */
2138+
export function applyDeclaredColumnDefault(this: Pick<SchemaSyncHost, 'nowColumnDefault'>, col: Knex.ColumnBuilder, field: any, type: string): void {
2139+
const dv = field?.defaultValue;
2140+
if (dv === undefined || dv === null) return;
2141+
if (isNowDefaultValue(dv)) {
2142+
col.defaultTo(this.nowColumnDefault(type));
2143+
return;
2144+
}
2145+
if (isRuntimeDefaultToken(dv)) return;
2146+
if (typeof dv === 'object') return;
2147+
col.defaultTo(dv as any);
2148+
}

‎packages/drivers/driver-sql/src/sql-driver.ts‎

Lines changed: 23 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import {
5555
// list of its own — which `defaultValue`s are instructions rather than literals,
5656
// so the engine and this driver can never disagree about what may become a
5757
// physical column DEFAULT.
58-
import { isNowDefaultToken, isRuntimeDefaultToken } from '@objectstack/spec/data';
58+
import { isNowDefaultToken } from '@objectstack/spec/data';
5959
// [#5702] The retired filter operators and the prescription each refusal
6060
// prints. Read from the spec rather than restated here for the reason the
6161
// #5701 table itself gives: five refusal sites that each write their own
@@ -168,7 +168,9 @@ import {
168168
// federated registration `registerExternalObject`.
169169
// [#25521 cut 18] And the deferred-DDL previews (`previewDeferredSchemaWork`, `previewDatetimeConvergence`,
170170
// `previewTimeConvergence`): the read-only plan of what the deferred schema sync would do.
171-
import { applyDriftOpInPlace, applyIndexDriftOp, applyMigrationEntries, applyNullSafeUniquePreflight, backfillCanonicalDatetimes, backfillCanonicalJsonEncoding, backfillCanonicalTimes, convergeJsonColumn, countNonTemporalTextRows, createNullSafeUniqueIndex, detectManagedDrift, detectTableDrift, detectTableIndexDrift, ensureRotation, legacyMysqlTimeColumns, legacyMysqlTimestampColumns, planMediaColumnMove, previewDatetimeConvergence, previewDeferredSchemaWork, previewTimeConvergence, rebuildSqliteTablePatched, reconcileAndWarnDrift, registerExternalObject, restoreBareIndexAfterFailedTighten, rotationColumnList, rotationShardKey, runWideningAlters, syncDeclaredIndexes, warnBuiltinColumnCollisions } from './sql-driver-schema-sync.js';
171+
// [#25521 cut 19] And the declared column DEFAULT emission (`nowColumnDefault`, `applyDeclaredColumnDefault`):
172+
// what a field's `defaultValue` becomes in the DDL of a fresh or rebuilt column.
173+
import { applyDeclaredColumnDefault, applyDriftOpInPlace, applyIndexDriftOp, applyMigrationEntries, applyNullSafeUniquePreflight, backfillCanonicalDatetimes, backfillCanonicalJsonEncoding, backfillCanonicalTimes, convergeJsonColumn, countNonTemporalTextRows, createNullSafeUniqueIndex, detectManagedDrift, detectTableDrift, detectTableIndexDrift, ensureRotation, legacyMysqlTimeColumns, legacyMysqlTimestampColumns, nowColumnDefault, planMediaColumnMove, previewDatetimeConvergence, previewDeferredSchemaWork, previewTimeConvergence, rebuildSqliteTablePatched, reconcileAndWarnDrift, registerExternalObject, restoreBareIndexAfterFailedTighten, rotationColumnList, rotationShardKey, runWideningAlters, syncDeclaredIndexes, warnBuiltinColumnCollisions } from './sql-driver-schema-sync.js';
172174
// [#25521 cut 11] The autonumber sequence path (`getNextSequenceValue`, `fillAutoNumberFields`,
173175
// `issueAutonumbers`, `maxAutonumberCounter`, and the #5495 collision re-seed `collidingAutoNumberReservations`
174176
// and `resyncSequenceToDataMax`); the methods of the same names below hand each body the members it reads
@@ -482,7 +484,7 @@ export const AUDIT_TIMESTAMP_COLUMNS = ['created_at', 'updated_at'] as const;
482484
* datetime field (`sys_activity.timestamp`, `sys_presence.last_seen`) was
483485
* never created, and its data door answered `500`.
484486
*/
485-
const MYSQL_DATETIME_PRECISION = 3;
487+
export const MYSQL_DATETIME_PRECISION = 3;
486488

487489
/**
488490
* Read-side repair for the builtin audit timestamps on SQLite.
@@ -572,7 +574,7 @@ function presentAuditTimestampOutput(value: unknown): unknown {
572574
* itself lives in `@objectstack/spec/data` so the engine's insert-time
573575
* resolution and this driver's DDL read one set (#4560).
574576
*/
575-
const isNowDefaultValue = isNowDefaultToken;
577+
export const isNowDefaultValue = isNowDefaultToken;
576578

577579
/**
578580
* Read-side normalization for user-declared `Field.datetime` columns on SQLite.
@@ -15474,57 +15476,18 @@ export class SqlDriver implements IDataDriver {
1547415476
* reads are uniform without a schema migration.
1547515477
*/
1547615478
protected nowColumnDefault(type: string): Knex.Raw {
15477-
if (!this.isSqlite) {
15478-
// A `time` column deserves the same reasoning on the native dialects
15479-
// (#3994): `knex.fn.now()` compiles to CURRENT_TIMESTAMP, which a TIME
15480-
// column resolves in the SERVER's timezone on Postgres and the INSERTING
15481-
// session's timezone on MySQL — measured as three different wall clocks
15482-
// for one instant across the three dialects. Pin the default to the UTC
15483-
// time-of-day, matching what the driver's own writes and the SQLite
15484-
// branch below produce. (MySQL 8.0 additionally REJECTS a plain
15485-
// CURRENT_TIMESTAMP default on a TIME column — only the parenthesised
15486-
// expression form, MySQL 8.0.13+/MariaDB 10.2+, is legal there at all.)
15487-
if (type === 'time') {
15488-
if (this.isMysql) return this.knex.raw('(cast(utc_timestamp(3) as time(3)))');
15489-
if (this.isPostgres) return this.knex.raw("(timezone('utc', now())::time(3))");
15490-
}
15491-
// `date` has the same two problems one type over (#4022): a bare
15492-
// CURRENT_TIMESTAMP default resolves the calendar day in the SERVER's
15493-
// timezone on Postgres (measured: a UTC-12 server records YESTERDAY),
15494-
// and MySQL 8.0 rejects it on a DATE column outright (MariaDB is merely
15495-
// permissive; the driver's UTC-pinned session masked the semantic half
15496-
// there). Same fix as `time`: an expression default reading the UTC
15497-
// clock, which is also what `formatInput`'s NOW() safety-net and the
15498-
// SQLite branch below store.
15499-
if (type === 'date') {
15500-
if (this.isMysql) return this.knex.raw('(cast(utc_timestamp() as date))');
15501-
if (this.isPostgres) return this.knex.raw("(timezone('utc', now())::date)");
15502-
}
15503-
// `datetime` on MySQL (#21241): the column is `DATETIME(n)` (see
15504-
// `createColumn`), and MySQL REFUSES a `CURRENT_TIMESTAMP` default whose
15505-
// precision differs from its column's — the whole `CREATE TABLE` fails
15506-
// with "Invalid default value". The bare `knex.fn.now()` below is
15507-
// precision 0, so the default carries the column's precision from the
15508-
// one source both read. Postgres keeps the bare form: its `timestamptz`
15509-
// takes `CURRENT_TIMESTAMP` at microsecond precision, measured accepted.
15510-
if (type === 'datetime' && this.isMysql) return this.knex.fn.now(MYSQL_DATETIME_PRECISION);
15511-
return this.knex.fn.now();
15512-
}
15513-
switch (type) {
15514-
case 'date': return this.knex.raw("(strftime('%Y-%m-%d', 'now'))");
15515-
// The CASE trims a zero-millisecond `.000` so a defaulted row is
15516-
// byte-canonical ({@link canonicalTimeOfDay}) — `%f` alone would store
15517-
// `'01:55:08.000'` once in a thousand inserts, and that row would then
15518-
// miss an equality filter against the canonical `'01:55:08'`. SQLite
15519-
// fixes `'now'` per statement, so the three calls cannot straddle a
15520-
// millisecond boundary.
15521-
case 'time': return this.knex.raw(
15522-
"(case when strftime('%H:%M:%f', 'now') like '%.000' " +
15523-
"then strftime('%H:%M:%S', 'now') else strftime('%H:%M:%f', 'now') end)",
15524-
);
15525-
// datetime (and any non-temporal field that opts into NOW()): canonical instant.
15526-
default: return this.knex.raw("(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))");
15527-
}
15479+
// [#25521 cut 19] The body moved to `sql-driver-schema-sync.ts` (see `SchemaSyncHost`).
15480+
const readIsMysql = () => this.isMysql;
15481+
const readIsPostgres = () => this.isPostgres;
15482+
const readIsSqlite = () => this.isSqlite;
15483+
const readKnex = () => this.knex;
15484+
const host: ThisParameterType<typeof nowColumnDefault> = {
15485+
get isMysql() { return readIsMysql(); },
15486+
get isPostgres() { return readIsPostgres(); },
15487+
get isSqlite() { return readIsSqlite(); },
15488+
get knex() { return readKnex(); },
15489+
};
15490+
return nowColumnDefault.call(host, type);
1552815491
}
1552915492

1553015493
/**
@@ -17380,15 +17343,11 @@ export class SqlDriver implements IDataDriver {
1738017343
* `sql-driver-option-default-ddl.test.ts`.
1738117344
*/
1738217345
protected applyDeclaredColumnDefault(col: Knex.ColumnBuilder, field: any, type: string): void {
17383-
const dv = field?.defaultValue;
17384-
if (dv === undefined || dv === null) return;
17385-
if (isNowDefaultValue(dv)) {
17386-
col.defaultTo(this.nowColumnDefault(type));
17387-
return;
17388-
}
17389-
if (isRuntimeDefaultToken(dv)) return;
17390-
if (typeof dv === 'object') return;
17391-
col.defaultTo(dv as any);
17346+
// [#25521 cut 19] The body moved to `sql-driver-schema-sync.ts` (see `SchemaSyncHost`).
17347+
const host: ThisParameterType<typeof applyDeclaredColumnDefault> = {
17348+
nowColumnDefault: (...a) => this.nowColumnDefault(...a),
17349+
};
17350+
return applyDeclaredColumnDefault.call(host, col, field, type);
1739217351
}
1739317352

1739417353
// ── Database helpers ────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)