Skip to content

driver-sql: SQLite introspection reports only the FIRST column of a composite primary key (pk === 1 vs SQLite's 1,2,3… numbering) #10997

Description

@os-warren

Found while fixing the introspection seam for #10676 (packages/services/service-datasource). The defect is upstream of that seam, in driver-sql, so it is filed separately rather than widened into that PR.

What

SqlDriver.introspectPrimaryKeys (packages/drivers/driver-sql/src/sql-driver.ts, SQLite arm) collects primary-key columns with:

constresult=awaitthis.knex.raw(`PRAGMA table_info(${safeTableName})`);for(constrowofresult){if(row.pk===1){primaryKeys.push(row.name);}}

PRAGMA table_info does not report pk as a boolean. It reports the column's 1-based position within the primary key0 for "not part of the key", 1 for the first key column, 2 for the second, and so on. Filtering on pk === 1 therefore keeps only the first member of a composite key and silently drops the rest.

Measured

Live in-memory SQLite at 368e7a06f, table created as t.primary(['order_id', 'line_no']):

PRAGMA table_info(order_lines):

namepk
order_id1
line_no2
sku0

SqlDriver.introspectSchema() for that table:

{
"name": "order_lines",
"primaryKeys": ["order_id"],
"columns": [
{ "name": "order_id", "isPrimary": true },
{ "name": "line_no", "isPrimary": false },
{ "name": "sku", "isPrimary": false }
]
}

line_no is a declared member of the primary key and is reported as not being one. Note that both output signals are wrong together, and for the same reason: introspectSchema derives col.isPrimary from primaryKeys (if (primaryKeys.includes(col.name)) col.isPrimary = true), so a consumer cannot recover the missing member by cross-checking the two.

Why it matters

Every consumer of SQLite introspection sees a composite-keyed table as single-keyed:

  • the federated-object codegen and the persisted external_catalog (ADR-0015) record a partial addressing/upsert key;
  • schema-drift comparison against a declared composite key reads as drift on the missing member.

SqliteWasmDriver extends SqlDriver and inherits this arm, so it is affected identically.

Fix direction

row.pk > 0 rather than row.pk === 1, and — since pk is an ordinal — push in pk order so primaryKeys reflects the declared key order rather than column order. Worth a pin over a real composite-keyed table; the current suite has none.

The Postgres and MySQL arms read from pg_index / KEY_COLUMN_USAGE and do not have this specific bug, though neither orders its result by key position either (ORDER BY is absent in both) — worth confirming in the same pass.

Not measured here

Only the SQLite arm was executed (better-sqlite3, in-memory). The Postgres and MySQL arms were read, not run — no server was reachable from this container.

Filed unassigned as a finding awaiting first-touch grading. Discovered from #10676.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions