can1357/oh-my-pi · error · ToolError
SQLite table '${table}' has a composite primary key; use '?w
Error message
SQLite table '${table}' has a composite primary key; use '?where=' instead What it means
resolveTableRowLookup determines how a single row can be addressed (`table:key`). A single-column primary key maps to `kind: pk`; when the table declares a composite (multi-column) primary key, no single value can identify a row, so the tool throws and points at the `?where=` selector instead.
Source
Thrown at packages/coding-agent/src/tools/sqlite-reader.ts:710
export function getTablePrimaryKey(db: Database, table: string): { column: string; type: string } | null {
const primaryKeyColumns = getPrimaryKeyColumns(db, table);
if (primaryKeyColumns.length !== 1) {
return null;
}
const column = primaryKeyColumns[0]!;
return { column: column.name, type: column.type };
}
export function resolveTableRowLookup(db: Database, table: string): SqliteRowLookup {
const primaryKeyColumns = getPrimaryKeyColumns(db, table);
if (primaryKeyColumns.length === 1) {
const column = primaryKeyColumns[0]!;
return { kind: "pk", column: column.name, type: column.type };
}
if (primaryKeyColumns.length > 1) {
throw new ToolError(`SQLite table '${table}' has a composite primary key; use '?where=' instead`);
}
const schema = getTableSchema(db, table);
if (/\bWITHOUT\s+ROWID\b/i.test(schema)) {
throw new ToolError(`SQLite table '${table}' does not expose ROWID; use '?where=' instead`);
}
return { kind: "rowid" };
}
export function queryRows(
db: Database,
table: string,
opts: { limit: number; offset: number; order?: string; where?: string },
): { columns: string[]; rows: Record<string, unknown>[]; totalCount: number } {
const columns = getTableColumns(db, table);
const validatedWhere = validateWhereClause(opts.where);
const whereClause = validatedWhere ? ` WHERE ${validatedWhere}` : "";View on GitHub (pinned to 9690622007)
Solutions
- Use the query selector with a where clause: `db.sqlite:join_table?where=user_id=1 AND role_id=2`
- Use a raw SQL query: `db.sqlite?q=SELECT * FROM join_table WHERE user_id=1 AND role_id=2`
- If single-row addressing is needed, alter the schema to use a single-column primary key or a UNIQUE single column
Example fix
// before sqlite://app.db?user_roles:5:9 // after sqlite://app.db?user_roles?where=user_id=5 AND role_id=9
Defensive patterns
Strategy: validation
Validate before calling
const pkCols = db
.query(`PRAGMA table_info(${quoteSqliteIdentifier(table)})`)
.all()
.filter(c => c.pk);
if (pkCols.length > 1) {
// composite key: use where= selector instead of table:key
} Try / catch
try {
const lookup = resolveTableRowLookup(db, table);
} catch (err) {
if (err instanceof ToolError && err.message.includes("composite primary key")) {
// route caller to ?where=col1=v1 AND col2=v2
} else throw err;
} Prevention
- Check PRAGMA table_info pk flags before attempting table:key lookups
- Use ?where= for any composite-key table (join/mapping tables)
- Prefer schemas with a single-column primary key or rowid for point lookups
When it happens
Trigger: Calling resolveTableRowLookup (indirectly via a `db.sqlite:table:key` selector) against a table whose CREATE TABLE has PRIMARY KEY (a, b) or multiple column-level PRIMARY KEYs.
Common situations: Join tables / many-to-many mapping tables (user_id, role_id); ORM-generated schemas with composite keys; trying row lookup on a table that was designed to be addressed by a tuple.
Related errors
- SQLite table '${table}' has no column named '${column}'
- SQLite schema for table '${table}' is unavailable
- SQLite table '${table}' does not expose ROWID; use '?where='
- Failed to open auth database at '${dbPath}' after ${maxAttem
- Persistent credential block store ${store} is unavailable af
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/ae7425cef30b9d8d.
Report an issue: GitHub.