can1357/oh-my-pi · error · ToolError

SQLite where clause changed the expected pagination paramete

Error message

SQLite where clause changed the expected pagination parameters; use q=SELECT ... for raw SQL

What it means

queryRows interpolates a pre-validated WHERE clause as raw SQL into a SELECT with exactly two bound parameters (LIMIT ? and OFFSET ?). After preparing, it asserts statement.paramsCount === 2; a mismatch means the where string introduced its own `?` placeholders, which the pagination bindings would incorrectly satisfy, so the tool refuses and suggests raw SQL.

Source

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

	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);
	if (statement.paramsCount !== 2) {
		throw new ToolError(
			"SQLite where clause changed the expected pagination parameters; use q=SELECT ... for raw SQL",
		);
	}
	const rows = statement.all(opts.limit, opts.offset);
	return { columns, rows, totalCount };
}

export function getRowByKey(
	db: Database,
	table: string,
	pk: { column: string; type?: string },
	key: string,
): Record<string, unknown> | null {
	getTableMasterRow(db, table);
	const sql = `SELECT * FROM ${quoteSqliteIdentifier(table)} WHERE ${quoteSqliteIdentifier(pk.column)} = ? LIMIT 1`;
	const binding = coerceLookupValue(key, pk.type ?? "");
	return db.prepare<SqliteRow, SQLQueryBindings[]>(sql).get(binding);
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Inline literal values in the where clause instead of `?` placeholders (e.g. `where=name='foo'`)
  2. Switch to a raw query with q= if you need bound parameters: `db.sqlite?q=SELECT * FROM t WHERE name = ?` is not supported — inline the value
  3. Keep the where clause to literal SQL comparisons; the where param is validated SQL text, not a binding API

Example fix

// before
sqlite://app.db?users?where=age > ?
// after
sqlite://app.db?users?where=age > 18
Defensive patterns

Strategy: validation

Validate before calling

// before passing where to the selector, ensure it contains no bind placeholders
if (whereClause.includes("?")) {
  throw new Error("where= must be literal SQL without ? placeholders; inline values instead");
}

Try / catch

try {
  const result = queryRows(db, table, opts);
} catch (err) {
  if (err instanceof ToolError && err.message.includes("pagination parameters")) {
    // rewrite where clause with literal values or switch to q= raw SQL
  } else throw err;
}

Prevention

When it happens

Trigger: Passing ?where= containing a question-mark placeholder, e.g. `?where=name = ?`, making the prepared statement expect 3 params while queryRows supplies 2.

Common situations: Developers writing parameterized-style conditions out of habit; pasting a prepared-statement fragment into the where param; encoding issues turning something into a literal `?`.

Related errors


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