/** * EXPLAIN-validates every export dataset against the real database. * * CLAUDE.md hard rule: raw SQL must be validated against a real DB before it * ships. Every dataset is hand-written SQL expressions over wide tables where * column drift is documented history, so a typo is a runtime 500 no type-check * can catch. This builds each dataset's WIDEST query (all fields selected, so * every join and every subquery is exercised) plus its count query, and runs * both through EXPLAIN. * * npx ts-node -r tsconfig-paths/register src/scripts/validate-export-datasets.ts */ import 'dotenv/config'; import AppDataSource from '../data-source'; import { buildExportCountQuery, buildExportQuery } from '../modules/exports/export-query.builder'; import { DATASETS } from '../modules/exports/export.registry'; async function main(): Promise { await AppDataSource.initialize(); let failed = 0; for (const dataset of DATASETS) { const ctx = { ds: AppDataSource, params: {}, directions: null }; const cases: [string, () => { sql: string; params: unknown[] }][] = [ [ `${dataset.key} (all ${dataset.fields.length} fields)`, () => { const qb = buildExportQuery(dataset, dataset.fields, ctx); return { sql: qb.getQuery(), params: qb.getParameters() as unknown as unknown[] }; }, ], [ `${dataset.key} (count)`, () => { const qb = buildExportCountQuery(dataset, ctx); return { sql: qb.getQuery(), params: qb.getParameters() as unknown as unknown[] }; }, ], ]; // Each field ALONE. The all-fields query above cannot catch a field that // references an alias it forgot to declare in `requires` — some other // field's `requires` pulls that join in, so it only 42P01s when that one // checkbox is ticked on its own. This is the check that finds it. for (const field of dataset.fields) { cases.push([ `${dataset.key}.${field.key}`, () => { const qb = buildExportQuery(dataset, [field], ctx); return { sql: qb.getQuery(), params: qb.getParameters() as unknown as unknown[] }; }, ]); } let fieldFailures = 0; for (const [label, build] of cases) { const isPerField = label.startsWith(`${dataset.key}.`); try { const { sql } = build(); // Parameters are all optional filters and unset here, so the generated // SQL carries no placeholders — EXPLAIN it directly. await AppDataSource.query(`EXPLAIN ${sql}`); if (!isPerField) console.log(` ok ${label}`); } catch (error) { failed += 1; if (isPerField) fieldFailures += 1; console.error(` FAIL ${label}`); console.error(` ${(error as Error).message.split('\n')[0]}`); } } if (!fieldFailures) { console.log(` ok ${dataset.key} (each of ${dataset.fields.length} fields alone)`); } } await AppDataSource.destroy(); console.log(failed ? `\n${failed} query/queries failed.` : '\nAll export dataset SQL validated.'); process.exit(failed ? 1 : 0); } void main();