欢迎光临
我们一直在努力

【Calcite 系列】深入解析 Apache Calcite 的函数依赖实现 RelMdFunctionalDependency

本文围绕 core/src/main/java/org/apache/calcite/rel/metadata/RelMdFunctionalDependency.java 这份实现,系统介绍 Calcite 是如何在关系代数树上推导函数依赖(Functional Dependency, FD)。相关 PR:CALCITE-5913、CALCITE-7218、CALCITE-7219


一、什么是函数依赖

在关系模型里,如果一组列 X 能唯一确定另一组列 Y,就称存在函数依赖:

X -> Y

比如在员工表里,如果 empid 是主键,那么通常有:

empid -> ename, deptno, hiredate, salary

函数依赖在优化器里非常有价值,因为它可以帮助回答这些问题:

  • 哪些列其实是冗余的?
  • 某个表达式是否可以由更少的列唯一确定?
  • 聚合之后哪些列依然保持唯一性或决定性?
  • Join 条件里的等值关系能否增强已有约束?

二、RelMdFunctionalDependency 的职责

这个类是 Calcite 内置元数据 BuiltInMetadata.FunctionalDependency 的默认处理器。它并不直接面向 SQL 用户,而是供优化器、规则和元数据查询框架使用。

它做的事情可以概括成 4 类:

  • 判断某列或列集能否决定另一列或列集
  • 计算给定列集的函数闭包
  • 求某些列的最小决定集
  • 针对不同的 RelNode 推导出该节点上的 FD 集合
  • 核心对外方法包括:

    • determines
    • determinesSet
    • dependents
    • determinants
    • getFDs

    三、底层表示:Arrow 与 ArrowSet

    Calcite 在这里没有用“字符串形式”的 X -> Y 来存函数依赖,而是使用:

    • Arrow:一条函数依赖
    • ArrowSet:一组函数依赖

    其中列使用 ImmutableBitSet 表示,也就是按字段 ordinal(位置)来描述。

    例如:

    {0} -> {1,2,3}
    {0,1} -> {4}

    这样的设计非常适合关系代数树,因为经过 Project、Join、Aggregate 之后,字段名可能变化,但 ordinal 体系更加稳定。


    四、入口方法:getFDs(RelNode rel, RelMetadataQuery mq)

    这是真正的总入口。它先做一层 rel.stripped(),然后根据不同的 RelNode 类型分发到具体实现。

    当前实现覆盖:

    • TableScan
    • Project
    • Aggregate
    • Join
    • Calc
    • Filter

    而对于:

    • SetOp
    • Correlate

    目前直接返回空集,属于保守策略。

    如果一个节点没有专门逻辑,代码会回退到 getFD(List<RelNode> inputs, mq):

    • 单输入节点:直接继承输入 FD
    • 多输入节点:返回空

    这体现了很典型的优化器元数据风格:宁可少推,不要错推。

    #mermaid-svg-UjekYIJUbo1mGAiZ{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UjekYIJUbo1mGAiZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UjekYIJUbo1mGAiZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UjekYIJUbo1mGAiZ .error-icon{fill:#552222;}#mermaid-svg-UjekYIJUbo1mGAiZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UjekYIJUbo1mGAiZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UjekYIJUbo1mGAiZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UjekYIJUbo1mGAiZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UjekYIJUbo1mGAiZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UjekYIJUbo1mGAiZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UjekYIJUbo1mGAiZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UjekYIJUbo1mGAiZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UjekYIJUbo1mGAiZ .marker.cross{stroke:#333333;}#mermaid-svg-UjekYIJUbo1mGAiZ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UjekYIJUbo1mGAiZ p{margin:0;}#mermaid-svg-UjekYIJUbo1mGAiZ .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-UjekYIJUbo1mGAiZ .cluster-label text{fill:#333;}#mermaid-svg-UjekYIJUbo1mGAiZ .cluster-label span{color:#333;}#mermaid-svg-UjekYIJUbo1mGAiZ .cluster-label span p{background-color:transparent;}#mermaid-svg-UjekYIJUbo1mGAiZ .label text,#mermaid-svg-UjekYIJUbo1mGAiZ span{fill:#333;color:#333;}#mermaid-svg-UjekYIJUbo1mGAiZ .node rect,#mermaid-svg-UjekYIJUbo1mGAiZ .node circle,#mermaid-svg-UjekYIJUbo1mGAiZ .node ellipse,#mermaid-svg-UjekYIJUbo1mGAiZ .node polygon,#mermaid-svg-UjekYIJUbo1mGAiZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UjekYIJUbo1mGAiZ .rough-node .label text,#mermaid-svg-UjekYIJUbo1mGAiZ .node .label text,#mermaid-svg-UjekYIJUbo1mGAiZ .image-shape .label,#mermaid-svg-UjekYIJUbo1mGAiZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-UjekYIJUbo1mGAiZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UjekYIJUbo1mGAiZ .rough-node .label,#mermaid-svg-UjekYIJUbo1mGAiZ .node .label,#mermaid-svg-UjekYIJUbo1mGAiZ .image-shape .label,#mermaid-svg-UjekYIJUbo1mGAiZ .icon-shape .label{text-align:center;}#mermaid-svg-UjekYIJUbo1mGAiZ .node.clickable{cursor:pointer;}#mermaid-svg-UjekYIJUbo1mGAiZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UjekYIJUbo1mGAiZ .arrowheadPath{fill:#333333;}#mermaid-svg-UjekYIJUbo1mGAiZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UjekYIJUbo1mGAiZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UjekYIJUbo1mGAiZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UjekYIJUbo1mGAiZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UjekYIJUbo1mGAiZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UjekYIJUbo1mGAiZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UjekYIJUbo1mGAiZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UjekYIJUbo1mGAiZ .cluster text{fill:#333;}#mermaid-svg-UjekYIJUbo1mGAiZ .cluster span{color:#333;}#mermaid-svg-UjekYIJUbo1mGAiZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UjekYIJUbo1mGAiZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UjekYIJUbo1mGAiZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-UjekYIJUbo1mGAiZ .icon-shape,#mermaid-svg-UjekYIJUbo1mGAiZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UjekYIJUbo1mGAiZ .icon-shape p,#mermaid-svg-UjekYIJUbo1mGAiZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UjekYIJUbo1mGAiZ .icon-shape rect,#mermaid-svg-UjekYIJUbo1mGAiZ .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UjekYIJUbo1mGAiZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UjekYIJUbo1mGAiZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UjekYIJUbo1mGAiZ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    getFDs(rel, mq)

    RelNode 类型

    TableScan从 table keys 构造基础 FD

    Project / Calc映射输入 FD + 表达式推导

    Aggregate保留 group 内 FD + group 决定聚合列

    Join左右 FD 合并 + 右侧偏移 + 等值增强

    Filter继承输入 FD + 等值谓词增强

    SetOp / Correlate当前保守返回空

    其他单输入节点默认继承输入 FD


    五、TableScan:函数依赖的源头

    TableScan 的 FD 推导最自然,因为它直接来自表的 key 信息:

    List<ImmutableBitSet> keys = table.getKeys();

    对于每个 key,代码生成:

    key -> allColumns – key

    例子

    如果表结构是:

    [0:id, 1:name, 2:age, 3:dept]

    且 id 是 key,那么得到:

    {0} -> {1,2,3}

    如果还有复合 key:

    {2,3}

    那么还会生成:

    {2,3} -> {0,1}

    这一步相当于为整个 FD 推导系统提供了“底层事实”。

    #mermaid-svg-jLHaGGEk2SDlNZXL{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jLHaGGEk2SDlNZXL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jLHaGGEk2SDlNZXL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jLHaGGEk2SDlNZXL .error-icon{fill:#552222;}#mermaid-svg-jLHaGGEk2SDlNZXL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jLHaGGEk2SDlNZXL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jLHaGGEk2SDlNZXL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jLHaGGEk2SDlNZXL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jLHaGGEk2SDlNZXL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jLHaGGEk2SDlNZXL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jLHaGGEk2SDlNZXL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jLHaGGEk2SDlNZXL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jLHaGGEk2SDlNZXL .marker.cross{stroke:#333333;}#mermaid-svg-jLHaGGEk2SDlNZXL svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jLHaGGEk2SDlNZXL p{margin:0;}#mermaid-svg-jLHaGGEk2SDlNZXL .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-jLHaGGEk2SDlNZXL .cluster-label text{fill:#333;}#mermaid-svg-jLHaGGEk2SDlNZXL .cluster-label span{color:#333;}#mermaid-svg-jLHaGGEk2SDlNZXL .cluster-label span p{background-color:transparent;}#mermaid-svg-jLHaGGEk2SDlNZXL .label text,#mermaid-svg-jLHaGGEk2SDlNZXL span{fill:#333;color:#333;}#mermaid-svg-jLHaGGEk2SDlNZXL .node rect,#mermaid-svg-jLHaGGEk2SDlNZXL .node circle,#mermaid-svg-jLHaGGEk2SDlNZXL .node ellipse,#mermaid-svg-jLHaGGEk2SDlNZXL .node polygon,#mermaid-svg-jLHaGGEk2SDlNZXL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jLHaGGEk2SDlNZXL .rough-node .label text,#mermaid-svg-jLHaGGEk2SDlNZXL .node .label text,#mermaid-svg-jLHaGGEk2SDlNZXL .image-shape .label,#mermaid-svg-jLHaGGEk2SDlNZXL .icon-shape .label{text-anchor:middle;}#mermaid-svg-jLHaGGEk2SDlNZXL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jLHaGGEk2SDlNZXL .rough-node .label,#mermaid-svg-jLHaGGEk2SDlNZXL .node .label,#mermaid-svg-jLHaGGEk2SDlNZXL .image-shape .label,#mermaid-svg-jLHaGGEk2SDlNZXL .icon-shape .label{text-align:center;}#mermaid-svg-jLHaGGEk2SDlNZXL .node.clickable{cursor:pointer;}#mermaid-svg-jLHaGGEk2SDlNZXL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jLHaGGEk2SDlNZXL .arrowheadPath{fill:#333333;}#mermaid-svg-jLHaGGEk2SDlNZXL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jLHaGGEk2SDlNZXL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jLHaGGEk2SDlNZXL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jLHaGGEk2SDlNZXL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jLHaGGEk2SDlNZXL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jLHaGGEk2SDlNZXL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jLHaGGEk2SDlNZXL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jLHaGGEk2SDlNZXL .cluster text{fill:#333;}#mermaid-svg-jLHaGGEk2SDlNZXL .cluster span{color:#333;}#mermaid-svg-jLHaGGEk2SDlNZXL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jLHaGGEk2SDlNZXL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jLHaGGEk2SDlNZXL rect.text{fill:none;stroke-width:0;}#mermaid-svg-jLHaGGEk2SDlNZXL .icon-shape,#mermaid-svg-jLHaGGEk2SDlNZXL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jLHaGGEk2SDlNZXL .icon-shape p,#mermaid-svg-jLHaGGEk2SDlNZXL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jLHaGGEk2SDlNZXL .icon-shape rect,#mermaid-svg-jLHaGGEk2SDlNZXL .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jLHaGGEk2SDlNZXL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jLHaGGEk2SDlNZXL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jLHaGGEk2SDlNZXL :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    TableScan EMPkeys = {a}

    生成 ArrowSet

    a -> b,c,d


    六、Project:最值得细看的推导逻辑

    Project 是这份实现里最有技术含量的一段,因为它不仅要“继承输入 FD”,还要考虑:

    • 列重排
    • 列裁剪
    • 常量投影
    • 相同表达式投影
    • 一般表达式(如 a + b)
    • 非确定性表达式

    6.1 先拿输入 FD

    ArrowSet inputFdSet = mq.getFDs(input);

    6.2 建立输入列到输出列的映射

    Mappings.TargetMapping inputToOutputMap =
    RelOptUtil.permutation(projections, input.getRowType()).inverse();

    这一步主要处理“直接列引用”的映射关系。

    比如输入:

    [a, b, c]

    投影:

    [b, a, 1, a+b]

    那么简单映射大致是:

    input 0 -> output 1
    input 1 -> output 0
    input 2 -> 无直接映射

    6.3 把输入 FD 映射到输出

    代码调用 mapInputFDs(…),策略非常关键:

    • 决定列必须全部可映射,否则这条 FD 作废
    • 被决定列只要部分还能映射,就尽量保留

    例如输入有:

    {0,1} -> {2,3}

    而输出只保留了输入 0、1、2,那么结果会变成:

    {mapped(0), mapped(1)} -> {mapped(2)}

    不会保留 3,但不会因此把整条依赖都丢掉。

    6.4 处理相同表达式

    如果同一个表达式在投影列表中出现多次,例如:

    SELECT a+b AS x, a+b AS y

    代码会加入:

    x <-> y

    也就是双向依赖。因为两个输出列其实永远相等。

    6.5 处理常量列

    如果某个输出是字面量,比如:

    SELECT empno, 1 AS tag

    则常量列本质上是固定值。当前实现不是显式写成:

    {} -> tag

    而是采用一种更工程化的方式:让非字面量输出列去决定它,例如:

    empno -> tag

    虽然不是最理论化的表达,但在已有 ArrowSet 体系下很实用。

    6.6 处理表达式依赖输入列

    这是最精彩的一步。

    代码会用:

    RelOptUtil.InputFinder.bits(expr)

    找出某个表达式依赖了哪些输入列。

    例如:

    • a+b 依赖 {a,b}
    • b+1 依赖 {b}
    • CASE WHEN a>0 THEN b ELSE c END 依赖 {a,b,c}

    然后再看输入 FD 是否足以说明:

    某个输入列 ref -> expr 所依赖的全部输入列

    如果成立,就说明该输入列对应的输出列也能决定这个表达式输出列。

    例子

    如果输入已有:

    a -> b

    投影是:

    SELECT a, b + 1 AS x

    那么 x 依赖 b,由于 a -> b,可推出:

    a -> x

    如果输入已有:

    a -> b,c

    投影是:

    SELECT a, b + c AS x

    则有:

    a -> x

    因为 a 已经足够决定 x 所需要的全部输入列。

    #mermaid-svg-xO8VpT64z5VmoDcn{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xO8VpT64z5VmoDcn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xO8VpT64z5VmoDcn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xO8VpT64z5VmoDcn .error-icon{fill:#552222;}#mermaid-svg-xO8VpT64z5VmoDcn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xO8VpT64z5VmoDcn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xO8VpT64z5VmoDcn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xO8VpT64z5VmoDcn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xO8VpT64z5VmoDcn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xO8VpT64z5VmoDcn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xO8VpT64z5VmoDcn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xO8VpT64z5VmoDcn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xO8VpT64z5VmoDcn .marker.cross{stroke:#333333;}#mermaid-svg-xO8VpT64z5VmoDcn svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xO8VpT64z5VmoDcn p{margin:0;}#mermaid-svg-xO8VpT64z5VmoDcn .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xO8VpT64z5VmoDcn .cluster-label text{fill:#333;}#mermaid-svg-xO8VpT64z5VmoDcn .cluster-label span{color:#333;}#mermaid-svg-xO8VpT64z5VmoDcn .cluster-label span p{background-color:transparent;}#mermaid-svg-xO8VpT64z5VmoDcn .label text,#mermaid-svg-xO8VpT64z5VmoDcn span{fill:#333;color:#333;}#mermaid-svg-xO8VpT64z5VmoDcn .node rect,#mermaid-svg-xO8VpT64z5VmoDcn .node circle,#mermaid-svg-xO8VpT64z5VmoDcn .node ellipse,#mermaid-svg-xO8VpT64z5VmoDcn .node polygon,#mermaid-svg-xO8VpT64z5VmoDcn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xO8VpT64z5VmoDcn .rough-node .label text,#mermaid-svg-xO8VpT64z5VmoDcn .node .label text,#mermaid-svg-xO8VpT64z5VmoDcn .image-shape .label,#mermaid-svg-xO8VpT64z5VmoDcn .icon-shape .label{text-anchor:middle;}#mermaid-svg-xO8VpT64z5VmoDcn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xO8VpT64z5VmoDcn .rough-node .label,#mermaid-svg-xO8VpT64z5VmoDcn .node .label,#mermaid-svg-xO8VpT64z5VmoDcn .image-shape .label,#mermaid-svg-xO8VpT64z5VmoDcn .icon-shape .label{text-align:center;}#mermaid-svg-xO8VpT64z5VmoDcn .node.clickable{cursor:pointer;}#mermaid-svg-xO8VpT64z5VmoDcn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xO8VpT64z5VmoDcn .arrowheadPath{fill:#333333;}#mermaid-svg-xO8VpT64z5VmoDcn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xO8VpT64z5VmoDcn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xO8VpT64z5VmoDcn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xO8VpT64z5VmoDcn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xO8VpT64z5VmoDcn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xO8VpT64z5VmoDcn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xO8VpT64z5VmoDcn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xO8VpT64z5VmoDcn .cluster text{fill:#333;}#mermaid-svg-xO8VpT64z5VmoDcn .cluster span{color:#333;}#mermaid-svg-xO8VpT64z5VmoDcn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xO8VpT64z5VmoDcn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xO8VpT64z5VmoDcn rect.text{fill:none;stroke-width:0;}#mermaid-svg-xO8VpT64z5VmoDcn .icon-shape,#mermaid-svg-xO8VpT64z5VmoDcn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xO8VpT64z5VmoDcn .icon-shape p,#mermaid-svg-xO8VpT64z5VmoDcn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xO8VpT64z5VmoDcn .icon-shape rect,#mermaid-svg-xO8VpT64z5VmoDcn .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xO8VpT64z5VmoDcn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xO8VpT64z5VmoDcn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xO8VpT64z5VmoDcn :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    input RelNode

    mq.getFDs(input)

    inputFdSet

    建立 input 到 output 映射

    mapInputFDs

    继承可映射的输入 FD

    扫描 projections

    跳过非确定性表达式

    识别相同表达式

    识别常量列

    记录输入引用列

    计算 expr 依赖的输入列集

    加入 prev <-> cur

    记录 literal 索引

    记录 refToIndex

    若输入 FD 可推出 expr 所需输入列则 ref 输出列 -> expr 输出列

    合并结果

    Project / Calc 的 ArrowSet


    七、Aggregate:分组后哪些依赖还能保留?

    聚合之后,原始行被压缩,很多依赖会失效,但也会产生新的依赖。

    7.1 保留 group 内的输入 FD

    当 Aggregate.isSimple(rel) 成立时,代码会保留那些决定列和被决定列都完全落在 groupSet 里的输入 FD。

    例如输入有:

    a -> b

    而聚合是:

    GROUP BY a, b

    那么聚合后仍然可以保留:

    a -> b

    因为 a 和 b 都还在输出里,而且它们是 group 字段。

    7.2 计算 group 内的传递闭包

    代码对每个 group 列都尝试计算:

    该列在输入 FD 下能推出哪些 group 列

    然后把这些结果再补回聚合节点。

    这相当于在 group 维度里再做一次 closure 的“截断保留”。

    7.3 group key 决定聚合列

    这是聚合最重要的一条:

    groupSet -> aggCols

    因为一旦 group key 确定,这一组的聚合结果就是唯一确定的。

    例如:

    SELECT deptno, COUNT(*) AS cnt
    FROM emp
    GROUP BY deptno

    显然:

    deptno -> cnt

    这是聚合后最典型的新依赖。

    #mermaid-svg-QCZhZ71g4FtzdPPI{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QCZhZ71g4FtzdPPI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QCZhZ71g4FtzdPPI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QCZhZ71g4FtzdPPI .error-icon{fill:#552222;}#mermaid-svg-QCZhZ71g4FtzdPPI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QCZhZ71g4FtzdPPI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QCZhZ71g4FtzdPPI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QCZhZ71g4FtzdPPI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QCZhZ71g4FtzdPPI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QCZhZ71g4FtzdPPI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QCZhZ71g4FtzdPPI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QCZhZ71g4FtzdPPI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QCZhZ71g4FtzdPPI .marker.cross{stroke:#333333;}#mermaid-svg-QCZhZ71g4FtzdPPI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QCZhZ71g4FtzdPPI p{margin:0;}#mermaid-svg-QCZhZ71g4FtzdPPI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-QCZhZ71g4FtzdPPI .cluster-label text{fill:#333;}#mermaid-svg-QCZhZ71g4FtzdPPI .cluster-label span{color:#333;}#mermaid-svg-QCZhZ71g4FtzdPPI .cluster-label span p{background-color:transparent;}#mermaid-svg-QCZhZ71g4FtzdPPI .label text,#mermaid-svg-QCZhZ71g4FtzdPPI span{fill:#333;color:#333;}#mermaid-svg-QCZhZ71g4FtzdPPI .node rect,#mermaid-svg-QCZhZ71g4FtzdPPI .node circle,#mermaid-svg-QCZhZ71g4FtzdPPI .node ellipse,#mermaid-svg-QCZhZ71g4FtzdPPI .node polygon,#mermaid-svg-QCZhZ71g4FtzdPPI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QCZhZ71g4FtzdPPI .rough-node .label text,#mermaid-svg-QCZhZ71g4FtzdPPI .node .label text,#mermaid-svg-QCZhZ71g4FtzdPPI .image-shape .label,#mermaid-svg-QCZhZ71g4FtzdPPI .icon-shape .label{text-anchor:middle;}#mermaid-svg-QCZhZ71g4FtzdPPI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QCZhZ71g4FtzdPPI .rough-node .label,#mermaid-svg-QCZhZ71g4FtzdPPI .node .label,#mermaid-svg-QCZhZ71g4FtzdPPI .image-shape .label,#mermaid-svg-QCZhZ71g4FtzdPPI .icon-shape .label{text-align:center;}#mermaid-svg-QCZhZ71g4FtzdPPI .node.clickable{cursor:pointer;}#mermaid-svg-QCZhZ71g4FtzdPPI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QCZhZ71g4FtzdPPI .arrowheadPath{fill:#333333;}#mermaid-svg-QCZhZ71g4FtzdPPI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QCZhZ71g4FtzdPPI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QCZhZ71g4FtzdPPI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QCZhZ71g4FtzdPPI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QCZhZ71g4FtzdPPI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QCZhZ71g4FtzdPPI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QCZhZ71g4FtzdPPI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QCZhZ71g4FtzdPPI .cluster text{fill:#333;}#mermaid-svg-QCZhZ71g4FtzdPPI .cluster span{color:#333;}#mermaid-svg-QCZhZ71g4FtzdPPI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QCZhZ71g4FtzdPPI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QCZhZ71g4FtzdPPI rect.text{fill:none;stroke-width:0;}#mermaid-svg-QCZhZ71g4FtzdPPI .icon-shape,#mermaid-svg-QCZhZ71g4FtzdPPI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QCZhZ71g4FtzdPPI .icon-shape p,#mermaid-svg-QCZhZ71g4FtzdPPI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QCZhZ71g4FtzdPPI .icon-shape rect,#mermaid-svg-QCZhZ71g4FtzdPPI .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QCZhZ71g4FtzdPPI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QCZhZ71g4FtzdPPI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QCZhZ71g4FtzdPPI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    input FD

    Aggregate.isSimple?

    保留 determinants 和 dependents 都落在 groupSet 的输入 FD

    对每个 group 列计算在 group 内的传递闭包

    跳过输入 FD 保留逻辑

    groupSet -> aggCols

    Aggregate 的 ArrowSet


    八、Filter:谓词中的等值关系会增强 FD

    Filter 本身不改 schema,所以最基础的行为是继承输入 FD。

    但它还有额外收益:

    过滤条件中的等值关系会新增双向函数依赖。

    代码会识别:

    • a = b
    • a IS NOT DISTINCT FROM b
    • AND 递归组合

    例如条件:

    WHERE a = b AND c = d

    则新增:

    a <-> b
    c <-> d

    再与输入 FD 做并集。

    为什么是双向?因为在经过该过滤条件之后,这些列在结果集中恒等:

    • 已知 a,就知道 b
    • 已知 b,也知道 a

    #mermaid-svg-3qx11T5DMwrfJ4iI{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3qx11T5DMwrfJ4iI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3qx11T5DMwrfJ4iI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3qx11T5DMwrfJ4iI .error-icon{fill:#552222;}#mermaid-svg-3qx11T5DMwrfJ4iI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3qx11T5DMwrfJ4iI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3qx11T5DMwrfJ4iI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3qx11T5DMwrfJ4iI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3qx11T5DMwrfJ4iI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3qx11T5DMwrfJ4iI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3qx11T5DMwrfJ4iI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3qx11T5DMwrfJ4iI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3qx11T5DMwrfJ4iI .marker.cross{stroke:#333333;}#mermaid-svg-3qx11T5DMwrfJ4iI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3qx11T5DMwrfJ4iI p{margin:0;}#mermaid-svg-3qx11T5DMwrfJ4iI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-3qx11T5DMwrfJ4iI .cluster-label text{fill:#333;}#mermaid-svg-3qx11T5DMwrfJ4iI .cluster-label span{color:#333;}#mermaid-svg-3qx11T5DMwrfJ4iI .cluster-label span p{background-color:transparent;}#mermaid-svg-3qx11T5DMwrfJ4iI .label text,#mermaid-svg-3qx11T5DMwrfJ4iI span{fill:#333;color:#333;}#mermaid-svg-3qx11T5DMwrfJ4iI .node rect,#mermaid-svg-3qx11T5DMwrfJ4iI .node circle,#mermaid-svg-3qx11T5DMwrfJ4iI .node ellipse,#mermaid-svg-3qx11T5DMwrfJ4iI .node polygon,#mermaid-svg-3qx11T5DMwrfJ4iI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3qx11T5DMwrfJ4iI .rough-node .label text,#mermaid-svg-3qx11T5DMwrfJ4iI .node .label text,#mermaid-svg-3qx11T5DMwrfJ4iI .image-shape .label,#mermaid-svg-3qx11T5DMwrfJ4iI .icon-shape .label{text-anchor:middle;}#mermaid-svg-3qx11T5DMwrfJ4iI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3qx11T5DMwrfJ4iI .rough-node .label,#mermaid-svg-3qx11T5DMwrfJ4iI .node .label,#mermaid-svg-3qx11T5DMwrfJ4iI .image-shape .label,#mermaid-svg-3qx11T5DMwrfJ4iI .icon-shape .label{text-align:center;}#mermaid-svg-3qx11T5DMwrfJ4iI .node.clickable{cursor:pointer;}#mermaid-svg-3qx11T5DMwrfJ4iI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3qx11T5DMwrfJ4iI .arrowheadPath{fill:#333333;}#mermaid-svg-3qx11T5DMwrfJ4iI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3qx11T5DMwrfJ4iI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3qx11T5DMwrfJ4iI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3qx11T5DMwrfJ4iI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3qx11T5DMwrfJ4iI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3qx11T5DMwrfJ4iI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3qx11T5DMwrfJ4iI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3qx11T5DMwrfJ4iI .cluster text{fill:#333;}#mermaid-svg-3qx11T5DMwrfJ4iI .cluster span{color:#333;}#mermaid-svg-3qx11T5DMwrfJ4iI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3qx11T5DMwrfJ4iI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3qx11T5DMwrfJ4iI rect.text{fill:none;stroke-width:0;}#mermaid-svg-3qx11T5DMwrfJ4iI .icon-shape,#mermaid-svg-3qx11T5DMwrfJ4iI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3qx11T5DMwrfJ4iI .icon-shape p,#mermaid-svg-3qx11T5DMwrfJ4iI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3qx11T5DMwrfJ4iI .icon-shape rect,#mermaid-svg-3qx11T5DMwrfJ4iI .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3qx11T5DMwrfJ4iI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3qx11T5DMwrfJ4iI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3qx11T5DMwrfJ4iI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    a = b

    c = d

    AND

    Filter 条件

    是否为 IS NOT DISTINCT FROM 或 AND

    加入 a <-> b

    加入 c <-> d

    递归处理每个子条件

    与输入 FD 做 union


    九、Join:左右 FD 的合并、偏移与增强

    Join 是另一个非常核心的节点。

    9.1 左右输入的 FD 合并

    先分别取左右输入的 FD:

    ArrowSet leftFdSet = mq.getFDs(rel.getLeft());
    ArrowSet rightFdSet = mq.getFDs(rel.getRight());

    左表字段的索引在 Join 输出中保持不变。

    右表字段则要做整体偏移:

    shiftFdSet(rightFdSet, leftFieldCount)

    比如左表有 3 列,那么右表原来的:

    0,1,2

    进入 Join 输出后就变成:

    3,4,5

    9.2 Join 条件也会产生双向依赖

    比如:

    ... JOIN ... ON left.a = right.b

    则在输出字段索引上会产生:

    left.a <-> shifted(right.b)

    9.3 不同 Join 类型的处理

    当前实现:

    • INNER / LEFT / RIGHT:合并左右输入 FD,并补充等值条件 FD
    • SEMI / ANTI:只保留左侧 FD
    • 其它(比如 FULL):返回空集

    FULL JOIN 被保守处理的原因很合理:它会引入 null 扩展,原始依赖关系很容易失真。

    #mermaid-svg-bCQnvLMaHFIPRVN0{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bCQnvLMaHFIPRVN0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bCQnvLMaHFIPRVN0 .error-icon{fill:#552222;}#mermaid-svg-bCQnvLMaHFIPRVN0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bCQnvLMaHFIPRVN0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bCQnvLMaHFIPRVN0 .marker.cross{stroke:#333333;}#mermaid-svg-bCQnvLMaHFIPRVN0 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bCQnvLMaHFIPRVN0 p{margin:0;}#mermaid-svg-bCQnvLMaHFIPRVN0 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-bCQnvLMaHFIPRVN0 .cluster-label text{fill:#333;}#mermaid-svg-bCQnvLMaHFIPRVN0 .cluster-label span{color:#333;}#mermaid-svg-bCQnvLMaHFIPRVN0 .cluster-label span p{background-color:transparent;}#mermaid-svg-bCQnvLMaHFIPRVN0 .label text,#mermaid-svg-bCQnvLMaHFIPRVN0 span{fill:#333;color:#333;}#mermaid-svg-bCQnvLMaHFIPRVN0 .node rect,#mermaid-svg-bCQnvLMaHFIPRVN0 .node circle,#mermaid-svg-bCQnvLMaHFIPRVN0 .node ellipse,#mermaid-svg-bCQnvLMaHFIPRVN0 .node polygon,#mermaid-svg-bCQnvLMaHFIPRVN0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bCQnvLMaHFIPRVN0 .rough-node .label text,#mermaid-svg-bCQnvLMaHFIPRVN0 .node .label text,#mermaid-svg-bCQnvLMaHFIPRVN0 .image-shape .label,#mermaid-svg-bCQnvLMaHFIPRVN0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-bCQnvLMaHFIPRVN0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bCQnvLMaHFIPRVN0 .rough-node .label,#mermaid-svg-bCQnvLMaHFIPRVN0 .node .label,#mermaid-svg-bCQnvLMaHFIPRVN0 .image-shape .label,#mermaid-svg-bCQnvLMaHFIPRVN0 .icon-shape .label{text-align:center;}#mermaid-svg-bCQnvLMaHFIPRVN0 .node.clickable{cursor:pointer;}#mermaid-svg-bCQnvLMaHFIPRVN0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bCQnvLMaHFIPRVN0 .arrowheadPath{fill:#333333;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bCQnvLMaHFIPRVN0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bCQnvLMaHFIPRVN0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bCQnvLMaHFIPRVN0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bCQnvLMaHFIPRVN0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bCQnvLMaHFIPRVN0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bCQnvLMaHFIPRVN0 .cluster text{fill:#333;}#mermaid-svg-bCQnvLMaHFIPRVN0 .cluster span{color:#333;}#mermaid-svg-bCQnvLMaHFIPRVN0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bCQnvLMaHFIPRVN0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bCQnvLMaHFIPRVN0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-bCQnvLMaHFIPRVN0 .icon-shape,#mermaid-svg-bCQnvLMaHFIPRVN0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bCQnvLMaHFIPRVN0 .icon-shape p,#mermaid-svg-bCQnvLMaHFIPRVN0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bCQnvLMaHFIPRVN0 .icon-shape rect,#mermaid-svg-bCQnvLMaHFIPRVN0 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bCQnvLMaHFIPRVN0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bCQnvLMaHFIPRVN0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bCQnvLMaHFIPRVN0 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    leftFdSet

    Join 输出 FD

    rightFdSet

    shiftFdSet(rightFdSet, leftFieldCount)

    join condition

    addFDsFromEqualityCondition

    INNER / LEFT / RIGHT合并左右 FD + 等值增强

    SEMI / ANTI仅保留左侧 FD

    FULL 等其它场景保守返回空


    十、Calc:本质上复用 Project

    Calc 这里的实现很直接:

  • 把 program 里的投影展开
  • 调用 getProjectionFD(…)
  • 所以当前版本中,Calc 的 FD 推导基本可以看成 Project 逻辑的复用。


    十一、一个完整例子:从底表到 Join 的 FD 演化

    假设底表 EMP(a,b,c),其中:

    a 是 key

    那么最初:

    a -> b,c

    步骤 1:Project

    SELECT a, b + 1 AS x, 1 AS k FROM EMP

    得到:

    • a -> x(因为 a -> b)
    • a -> k(因为 k 是常量)
    • x -> k(表达式列也可决定常量列)

    步骤 2:Filter

    WHERE a = x

    新增:

    a <-> x

    步骤 3:Aggregate

    SELECT a, COUNT(*) AS cnt GROUP BY a

    新增:

    a -> cnt

    步骤 4:Join

    再与 D(did, name) 做 Join:

    ... JOIN D ON a = did

    若右表有:

    did -> name

    那么通过 Join 的等值关系,可以进一步推出:

    a -> name

    这正体现了 FD 在 RelNode 树中不断传播和增强的过程。

    #mermaid-svg-O4rPCfzHgyW4S45i{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-O4rPCfzHgyW4S45i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-O4rPCfzHgyW4S45i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-O4rPCfzHgyW4S45i .error-icon{fill:#552222;}#mermaid-svg-O4rPCfzHgyW4S45i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-O4rPCfzHgyW4S45i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-O4rPCfzHgyW4S45i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-O4rPCfzHgyW4S45i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-O4rPCfzHgyW4S45i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-O4rPCfzHgyW4S45i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-O4rPCfzHgyW4S45i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-O4rPCfzHgyW4S45i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-O4rPCfzHgyW4S45i .marker.cross{stroke:#333333;}#mermaid-svg-O4rPCfzHgyW4S45i svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-O4rPCfzHgyW4S45i p{margin:0;}#mermaid-svg-O4rPCfzHgyW4S45i .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-O4rPCfzHgyW4S45i .cluster-label text{fill:#333;}#mermaid-svg-O4rPCfzHgyW4S45i .cluster-label span{color:#333;}#mermaid-svg-O4rPCfzHgyW4S45i .cluster-label span p{background-color:transparent;}#mermaid-svg-O4rPCfzHgyW4S45i .label text,#mermaid-svg-O4rPCfzHgyW4S45i span{fill:#333;color:#333;}#mermaid-svg-O4rPCfzHgyW4S45i .node rect,#mermaid-svg-O4rPCfzHgyW4S45i .node circle,#mermaid-svg-O4rPCfzHgyW4S45i .node ellipse,#mermaid-svg-O4rPCfzHgyW4S45i .node polygon,#mermaid-svg-O4rPCfzHgyW4S45i .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-O4rPCfzHgyW4S45i .rough-node .label text,#mermaid-svg-O4rPCfzHgyW4S45i .node .label text,#mermaid-svg-O4rPCfzHgyW4S45i .image-shape .label,#mermaid-svg-O4rPCfzHgyW4S45i .icon-shape .label{text-anchor:middle;}#mermaid-svg-O4rPCfzHgyW4S45i .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-O4rPCfzHgyW4S45i .rough-node .label,#mermaid-svg-O4rPCfzHgyW4S45i .node .label,#mermaid-svg-O4rPCfzHgyW4S45i .image-shape .label,#mermaid-svg-O4rPCfzHgyW4S45i .icon-shape .label{text-align:center;}#mermaid-svg-O4rPCfzHgyW4S45i .node.clickable{cursor:pointer;}#mermaid-svg-O4rPCfzHgyW4S45i .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-O4rPCfzHgyW4S45i .arrowheadPath{fill:#333333;}#mermaid-svg-O4rPCfzHgyW4S45i .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-O4rPCfzHgyW4S45i .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-O4rPCfzHgyW4S45i .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O4rPCfzHgyW4S45i .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-O4rPCfzHgyW4S45i .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O4rPCfzHgyW4S45i .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-O4rPCfzHgyW4S45i .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-O4rPCfzHgyW4S45i .cluster text{fill:#333;}#mermaid-svg-O4rPCfzHgyW4S45i .cluster span{color:#333;}#mermaid-svg-O4rPCfzHgyW4S45i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-O4rPCfzHgyW4S45i .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-O4rPCfzHgyW4S45i rect.text{fill:none;stroke-width:0;}#mermaid-svg-O4rPCfzHgyW4S45i .icon-shape,#mermaid-svg-O4rPCfzHgyW4S45i .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O4rPCfzHgyW4S45i .icon-shape p,#mermaid-svg-O4rPCfzHgyW4S45i .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-O4rPCfzHgyW4S45i .icon-shape rect,#mermaid-svg-O4rPCfzHgyW4S45i .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O4rPCfzHgyW4S45i .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-O4rPCfzHgyW4S45i .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-O4rPCfzHgyW4S45i :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    TableScan EMPa 是 key得到: a -> b,c

    ProjectSELECT a, b+1 AS x, 1 AS k得到: a -> x, a -> k

    FilterWHERE a = x新增: a <-> x

    AggregateGROUP BY a, COUNT(*) AS cnt得到: a -> cnt

    Join DON a = did若 did -> name则可推出 a -> name


    十二、如何在代码里使用

    在规则或元数据查询上下文里,如果你有:

    • RelNode rel
    • RelMetadataQuery mq

    可以这样用。

    获取整个 FD 集合

    ArrowSet fdSet = mq.getFDs(rel);

    判断单列依赖

    Boolean ok = mq.getFunctionalDependency(rel)
    .determines(rel, mq, 0, 2);

    判断集合依赖

    Boolean ok = mq.getFunctionalDependency(rel)
    .determinesSet(rel, mq,
    ImmutableBitSet.of(0, 1),
    ImmutableBitSet.of(3));

    计算闭包

    ImmutableBitSet closure = mq.getFunctionalDependency(rel)
    .dependents(rel, mq, ImmutableBitSet.of(0));

    求最小决定集

    Set<ImmutableBitSet> dets = mq.getFunctionalDependency(rel)
    .determinants(rel, mq, ImmutableBitSet.of(3));


    十三、这份实现的优点与边界

    优点

  • 结构清晰:每类 RelNode 都有独立推导逻辑
  • 保守可靠:不确定的场景宁可不推
  • 足够实用:已覆盖优化主干节点
  • 表达式感知:不仅处理列映射,也能感知表达式与常量
  • 局限

  • SetOp 尚未实现
  • Correlate 尚未实现
  • FULL JOIN 过于保守
  • 等值识别只覆盖有限谓词
  • 表达式等价分析还不够强,比如 a+b 与 b+a

  • 十四、总结

    RelMdFunctionalDependency 的价值,不只是“判断某列能不能决定另一列”。

    更重要的是,把函数依赖这个经典关系理论概念,真正嵌进了 Calcite 的关系代数优化流程里:

    • 在 TableScan 里从键出发建立事实
    • 在 Project/Calc 里做映射和表达式推导
    • 在 Filter/Join 里吸收等值谓词信息
    • 在 Aggregate 里生成新的 group key 依赖
    赞(0)
    未经允许不得转载:171主机测评 » 【Calcite 系列】深入解析 Apache Calcite 的函数依赖实现 RelMdFunctionalDependency
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址