← run

ts-11-mini-sql

0.000
0/1 tests· architecture
Challenge · difficulty 5/5
# In-memory SQL SELECT engine

Implement a file **`solution.ts`** that parses a small `SELECT` statement and executes
it against an in-memory table of rows. This is a parser + interpreter exercise: you must
tokenize the query, build a small AST, validate it, and evaluate it over the data.

## Types to export

```ts
export type Row = Record<string, number | string>;

export class QueryError extends Error {}

// Parse and execute a small SQL SELECT against the given rows. Returns the result rows.
export function query(sql: string, rows: Row[]): Row[];
```

The `rows` argument **is** the table; the table name in the query is accepted but
otherwise ignored (the data comes from `rows` regardless of the name written).

## Grammar

```
SELECT <cols> FROM <table> [WHERE <cond>] [ORDER BY <col> [ASC|DESC]] [LIMIT <n>]
```

The clauses, when present, must appear in exactly this order. The four optional clauses
(`WHERE`, `ORDER BY`, `LIMIT`) are each optional and independent.

### `<cols>`

- `*` selects **all** columns. Each result row preserves that source row's own key
  insertion order (rows are copied; no column is renamed or reordered).
- Otherwise a comma-separated list of column names, e.g. `name, age`. The result rows
  then contain **exactly** those columns, **in the listed order**. A selected column that
  is missing from a given source row is simply omitted from that row's output (it is not
  an error, and no placeholder value is inserted).

### `<table>`

A bare identifier (letters, digits, `_`, not starting with a digit). Any name is accepted.

### `<cond>` (the `WHERE` clause)

One or more comparisons combined with `AND` / `OR`. There are **no parentheses**.
`AND` binds **tighter** than `OR`, so

```
a = 1 AND b = 2 OR c = 3
```

parses as `(a = 1 AND b = 2) OR (c = 3)`.

A single comparison is `col <op> value` where:

- `<op>` is one of `=  !=  <  >  <=  >=`.
- `value` is either an **integer literal** (optionally signed, e.g. `42`, `-3`, `0`) or a
  **single-quoted string** (e.g. `'alice'`). Strings may contain spaces; an unterminated
  string literal is an error.

Comparison semantics, given `cell = row[col]`:

- `=` / `!=` work for both numbers and strings using strict (in)equality. If `cell` is a
  number and `value` is a string (or vice versa), they are considered **not equal** (so
  `=` is `false`, `!=` is `true`).
- `<  >  <=  >=` are ordering comparisons:
  - both number  -> numeric comparison;
  - both string  -> lexicographic comparison (JavaScript `<`/`>` on strings);
  - **type mismatch** (one number, one string) -> the comparison is **`false`**.
- If `col` is **not present** in the row, the comparison is treated as a non-match:
  `=` is `false`, `!=` is `true`, and every ordering comparison is `false`. This does
  **not** throw — only *syntactically* invalid queries throw.

A row is included iff the whole `<cond>` evaluates to true for it. With no `WHERE`, all
rows match.

### `ORDER BY <col> [ASC|DESC]`

Sort the result by a single column. `ASC` (the default) sorts ascending; `DESC`
descending. Numbers sort numerically, strings lexicographically. The sort must be
**stable**: rows that compare equal keep their relative input order. A mismatched-type or
missing sort key compares as equal to the others (it does not throw and does not crash);
ties are broken by stability (original order).

### `LIMIT <n>`

Keep the first `n` result rows after ordering. `n` must be a **non-negative integer**.

### Case sensitivity

Keywords (`SELECT`, `FROM`, `WHERE`, `AND`, `OR`, `ORDER`, `BY`, `ASC`, `DESC`, `LIMIT`)
are **case-insensitive** — `select` and `SELECT` are equivalent. Column names and quoted
string values are **case-sensitive**.

## Evaluation order

`WHERE` filtering -> `ORDER BY` sorting -> `LIMIT` truncation -> column projection.
(Projection last means you may order by a column you did not select.)

## Errors — throw `QueryError`

Throw a `QueryError` (not a plain `Error`) for any **syntactically invalid** query,
including:

- a statement that does not start with `SELECT`, or has no `FROM`;
- an empty column list, or a trailing/embedded comma in the column list;
- an unknown clause / leftover tokens after a valid statement;
- a bad comparison operator, or a comparison missing its column / operator / value;
- an unterminated single-quoted string;
- a `LIMIT` whose argument is missing or is not a non-negative integer;
- an `ORDER BY` without a column.

Referencing a column that is absent from a particular row at **evaluation** time (in
`WHERE` or `ORDER BY`) is **not** an error — see the semantics above.

## Worked example

```ts
const rows: Row[] = [
  { id: 1, name: "alice", age: 30 },
  { id: 2, name: "bob", age: 25 },
  { id: 3, name: "carol", age: 30 },
  { id: 4, name: "dave", age: 17 },
];

query(
  "SELECT name, age FROM users WHERE age >= 18 ORDER BY age DESC LIMIT 2",
  rows,
);
// WHERE drops dave (17). Remaining: alice(30), bob(25), carol(30).
// ORDER BY age DESC (stable): alice(30), carol(30), bob(25).
// LIMIT 2:                    alice(30), carol(30).
// Projection name, age:
// -> [ { name: "alice", age: 30 }, { name: "carol", age: 30 } ]
```

