can1357/oh-my-pi · error · ToolError

SQLite table '${table}' does not expose ROWID; use '?where='

Error message

SQLite table '${table}' does not expose ROWID; use '?where=' instead

What it means

When a table has no declared primary key, row lookup falls back to the implicit rowid. Tables declared `WITHOUT ROWID` have no rowid at all, so single-value addressing is impossible and the tool throws, directing callers to `?where=`.

Source

Thrown at packages/coding-agent/src/tools/sqlite-reader.ts:715

	}

	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}` : "";
	const orderClause = resolveOrderClause(opts.order, columns);
	const countSql = `SELECT COUNT(*) AS count FROM ${quoteSqliteIdentifier(table)}${whereClause}`;
	const selectSql = `SELECT * FROM ${quoteSqliteIdentifier(table)}${whereClause}${orderClause} LIMIT ? OFFSET ?`;
	const totalCount = db.prepare<SqliteCountRow, []>(countSql).get()?.count ?? 0;
	const statement = db.prepare<SqliteRow, SQLQueryBindings[]>(selectSql);

View on GitHub (pinned to 9690622007)

Solutions

  1. Look the row up by its actual column(s): `db.sqlite:kv?where=k='foo'`
  2. Use a raw query: `db.sqlite?q=SELECT * FROM kv WHERE k='foo'`
  3. Recreate the table as a normal rowid table with a single-column primary key if row addressing is required

Example fix

// before
sqlite://app.db?settings:some_key
// after
sqlite://app.db?settings?where=key='some_key'
Defensive patterns

Strategy: validation

Validate before calling

const schemaRow = db.query("SELECT sql FROM sqlite_master WHERE name = ?").get(table);
if (schemaRow?.sql && /\bWITHOUT\s+ROWID\b/i.test(schemaRow.sql)) {
  // no rowid: use ?where= or q= instead of table:key
}

Try / catch

try {
  const lookup = resolveTableRowLookup(db, table);
} catch (err) {
  if (err instanceof ToolError && err.message.includes("does not expose ROWID")) {
    // use where= selector on the table's real columns
  } else throw err;
}

Prevention

When it happens

Trigger: Calling resolveTableRowLookup on a table created with `CREATE TABLE t (...) WITHOUT ROWID` that also lacks a single-column PRIMARY KEY (note: most WITHOUT ROWID tables DO have a PRIMARY KEY and would hit the composite branch instead; this fires for keyless WITHOUT ROWID tables).

Common situations: Key-value stores created as `CREATE TABLE kv(k TEXT PRIMARY KEY, v) WITHOUT ROWID` with a composite or unusual key setup; hand-rolled schemas copying the WITHOUT ROWID optimization without a usable single key.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/3f9ccb43063b720e. Report an issue: GitHub.