中文问库 AI 出 DELETE?Lab-2 只读 NL2SQL + 代码层拦截。
她试过把 schema 贴进 Claude Code 让 AI 写 SQL,结果一次生成了带 DELETE 的语句,还好她先 EXPLAIN 没真跑。
Lab-2 要做的是:中文问题 → 生成 SELECT → 在本地 SQLite 样例库只读执行 → 表格输出 。不写库、不连线上。这篇是 v0.1:单库、单轮问答、人眼确认 SQL 再执行。
链路总览

| 步骤 | 谁做 | 说明 |
|---|---|---|
| 1. 加载 schema | 程序 / @ 文件 | schema/orders.sql 或 samples/ 脱敏 DDL |
| 2. 生成 SQL | Claude Code | Prompt 约束 仅 SELECT |
| 3. 人审 | 必须 | 看有没有 JOIN 爆炸、全表扫 |
| 4. 执行 | 程序 | 只读连接,超时、行数上限 |
| 5. 展示 | 终端表格 | 或 CSV |
铁律: 生成 SQL 默认 不自动执行;v0.1 可以「打印 SQL + 问 y/n」,v0.2 再接 Command 自动化(第 13 篇)。
v0.1 范围
| 项 | 内容 |
|---|---|
| 数据库 | 单个 SQLite 文件 samples/demo.db(培训脱敏数据) |
| 输入 | 中文一句,如「有多少订单状态是 cancelled」 |
| 输出 | SQL 文本 + 查询结果(≤100 行) |
| 禁止 | INSERT/UPDATE/DELETE/DDL;多库;连线上 |
schema 怎么喂给 Claude Code
不要把整个线上 dump 贴进去。Lab 用 精简 DDL + 列注释:
sql
-- samples/schema/orders.sql
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
status TEXT NOT NULL, -- pending|paid|cancelled|shipped
amount_cents INTEGER NOT NULL,
created_at TEXT NOT NULL
);
Prompt:
text
@samples/schema/orders.sql @CLAUDE.md
用户问题:有多少订单 status 是 cancelled?
只输出一条 SQLite SELECT,不要 markdown 代码块包裹。
禁止 UPDATE/DELETE/INSERT/DDL。
表名与列名必须与 schema 完全一致,禁止猜。
执行层:Python 只读守卫
python
FORBIDDEN = ("insert", "update", "delete", "drop", "alter", "create", "attach")
def assert_readonly(sql: str) -> None:
head = sql.strip().split()[0].lower()
if head != "select" and head != "with":
raise ValueError("only SELECT allowed")
low = sql.lower()
for kw in FORBIDDEN:
if kw in low:
raise ValueError(f"forbidden keyword: {kw}")
def run_query(conn, sql: str, max_rows: int = 100):
assert_readonly(sql)
cur = conn.execute(sql)
rows = cur.fetchmany(max_rows + 1)
if len(rows) > max_rows:
raise ValueError(f"result exceeds {max_rows} rows")
return rows
为什么不用「Prompt 说只读就够了」: 模型会漏;代码层拦截是 DBA 的底线。
CLI v0.1 用法
bash
python -m nl2sql ask "有多少 cancelled 订单"
实现可以拆两阶段对话:
- Claude Code 只 生成 SQL (人复制到文件
last.sql检查) - 本地脚本
nl2sql run --file last.sql执行
培训初期 强制人工 y/n,习惯养成后再做 Command。
踩坑实录
坑 1:AI 编造 order_status 列
schema 里是 status。
修正: Prompt 写「必须与 @schema 一致」;执行前用 EXPLAIN QUERY PLAN 或简单 PRAGMA table_info。
坑 2:COUNT 变 DELETE(刘姐那次)
修正: assert_readonly + 团队 Prompt 模板 prompts/nl2sql-select-only.md 写入 CLAUDE.md 索引。
坑 3:一次 Agent 写「生成+执行+Web 界面」
修正: v0.1 scope 写死「终端 + 单库」;Web 进非目标。
四阶段怎么套 Lab-2
| 阶段 | 产出 |
|---|---|
| 澄清 | notes/nl2sql-v0.1-scope.md(样例库、行数上限、确认流程) |
| 方案 | 模块划分:schema_loader, generator(调 Claude), runner |
| 实现 | 先 runner + 假 SQL;再接 Claude 生成 |
| 验证 | 10 个固定中文问题 + 期望 SQL 或期望结果快照 |
小结
- Lab-2 v0.1 = 中文 → SELECT → 人审 → 只读执行。
- schema @ 引用 + 代码层禁写 双保险。
- 结果行数上限、禁止多语句(
;分割检测)都要写进 runner。 - 下篇加 Command、多库与测试自动化。
下一篇
第 12 篇《Command、Agent、Hook 各干什么》 ------ 固定流程用 Command,长任务用 Agent,提交前用 Hook。
第 13 篇《Lab-2(下):多库支持与自动化》 ------ 多 SQLite、schema 切换、/ask-db Command。