AggregateValuesRule 处理的是 Aggregate 压在 Values 之上的极端小输入场景,尤其是空 Values 和"纯去重 aggregate"这两类情况。它不会试图通用执行所有聚合,而是专注做一些完全可以在优化阶段直接折叠的改写。本文结合源码实现,分析这条规则如何在空输入上直接构造聚合结果,以及它是怎样把 Aggregate(Values) 规约成去重后的 Values 的。
1. 规则要解决什么问题
有些查询在优化阶段就已经变成:
text
Aggregate
Values(...)
例如:
WHERE 1 = 0之后的空输入;IN (...)常量列表展开后的 values;- 仅用 aggregate 去重的极小关系。
这时没必要等到执行期再跑真正的聚合算子。
2. 第一类场景:空 Values + 全局聚合
源码首先处理:
java
aggregate.getGroupCount() == 0 && values.getTuples().isEmpty()
也就是 GROUP BY () 且输入为空。
这时它会直接按聚合函数种类构造结果字面量:
COUNT/SUM0->0MIN/MAX/SUM->NULL
然后直接生成一个单行 Values 作为结果。
3. 为什么只支持这几类聚合
对于空输入,不同聚合函数的返回值语义并不统一。
源码只对以下几种做了明确支持:
COUNTSUM0MINMAXSUM
其他未知聚合函数会直接退出,避免错误推断其空输入语义。
4. 第二类场景:Aggregate 只是做 DISTINCT
如果满足:
Aggregate.isSimple(aggregate);aggCallList为空;aggregate.getRowType().equals(values.getRowType())
说明这个 Aggregate 本质上只是对 Values 去重。
这时规则会直接:
java
values.getTuples().stream().distinct()
把重复元组去掉,再生成一个新的 Values。
5. 为什么要求 row type 相同
如果 aggregate 输出行类型和 values 输入行类型不一样,那么简单去重后直接返回 Values 可能会破坏 schema。
所以源码只在两者 row type 完全一致时,才把它规约成纯 Values。
6. 这条规则为什么很有用
虽然它看起来只处理很小的 corner case,但这些场景在规则链里非常常见:
- 常量折叠后产生空关系;
- 子查询展开后变成
Values; IN列表去重;- grand total on empty input。
如果不提前规约,后续规则和执行器都得带着这些"本可常量化"的节点继续走。
7. 代表性测试
RelOptRulesTest 中这条规则的直接测试非常集中:
testAggregateValuesRuleWithInRepeatingValuestestAggregateValuesRuleWithRepeatingValuestestAggregateValuesRuleWithAggregationtestAggregateValuesRuleWithSameRowTypetestAggregateValuesRuleWithDifferentRowTypestestEmptyAggregateEmptyKeyWithAggregateValuesRule
这些测试分别覆盖:
Values去重;- 空输入上的聚合结果;
- row type 相同/不同的区别;
- grand total on empty input。
总结
AggregateValuesRule 是一条典型的"小输入折叠规则":
- 空
Values+ 全局聚合时,直接产出一行常量结果; - 纯去重
Aggregate时,直接把Values自身去重; - 只在语义非常明确的场景下生效。
它的价值不在复杂度,而在于把一类完全可以在优化阶段完成的计算提前消掉。