# `cmd/spdump` —— 读旧库定义的常备工具 ```bash go run ./cmd/spdump proc sp_user_expense_baddebt # SHOW CREATE PROCEDURE go run ./cmd/spdump routines -like "%expense%" # 有哪些 routine(先看清单) go run ./cmd/spdump view yuansiwei.t_school_campus # 跨库视图怎么定义的 go run ./cmd/spdump tables -db finance_bk -like "%activity%" go run ./cmd/spdump columns t_campus -db yuansiwei_2026 # 列类型/默认值/注释 go run ./cmd/spdump sql -db legacy_oracle "select max(id) from t_courses_activity" ``` `-db` 默认 `legacy_oracle`;flags 与位置参数**任意顺序**都行。 ## 为什么它必须存在(不是"方便一下") AC-15 阶段 2 用三次实测证明:**「照抄 SP」是不可行的策略** —— 不是因为它难, 是因为旧库里混着**错注释**和**坏 SQL**,照抄会把缺陷一起搬过来。 | # | 事实 | 如果照抄 / 按名字猜会怎样 | |---|---|---| | 1 | `sp_user_expense_baddebt` 的 `COMMENT` 写着 **`用户余额充值`**(从 recharge 复制忘改) | 按注释判断会把**坏账**当成充值 | | 2 | `sp_user_expense_school_spend` 的 INSERT 分支是 `INSERT INTO t_user_expense(userid,spend) VALUES(inputUserid, inputMoney, inputSpendMoney)` —— **2 列配 3 值**,且 `inputMoney` 在该 SP 里根本不存在 | 照抄会把这句坏 SQL 搬进 Go;它只在"用户没有账户行"时触发,线上从不走到,所以测也测不出来 | | 3 | `sp_user_expense_school_transfer_expenditure` **没有默认备注**(SP1/2/3/4 都有 `IF inputRemark='' THEN SET …`) | 用空备注跑双跑,"备注逐字一致"是**空对空**,等于没测 | | 4 | `sp_user_expense.school_cleared_balance` 名字叫「余额清零」,实际清的是**校区**余额,全局只减去同一个 `campusBalance` | 会断言"清零后全局余额 = 0",而它一般不是 0 | ## 行为从哪里读:证据强度排序 | 依据 | 可靠度 | 反例 | |---|---|---| | **SQL 语句**(UPDATE / INSERT / IF 守卫) | 最高 | 无(这是唯一的事实来源) | | **参数签名** | 高 | 无 —— 签名能反推"不可能发生的事":SP11 只有 4 个参数、连 `requestId` 都没有 ⇒ 它**不可能**幂等、**不可能**写流水 | | 自己的 `COMMENT` | **会骗人** | `baddebt` 的注释写着"用户余额充值" | | 表名 / 函数名 | **会骗人** | `t_user_expense` 字面像"全局表",实际两个充值 SP 都写双表(这一条当时把 team-lead 也带偏了) | ⇒ **证据强度:SQL 语句 > 参数签名 > COMMENT > 表名/函数名。** ## 安全约束(硬性,不是约定) - 只连 `fixture.allowedDatabases` 白名单里的**本机**库(`OpenReadOnlyDB` 里守); - `sql` 子命令只接受 `SELECT / SHOW / DESC / EXPLAIN / WITH` 开头的**单条**语句(分号后还有内容就拒绝); - 连接建立后立刻 `SET SESSION TRANSACTION READ ONLY`,服务端也会拒绝本会话的写; - 口令只经 `AC15_*` 环境变量或仓库既有 `config/dev.json` 读取,**不打印、不落盘**。 ## 一个自己踩过的坑(写在这里免得下一个人再踩) `spdump columns t_campus -db yuansiwei_2026` —— 这种"先对象、后库名"的写法, 如果直接用 Go 的 `flag` 包解析,`-db` 会被**静默丢弃**(`flag` 遇到第一个非 flag 参数就停止解析), 于是连到默认库、查不到列、打印 `(0 行)`。 **`(0 行)` 看起来像"这表没有列",是个错误答案,而不是一个错误。** 这类失败比报错危险。 所以 `main.go` 里先做 `splitFlagsAndArgs` 重排,并有单测锁住(`TestSplitFlagsAndArgs`)。