Files
OJ2/apps/web
yuetsh e3faa689e7 feat(阶段2补课): SQL 判题链路 + 最后两个后台端点
新后端此前完全没有 SQL 判题(旧 judge/sql_runner.py 378 行 + sql_dispatcher.py
113 行无对应实现),阶段 2 纵切时漏了这条与沙箱完全不同的路径。

  judge/sql/engine.ts   判题核心,移植自 sql_runner.py,判定口径逐条对齐
  judge/sql/child.ts    子进程入口
  judge/sql/index.ts    父进程:spawn + 硬超时
  judge/run.ts          language === "SQL" 时分流,不经判题沙箱
  POST admin/sql-test-cases/preview    题目页展示数据预览
  POST admin/sql-test-cases/generate   AI 按标准答案倒推初始化脚本

题目保存时重新生成 sqlDisplay(对齐旧 generate_sql_display):取测试点 1 的初始化
脚本 + 标准答案跑一遍,失败一律拦下不让保存 —— 展示数据直接决定学生看到的表结构
与期望结果,宁可不保存也不能存错的。

## 防护换了实现,逐条实测

bun:sqlite 没有 authorizer / progress_handler / setlimit,且实测 Worker.terminate()
杀不掉跑飞的查询(原生代码占着线程)。改用「WASM 引擎 + 独立子进程」:

  ATTACH  → WASM 无宿主文件系统绑定,结构上够不到(比旧的 authorizer 更强)
  查询题只读 → PRAGMA query_only=1
  超时    → 子进程外部 SIGKILL
  单值内存 → 子进程 ulimit -d

八条提交实测:正确→Accepted;列少一个/漏过滤→Wrong Answer;语法错误→Compile Error;
查询题里 INSERT→运行错误并说明;递归 CTE 死循环→CPU 超时;hex(zeroblob(2e8))→内存超限;
attach '/etc/passwd'→打不开。

## 踩到的两个坑(已写进 docs/specs/phase3-coverage.md)

1. ulimit 必须用 -d 不能用 -v。-v 限虚拟地址空间而 JS 引擎预留巨量地址,实测 -v 之下
   Bun 退出时有概率 panic(SIGILL),结果早已写出但进程异常终止,父进程读到空串
   误判成超时 —— 6 次里坏 2 次。换 -d 后 12/12 稳定。
2. 子进程写完结果直接 SIGKILL 自己,不走 process.exit()——后者仍有清理会撞限额。

至此 admin/api.ts 已无任何指向旧后端的调用。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:03:41 -06:00
..