从 #4081 的温度一致性矩阵补第六个消费者(NativeSQLStrategy)时发现的一类缺陷,$between 那一格已在该 PR 内修掉,但成因还在 ,这里单独立项。
成因
packages/services/service-analytics/src/strategies/filter-normalizer.ts 把 Mongo 风格的 where 拍平成内部管线形式,靠一张 MONGO_TO_CUBE_OP 映射表:
const cubeOp = MONGO_TO_CUBE_OP [ opKey ] ;
if ( ! cubeOp ) continue ; // ← 未映射 = 谓词凭空消失
continue 掉一个谓词不是「不支持」,是把查询放宽 。少一个 WHERE 条件的 SQL 依然合法,只是多返回行——所以它对断言 SQL 字符串的测试完全隐形,对用户表现为图表画出全部历史(#3650 的症状)。
normalizeAnalyticsFilters 同时被 NativeSQLStrategy 和 ObjectQLStrategy 使用,两条路径都受影响。
现状盘点
spec 的 FilterCondition 词汇(filter.zod.ts)对照映射表:
算子
状态
$eq $ne $gt $gte $lt $lte $in $nin $contains $notContains $exists
✅ 已映射
$between
✅ 已修(#4081 follow-up:下降为 gte+lte,畸形输入抛错)
$startsWith
❌ 静默丢弃
$endsWith
❌ 静默丢弃
$null
❌ 静默丢弃
$regex
❌ 静默丢弃
$and
✅ 递归拍平
$or / $not
⚠️ 有注释的刻意跳过——但同样是放宽,见下
四个 ❌ 都是作者可以合法写进 dashboard widget / dataset 过滤器的 spec 词汇($null 尤其常见:「未指派」「无关闭日期」这类看板)。今天它们不会报错、不会告警,只会多给行。
建议的方向
两步,第二步需要决策:
实现能实现的。 $startsWith / $endsWith / $regex 都能落到已有的 contains(LIKE)家族上;$null 落到已有的 set / notSet(IS [NOT] NULL,两条策略都已支持)。这四个都不需要新概念,只是映射表缺项。
把兜底从 continue 改成 throw。 这是 A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 在 driver-memory 上已经做过的同一个决定(convertConditionToMongo 从 return null 改为抛错,注释原文:「Returning no predicate would silently match every record」),也是 Prime Directive chore: version packages #10 「declared ≠ enforced 要么实现要么报错,别假装覆盖」的直接应用。爆炸半径需要评估:改完之后,任何今天在用未映射算子的看板会从「悄悄画错」变成「明确报错」——方向正确,但属于行为变更,值得单独确认。
$or / $not 是第三档:注释写明「策略尚不支持递归 WHERE 构建,忽略以便部分查询仍能运行」。这条理由在 compileScopedFilterToSql(read-scope 已能编译完整的 $or 树,见 #3774 的一致性表)存在之后是否还成立,值得复查——如果能复用它,$or 就不必再被丢。注意 RLS read scope 走的是另一条路径,不受影响;受影响的是作者自己写的 where。
复现
矩阵消费者本身就是复现器(packages/services/service-analytics/src/__tests__/native-sql-temporal-conformance.test.ts,真实 sql.js 引擎 + 断言行 id)。把 $between 换成上表任一 ❌ 算子,返回的就是全表。
关联
#4081 (矩阵)、#4098 (矩阵落地 + preview 侧同species 的 $between 缺陷)、#3650 (窗口被丢弃画出全部历史)、#3948 (driver-memory 同一决定的先例);ADR-0053 D-A3.1。
从 #4081 的温度一致性矩阵补第六个消费者(
NativeSQLStrategy)时发现的一类缺陷,$between那一格已在该 PR 内修掉,但成因还在,这里单独立项。成因
packages/services/service-analytics/src/strategies/filter-normalizer.ts把 Mongo 风格的where拍平成内部管线形式,靠一张MONGO_TO_CUBE_OP映射表:continue掉一个谓词不是「不支持」,是把查询放宽。少一个 WHERE 条件的 SQL 依然合法,只是多返回行——所以它对断言 SQL 字符串的测试完全隐形,对用户表现为图表画出全部历史(#3650 的症状)。normalizeAnalyticsFilters同时被NativeSQLStrategy和ObjectQLStrategy使用,两条路径都受影响。现状盘点
spec 的
FilterCondition词汇(filter.zod.ts)对照映射表:$eq$ne$gt$gte$lt$lte$in$nin$contains$notContains$exists$betweengte+lte,畸形输入抛错)$startsWith$endsWith$null$regex$and$or/$not四个 ❌ 都是作者可以合法写进 dashboard widget / dataset 过滤器的 spec 词汇(
$null尤其常见:「未指派」「无关闭日期」这类看板)。今天它们不会报错、不会告警,只会多给行。建议的方向
两步,第二步需要决策:
实现能实现的。
$startsWith/$endsWith/$regex都能落到已有的contains(LIKE)家族上;$null落到已有的set/notSet(IS [NOT] NULL,两条策略都已支持)。这四个都不需要新概念,只是映射表缺项。把兜底从
continue改成throw。 这是 A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 在 driver-memory 上已经做过的同一个决定(convertConditionToMongo从return null改为抛错,注释原文:「Returning no predicate would silently match every record」),也是 Prime Directive chore: version packages #10「declared ≠ enforced 要么实现要么报错,别假装覆盖」的直接应用。爆炸半径需要评估:改完之后,任何今天在用未映射算子的看板会从「悄悄画错」变成「明确报错」——方向正确,但属于行为变更,值得单独确认。$or/$not是第三档:注释写明「策略尚不支持递归 WHERE 构建,忽略以便部分查询仍能运行」。这条理由在compileScopedFilterToSql(read-scope 已能编译完整的$or树,见 #3774 的一致性表)存在之后是否还成立,值得复查——如果能复用它,$or就不必再被丢。注意 RLS read scope 走的是另一条路径,不受影响;受影响的是作者自己写的where。复现
矩阵消费者本身就是复现器(
packages/services/service-analytics/src/__tests__/native-sql-temporal-conformance.test.ts,真实 sql.js 引擎 + 断言行 id)。把$between换成上表任一 ❌ 算子,返回的就是全表。关联
#4081(矩阵)、#4098(矩阵落地 + preview 侧同species 的
$between缺陷)、#3650(窗口被丢弃画出全部历史)、#3948(driver-memory 同一决定的先例);ADR-0053 D-A3.1。