Skip to content

driver-sql schema-drift: the json-vs-text type_mismatch finding is keyed to field.multiple only, so a SINGLE-value JSON-class column (file family, STRUCTURED_JSON_TYPES) on a char/text column is never reported — the column a hand-run generated migration creates today #15771

Description

@claude

Found while measuring #15041 (the FILE_REFERENCE_TYPES column fork). Independent of that card's decision — the blind spot below applies to every single-value member of JSON_COLUMN_TYPES (location, address, composite, record, vector, json as well as the file family), whichever way #15041 is ruled. Filed unassigned; no severity asserted.

The gap

Measured on origin/main 8e500f23e (2026-09-05T06:58Z).

packages/drivers/driver-sql/src/schema-drift.ts:944:

const declaresJsonColumn = field.multiple === true;
if (declaresJsonColumn && multiValueColumnTypeIsLoadBearing(dialect) && acceptsStringifiedJson(col.type)) {
  out.push({ kind: 'type_mismatch', ..., expected: 'json', actual: col.type, ... });

The type_mismatch finding that reports "metadata declares a JSON column but the database has a char/text one" is keyed to multiple alone (#11535 / #11733 — the single-to-multi change). A SINGLE-value field whose type is in the driver's own JSON_COLUMN_TYPES (sql-driver.ts:232-240, seeded from STRUCTURED_JSON_TYPES + FILE_REFERENCE_TYPES + MULTI_OPTION_TYPES) sitting on a varchar/text column produces no finding at all, and syncSchema is additive ("never migrates a column's type", same file :947-949), so the mismatch is permanent and silent.

Why it is reachable today

packages/cli/src/commands/generate.ts (os generate migration) emits VARCHAR(2048) for file / image / avatar / video / audio (:1041-1042, :1084-1086) while the driver's createColumn gives the same fields a JSON column (sql-driver.ts:16066 catch-all, jsonColumn :15752 = table.json off SQLite). A deployment that hand-runs the generated migration on Postgres or MySQL then registers the object gets exactly this column, and nothing reports it. (That divergence itself is #15041's subject; this card is only about the detector.)

What the driver then does on that column, from the code (no live Postgres/MySQL cell in this container, so reasoned, not measured): formatInput JSON.stringifys every isJsonField value unconditionally (sql-driver.ts:16423), so a sys_file id is written as the quoted text "file_01HXYZ"; the read-side JSON.parse lives only inside the if (this.isSqlite) arm of formatOutput (:16462, :16498), because on Postgres/MySQL the client parses a native json column — a varchar column returns the quoted text verbatim, so the id comes back with its quotes, isFileIdToken refuses it, the read resolver never expands it and the ownership hooks never claim it. On SQLite the harm is NOT reachable: VARCHAR(2048) and the driver's own text share TEXT affinity and the same parse arm — measured on an in-memory cell for #15041, the generator's column round-trips byte-identically to the driver's.

What a fix looks like

Extend declaresJsonColumn to field.multiple === true || JSON_COLUMN_TYPES.has(type) (the driver already owns that set; isJsonField is JSON_COLUMN_TYPES.has(type) || !!field.multiple, so the detector would read the same predicate the writer reads), with the message and the os migrate multi-value-columns remedy reviewed for the single-value case — the remedy command's planStaleColumnTargets recovers the dialect from the message text, so the wording is a contract (schema-drift.ts:925-943). Pin it in schema-drift.base-type-mismatch.test.ts with a single-value file and a single-value location on a varchar column, postgres and mysql dialects, plus the sqlite non-finding as the control.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions