This is an automated email from the ASF dual-hosted git repository.
xuzifu666 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new e4b2baa8b4 [CALCITE-7338] Window hints are not propagated to window
rel nodes
e4b2baa8b4 is described below
commit e4b2baa8b4aa5d399a808a3ac32fd77c537ac59d
Author: Zhen Chen <[email protected]>
AuthorDate: Sat Dec 20 09:48:10 2025 +0800
[CALCITE-7338] Window hints are not propagated to window rel nodes
---
.../apache/calcite/rel/logical/LogicalWindow.java | 32 ++++++++++----
.../calcite/rel/logical/ToLogicalConverter.java | 4 +-
.../apache/calcite/rel/mutable/MutableRels.java | 2 +-
.../apache/calcite/rel/rules/CalcRelSplitter.java | 49 +++++++++++++++++++++-
.../calcite/rel/rules/ProjectToWindowRule.java | 13 +++---
.../rel/rules/ProjectWindowTransposeRule.java | 4 +-
.../calcite/rel/rules/ReduceExpressionsRule.java | 2 +-
.../apache/calcite/test/SqlHintsConverterTest.java | 24 +++++++++++
8 files changed, 110 insertions(+), 20 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
index d17b8aef85..815b8f8ca5 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
@@ -102,36 +102,54 @@ public LogicalWindow(RelOptCluster cluster, RelTraitSet
traitSet, RelNode input,
@Override public LogicalWindow copy(RelTraitSet traitSet,
List<RelNode> inputs) {
- return new LogicalWindow(getCluster(), traitSet, sole(inputs), constants,
+ return new LogicalWindow(getCluster(), traitSet, hints, sole(inputs),
constants,
getRowType(), groups);
}
@Override public Window copy(List<RexLiteral> constants) {
- return new LogicalWindow(getCluster(), getTraitSet(), getInput(),
+ return new LogicalWindow(getCluster(), getTraitSet(), hints, getInput(),
constants, getRowType(), groups);
}
+ @Deprecated // to be removed before 2.0
+ public static LogicalWindow create(RelTraitSet traitSet, RelNode input,
+ List<RexLiteral> constants, RelDataType rowType, List<Group> groups) {
+ return create(traitSet, Collections.emptyList(), input, constants,
rowType, groups);
+ }
+
/**
* Creates a LogicalWindow.
*
- * @param input Input relational expression
* @param traitSet Trait set
+ * @param hints Hints
+ * @param input Input relational expression
* @param constants List of constants that are additional inputs
* @param rowType Output row type
* @param groups Window groups
*/
- public static LogicalWindow create(RelTraitSet traitSet, RelNode input,
- List<RexLiteral> constants, RelDataType rowType, List<Group> groups) {
- return new LogicalWindow(input.getCluster(), traitSet, input, constants,
+ public static LogicalWindow create(RelTraitSet traitSet, List<RelHint> hints,
+ RelNode input, List<RexLiteral> constants, RelDataType rowType,
+ List<Group> groups) {
+ return new LogicalWindow(input.getCluster(), traitSet, hints, input,
constants,
rowType, groups);
}
/**
* Creates a LogicalWindow by parsing a {@link RexProgram}.
*/
+ @Deprecated // to be removed before 2.0
public static RelNode create(RelOptCluster cluster,
RelTraitSet traitSet, RelBuilder relBuilder, RelNode child,
final RexProgram program) {
+ return create(cluster, traitSet, relBuilder, child, program,
Collections.emptyList());
+ }
+
+ /**
+ * Creates a LogicalWindow by parsing a {@link RexProgram}.
+ */
+ public static RelNode create(RelOptCluster cluster,
+ RelTraitSet traitSet, RelBuilder relBuilder, RelNode child,
+ final RexProgram program, List<RelHint> hints) {
final RelDataType outRowType = program.getOutputRowType();
// Build a list of distinct groups, partitions and aggregate
// functions.
@@ -288,7 +306,7 @@ public static RelNode create(RelOptCluster cluster,
};
final LogicalWindow window =
- LogicalWindow.create(traitSet, child, constants, intermediateRowType,
+ LogicalWindow.create(traitSet, hints, child, constants,
intermediateRowType,
groups);
// The order that the "over" calls occur in the groups and
diff --git
a/core/src/main/java/org/apache/calcite/rel/logical/ToLogicalConverter.java
b/core/src/main/java/org/apache/calcite/rel/logical/ToLogicalConverter.java
index eaf8513314..86d08f5641 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/ToLogicalConverter.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/ToLogicalConverter.java
@@ -144,8 +144,8 @@ public ToLogicalConverter(RelBuilder relBuilder) {
if (relNode instanceof Window) {
final Window window = (Window) relNode;
final RelNode input = visit(window.getInput());
- return LogicalWindow.create(input.getTraitSet(), input, window.constants,
- window.getRowType(), window.groups);
+ return LogicalWindow.create(input.getTraitSet(), window.getHints(),
+ input, window.constants, window.getRowType(), window.groups);
}
if (relNode instanceof Calc) {
diff --git a/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
b/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
index 54761eeef0..ed509b6d60 100644
--- a/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
+++ b/core/src/main/java/org/apache/calcite/rel/mutable/MutableRels.java
@@ -262,7 +262,7 @@ public static RelNode fromMutable(MutableRel node,
RelBuilder relBuilder) {
case WINDOW: {
final MutableWindow window = (MutableWindow) node;
final RelNode child = fromMutable(window.getInput(), relBuilder);
- return LogicalWindow.create(child.getTraitSet(),
+ return LogicalWindow.create(child.getTraitSet(), ImmutableList.of(),
child, window.constants, window.rowType, window.groups);
}
case MATCH: {
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/CalcRelSplitter.java
b/core/src/main/java/org/apache/calcite/rel/rules/CalcRelSplitter.java
index fde19567e8..67bbbae246 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CalcRelSplitter.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CalcRelSplitter.java
@@ -21,6 +21,8 @@
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Calc;
+import org.apache.calcite.rel.hint.Hintable;
+import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.logical.LogicalCalc;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -88,6 +90,7 @@ public abstract class CalcRelSplitter {
//~ Instance fields --------------------------------------------------------
protected final RexProgram program;
+ protected final List<RelHint> hints;
private final RelDataTypeFactory typeFactory;
private final List<RelType> relTypes;
@@ -108,6 +111,7 @@ public abstract class CalcRelSplitter {
CalcRelSplitter(Calc calc, RelBuilder relBuilder, RelType[] relTypes) {
this.relBuilder = relBuilder;
this.program = calc.getProgram();
+ this.hints = calc.getHints();
this.cluster = calc.getCluster();
this.traits = calc.getTraitSet();
this.typeFactory = calc.getCluster().getTypeFactory();
@@ -224,8 +228,35 @@ RelNode execute() {
projectExprOrdinals,
conditionExprOrdinal,
outputRowType);
+
+ // Propagate hints to each level. Since CalcRelSplitter builds a
vertical stack of
+ // relational expressions (bottom-up), the relative depth of a level
from the
+ // original top-level Calc determines how many '0's must be appended to
the
+ // hint's inheritPath to maintain correct mapping.
+ //
+ // Example: SELECT /*+ Hint message */ SUM(v1) OVER(P1), SUM(v2)
OVER(P2) FROM t
+ // split into 3 levels:
+ // LogicalProject, relativeDepth = 0, path = []
+ // LogicalWindow (P2), relativeDepth = 1, path = [0]
+ // LogicalWindow (P1), relativeDepth = 2, path = [0, 0]
+ final List<RelHint> levelHints;
+ final int relativeDepth = (levelCount - 1) - level;
+ if (hints.isEmpty() || relativeDepth == 0) {
+ levelHints = hints;
+ } else {
+ levelHints = new ArrayList<>(hints.size());
+ for (RelHint hint : hints) {
+ List<Integer> newPath = new ArrayList<>(hint.inheritPath.size() +
relativeDepth);
+ newPath.addAll(hint.inheritPath);
+ for (int i = 0; i < relativeDepth; i++) {
+ newPath.add(0);
+ }
+ levelHints.add(hint.copy(newPath));
+ }
+ }
+
rel =
- relType.makeRel(cluster, traits, relBuilder, rel, program1);
+ relType.makeRel(cluster, traits, relBuilder, rel, program1,
levelHints);
// Sometimes a level's program merely projects its inputs. We don't
// want these. They cause an explosion in the search space.
@@ -757,10 +788,24 @@ protected boolean supportsCondition() {
return true;
}
+ @Deprecated // to be removed before 2.0
protected RelNode makeRel(RelOptCluster cluster,
RelTraitSet traitSet, RelBuilder relBuilder, RelNode input,
RexProgram program) {
- return LogicalCalc.create(input, program);
+ return makeRel(cluster, traitSet, relBuilder, input, program,
ImmutableList.of());
+ }
+
+ protected RelNode makeRel(RelOptCluster cluster,
+ RelTraitSet traitSet,
+ RelBuilder relBuilder,
+ RelNode input,
+ RexProgram program,
+ List<RelHint> hints) {
+ RelNode rel = LogicalCalc.create(input, program);
+ if (!hints.isEmpty()) {
+ rel = ((Hintable) rel).withHints(hints);
+ }
+ return rel;
}
/**
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectToWindowRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectToWindowRule.java
index 37ef7b888c..931169135f 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectToWindowRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectToWindowRule.java
@@ -23,6 +23,7 @@
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Calc;
import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.logical.LogicalCalc;
import org.apache.calcite.rel.logical.LogicalWindow;
import org.apache.calcite.rex.RexBiVisitorImpl;
@@ -156,7 +157,9 @@ public ProjectToLogicalProjectAndWindowRule(
project.getRowType(),
project.getCluster().getRexBuilder());
// temporary LogicalCalc, never registered
- final LogicalCalc calc = LogicalCalc.create(input, program);
+ final LogicalCalc calc =
+ new LogicalCalc(project.getCluster(), project.getTraitSet(),
+ project.getHints(), input, program);
final CalcRelSplitter transform =
new WindowedAggRelSplitter(calc, call.builder()) {
@Override protected RelNode handle(RelNode rel) {
@@ -226,11 +229,11 @@ static class WindowedAggRelSplitter extends
CalcRelSplitter {
@Override protected RelNode makeRel(RelOptCluster cluster,
RelTraitSet traitSet, RelBuilder relBuilder, RelNode input,
- RexProgram program) {
+ RexProgram program, List<RelHint> hints) {
assert !program.containsAggs();
program = program.normalize(cluster.getRexBuilder(), null);
return super.makeRel(cluster, traitSet, relBuilder, input,
- program);
+ program, hints);
}
},
new RelType("WinAggRelType") {
@@ -255,11 +258,11 @@ static class WindowedAggRelSplitter extends
CalcRelSplitter {
}
@Override protected RelNode makeRel(RelOptCluster cluster,
RelTraitSet traitSet,
- RelBuilder relBuilder, RelNode input, RexProgram program) {
+ RelBuilder relBuilder, RelNode input, RexProgram program,
List<RelHint> hints) {
checkArgument(program.getCondition() == null,
"WindowedAggregateRel cannot accept a condition");
return LogicalWindow.create(cluster, traitSet, relBuilder, input,
- program);
+ program, hints);
}
}
};
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
index fac0f459ac..e606fc8b72 100644
---
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
+++
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
@@ -172,8 +172,8 @@ public ProjectWindowTransposeRule(RelBuilderFactory
relBuilderFactory) {
}
final LogicalWindow newLogicalWindow =
- LogicalWindow.create(window.getTraitSet(), projectBelowWindow,
- window.constants, outputBuilder.build(), groups);
+ LogicalWindow.create(window.getTraitSet(), window.getHints(),
+ projectBelowWindow, window.constants, outputBuilder.build(),
groups);
// Modify the top LogicalProject
final List<RexNode> topProjExps =
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
index fa6864ac42..88ffaf72fc 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
@@ -625,7 +625,7 @@ public WindowReduceExpressionsRule(Class<? extends Window>
windowClass,
}
if (reduced) {
call.transformTo(LogicalWindow
- .create(window.getTraitSet(), window.getInput(),
+ .create(window.getTraitSet(), window.getHints(), window.getInput(),
window.getConstants(), window.getRowType(), groups));
call.getPlanner().prune(window);
}
diff --git
a/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
b/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
index 9a75c50ada..cff0c20474 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlHintsConverterTest.java
@@ -58,6 +58,7 @@
import org.apache.calcite.rel.logical.LogicalUnion;
import org.apache.calcite.rel.logical.LogicalValues;
import org.apache.calcite.rel.rules.CoreRules;
+import org.apache.calcite.rel.rules.ProjectToWindowRule;
import org.apache.calcite.sql.SqlDelete;
import org.apache.calcite.sql.SqlInsert;
import org.apache.calcite.sql.SqlMerge;
@@ -270,6 +271,29 @@ public final Fixture sql(String sql) {
sql(sql).ok();
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7338">[CALCITE-7338]
+ * Window hints are not propagated to window rel nodes</a>. */
+ @Test void testWindowHintsPropagateAfterProjectToWindowRule() {
+ final String sql = "select /*+ mini_batch */ last_value(deptno)\n"
+ + "over (order by empno rows 2 following) from emp";
+
+ // Build rel with the same HintStrategyTable as this class
+ final RelNode rel = sql(sql).toRel();
+
+ // Run the rule that materializes LogicalWindow
+ HepProgramBuilder builder = new HepProgramBuilder();
+ builder.addRuleClass(ProjectToWindowRule.class);
+ HepPlanner planner = new HepPlanner(builder.build());
+ planner.addRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW);
+ planner.setRoot(rel);
+ RelNode transformed = planner.findBestExp();
+
+ // Expect the hint to be on LogicalWindow after the rule.
+ final RelHint expected =
RelHint.builder("MINI_BATCH").inheritPath(0).build();
+ new ValidateHintVisitor(expected, Window.class).go(transformed);
+ }
+
@Test void testHintsInSubQueryWithDecorrelation() {
final String sql = "select /*+ resource(parallelism='3'),
AGG_STRATEGY(TWO_PHASE) */\n"
+ "sum(e1.empno) from emp e1, dept d1\n"