|
@@ -1599,3 +1599,83 @@ target MIN(start_time) 折平 ⇒ 60 + 60 = 120.00,两行都核完,不报
|
|
|
>
|
|
>
|
|
|
> ⇒ **凡候选集由 repo 层过滤提供的 SP,双跑都缺一层"候选集非空/钱真的动了"的正向断言。**
|
|
> ⇒ **凡候选集由 repo 层过滤提供的 SP,双跑都缺一层"候选集非空/钱真的动了"的正向断言。**
|
|
|
> 这是结构性缺口,不是 RA 两支的局部问题。
|
|
> 这是结构性缺口,不是 RA 两支的局部问题。
|
|
|
|
|
+
|
|
|
|
|
+## §22 只读结果集双跑(RS-1 ~ RS-3)
|
|
|
|
|
+
|
|
|
|
|
+### 为什么另起一套机器
|
|
|
|
|
+
|
|
|
|
|
+写路径那 55 条比的是**写入终态**(before/after 快照 + Δ 断言)。有三支 SP **只读、不写库**:
|
|
|
|
|
+
|
|
|
|
|
+- `sp_user_expense_get_bill`(端点 17 学生账单,一行 9 列)
|
|
|
|
|
+- `sp_user_expense_school_get_refundable_amount`(可退金额,一行一列)
|
|
|
|
|
+- `sp_course_payment_detail_get_teacher_detail`(端点 59 试算的底层,整行 17 列)
|
|
|
|
|
+
|
|
|
|
|
+对它们,Δ 恒为 0 ⇒ 塞进通用比对只会得到「双方都没动 ⇒ 双方一致」的**假绿**。
|
|
|
|
|
+所以判据换成:**同一份数据 + 同一组入参 → 两侧各拿结果集 → 逐列比**。
|
|
|
|
|
+
|
|
|
|
|
+入口独立(`go run ./cmd/dualrun resultsets`),**不进 `DefaultCases`**:两条链不同构,不共用一个分母。
|
|
|
|
|
+
|
|
|
|
|
+### 机器的四条判据(`resultset_compare.go`)
|
|
|
|
|
+
|
|
|
|
|
+1. **列覆盖**:`dump 结果集列数 == 认领列 + 显式豁免列`,且每一列**恰好**一条声明。
|
|
|
|
|
+ 无人认领的列 = 永远不被比对 —— 与「判定块整体不执行」同族,所以它是基建断言、必须 FAIL 而不是跳过。
|
|
|
|
|
+2. **侧向覆盖**:target 返回的每个字段也必须被认领或被显式列为「target 独有」,否则同样是盲区。
|
|
|
|
|
+3. **三态**:`NULL ≠ 0.00`(`SUM` 无行是 NULL;折成 0 会把「没算过」伪装成「算出来是 0」)。
|
|
|
|
|
+4. **第三腿**:每列除了「两侧相等」,还要与**由种子声明算出的期望**比 ——
|
|
|
|
|
+ 只有两侧相等时,两侧同时错会得到一条完全对称的绿。
|
|
|
|
|
+5. **精度口径**:金额按 2 位归一;`PrecisionCheck` 额外断言 dump 侧第 3 位小数为 0
|
|
|
|
|
+ (3→2 的「无损」从假设变事实)。**但每支 SP 的精度形态不同,不能一概而论**:
|
|
|
|
|
+ bill 是 `DECIMAL(10,3)`(3→2 收敛)、refundable 是 `FLOAT`(4 字节单精度)、
|
|
|
|
|
+ teacher_detail 是 `float(10,1)`(1→2 **扩位**)——后两者各写一条自己的断言说明。
|
|
|
|
|
+
|
|
|
|
|
+### 三条 case 的实测覆盖
|
|
|
|
|
+
|
|
|
|
|
+| case | dump 列 | 认领 | 豁免 | 业务断言 | 基建 |
|
|
|
|
|
+| --- | --- | --- | --- | --- | --- |
|
|
|
|
|
+| RS-1 可退金额 | 1 | 1 | 0 | 3 | 4 |
|
|
|
|
|
+| RS-2 账单 | 9 | 9 | 0 | 24 | 4 |
|
|
|
|
|
+| RS-3 课酬明细 | 17 | 6 | 11 | 17 | 4 |
|
|
|
|
|
+
|
|
|
|
|
+- RS-1 用六张工单各钉 SP 的一个 `WHERE` 条件(审批 / 类型 / 校区 / 赠送已用尽 / 一单两行扇出),
|
|
|
|
|
+ 期望 `min(800,1000) − 115 = 685.00` 由声明算出。
|
|
|
|
|
+- RS-2 的 9 列来自**三个不同集合**(`Gift` / 期初结余 / 期末结余),
|
|
|
|
|
+ 所以 `currentUsedGiveCost = Gift + 期初 − 期末` 不是恒等式能凑出来的;
|
|
|
|
|
+ 窗口后一条流水钉「期末不含右端」;账单恒等式两侧各自成立。
|
|
|
|
|
+ `BillSummary` 另 3 个字段(`FlowExclusiveEnd` / `FlowInclusiveEnd` / `CampusIDs`)**不属于 9 列**,
|
|
|
|
|
+ 显式列为 target 独有并对声明断言。
|
|
|
|
|
+- RS-3 是**部分映射**:target 端点 59 只回 9 个派生字段,dump 回整行。
|
|
|
|
|
+ 11 个豁免列逐条带理由,分三类:命中判据(折成 `ruleTier` 下发)/ 身份归属(int vs ULID)/
|
|
|
|
|
+ 展示性文本。target 独有 3 个字段(`schemeCampusId` / `schemeScope` / `ruleTier`)单独对声明断言。
|
|
|
|
|
+
|
|
|
|
|
+### 有牙的证据(不是"这次数据没踩到")
|
|
|
|
|
+
|
|
|
|
|
+- 骨架单测 10 条把每条判据**故意弄红一次**(无人认领列 / 声明错列名 / 豁免无理由 /
|
|
|
|
|
+ target 多字段 / NULL 当 0 / 0 行 / 第 3 位非零)。
|
|
|
|
|
+- 集成侧做过一次变异:把 target 的可退金额减 1 分,实测**转红 3 项**(列差异 + 列断言 + 期望腿),
|
|
|
|
|
+ 还原后复跑 PASS。
|
|
|
|
|
+
|
|
|
|
|
+### 未覆盖 / 无法比对(按 case 列,不许写"无")
|
|
|
|
|
+
|
|
|
|
|
+- **RS-1**:三个早退分支(校区 ≤0 / 全局 ≤0 / 截断后 ≤0)与「截断真正生效」(campus > global)
|
|
|
|
|
+ 未覆盖 —— 本种子 `campus=800 < global=1000`。要覆盖需再加 3~4 条 case。
|
|
|
|
|
+- **RS-1**:`refundableAmount` 是 `FLOAT`(4 字节)。本种子的值都精确可表示,
|
|
|
|
|
+ **对真实数据不成立**(float32 在 0.01 级不可精确表示)。需要产品确认「可退金额是否允许容差」,
|
|
|
|
|
+ 这不是 target 单侧能修的。
|
|
|
|
|
+- **RS-2**:`inCampusids` 是**死参数**(dump 全程未引用)。流水表无 campus 列,
|
|
|
|
|
+ 所以「不过滤」这件事**无法用结果集证伪** —— 只能按契约声明它不过滤。
|
|
|
|
|
+- **RS-2**:`FlowInclusiveEnd` 与 `FlowExclusiveEnd` 在本种子下相等(没有 `t = to` 的流水),
|
|
|
|
|
+ 「含右端 vs 不含右端」的差别**没有被区分**。要区分需再种一条 `create_time = end` 的流水。
|
|
|
|
|
+- **RS-2**:§3.8-A21 的扇出缺陷(同一活动多条 signed 会让 `used` 额重复计入)未覆盖 ——
|
|
|
|
|
+ 种子每个活动只有 1 条 signed。Go **刻意不复刻** A21,所以补这条会得到**预期红**,
|
|
|
|
|
+ 应单独做成「登记差异」case,而不是塞进本 case 求绿。
|
|
|
|
|
+- **RS-2**:`type=108` 双来源「只归因不裁剪」未覆盖。
|
|
|
|
|
+- **RS-3**:`coursePaymentDetailid>1` 的**有意差异**(dump 是「≥2 才短路」⇒ id=1 的规则永不生效;
|
|
|
|
|
+ Go 是 `>0` ⇒ 会生效)未覆盖 —— 本 case 规则 id=990002。要验必须专门造 id=1。
|
|
|
|
|
+- **RS-3**:持证档③、年级/科目通配档②、机构级方案回退(`campus_id=0`)、
|
|
|
|
|
+ 分类不存在 ⇒ 空结果集 ⇒ 3112,**均未覆盖**(本 case 只钉个人精确档①)。
|
|
|
|
|
+- **RS-3**:`t_course_payment_detail.teacher_exp_min/max` 在 target **实际 DDL 是 `decimal(6,2)`**,
|
|
|
|
|
+ 而 `model.CoursePaymentDetail` 的 tag 与 service 注释都写 `decimal(6,3)`
|
|
|
|
|
+ (注释理由是「收敛到 2 位会让 0.999 与 1.000 并列命中」)。这是**实测漂移,未修**,
|
|
|
|
|
+ 需软件侧确认 —— 它直接影响 `BETWEEN` 的命中集合。
|
|
|
|
|
+- **三支共同**:只覆盖「1 个结果集 / 1 行」形态;多结果集、多行未覆盖
|
|
|
|
|
+ (这三支 SP 结构上只返回 1 行,所以这是结构性上限,不是遗漏)。
|