Skip to content

Commit 9cc1a76

Browse files
committed
fix(cli): size a KEYED text-family column from its declaration, as the driver does
CORRECTING THE RECORD. This branch's first commit, the new pin's docblock and three comments in `generate.ts` all said: "the text family branches on KEYED, and a generated migration emits no index, so no generated column is ever keyed." That sentence describes this GENERATOR'S OUTPUT. `createColumn` reads the object's INPUT. Its `keyed` argument is `indexedKeyColumns(...).get(name)`, and `indexedKeyColumns` composes `uniqueIndexesFromFields` -- which keys a column on `field.unique`, a key every `FieldSchema` carries -- with the object's declared `indexes[]`. Both are DECLARATIONS, both are in the config these generators already read, and neither has anything to do with what a migration emits. The generator could have read `unique`; it simply did not. So a keyed text-family column IS sized from its declaration, at `keyableTextLength`'s width: the declared `maxLength` verbatim up to MAX_KEYABLE_VARCHAR_CHARS (768, the widest one utf8mb4 key part holds), and unbounded above that ceiling or with no usable declaration. Driven on live PostgreSQL 16.13 against the pre-change tree, one 300-character write into `{ type: 'text', unique: true, maxLength: 100 }`: driver varchar(100) REFUSED -- 22001 character varying(100) sql gen text ACCEPTED -- read back at length 300 ts gen text ACCEPTED -- read back at length 300 The wide direction, which this branch's own body calls the quieter of the two, inside the family it claimed to have closed. Re-driven after the change, all three producers REFUSE it, and 0 of 32 keyed character columns diverge. WHAT MOVES * `generate.ts` gains `indexKeyColumns`, a mirror of the driver's own composition -- field-level `unique` at all three spellings, object-level `indexes[]` unique or not, and the ADR-0120 D3 tenant key part, whose resolution (`tenancy.enabled`, `tenancy.tenantField`, an `organization_id` column) is computable from the object alone and so is mirrored rather than skipped. It also gains `keyableTextChars` and the transcribed 768 ceiling, kept deliberately separate from `declaredVarchar`: the two answer different questions of the same key. * The false sentence is corrected in all four places it reached. * The new pin gains the keyed arm: the driver-source chain at every link, the arm membership held equal to `createColumn`'s case labels, the width sweep at both outcomes, the three unique spellings against the words the spec rejects, the object-level index half, and the tenant-column half -- each of the last two confirmed against the live cluster before pinning. TWO RIDERS FROM THE SAME REVIEW * The pin's catch-all case skipped any member whose plain answer had already drifted, so it measured that the catch-all takes the driver's default width only where that already held. Mutating `radio` or `secret` to 'TEXT' passed all 61 tests across all four pin files. The character half of the catch-all is now DERIVED from the three spec classes `driver-sql` seeds `JSON_COLUMN_TYPES` from -- imported, never listed -- and `VARCHAR(255)` is asserted on the rest. Both mutations now redden. * A comment gave a false reason for transcribing `MAX_VARCHAR_CHARS`: "`packages/cli` does not depend on the driver at runtime". It does -- `@objectstack/driver-sql` is in this package's `dependencies` at `workspace:^`. The transcription is still necessary, for two other reasons: the constant is `protected static`, and #5726 forbids a CLI production module any static value import of a driver package. The reason moves; the transcription does not. The changeset stays `minor` and states the keyed half. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
1 parent 83edbc5 commit 9cc1a76

3 files changed

Lines changed: 612 additions & 80 deletions

File tree

.changeset/generated-migration-character-column-widths.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
A `text` field took `VARCHAR(255)` from both generators while the platform creates an unbounded `text` column for it, so a 300-character value the platform stores was refused by every generated table with `value too long for type character varying(255)`. Enumerating the whole character-column family found the same disagreement in eight more places: `url` and `phone` and `color` carried widths the generators invented (2048, 50 and 7 against the platform's 255), and neither generator read a field's declared `maxLength` at all, so a `maxLength: 400` email was `varchar(400)` on the platform and `varchar(255)` in the migration generated for it.
88

9-
All of them now follow the platform's own three answers: the text family is unbounded (its declared bound is enforced at the write seam, not by the column), the string family takes its declared `maxLength` — verbatim in both directions, and TEXT rather than a clamp when it exceeds what a `varchar` can express — and the remaining string-valued types keep the default width and ignore a declaration, because their stored value is an option code or another row's id rather than the declared string.
9+
All of them now follow the platform's own three answers: the text family is unbounded unless the object KEYS the column — a field declared `unique`, or one an object-level `indexes[]` entry lists, takes `varchar(maxLength)` up to the 768-character key-part ceiling, exactly as the platform builds it, and stays unbounded above that ceiling or with no declared bound, where the declared bound is enforced at the write seam instead — the string family takes its declared `maxLength` verbatim in both directions, and TEXT rather than a clamp when it exceeds what a `varchar` can express, and the remaining string-valued types keep the default width and ignore a declaration, because their stored value is an option code or another row's id rather than the declared string.
10+
11+
The keyed half was measured after the rest: `{ type: 'text', unique: true, maxLength: 100 }` built `varchar(100)` on the platform and `text` in both generated tables, so a 300-character value the platform REFUSES was accepted by every generated table — the same disagreement as the headline row, pointing the other way.
1012

1113
This scopes to PostgreSQL, which is the only dialect `os generate migration --format sql` claims.

0 commit comments

Comments
 (0)