Skip to content

Commit a463a62

Browse files
Fix
1 parent e73a235 commit a463a62

File tree

4 files changed

+69
-40
lines changed

4 files changed

+69
-40
lines changed

core/src/main/java/org/apache/calcite/rex/RexWindow.java

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -146,52 +146,16 @@ private StringBuilder appendDigest_(StringBuilder sb, boolean allowFraming) {
146146
sb.append(orderKeys.get(i));
147147
}
148148
}
149-
// There are 3 reasons to skip the ROWS/RANGE clause.
150-
// 1. If this window is being used with a RANK-style function that does not
151-
// allow framing, or
152-
// 2. If it is RANGE without ORDER BY (in which case all frames yield all
153-
// rows),
154-
// 3. If it is an unbounded range
155-
// ("RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
156-
// or "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING")
157-
// with no ORDER BY.
158-
if (!allowFraming // 1
159-
|| (!isRows && orderKeys.isEmpty()) // 2
160-
|| (orderKeys.isEmpty()
161-
&& lowerBound.isUnboundedPreceding() // 3
162-
&& upperBound.isUnboundedFollowing())) {
163-
// Don't print a ROWS or RANGE clause
164-
} else if (upperBound.isCurrentRow()) {
165-
// Per MSSQL: If ROWS/RANGE is specified and <window frame preceding>
166-
// is used for <window frame extent> (short syntax) then this
167-
// specification is used for the window frame boundary starting point and
168-
// CURRENT ROW is used for the boundary ending point. For example
169-
// "ROWS 5 PRECEDING" is equal to "ROWS BETWEEN 5 PRECEDING AND CURRENT
170-
// ROW".
171-
//
172-
// We print the shorter option if it is
173-
// the default. If the RexWindow is, say, "ROWS BETWEEN 5 PRECEDING AND
174-
// CURRENT ROW", we output "ROWS 5 PRECEDING" because it is equivalent and
175-
// is shorter.
176-
if (!isRows && lowerBound.isUnboundedPreceding()) {
177-
// OVER (ORDER BY x)
178-
// OVER (ORDER BY x RANGE UNBOUNDED PRECEDING)
179-
// OVER (ORDER BY x RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
180-
// are equivalent, so print the first (i.e. nothing).
181-
} else {
182-
sb.append(sb.length() > initialLength ? " " : "")
183-
.append(isRows ? "ROWS" : "RANGE")
184-
.append(' ')
185-
.append(lowerBound);
186-
}
187-
} else {
149+
150+
if (allowFraming) {
188151
sb.append(sb.length() > initialLength ? " " : "")
189152
.append(isRows ? "ROWS" : "RANGE")
190153
.append(" BETWEEN ")
191154
.append(lowerBound)
192155
.append(" AND ")
193156
.append(upperBound);
194157
}
158+
195159
if (exclude != RexWindowExclusion.EXCLUDE_NO_OTHER) {
196160
sb.append(" ").append(exclude).append(" ");
197161
}

core/src/main/java/org/apache/calcite/sql/SqlRankFunction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public SqlRankFunction(SqlKind kind, SqlReturnTypeInference returnTypes,
4242
//~ Methods ----------------------------------------------------------------
4343

4444
@Override public boolean allowsFraming() {
45-
return false;
45+
return true;
4646
}
4747

4848
}

core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import org.apache.calcite.rex.RexSubQuery;
7474
import org.apache.calcite.rex.RexUtil;
7575
import org.apache.calcite.rex.RexVisitorImpl;
76+
import org.apache.calcite.rex.RexWindowBounds;
7677
import org.apache.calcite.runtime.PairList;
7778
import org.apache.calcite.sql.SqlAggFunction;
7879
import org.apache.calcite.sql.SqlExplainFormat;
@@ -1108,6 +1109,7 @@ private static void shiftMapping(Map<Integer, Integer> mapping, int startIndex,
11081109
.over()
11091110
.partitionBy(corVarProjects.leftList())
11101111
.orderBy(sortExprs)
1112+
.let(c -> c.rowsBetween(RexWindowBounds.UNBOUNDED_PRECEDING, RexWindowBounds.CURRENT_ROW))
11111113
.toRex();
11121114
newProjExprs.add(rowNumberCall, "rn"); // Add the row number column
11131115
relBuilder.project(newProjExprs.leftList(), newProjExprs.rightList());

core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,4 +584,67 @@ public static Frameworks.ConfigBuilder config() {
584584
+ " LogicalTableScan(table=[[scott, DEPT]])\n";
585585
assertThat(decorrelatedNoRules, hasTree(planDecorrelatedNoRules));
586586
}
587+
588+
@Test void testDecorrelateCorrelatedOrderByLimitToRowNumberRows() {
589+
final FrameworkConfig frameworkConfig = config().build();
590+
final RelBuilder builder = RelBuilder.create(frameworkConfig);
591+
final RelOptCluster cluster = builder.getCluster();
592+
final Planner planner = Frameworks.getPlanner(frameworkConfig);
593+
final String sql = ""
594+
+ "SELECT dname FROM dept WHERE 2000 > (\n"
595+
+ "SELECT emp.sal FROM emp where dept.deptno = emp.deptno\n"
596+
+ "ORDER BY year(hiredate), emp.sal limit 1)";
597+
final RelNode originalRel;
598+
try {
599+
final SqlNode parse = planner.parse(sql);
600+
final SqlNode validate = planner.validate(parse);
601+
originalRel = planner.rel(validate).rel;
602+
} catch (Exception e) {
603+
throw TestUtil.rethrow(e);
604+
}
605+
606+
final HepProgram hepProgram = HepProgram.builder()
607+
.addRuleCollection(
608+
ImmutableList.of(
609+
// SubQuery program rules
610+
CoreRules.FILTER_SUB_QUERY_TO_CORRELATE,
611+
CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE,
612+
CoreRules.JOIN_SUB_QUERY_TO_CORRELATE))
613+
.build();
614+
final Program program =
615+
Programs.of(hepProgram, true,
616+
requireNonNull(cluster.getMetadataProvider()));
617+
final RelNode before =
618+
program.run(cluster.getPlanner(), originalRel, cluster.traitSet(),
619+
Collections.emptyList(), Collections.emptyList());
620+
final String planBefore = ""
621+
+ "LogicalProject(DNAME=[$1])\n"
622+
+ " LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2])\n"
623+
+ " LogicalFilter(condition=[>(2000.00, CAST($3):DECIMAL(12, 2))])\n"
624+
+ " LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{0}])\n"
625+
+ " LogicalTableScan(table=[[scott, DEPT]])\n"
626+
+ " LogicalProject(SAL=[$0])\n"
627+
+ " LogicalSort(sort0=[$1], sort1=[$0], dir0=[ASC], dir1=[ASC], fetch=[1])\n"
628+
+ " LogicalProject(SAL=[$5], EXPR$1=[EXTRACT(FLAG(YEAR), $4)])\n"
629+
+ " LogicalFilter(condition=[=($cor0.DEPTNO, $7)])\n"
630+
+ " LogicalTableScan(table=[[scott, EMP]])\n";
631+
assertThat(before, hasTree(planBefore));
632+
633+
// Decorrelate without any rules, just "purely" decorrelation algorithm on RelDecorrelator
634+
final RelNode after =
635+
RelDecorrelator.decorrelateQuery(before, builder, RuleSets.ofList(Collections.emptyList()),
636+
RuleSets.ofList(Collections.emptyList()));
637+
// Verify plan
638+
final String planAfter = ""
639+
+ "LogicalProject(DNAME=[$1])\n"
640+
+ " LogicalJoin(condition=[=($0, $4)], joinType=[inner])\n"
641+
+ " LogicalTableScan(table=[[scott, DEPT]])\n"
642+
+ " LogicalFilter(condition=[>(2000.00, CAST($0):DECIMAL(12, 2))])\n"
643+
+ " LogicalProject(SAL=[$0], DEPTNO=[$2])\n"
644+
+ " LogicalFilter(condition=[<=($3, 1)])\n"
645+
+ " LogicalProject(SAL=[$5], EXPR$1=[EXTRACT(FLAG(YEAR), $4)], DEPTNO=[$7], rn=[ROW_NUMBER() OVER (PARTITION BY $7 ORDER BY EXTRACT(FLAG(YEAR), $4) NULLS LAST, $5 NULLS LAST ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)])\n"
646+
+ " LogicalFilter(condition=[IS NOT NULL($7)])\n"
647+
+ " LogicalTableScan(table=[[scott, EMP]])\n";
648+
assertThat(after, hasTree(planAfter));
649+
}
587650
}

0 commit comments

Comments
 (0)