Fix the metrics query with a caller-chosen sort column

Review the AI's codeEasy15 min

Your task

An analytics service fetches a tenant's metrics, sorted by a column the caller picks. The first version built the whole query by joining strings, so both the tenant and the column could inject SQL.

An assistant wrote build_metrics_query(tenant, sort_col, allowed_cols). It is in the editor. It has a bug. Find it and fix it.

Input and output:

  • tenant: the tenant id. It becomes a bound value, so any text is fine.
  • sort_col: the column name the caller asked to sort by.
  • allowed_cols: the real column names, spelled as in the table.
  • Return [sql, tenant], or [] to refuse the request.

Rules the tests check:

  • The SQL text is SELECT * FROM metrics WHERE tenant_id = $1 ORDER BY followed by the quoted column. The tenant never appears in it.
  • Return tenant exactly as given, as the second item.
  • Trim whitespace at both ends of sort_col and lowercase it. If the result is empty, return [].
  • Trim each entry of allowed_cols and skip empty entries. The column is the first entry whose lowercase form equals the trimmed, lowercased sort_col. If none does, return []. A near match such as values for value, or created_at ASC, is refused.
  • Write the column as the trimmed entry spells it, never as the caller typed it.
  • Quote the column the way Postgres quotes an identifier: wrap it in double quotes and double any double quote inside it.

Examples:

  • build_metrics_query("acme", "created_at", ["created_at", "value"]) returns ["SELECT * FROM metrics WHERE tenant_id = $1 ORDER BY \"created_at\"", "acme"].
  • build_metrics_query("acme", "id; DROP TABLE metrics; --", ["created_at", "value"]) returns [].
  • build_metrics_query("x' OR '1'='1", "value", ["created_at", "value"]) returns ["SELECT * FROM metrics WHERE tenant_id = $1 ORDER BY \"value\"", "x' OR '1'='1"]. The tenant is a bound value, so the quotes do nothing.

Then, in a sentence or two: how would you catch this kind of bug in review?

Examples

build_metrics_query(tenant: str, sort_col: str, allowed_cols: list[str]) -> list[str]
Example 1
Inputbuild_metrics_query("acme", "created_at", ["created_at", "value"])
Output["SELECT * FROM metrics WHERE tenant_id = $1 ORDER BY \"created_at\"", "acme"]
Example 2
Inputbuild_metrics_query("acme", "id; DROP TABLE metrics; --", ["created_at", "value"])
Output[]
Example 3
Inputbuild_metrics_query("x' OR '1'='1", "value", ["created_at", "value"])
Output["SELECT * FROM metrics WHERE tenant_id = $1 ORDER BY \"value\"", "x' OR '1'='1"]