出自 #5298 「第一棒:成本清单」的消费点清点(纯读调查),范围外发现,按 Prime Directive #10 记录,不指派。
事实(origin/main 5c94f833c,静态对读)
packages/objectql/src/having-filter.ts 用自己的一套 arm 求值 HAVING 子句,算子词表与 FilterCondition 同一套(having-filter.ts:35-37 列了 $eq $ne $gt $gte $lt $lte $between $in $nin $exists $null $contains $notContains $startsWith $endsWith $regex)。它是 driver-sql / driver-memory 的 mingo 面 / driver-memory 的参考匹配器 / formula.matchesFilterCondition 之外的第五个求值面 ,#5146 / #5299 / #5298 的清点里都没有出现过。
两处与 #5299 记录的 driver-memory 分叉形状完全相同 :
having-filter.ts:112
if (value === undefined && op !== '$exists' && op !== '$ne' && op !== '$null') return false;
—— 豁免名单里没有 $nin(也没有 $notContains),与 driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 正文点名的 memory-matcher.ts 早退守卫逐字同构。
having-filter.ts:137
case '$notContains': if (typeof value !== 'string' || value.includes(target)) return false;
—— 值不是字符串(null / undefined)一律判否,与 driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 记录的 driver-memory 读法一致、与 formula 相反(formula/src/matches-filter.ts:183 判是)。
另外 having-filter.ts:126 的 $nin 本体 if (Array.isArray(target) && target.includes(value)) return false; 对 value === undefined 本来是「通过」的,是上面那条早退守卫先把它拦成了「不通过」—— 与 #5299 描述的机制一模一样。
为什么按 finding 记(不代 triage 定级)
HAVING 求值的输入是聚合输出行 ,measure 键通常总是存在,所以 value === undefined 这一格今天不太容易被真实用户撞到;$notContains 对 null 值那一格则取决于聚合是否产出 null measure。没有量化真实 dashboard 里出现这两种形状的频率。按「观察类」记,不因为「看着小」就压着不报 —— 定级请按 triage,不代表我判断它低。
与既有账的关系
关联
#5299 (driver-memory vs formula 的同款三格)、#5298 (非否定路径 NULL-safe 裁决)、#5146 / PR #5296 (极性表)、#5239 (FILTER_LOGIC_CASES —— 今天不覆盖 HAVING 路径)。
出自 #5298「第一棒:成本清单」的消费点清点(纯读调查),范围外发现,按 Prime Directive #10 记录,不指派。
事实(origin/main
5c94f833c,静态对读)packages/objectql/src/having-filter.ts用自己的一套 arm 求值 HAVING 子句,算子词表与FilterCondition同一套(having-filter.ts:35-37列了$eq $ne $gt $gte $lt $lte $between $in $nin $exists $null $contains $notContains $startsWith $endsWith $regex)。它是driver-sql/driver-memory的 mingo 面 /driver-memory的参考匹配器 /formula.matchesFilterCondition之外的第五个求值面,#5146 / #5299 / #5298 的清点里都没有出现过。两处与 #5299 记录的
driver-memory分叉形状完全相同:having-filter.ts:112if (value === undefined && op !== '$exists' && op !== '$ne' && op !== '$null') return false;—— 豁免名单里没有
$nin(也没有$notContains),与 driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 正文点名的memory-matcher.ts早退守卫逐字同构。having-filter.ts:137case '$notContains': if (typeof value !== 'string' || value.includes(target)) return false;—— 值不是字符串(null / undefined)一律判否,与 driver-memory 与 formula 对「字段没有值」给出三处不同答案:
$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 记录的driver-memory读法一致、与formula相反(formula/src/matches-filter.ts:183判是)。另外
having-filter.ts:126的$nin本体if (Array.isArray(target) && target.includes(value)) return false;对value === undefined本来是「通过」的,是上面那条早退守卫先把它拦成了「不通过」—— 与 #5299 描述的机制一模一样。为什么按
finding记(不代 triage 定级)HAVING 求值的输入是聚合输出行,measure 键通常总是存在,所以
value === undefined这一格今天不太容易被真实用户撞到;$notContains对 null 值那一格则取决于聚合是否产出 null measure。没有量化真实 dashboard 里出现这两种形状的频率。按「观察类」记,不因为「看着小」就压着不报 —— 定级请按 triage,不代表我判断它低。与既有账的关系
$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 裁的是「字段没有值」三格的 canonical 读法,正文只清点了driver-memory与formula。若那次裁决落地时只改这两家,本文件会成为新的少数派,并且是唯一没被任何 conformance 表覆盖的一面(FILTER_LOGIC_CASES不驱动 HAVING 路径)。$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298 的裁决(非否定路径 NULL-safe)方向上要求$nin/$notContains对无值行成立,与本文件现行答案相反。$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 / 非否定路径上的$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298 落地时把这一面一并对齐,或明确裁定「HAVING 面按聚合语义另算」并把理由写在文件头 —— 两条都行,别默认它不存在。关联
#5299(driver-memory vs formula 的同款三格)、#5298(非否定路径 NULL-safe 裁决)、#5146 / PR #5296(极性表)、#5239(
FILTER_LOGIC_CASES—— 今天不覆盖 HAVING 路径)。