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 key — 0 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):
| name | pk |
|---|
order_id | 1 |
line_no | 2 |
sku | 0 |
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.
Found while fixing the introspection seam for #10676 (
packages/services/service-datasource). The defect is upstream of that seam, indriver-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:PRAGMA table_infodoes not reportpkas a boolean. It reports the column's 1-based position within the primary key —0for "not part of the key",1for the first key column,2for the second, and so on. Filtering onpk === 1therefore keeps only the first member of a composite key and silently drops the rest.Measured
Live in-memory SQLite at
368e7a06f, table created ast.primary(['order_id', 'line_no']):PRAGMA table_info(order_lines):order_idline_noskuSqlDriver.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_nois 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:introspectSchemaderivescol.isPrimaryfromprimaryKeys(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:
external_catalog(ADR-0015) record a partial addressing/upsert key;SqliteWasmDriverextendsSqlDriverand inherits this arm, so it is affected identically.Fix direction
row.pk > 0rather thanrow.pk === 1, and — sincepkis an ordinal — push inpkorder soprimaryKeysreflects 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_USAGEand do not have this specific bug, though neither orders its result by key position either (ORDER BYis 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.