从 #3602 的第 3 项(让 ObjectQLStrategy.generateSql 预览与实际执行相符)里发现的相邻缺口。为了让预览"忠实",我需要确认 execute() 到底施加了哪些谓词 —— 结果发现它根本不读 dateRange。
现象
ObjectQLStrategy.execute() 只把 query.where 送进 engine:
// objectql-strategy.ts — timeDimensions 只被读了 granularity
for (const td of query.timeDimensions ?? []) {
if (td.granularity) granByDim.set(td.dimension, td.granularity); // ← 只有 granularity
}
...
const filter = {}; // ← 只来自 normalizeAnalyticsFilters(query)
而 normalizeAnalyticsFilters 只读 query.where(filter-normalizer.ts 的 normalizeAnalyticsFilters),dateRange 从来不在 where 里 —— dataset-executor.ts 的 buildQuery 把 selection.timeDimensions 原样透传成 q.timeDimensions,q.where 是独立的另一支。
对比另外两条路,它们都老老实实处理了:
NativeSQLStrategy:td.dateRange → col BETWEEN $n AND $n+1(还顺带做了 SQLite epoch 的 storage 强转)。
preview-evaluator.ts:// 1. Row-level filters: where, then timeDimension dateRanges.
只有 ObjectQL 这条路把它丢了。
为什么这个特别值得修
不是"少数驱动才会踩"的边角:带 dateGranularity 的查询会强制走到这条路上。NativeSQLStrategy.canHandle 对任何携带 granularity 的查询直接 decline,于是即便在 Postgres/SQLite 这种完全支持原生 SQL 的部署上,一个按天/周/月分桶的趋势图也会落到 ObjectQLStrategy。
而"按时间分桶的趋势图"恰恰是最可能同时带 dateRange 的查询形态(近 12 个月、本季度……)。所以实际后果是:
一个限定了时间范围的趋势图,在任意驱动上都会画出全量历史,而不是所选区间。
没有任何报错,图表看上去正常,只是数字不对 —— 静默的错误结果比抛异常更难被发现。
不是越权
dateRange 是展示性过滤,不是安全边界;read scope 与 ExecutionContext 两条带子(#3601 / #3602)都不受影响。这是正确性问题,不是泄露。
落点
objectql-strategy.ts 的 execute(),把 timeDimensions[].dateRange 编进送往 engine.aggregate 的 filter,大致对应 NativeSQLStrategy 那段:
for (const td of query.timeDimensions ?? []) {
if (!td.dateRange) continue;
const range = Array.isArray(td.dateRange) ? td.dateRange : [td.dateRange, td.dateRange];
// → filter[field] = { $gte: range[0], $lte: range[1] } (与既有同字段合并逻辑一致,勿覆盖)
}
两处要当心:
- 同字段多操作符的合并。
execute() 里已有的 merge 逻辑({...existing, ...converted})是为 {$gte,$lte} range 准备的 —— dateRange 必须走同一条合并路径,否则会和用户自己写的 where 里同字段条件互相覆盖。
- 值的存储形态。NativeSQL 走
coerceTemporal 是因为它绕过了驱动的 CRUD 强转;ObjectQL 经 engine.aggregate 应当由驱动自己处理,但需要验证 —— SQLite 的 Field.datetime 存 epoch 整数,ISO 字符串比较是 affinity 陷阱(参见 native-sql-strategy 里那段注释描述的 "No rows" bug)。
修完后 #3602 第 3 项里 generateSql 那段"故意不渲染 BETWEEN"的注释需要同步更新 —— 届时预览应当把 dateRange 一起渲染出来。
从 #3602 的第 3 项(让
ObjectQLStrategy.generateSql预览与实际执行相符)里发现的相邻缺口。为了让预览"忠实",我需要确认execute()到底施加了哪些谓词 —— 结果发现它根本不读dateRange。现象
ObjectQLStrategy.execute()只把query.where送进 engine:而
normalizeAnalyticsFilters只读query.where(filter-normalizer.ts的normalizeAnalyticsFilters),dateRange从来不在where里 ——dataset-executor.ts的buildQuery把selection.timeDimensions原样透传成q.timeDimensions,q.where是独立的另一支。对比另外两条路,它们都老老实实处理了:
NativeSQLStrategy:td.dateRange→col BETWEEN $n AND $n+1(还顺带做了 SQLite epoch 的 storage 强转)。preview-evaluator.ts:// 1. Row-level filters: where, then timeDimension dateRanges.只有 ObjectQL 这条路把它丢了。
为什么这个特别值得修
不是"少数驱动才会踩"的边角:带
dateGranularity的查询会强制走到这条路上。NativeSQLStrategy.canHandle对任何携带 granularity 的查询直接 decline,于是即便在 Postgres/SQLite 这种完全支持原生 SQL 的部署上,一个按天/周/月分桶的趋势图也会落到 ObjectQLStrategy。而"按时间分桶的趋势图"恰恰是最可能同时带
dateRange的查询形态(近 12 个月、本季度……)。所以实际后果是:没有任何报错,图表看上去正常,只是数字不对 —— 静默的错误结果比抛异常更难被发现。
不是越权
dateRange是展示性过滤,不是安全边界;read scope 与ExecutionContext两条带子(#3601 / #3602)都不受影响。这是正确性问题,不是泄露。落点
objectql-strategy.ts的execute(),把timeDimensions[].dateRange编进送往engine.aggregate的filter,大致对应NativeSQLStrategy那段:两处要当心:
execute()里已有的 merge 逻辑({...existing, ...converted})是为{$gte,$lte}range 准备的 —— dateRange 必须走同一条合并路径,否则会和用户自己写的where里同字段条件互相覆盖。coerceTemporal是因为它绕过了驱动的 CRUD 强转;ObjectQL 经engine.aggregate应当由驱动自己处理,但需要验证 —— SQLite 的Field.datetime存 epoch 整数,ISO 字符串比较是 affinity 陷阱(参见 native-sql-strategy 里那段注释描述的 "No rows" bug)。修完后 #3602 第 3 项里
generateSql那段"故意不渲染 BETWEEN"的注释需要同步更新 —— 届时预览应当把 dateRange 一起渲染出来。