Skip to content

SQL Pipeline

SQL Practice runs on a decision that shapes everything else: queries execute entirely in the browser via sql.js (SQLite compiled to WASM). No server-side SQL, no sandboxing arbitrary queries against a real database, no round-trip for something that should feel instant.

The question format's special power: <!--SETUP-->

Each SQL question carries its own schema and seed data in a <!--SETUP--> block — plain SQL executed into a fresh in-memory SQLite instance when the question opens. Every question is therefore a self-contained database: authors design exactly the tables/rows a problem needs, and no two questions can interfere. The rest of the format matches the bank conventions (frontmatter, <!--STATEMENT-->, <!--SOLUTION-->, expected result).

Authoring: tools/sql_pipeline.py

Same shape as the CS/aptitude siblings: drafts in drafts/sql-practice/, validation (including that SETUP actually parses and the reference solution produces the expected result set against it), then move into the website's src/content/sql-practice/ behind the shared lint gate. 24 questions live. tools/sql_eval/ holds the offline checker.

Grading: deterministic first, AI second

Mirrors the aptitude split:

  1. Correctness — client-side: run the user's query and the reference in the same sql.js instance, compare result sets (sqlGrading.js). Order-insensitive unless the question demands ORDER BY; column-order aware. No model involved.
  2. Reviewfunctions/grade-sql-practice.js layers AI feedback on quality (approach, unnecessary subqueries, missed indexes-in-spirit) on top of the known verdict, via the shared provider chain.

Note the contrast with CS-fundamentals concept questions about SQL: those are judged as text by grade-concept and never executed — the executed path exists only here, where sql.js provides a safe sandbox.

Shipping WASM: the three sequential build failures

Integrating sql.js broke the build three times, each one layer deeper — kept here because it's a reusable lesson in shipping WASM through Vite:

#FailureRoot cause → fix
1Cannot find module 'sql.js'dependency never added (dev had it hoisted from elsewhere) → add to package.json
2runtime 404 on sql-wasm.wasmthe .wasm binary isn't part of the JS module graph; bundler ships the loader, not the binary → tools/copy-sql-wasm.mjs copies it into public/ during build, locateFile points at it
3wrong file resolved in the browserpackage exports browser condition resolves a different filename than the Node path the copy script assumed → copy script matches the browser-condition filename exactly

The general rule extracted: WASM assets ride outside the bundle — treat them like fonts/images (explicit copy + explicit URL), not like imports.

Why not Judge0 with a SQL runtime?

Considered and rejected: it would round-trip every keystroke-run to the shared free-tier VM, add queueing latency to a feature whose whole appeal is immediacy, and require sandboxing discipline sql.js gives for free (the database is the user's own tab). The tradeoff — grading is result-set comparison rather than a full judge verdict — is the right scope for a practice tool, as noted in Code Execution.

Ediky Workflow — internal engineering documentation.