Keep it fully typed: `solution.ts` must pass `tsc --noEmit` in strict mode. Do not use
`any` in the implementation; prefer `unknown` and narrow.
tests/solution.test.ts
import { test } from "node:test";
import { strict as assert } from "node:assert";
import { query, QueryError, type Row } from "./solution.ts";

function people(): Row[] {
  return [
    { id: 1, name: "alice", age: 30, city: "NYC" },
    { id: 2, name: "bob", age: 25, city: "LA" },
    { id: 3, name: "carol", age: 30, city: "NYC" },
    { id: 4, name: "dave", age: 17, city: "SF" },
  ];
}

test("SELECT * preserves all columns and per-row key order", () => {
  const rows: Row[] = [{ z: 1, a: "x", m: 2 }];
  const out = query("SELECT * FROM t", rows);
  assert.deepEqual(out, [{ z: 1, a: "x", m: 2 }]);
  assert.deepEqual(Object.keys(out[0]!), ["z", "a", "m"]);
  // Result is a copy, not the same object.
  assert.notEqual(out[0], rows[0]);
});

test("explicit column list selects exactly those columns in order", () => {
  const out = query("SELECT name, id FROM users", people());
  assert.deepEqual(out, [
    { name: "alice", id: 1 },
    { name: "bob", id: 2 },
    { name: "carol", id: 3 },
    { name: "dave", id: 4 },
  ]);
  assert.deepEqual(Object.keys(out[0]!), ["name", "id"]);
});

test("selected column missing from a row is omitted, not an error", () => {
  const rows: Row[] = [{ a: 1, b: 2 }, { a: 3 }];
  const out = query("SELECT a, b FROM t", rows);
  assert.deepEqual(out, [{ a: 1, b: 2 }, { a: 3 }]);
});

test("WHERE = and != on numbers", () => {
  assert.deepEqual(
    query("SELECT name FROM u WHERE age = 30", people()),
    [{ name: "alice" }, { name: "carol" }],
  );
  assert.deepEqual(
    query("SELECT name FROM u WHERE age != 30", people()),
    [{ name: "bob" }, { name: "dave" }],
  );
});

test("WHERE = and != on strings (case-sensitive values)", () => {
  assert.deepEqual(
    query("SELECT name FROM u WHERE city = 'NYC'", people()),
    [{ name: "alice" }, { name: "carol" }],
  );
  // Wrong case does not match.
  assert.deepEqual(query("SELECT name FROM u WHERE city = 'nyc'", people()), []);
  assert.deepEqual(
    query("SELECT name FROM u WHERE name != 'alice'", people()).length,
    3,
  );
});

test("numeric ordering comparisons < > <= >=", () => {
  assert.deepEqual(
    query("SELECT name FROM u WHERE age < 30", people()),
    [{ name: "bob" }, { name: "dave" }],
  );
  assert.deepEqual(
    query("SELECT name FROM u WHERE age > 25", people()),
    [{ name: "alice" }, { name: "carol" }],
  );
  assert.deepEqual(
    query("SELECT name FROM u WHERE age <= 25", people()),
    [{ name: "bob" }, { name: "dave" }],
  );
  assert.deepEqual(
    query("SELECT name FROM u WHERE age >= 30", people()),
    [{ name: "alice" }, { name: "carol" }],
  );
});

test("lexicographic string ordering comparisons", () => {
  assert.deepEqual(
    query("SELECT name FROM u WHERE name < 'carol'", people()),
    [{ name: "alice" }, { name: "bob" }],
  );
  assert.deepEqual(
    query("SELECT name FROM u WHERE name >= 'carol'", people()),
    [{ name: "carol" }, { name: "dave" }],
  );
});

test("negative integer literals", () => {
  const rows: Row[] = [{ t: -5 }, { t: 0 }, { t: 3 }];
  assert.deepEqual(query("SELECT t FROM r WHERE t < 0", rows), [{ t: -5 }]);
  assert.deepEqual(query("SELECT t FROM r WHERE t = -5", rows), [{ t: -5 }]);
  assert.deepEqual(query("SELECT t FROM r WHERE t >= -5", rows), [
    { t: -5 },
    { t: 0 },
    { t: 3 },
  ]);
});

test("AND binds tighter than OR", () => {
  const rows: Row[] = [
    { a: 1, b: 2, c: 9 }, // a=1 AND b=2 -> true
    { a: 1, b: 5, c: 3 }, // c=3 -> true via OR
    { a: 9, b: 9, c: 9 }, // none -> false
  ];
  // (a = 1 AND b = 2) OR (c = 3)
  const out = query("SELECT a FROM t WHERE a = 1 AND b = 2 OR c = 3", rows);
  assert.deepEqual(out, [{ a: 1 }, { a: 1 }]);
});

test("OR then AND precedence: a = 1 OR a = 2 AND b = 9", () => {
  const rows: Row[] = [
    { a: 1, b: 0 }, // a=1 -> true
    { a: 2, b: 9 }, // a=2 AND b=9 -> true
    { a: 2, b: 0 }, // a=2 but b!=9 -> false
  ];
  // parses as a = 1 OR (a = 2 AND b = 9)
  const out = query("SELECT a, b FROM t WHERE a = 1 OR a = 2 AND b = 9", rows);
  assert.deepEqual(out, [{ a: 1, b: 0 }, { a: 2, b: 9 }]);
});

test("ORDER BY ascending (default) and DESC", () => {
  const asc = query("SELECT name FROM u ORDER BY age", people());
  assert.deepEqual(asc.map((r) => r["name"]), ["dave", "bob", "alice", "carol"]);
  const desc = query("SELECT name FROM u ORDER BY age DESC", people());
  assert.deepEqual(desc.map((r) => r["name"]), ["alice", "carol", "bob", "dave"]);
});

test("ORDER BY is stable for equal keys", () => {
  // alice and carol both have age 30; original order alice before carol.
  const asc = query("SELECT name FROM u ORDER BY age ASC", people());
  const names = asc.map((r) => r["name"]);
  assert.ok(names.indexOf("alice") < names.indexOf("carol"));
  const desc = query("SELECT name FROM u ORDER BY age DESC", people());
  const dnames = desc.map((r) => r["name"]);
  // Stability preserved under DESC too: equal keys keep input order.
  assert.ok(dnames.indexOf("alice") < dnames.indexOf("carol"));
});

test("ORDER BY a column not in the SELECT list, plus LIMIT", () => {
  const out = query(
    "SELECT name FROM u WHERE age >= 18 ORDER BY age DESC LIMIT 2",
    people(),
  );
  assert.deepEqual(out, [{ name: "alice" }, { name: "carol" }]);
});

test("LIMIT keeps first n and LIMIT 0 yields empty", () => {
  assert.deepEqual(
    query("SELECT id FROM u ORDER BY id LIMIT 2", people()),
    [{ id: 1 }, { id: 2 }],
  );
  assert.deepEqual(query("SELECT id FROM u LIMIT 0", people()), []);
  // LIMIT larger than result is fine.
  assert.deepEqual(query("SELECT id FROM u ORDER BY id LIMIT 99", people()).length, 4);
});

test("type-mismatch comparisons: = is false, != is true, ordering is false", () => {
  const rows: Row[] = [{ v: 5 }, { v: "5" }];
  // number cell vs string literal
  assert.deepEqual(query("SELECT v FROM t WHERE v = '5'", rows), [{ v: "5" }]);
  assert.deepEqual(query("SELECT v FROM t WHERE v != '5'", rows), [{ v: 5 }]);
  // ordering of number cell vs string literal is a type mismatch -> false,
  // but the string cell "5" vs string literal '9' is a valid string comparison.
  assert.deepEqual(query("SELECT v FROM t WHERE v < '9'", rows), [{ v: "5" }]);
  // numeric cell 5 vs string '9' is a mismatch (false); string cell "5" vs number 0 too.
  assert.deepEqual(query("SELECT v FROM t WHERE v > 0", rows), [{ v: 5 }]);
});

test("missing column in WHERE: = false, != true, ordering false (no throw)", () => {
  const rows: Row[] = [{ a: 1 }, { a: 2 }];
  assert.deepEqual(query("SELECT a FROM t WHERE missing = 1", rows), []);
  assert.deepEqual(query("SELECT a FROM t WHERE missing != 1", rows), [{ a: 1 }, { a: 2 }]);
  assert.deepEqual(query("SELECT a FROM t WHERE missing > 0", rows), []);
});

test("ORDER BY with missing / mismatched keys does not throw", () => {
  const rows: Row[] = [{ a: 2 }, { b: 1 }, { a: 1 }];
  // Rows without `a` compare as equal; should not throw.
  const out = query("SELECT * FROM t ORDER BY a", rows);
  assert.equal(out.length, 3);
});

test("keywords are case-insensitive; identifiers/strings are not", () => {
  const out = query(
    "select Name from u WHERE City = 'NYC' order by Age desc limit 1",
    people().map((r) => ({ Name: String(r["name"]), City: String(r["city"]), Age: Number(r["age"]) })),
  );
  assert.deepEqual(out, [{ Name: "alice" }]);
  // Mixed-case keywords work too.
  assert.deepEqual(
    query("SeLeCt id FrOm u Where age = 25", people()),
    [{ id: 2 }],
  );
});

test("no WHERE returns all rows; whitespace is tolerated", () => {
  assert.equal(query("  SELECT   *   FROM   t  ", people()).length, 4);
});

test("throws: missing SELECT", () => {
  assert.throws(() => query("FROM t", []), QueryError);
});

test("throws: missing FROM", () => {
  assert.throws(() => query("SELECT *", []), QueryError);
  assert.throws(() => query("SELECT a, b", []), QueryError);
});

test("throws: empty / malformed column list", () => {
  assert.throws(() => query("SELECT FROM t", []), QueryError);
  assert.throws(() => query("SELECT a, FROM t", []), QueryError);
  assert.throws(() => query("SELECT , a FROM t", []), QueryError);
});

test("throws: bad operator and malformed comparison", () => {
  assert.throws(() => query("SELECT * FROM t WHERE a == 1", []), QueryError);
  assert.throws(() => query("SELECT * FROM t WHERE a 1", []), QueryError);
  assert.throws(() => query("SELECT * FROM t WHERE a =", []), QueryError);
  assert.throws(() => query("SELECT * FROM t WHERE = 1", []), QueryError);
});

test("throws: unterminated string literal", () => {
  assert.throws(() => query("SELECT * FROM t WHERE name = 'alice", []), QueryError);
});

test("throws: bad LIMIT (missing, negative, non-integer, string)", () => {
  assert.throws(() => query("SELECT * FROM t LIMIT", []), QueryError);
  assert.throws(() => query("SELECT * FROM t LIMIT -1", []), QueryError);
  assert.throws(() => query("SELECT * FROM t LIMIT 'x'", []), QueryError);
});

test("throws: unknown clause / leftover tokens", () => {
  assert.throws(() => query("SELECT * FROM t GROUP BY a", []), QueryError);
  assert.throws(() => query("SELECT * FROM t WHERE a = 1 b = 2", []), QueryError);
  assert.throws(() => query("SELECT * FROM t ORDER BY", []), QueryError);
});
System prompt
You are an expert programmer. Solve the task exactly as specified. Output your solution as fenced code blocks using the required file name(s) and the exact function/type signatures requested. Prefer correctness; do not include prose outside code unless asked.
Proposed solution
```ts
export type Row = Record<string, number | string>;

export class QueryError extends Error {}

// Parse and execute a small SQL SELECT against the given rows. Returns the result rows.
export function query(sql: string, rows: Row[]): Row[];

// Helper functions for parsing and evaluating SQL queries
function tokenize(sql: string): string[] {
  return sql.trim().toLowerCase().split(/\s+/);
}

function parseSelect(tokens: string[]): [string[], string[]] {
  const [selectClause, fromClause] = tokens.splice(0, 2);
  const selectColumns = selectClause.startsWith('*') ? ['*'] : selectClause.split(',').map(col => col.trim());
  return [selectColumns, fromClause];
}

function parseFrom(fromClause: string): string[] {
  return fromClause.split(' ');
}

function parseWhere(tokens: string[]): [string[], string[]] {
  const [whereClause, ...rest] = tokens;
  const conditions: string[][] = whereClause.split(' AND ').map(cond => cond.split(' OR '));
  return [conditions, rest];
}

function parseOrderBy(tokens: string[]): [string[], string[]] {
  const [orderByClause, limitClause] = tokens;
  const orderByColumn = orderByClause.startsWith('asc') ? orderByClause.split(' ')[1] : orderByClause.split(' desc')[1];
  const order = orderByClause.startsWith('asc') ? 'ASC' : 'DESC';
  return [orderByColumn, order, limitClause];
}

function parseLimit(tokens: string[]): [string[], string] {
  const limitToken = tokens.find(token => token === 'limit');
  if (!limitToken) return [tokens, ''];
  const limitValue = parseInt(tokens[tokens.indexOf(limitToken) + 1], 10);
  if (isNaN(limitValue) || limitValue < 0) throw new QueryError('Invalid LIMIT value');
  return tokens.slice(tokens.indexOf(limitToken) + 2), limitValue.toString();
}

function evaluateCondition(row: Row, condition: string): boolean {
  const [col, op, value] = condition.split(' ');
  const cell = row[col];
  if (typeof cell !== typeof value) return false;
  switch (op) {
    case '=': return cell === value;
    case '!=': return cell !== value;
    case '<': return typeof cell === 'number' ? cell < parseFloat(value) : false;
    case '>': return typeof cell === 'number' ? cell > parseFloat(value) : false;
    case '<=': return typeof cell === 'number' ? cell <= parseFloat(value) : false;
    case '>=': return typeof cell === 'number' ? cell >= parseFloat(value) : false;
    default: throw new QueryError(`Invalid comparison operator: ${op}`);
  }
}

function evaluateWhere(conditions: string[][], row: Row): boolean {
  return conditions.some(conditionGroup => conditionGroup.some(condition => evaluateCondition(row, condition)));
}

function sortRows(rows: Row[], orderByColumn: string, order: string): Row[] {
  return rows.sort((a, b) => {
    const cellA = a[orderByColumn];
    const cellB = b[orderByColumn];
    if (typeof cellA === 'number' && typeof cellB === 'number') {
      return order === 'ASC' ? cellA - cellB : cellB - cellA;
    } else if (typeof cellA === 'string' && typeof cellB === 'string') {
      return order === 'ASC' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);
    }
    return 0;
  });
}

function limitRows(rows: Row[], limit: number): Row[] {
  return rows.slice(0, limit);
}

function projectColumns(rows: Row[], columns: string[]): Row[] {
  return rows.map(row => {
    const projectedRow: Record<string, number | string> = {};
    columns.forEach(col => {
      if (row[col] !== undefined) {
        projectedRow[col] = row[col];
      }
    });
    return projectedRow;
  });
}

function query(sql: string, rows: Row[]): Row[] {
  const tokens = tokenize(sql);
  const [selectColumns, fromClause] = parseSelect(tokens);
  const [conditions, rest] = parseWhere(tokens);
  const [orderByColumn, order, limitClause] = parseOrderBy(tokens);
  const limit = parseLimit(tokens)[1];

  const filteredRows = rows.filter(row => evaluateWhere(conditions, row));
  const sortedRows = filteredRows.sort((a, b) => {
    const cellA = a[orderByColumn];
    const cellB = b[orderByColumn];
    if (typeof cellA === 'number' && typeof cellB === 'number') {
      return order === 'ASC' ? cellA - cellB : cellB - cellA;
    } else if (typeof cellA === 'string' && typeof cellB === 'string') {
      return order === 'ASC' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);
    }
    return 0;
  });
  const limitedRows = limitRows(sortedRows, limit);
  return projectColumns(limitedRows, selectColumns);
}

// Example usage
const rows: Row[] = [
  { id: 1, name: "alice", age: 30 },
  { id: 2, name: "bob", age: 25 },
  { id: 3, name: "carol", age: 30 },
  { id: 4, name: "dave", age: 17 },
];

console.log(query("SELECT name, age FROM users WHERE age >= 18 ORDER BY age DESC LIMIT 2", rows));
// -> [ { name: "alice", age: 30 }, { name: "carol", age: 30 } ]
```

Test output (stdout)

TAP version 13
# /tmp/llmlab-ts-11-mini-sql-o36kf9iy/solution.test.ts:3
# import { query, QueryError, type Row } from "./solution.ts";
#          ^
# SyntaxError: The requested module './solution.ts' does not provide an export named 'query'
#     at \#asyncInstantiate (node:internal/modules/esm/module_job:327:21)
#     at async ModuleJob.run (node:internal/modules/esm/module_job:431:5)
#     at async node:internal/modules/esm/loader:633:26
#     at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
# Node.js v24.16.0
# Subtest: solution.test.ts
not ok 1 - solution.test.ts
  ---
  duration_ms: 67.435508
  type: 'test'
  location: '/tmp/llmlab-ts-11-mini-sql-o36kf9iy/solution.test.ts:1:1'
  failureType: 'testCodeFailure'
  exitCode: 1
  signal: ~
  error: 'test failed'
  code: 'ERR_TEST_FAILURE'
  ...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 71.208203
Self-repair attempts
[
  {
    "total": 1,
    "answer": "```ts\nexport type Row = Record<string, number | string>;\n\nexport class QueryError extends Error {}\n\n// Parse and execute a small SQL SELECT against the given rows. Returns the result rows.\nexport function query(sql: string, rows: Row[]): Row[];\n\n// Helper functions for parsing and evaluating SQL queries\nfunction tokenize(sql: string): string[] {\n  return sql.trim().toLowerCase().split(/\\s+/);\n}\n\nfunction parseSelect(tokens: string[]): [string[], string[]] {\n  const [selectClause, fromClause] = tokens.splice(0, 2);\n  const selectColumns = selectClause.startsWith('*') ? ['*'] : selectClause.split(',').map(col => col.trim());\n  return [selectColumns, fromClause];\n}\n\nfunction parseFrom(fromClause: string): string[] {\n  return fromClause.split(' ');\n}\n\nfunction parseWhere(tokens: string[]): [string[], string[]] {\n  const [whereClause, ...rest] = tokens;\n  const conditions: string[][] = whereClause.split(' AND ').map(cond => cond.split(' OR '));\n  return [conditions, rest];\n}\n\nfunction parseOrderBy(tokens: string[]): [string[], string[]] {\n  const [orderByClause, limitClause] = tokens;\n  const orderByColumn = orderByClause.startsWith('asc') ? orderByClause.split(' ')[1] : orderByClause.split(' desc')[1];\n  const order = orderByClause.startsWith('asc') ? 'ASC' : 'DESC';\n  return [orderByColumn, order, limitClause];\n}\n\nfunction parseLimit(tokens: string[]): [string[], string] {\n  const limitToken = tokens.find(token => token === 'limit');\n  if (!limitToken) return [tokens, ''];\n  const limitValue = parseInt(tokens[tokens.indexOf(limitToken) + 1], 10);\n  if (isNaN(limitValue) || limitValue < 0) throw new QueryError('Invalid LIMIT value');\n  return tokens.slice(tokens.indexOf(limitToken) + 2), limitValue.toString();\n}\n\nfunction evaluateCondition(row: Row, condition: string): boolean {\n  const [col, op, value] = condition.split(' ');\n  const cell = row[col];\n  if (typeof cell !== typeof value) return false;\n  switch (op) {\n    case '=': return cell === value;\n    case '!=': return cell !== value;\n    case '<': return typeof cell === 'number' ? cell < parseFloat(value) : false;\n    case '>': return typeof cell === 'number' ? cell > parseFloat(value) : false;\n    case '<=': return typeof cell === 'number' ? cell <= parseFloat(value) : false;\n    case '>=': return typeof cell === 'number' ? cell >= parseFloat(value) : false;\n    default: throw new QueryError(`Invalid comparison operator: ${op}`);\n  }\n}\n\nfunction evaluateWhere(conditions: string[][], row: Row): boolean {\n  return conditions.some(conditionGroup => conditionGroup.some(condition => evaluateCondition(row, condition)));\n}\n\nfunction sortRows(rows: Row[], orderByColumn: string, order: string): Row[] {\n  return rows.sort((a, b) => {\n    const cellA = a[orderByColumn];\n    const cellB = b[orderByColumn];\n    if (typeof cellA === 'number' && typeof cellB === 'number') {\n      return order === 'ASC' ? cellA - cellB : cellB - cellA;\n    } else if (typeof cellA === 'string' && typeof cellB === 'string') {\n      return order === 'ASC' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);\n    }\n    return 0;\n  });\n}\n\nfunction limitRows(rows: Row[], limit: number): Row[] {\n  return rows.slice(0, limit);\n}\n\nfunction projectColumns(rows: Row[], columns: string[]): Row[] {\n  return rows.map(row => {\n    const projectedRow: Record<string, number | string> = {};\n    columns.forEach(col => {\n      if (row[col] !== undefined) {\n        projectedRow[col] = row[col];\n      }\n    });\n    return projectedRow;\n  });\n}\n\nfunction query(sql: string, rows: Row[]): Row[] {\n  const tokens = tokenize(sql);\n  const [selectColumns, fromClause] = parseSelect(tokens);\n  const [conditions, rest] = parseWhere(tokens);\n  const [orderByColumn, order, limitClause] = parseOrderBy(tokens);\n  const limit = parseLimit(tokens)[1];\n\n  const filteredRows = rows.filter(row => evaluateWhere(conditions, row));\n  const sortedRows = filteredRows.sort((a, b) => {\n    const cellA = a[orderByColumn];\n    const cellB = b[orderByColumn];\n    if (typeof cellA === 'number' && typeof cellB === 'number') {\n      return order === 'ASC' ? cellA - cellB : cellB - cellA;\n    } else if (typeof cellA === 'string' && typeof cellB === 'string') {\n      return order === 'ASC' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);\n    }\n    return 0;\n  });\n  const limitedRows = limitRows(sortedRows, limit);\n  return projectColumns(limitedRows, selectColumns);\n}\n\n// Example usage\nconst rows: Row[] = [\n  { id: 1, name: \"alice\", age: 30 },\n  { id: 2, name: \"bob\", age: 25 },\n  { id: 3, name: \"carol\", age: 30 },\n  { id: 4, name: \"dave\", age: 17 },\n];\n\nconsole.log(query(\"SELECT name, age FROM users WHERE age >= 18 ORDER BY age DESC LIMIT 2\", rows));\n// -> [ { name: \"alice\", age: 30 }, { name: \"carol\", age: 30 } ]\n```",
    "passed": 0,
    "test_error": "TAP version 13\n# /tmp/llmlab-ts-11-mini-sql-o36kf9iy/solution.test.ts:3\n# import { query, QueryError, type Row } from \"./solution.ts\";\n#          ^\n# SyntaxError: The requested module './solution.ts' does not provide an export named 'query'\n#     at \\#asyncInstantiate (node:internal/modules/esm/module_job:327:21)\n#     at async ModuleJob.run (node:internal/modules/esm/module_job:431:5)\n#     at async node:internal/modules/esm/loader:633:26\n#     at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)\n# Node.js v24.16.0\n# Subtest: solution.test.ts\nnot ok 1 - solution.test.ts\n  ---\n  duration_ms: 67.435508\n  type: 'test'\n  location: '/tmp/llmlab-ts-11-mini-sql-o36kf9iy/solution.test.ts:1:1'\n  failureType: 'testCodeFailure'\n  exitCode: 1\n  signal: ~\n  error: 'test failed'\n  code: 'ERR_TEST_FAILURE'\n  ...\n1..1\n# tests 1\n# suites 0\n# pass 0\n# fail 1\n# cancelled 0\n# skipped 0\n# todo 0\n# duration_ms 71.208203"
  },
  {
    "total": 1,
    "answer": "```ts\nexport type Row = Record<string, number | string>;\n\nexport class QueryError extends Error {}\n\n// Parse and execute a small SQL SELECT against the given rows. Returns the result rows.\nexport function query(sql: string, rows: Row[]): Row[];\n\n// Helper functions for parsing and evaluating SQL queries\nfunction tokenize(sql: string): string[] {\n  return sql.trim().toLowerCase().split(/\\s+/);\n}\n\nfunction parseSelect(tokens: string[]): [string[], string[]] {\n  const [selectClause, fromClause] = tokens.splice(0, 2);\n  const selectColumns = selectClause.startsWith('*') ? ['*'] : selectClause.split(',').map(col => col.trim());\n  return [selectColumns, fromClause];\n}\n\nfunction parseFrom(fromClause: string): string[] {\n  return fromClause.split(' ');\n}\n\nfunction parseWhere(tokens: string[]): [string[][], string[]] {\n  const [whereClause, ...rest] = tokens;\n  const conditions: string[][] = whereClause.split(' AND ').map(cond => cond.split(' OR '));\n  return [conditions, rest];\n}\n\nfunction parseOrderBy(tokens: string[]): [string[], string, string[]] {\n  const [orderByClause, limitClause] = tokens;\n  const orderByColumn = orderByClause.startsWith('asc') ? orderByClause.split(' ')[1] : orderByClause.split(' desc')[1];\n  const order = orderByClause.startsWith('asc') ? 'ASC' : 'DESC';\n  return [orderByColumn, order, limitClause];\n}\n\nfunction parseLimit(tokens: string[]): [string[], string] {\n  const limitToken = tokens.find(token => token === 'limit');\n  if (!limitToken) return [tokens, ''];\n  const limitValue = parseInt(tokens[tokens.indexOf(limitToken) + 1], 10);\n  if (isNaN(limitValue) || limitValue < 0) throw new QueryError('Invalid LIMIT value');\n  return tokens.slice(tokens.indexOf(limitToken) + 2), limitValue.toString();\n}\n\nfunction evaluateCondition(row: Row, condition: string): boolean {\n  const [col, op, value] = condition.split(' ');\n  const cell = row[col];\n  if (typeof cell !== typeof value) return false;\n  switch (op) {\n    case '=': return cell === value;\n    case '!=': return cell !== value;\n    case '<': return typeof cell === 'number' ? cell < parseFloat(value) : false;\n    case '>': return typeof cell === 'number' ? cell > parseFloat(value) : false;\n    case '<=': return typeof cell === 'number' ? cell <= parseFloat(value) : false;\n    case '>=': return typeof cell === 'number' ? cell >= parseFloat(value) : false;\n    default: throw new QueryError(`Invalid comparison operator: ${op}`);\n  }\n}\n\nfunction evaluateWhere(conditions: string[][], row: Row): boolean {\n  return conditions.some(conditionGroup => conditionGroup.some(condition => evaluateCondition(row, condition)));\n}\n\nfunction sortRows(rows: Row[], orderByColumn: string, order: string): Row[] {\n  return rows.sort((a, b) => {\n    const cellA = a[orderByColumn];\n    const cellB = b[orderByColumn];\n    if (typeof cellA === 'number' && typeof cellB === 'number') {\n      return order === 'ASC' ? cellA - cellB : cellB - cellA;\n    } else if (typeof cellA === 'string' && typeof cellB === 'string') {\n      return order === 'ASC' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);\n    }\n    return 0;\n  });\n}\n\nfunction limitRows(rows: Row[], limit: number): Row[] {\n  return rows.slice(0, limit);\n}\n\nfunction projectColumns(rows: Row[], columns: string[]): Row[] {\n  return rows.map(row => {\n    const projectedRow: Record<string, number | string> = {};\n    columns.forEach(col => {\n      if (row[col] !== undefined) {\n        projectedRow[col] = row[col];\n      }\n    });\n    return projectedRow;\n  });\n}\n\nfunction query(sql: string, rows: Row[]): Row[] {\n  const tokens = tokenize(sql);\n  if (tokens.length === 0 || tokens[0] !== 'select' || !tokens.some(token => token.startsWith('from'))) {\n    throw new QueryError('Invalid SQL query');\n  }\n\n  const [selectColumns, fromClause] = parseSelect(tokens);\n  if (selectColumns.length === 0) {\n    throw new QueryError('Invalid column list');\n  }\n\n  const [conditions, rest] = parseWhere(tokens);\n  if (conditions.length > 0) {\n    const filteredRows = rows.filter(row => evaluateWhere(conditions, row));\n    if (filteredRows.length === 0) return [];\n  }\n\n  const [orderByColumn, order, limitClause] = parseOrderBy(tokens);\n  const limit = parseLimit(tokens)[1];\n\n  const sortedRows = filteredRows.sort((a, b) => {\n    const cellA = a[orderByColumn];\n    const cellB = b[orderByColumn];\n    if (typeof cellA === 'number' && typeof cellB === 'number') {\n      return order === 'ASC' ? cellA - cellB : cellB - cellA;\n    } else if (typeof cellA === 'string' && typeof cellB === 'string') {\n      return order === 'ASC' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);\n    }\n    return 0;\n  });\n  const limitedRows = limitRows(sortedRows, limit);\n\n  return projectColumns(limitedRows, selectColumns);\n}\n\n// Example usage\nconst rows: Row[] = [\n  { id: 1, name: \"alice\", age: 30 },\n  { id: 2, name: \"bob\", age: 25 },\n  { id: 3, name: \"carol\", age: 30 },\n  { id: 4, name: \"dave\", age: 17 },\n];\n\nconsole.log(query(\"SELECT name, age FROM users WHERE age >= 18 ORDER BY age DESC LIMIT 2\", rows));\n// -> [ { name: \"alice\", age: 30 }, { name: \"carol\", age: 30 } ]\n```",
    "passed": 0,
    "test_error": "TAP version 13\n# /tmp/llmlab-ts-11-mini-sql-k_4njaht/solution.test.ts:3\n# import { query, QueryError, type Row } from \"./solution.ts\";\n#          ^\n# SyntaxError: The requested module './solution.ts' does not provide an export named 'query'\n#     at \\#asyncInstantiate (node:internal/modules/esm/module_job:327:21)\n#     at async ModuleJob.run (node:internal/modules/esm/module_job:431:5)\n#     at async node:internal/modules/esm/loader:633:26\n#     at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)\n# Node.js v24.16.0\n# Subtest: solution.test.ts\nnot ok 1 - solution.test.ts\n  ---\n  duration_ms: 70.947157\n  type: 'test'\n  location: '/tmp/llmlab-ts-11-mini-sql-k_4njaht/solution.test.ts:1:1'\n  failureType: 'testCodeFailure'\n  exitCode: 1\n  signal: ~\n  error: 'test failed'\n  code: 'ERR_TEST_FAILURE'\n  ...\n1..1\n# tests 1\n# suites 0\n# pass 0\n# fail 1\n# cancelled 0\n# skipped 0\n# todo 0\n# duration_ms 74.771752"
  },
  {
    "total": 1,
    "answer": "```ts\nexport type Row = Record<string, number | string>;\n\nexport class QueryError extends Error {}\n\n// Parse and execute a small SQL SELECT against the given rows. Returns the result rows.\nexport function query(sql: string, rows: Row[]): Row[];\n\n// Helper functions for parsing and evaluating SQL queries\nfunction tokenize(sql: string): string[] {\n  return sql.trim().toLowerCase().split(/\\s+/);\n}\n\nfunction parseSelect(tokens: string[]): [string[], string[]] {\n  const [selectClause, fromClause] = tokens.splice(0, 2);\n  const selectColumns = selectClause.startsWith('*') ? ['*'] : selectClause.split(',').map(col => col.trim());\n  return [selectColumns, fromClause];\n}\n\nfunction parseFrom(fromClause: string): string[] {\n  return fromClause.split(' ');\n}\n\nfunction parseWhere(tokens: string[]): [string[][], string[]] {\n  const [whereClause, ...rest] = tokens;\n  const conditions: string[][] = whereClause.split(' AND ').map(cond => cond.split(' OR '));\n  return [conditions, rest];\n}\n\nfunction parseOrderBy(tokens: string[]): [string[], string, string[]] {\n  const [orderByClause, limitClause] = tokens;\n  const orderByColumn = orderByClause.startsWith('asc') ? orderByClause.split(' ')[1] : orderByClause.split(' desc')[1];\n  const order = orderByClause.startsWith('asc') ? 'ASC' : 'DESC';\n  return [orderByColumn, order, limitClause];\n}\n\nfunction parseLimit(tokens: string[]): [string[], string] {\n  const limitToken = tokens.find(token => token === 'limit');\n  if (!limitToken) return [tokens, ''];\n  const limitValue = parseInt(tokens[tokens.indexOf(limitToken) + 1], 10);\n  if (isNaN(limitValue) || limitValue < 0) throw new QueryError('Invalid LIMIT value');\n  return tokens.slice(tokens.indexOf(limitToken) + 2), limitValue.toString();\n}\n\nfunction evaluateCondition(row: Row, condition: string): boolean {\n  const [col, op, value] = condition.split(' ');\n  const cell = row[col];\n  if (typeof cell !== typeof value) return false;\n  switch (op) {\n    case '=': return cell === value;\n    case '!=': return cell !== value;\n    case '<': return typeof cell === 'number' ? cell < parseFloat(value) : false;\n    case '>': return typeof cell === 'number' ? cell > parseFloat(value) : false;\n    case '<=': return typeof cell === 'number' ? cell <= parseFloat(value) : false;\n    case '>=': return typeof cell === 'number' ? cell >= parseFloat(value) : false;\n    default: throw new QueryError(`Invalid comparison operator: ${op}`);\n  }\n}\n\nfunction evaluateWhere(conditions: string[][], row: Row): boolean {\n  return conditions.some(conditionGroup => conditionGroup.some(condition => evaluateCondition(row, condition)));\n}\n\nfunction sortRows(rows: Row[], orderByColumn: string, order: string): Row[] {\n  return rows.sort((a, b) => {\n    const cellA = a[orderByColumn];\n    const cellB = b[orderByColumn];\n    if (typeof cellA === 'number' && typeof cellB === 'number') {\n      return order === 'ASC' ? cellA - cellB : cellB - cellA;\n    } else if (typeof cellA === 'string' && typeof cellB === 'string') {\n      return order === 'ASC' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);\n    }\n    return 0;\n  });\n}\n\nfunction limitRows(rows: Row[], limit: number): Row[] {\n  return rows.slice(0, limit);\n}\n\nfunction projectColumns(rows: Row[], columns: string[]): Row[] {\n  return rows.map(row => {\n    const projectedRow: Record<string, number | string> = {};\n    columns.forEach(col => {\n      if (row[col] !== undefined) {\n        projectedRow[col] = row[col];\n      }\n    });\n    return projectedRow;\n  });\n}\n\nfunction query(sql: string, rows: Row[]): Row[] {\n  const tokens = tokenize(sql);\n  if (tokens.length === 0 || tokens[0] !== 'select' || !tokens.some(token => token.startsWith('from'))) {\n    throw new QueryError('Invalid SQL query');\n  }\n\n  const [selectColumns, fromClause] = parseSelect(tokens);\n  if (selectColumns.length === 0) {\n    throw new QueryError('Invalid column list');\n  }\n\n  const [conditions, rest] = parseWhere(tokens);\n  if (conditions.length > 0) {\n    const filteredRows = rows.filter(row => evaluateWhere(conditions, row));\n    if (filteredRows.length === 0) return [];\n  }\n\n  const [orderByColumn, order, limitClause] = parseOrderBy(tokens);\n  const limit = parseLimit(tokens)[1];\n\n  const sortedRows = filteredRows.sort((a, b) => {\n    const cellA = a[orderByColumn];\n    const cellB = b[orderByColumn];\n    if (typeof cellA === 'number' && typeof cellB === 'number') {\n      return order === 'ASC' ? cellA - cellB : cellB - cellA;\n    } else if (typeof cellA === 'string' && typeof cellB === 'string') {\n      return order === 'ASC' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);\n    }\n    return 0;\n  });\n  const limitedRows = limitRows(sortedRows, limit);\n\n  return projectColumns(limitedRows, selectColumns);\n}\n\n// Example usage\nconst rows: Row[] = [\n  { id: 1, name: \"alice\", age: 30 },\n  { id: 2, name: \"bob\", age: 25 },\n  { id: 3, name: \"carol\", age: 30 },\n  { id: 4, name: \"dave\", age: 17 },\n];\n\nconsole.log(query(\"SELECT name, age FROM users WHERE age >= 18 ORDER BY age DESC LIMIT 2\", rows));\n// -> [ { name: \"alice\", age: 30 }, { name: \"carol\", age: 30 } ]\n```",
    "passed": 0,
    "test_error": ""
  }
]