|
|
8 цаг өмнө | |
|---|---|---|
| .. | ||
| README.md | 8 цаг өмнө | |
| main.go | 10 цаг өмнө | |
| main_test.go | 10 цаг өмнө | |
cmd/spdump —— 读旧库定义的常备工具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 > 表名/函数名。
| # | 坑 | 错在哪 | 怎么防 |
|---|---|---|---|
| 1 | DECLARE x DECIMAL 不带精度就是 DECIMAL(10,0) |
sp_user_expense_custom_refund 的 _balance / _money / _badDate 全是整数,赋值即四舍五入,而且收敛两次(_balance 一次、_badDate 再算一次)。照"变量就是数字"的直觉写期望,会整整差一元。 |
逐个变量看有没有精度参数;把每一层收敛写成一条等式(见 c2_refund.go 的 round0Cents)。 |
| 2 | 连续两个 IF 不等于 if/else |
IF(inputMoney>_balance) THEN SET _badDate=…; SET _money=…; END IF; 之后另起 IF(_badDate>0) THEN … ELSE 扣 inputMoney END IF;。差额不足 0.5 时 _badDate 收敛成 0 ⇒ 进了第一个 IF 却走 ELSE,扣的是 inputMoney 而不是 _money。按 if/else 读会期望扣 1.00 而实测 1.30。 |
任何 SP 出现连续两个 IF,都要单独确认它们是否互斥;不互斥就分别判断,别合成一个二分支。锁在 TestLegacyRefundExpectBadDateRoundsToZero。 |
| 3 | SELECT … INTO 无行可赋时不改变量 |
变量停在 DEFAULT 0,于是"账户行不存在"与"余额真的是 0"在 SP 里不可区分 —— custom_refund 会因此静默 success=0,调用方看到的是"成功"。 |
断言"账户行是否存在"要单独读,不能从变量值反推。 |
| 4 | OR 的短路能把 NULL 变成"走另一条分支" |
IF(ISNULL(_balance) OR _balance<=0):NULL 时左边就成立、右边根本不求值 ⇒ 结果是"拒绝",不是"NULL 参与比较"。 |
遇到 ISNULL(x) OR y 一定要想"x 为 NULL 时 y 会不会被求值"。 |
| 5 | 同名逻辑出现在多处,执行位置可能不同 | tmp_user_activity 那段在 cleared_balance 里跑在写流水之前、在 custom_refund 里跑在写流水之后。SQL 逐字相同,事务内顺序不同 ⇒ 失败终态组合不同。 |
同一段 SQL 出现在 N 处就测 N 次;并且各自记下"它在流水之前还是之后"(见 C2-10)。 |
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)。