Skip to content

driver-sql: introspectSchema() still diverges from the spec contract on indexes (required, never emitted) and defaultValue (declared string, emitted null) #11122

Description

@os-zhuang

Found while landing #10676 / #10998 (the driver aligning to the spec introspection contract, maintainer ruling 2026-08-22 「同意所有」 item 9). That ruling names exactly three keys — primaryKey on columns, plus dialect and introspectedAt on the schema — and those are repaired. Two other keys of the same contract are still unconformed, and they are recorded here rather than absorbed into that PR, because neither is covered by the ruling.

1. IntrospectedTable.indexes is declared REQUIRED and is never emitted

packages/spec/src/contracts/schema-diff-service.ts:

exportinterfaceIntrospectedTable{name: string;columns: IntrospectedColumn[];indexes: IntrospectedIndex[];// required}

SqlDriver.introspectSchema() builds { name, columns, foreignKeys, primaryKeys } — no indexes, on every dialect (one construction site, sql-driver.ts). So a consumer typed against the spec contract may read table.indexes without a guard and get undefined.

This is cheap to repair and deliberately not repaired in that PR: the capability already exists. SqlDriver.introspectIndexes(tableName, { onFailure }) returns PhysicalIndex[] ({ name, columns, unique, primary }), which is the spec's IntrospectedIndex ({ name, columns, unique }) plus one member. What is missing is the wiring plus two decisions that are not a dev's to take alone:

⚠️ In the meantime the type in driver-sql / objectqlOmits indexes from the spec table rather than emitting indexes: [], on purpose: an empty array tells a schema differ the table HAS no indexes, which is worse than an absent key.

2. IntrospectedColumn.defaultValue is declared string, and the producers emit other types

The spec declares defaultValue?: string. Measured on a live in-memory SQLite database, the driver's own output for t.string('id').primary():

{"name":"id","type":"varchar","nullable":true,"defaultValue":null,"primaryKey":true,"isUnique":true,"maxLength":"255"}

defaultValue is null — it is whatever knex.columnInfo() reported. In-tree fixtures also carry non-strings (packages/objectql/src/util.test.ts has defaultValue: true for a boolean column). The IntrospectedColumn in driver-sql and objectql therefore keeps defaultValue?: unknown (spelled as an explicit Omit from the spec type, so the divergence is visible rather than accidental).

Repairing it means deciding which side is right: normalise the producer to a string expression (and drop null rather than emit it), or widen the spec declaration. Both are contract decisions.

Same measurement note: maxLength came back as the STRING "255" while the SQL-layer type declares number. Same class, same site, listed here so it is not re-discovered separately.

Not measured

SQLite only. Postgres and MySQL were read, not run — no server is reachable from the container this was measured in. Both keys are built at a single dialect-independent site, so the SHAPE cannot vary by dialect; per-dialect CONTENT is not claimed.

Filed unassigned as a finding from #10676 / #10998; not fixed there because the ruling that released those cards does not cover these keys.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions