This is an automated email from the ASF dual-hosted git repository.

jhyde 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 99a0df108a [CALCITE-5883] ROWS window aggregates ignore frames when 
there is no ORDER BY clause
99a0df108a is described below

commit 99a0df108a9f72805afb6d87ec5b2c0ed258f1ec
Author: Julian Hyde <[email protected]>
AuthorDate: Mon Aug 19 20:02:32 2024 -0700

    [CALCITE-5883] ROWS window aggregates ignore frames when there is no ORDER 
BY clause
    
    Before this change, `SUM(x) OVER (ROWS 1 PRECEDING)` was
    treated the same as `SUM(x) OVER (ROWS BETWEEN UNBOUNDED
    PRECEDING AND UNBOUNDED FOLLOWING)`. This was wrong -
    removing frames is correct for RANGE but not for ROWS.
    
    This change also fixes a special case,
    [CALCITE-6538] OVER (ROWS CURRENT ROW) should return a
    window with one row, not all rows
    
    In class RexWindowBound, add `isUnboundedPreceding()` and
    `isUnboundedFollowing()` convenience methods.
    
    Fix `RexWindow digest`, so that `OVER ()` is equivalent to
    `OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)`
    but not equivalent to `OVER (ROWS CURRENT ROW)`.
    
    Add Quidem tests for OVER, checked against Postgres.
    
    A few cosmetic changes, such as removing `assert x != null`
    when x is a parameter not declared `@Nullable`, adding static
    imports for `Objects.requireNonNull`, and
    changing `size() = 0` to `isEmpty()`.
    
    Close apache/calcite#3925
---
 .../adapter/enumerable/EnumerableWindow.java       |   2 +-
 .../java/org/apache/calcite/rel/core/Window.java   |  26 +--
 .../java/org/apache/calcite/rex/RexBuilder.java    |  16 +-
 .../java/org/apache/calcite/rex/RexShuttle.java    |   8 +-
 .../java/org/apache/calcite/rex/RexWindow.java     |  55 +++--
 .../org/apache/calcite/rex/RexWindowBound.java     |  10 +
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  17 +-
 .../org/apache/calcite/plan/RelWriterTest.java     |  65 ++++--
 .../org/apache/calcite/rex/RexProgramTest.java     |   4 +-
 .../apache/calcite/test/SqlToRelConverterTest.java |  24 ++
 .../org/apache/calcite/test/RelOptRulesTest.xml    |  10 +-
 .../apache/calcite/test/SqlToRelConverterTest.xml  |  29 ++-
 core/src/test/resources/sql/winagg.iq              | 245 +++++++++++++++++++++
 .../calcite/piglet/PigRelToSqlConverter.java       |  11 +-
 .../java/org/apache/calcite/test/PigRelOpTest.java |   3 +-
 15 files changed, 431 insertions(+), 94 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableWindow.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableWindow.java
index 7a4b2687be..c180e3fc0f 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableWindow.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableWindow.java
@@ -401,7 +401,7 @@ public class EnumerableWindow extends Window implements 
EnumerableRel {
       }
 
       Expression lowerBoundCanChange =
-          group.lowerBound.isUnbounded() && group.lowerBound.isPreceding()
+          group.lowerBound.isUnboundedPreceding()
           ? Expressions.constant(false)
           : Expressions.notEqual(startX, prevStart);
 
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Window.java 
b/core/src/main/java/org/apache/calcite/rel/core/Window.java
index 86a8822dc7..f1a0418127 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Window.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Window.java
@@ -55,7 +55,9 @@ import 
org.checkerframework.checker.nullness.qual.RequiresNonNull;
 import java.util.AbstractList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
+
+import static java.util.Objects.hash;
+import static java.util.Objects.requireNonNull;
 
 /**
  * A relational expression representing a set of window aggregates.
@@ -91,8 +93,7 @@ public abstract class Window extends SingleRel implements 
Hintable {
       RelNode input, List<RexLiteral> constants, RelDataType rowType, 
List<Group> groups) {
     super(cluster, traitSet, input);
     this.constants = ImmutableList.copyOf(constants);
-    assert rowType != null;
-    this.rowType = rowType;
+    this.rowType = requireNonNull(rowType, "rowType");
     this.groups = ImmutableList.copyOf(groups);
     this.hints = ImmutableList.copyOf(hints);
   }
@@ -267,12 +268,12 @@ public abstract class Window extends SingleRel implements 
Hintable {
         RexWindowExclusion exclude,
         RelCollation orderKeys,
         List<RexWinAggCall> aggCalls) {
-      this.keys = Objects.requireNonNull(keys, "keys");
+      this.keys = requireNonNull(keys, "keys");
       this.isRows = isRows;
-      this.lowerBound = Objects.requireNonNull(lowerBound, "lowerBound");
-      this.upperBound = Objects.requireNonNull(upperBound, "upperBound");
+      this.lowerBound = requireNonNull(lowerBound, "lowerBound");
+      this.upperBound = requireNonNull(upperBound, "upperBound");
       this.exclude = exclude;
-      this.orderKeys = Objects.requireNonNull(orderKeys, "orderKeys");
+      this.orderKeys = requireNonNull(orderKeys, "orderKeys");
       this.aggCalls = ImmutableList.copyOf(aggCalls);
       this.digest = computeString();
     }
@@ -297,17 +298,14 @@ public abstract class Window extends SingleRel implements 
Hintable {
         buf.append(orderKeys);
       }
       if (orderKeys.getFieldCollations().isEmpty()
-          && lowerBound.isUnbounded()
-          && lowerBound.isPreceding()
-          && upperBound.isUnbounded()
-          && upperBound.isFollowing()) {
+          && lowerBound.isUnboundedPreceding()
+          && upperBound.isUnboundedFollowing()) {
         // skip bracket if no ORDER BY, and if bracket is the default,
         // "RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING",
         // which is equivalent to
         // "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
       } else if (!orderKeys.getFieldCollations().isEmpty()
-          && lowerBound.isUnbounded()
-          && lowerBound.isPreceding()
+          && lowerBound.isUnboundedPreceding()
           && upperBound.isCurrentRow()
           && !isRows) {
         // skip bracket if there is ORDER BY, and if bracket is the default,
@@ -467,7 +465,7 @@ public abstract class Window extends SingleRel implements 
Hintable {
 
     @Override public int hashCode() {
       if (hash == 0) {
-        hash = Objects.hash(super.hashCode(), ordinal, distinct, ignoreNulls);
+        hash = hash(super.hashCode(), ordinal, distinct, ignoreNulls);
       }
       return hash;
     }
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java 
b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index 4d40f2be03..0814f474df 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -512,8 +512,12 @@ public class RexBuilder {
       RexWindowBound upperBound,
       boolean rows,
       RexWindowExclusion exclude) {
-    if (lowerBound.isUnbounded() && lowerBound.isPreceding()
-        && upperBound.isUnbounded() && upperBound.isFollowing()) {
+    if (orderKeys.isEmpty() && !rows) {
+      lowerBound = RexWindowBounds.UNBOUNDED_PRECEDING;
+      upperBound = RexWindowBounds.UNBOUNDED_FOLLOWING;
+    }
+    if (lowerBound.isUnboundedPreceding()
+        && upperBound.isUnboundedFollowing()) {
       // RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
       //   is equivalent to
       // ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
@@ -936,7 +940,7 @@ public class RexBuilder {
    * Makes a reinterpret cast.
    *
    * @param type          type returned by the cast
-   * @param exp           expression to be casted
+   * @param exp           expression to be cast
    * @param checkOverflow whether an overflow check is required
    * @return a RexCall with two operands and a special return type
    */
@@ -945,7 +949,7 @@ public class RexBuilder {
       RexNode exp,
       RexNode checkOverflow) {
     List<RexNode> args;
-    if ((checkOverflow != null) && checkOverflow.isAlwaysTrue()) {
+    if (checkOverflow.isAlwaysTrue()) {
       args = ImmutableList.of(exp, checkOverflow);
     } else {
       args = ImmutableList.of(exp);
@@ -1268,8 +1272,7 @@ public class RexBuilder {
    * @return Character string literal
    */
   protected RexLiteral makePreciseStringLiteral(String s) {
-    assert s != null;
-    if (s.equals("")) {
+    if (s.isEmpty()) {
       return charEmpty;
     }
     return makeCharLiteral(new NlsString(s, null, null));
@@ -1342,7 +1345,6 @@ public class RexBuilder {
    * defaults.
    */
   public RexLiteral makeCharLiteral(NlsString str) {
-    assert str != null;
     RelDataType type = SqlUtil.createNlsStringType(typeFactory, str);
     return makeLiteral(str, type, SqlTypeName.CHAR);
   }
diff --git a/core/src/main/java/org/apache/calcite/rex/RexShuttle.java 
b/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
index 466a0df6fb..189cd8954b 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
@@ -75,16 +75,14 @@ public class RexShuttle implements RexVisitor<RexNode> {
         visitList(window.partitionKeys, update);
     final RexWindowBound lowerBound = window.getLowerBound().accept(this);
     final RexWindowBound upperBound = window.getUpperBound().accept(this);
-    if (lowerBound == null
-        || upperBound == null
-        || !update[0]
+    if (!update[0]
         && lowerBound == window.getLowerBound()
         && upperBound == window.getUpperBound()) {
       return window;
     }
     boolean rows = window.isRows();
-    if (lowerBound.isUnbounded() && lowerBound.isPreceding()
-        && upperBound.isUnbounded() && upperBound.isFollowing()) {
+    if (lowerBound.isUnboundedPreceding()
+        && upperBound.isUnboundedFollowing()) {
       // RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
       //   is equivalent to
       // ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
diff --git a/core/src/main/java/org/apache/calcite/rex/RexWindow.java 
b/core/src/main/java/org/apache/calcite/rex/RexWindow.java
index 9c1b39f5b7..6e3ee28ff8 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexWindow.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexWindow.java
@@ -23,10 +23,11 @@ import com.google.common.collect.ImmutableList;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.List;
-import java.util.Objects;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * Specification of the window of rows over which a {@link RexOver} windowed
  * aggregate is evaluated.
@@ -81,15 +82,16 @@ public class RexWindow {
       RexWindowExclusion exclude) {
     this.partitionKeys = ImmutableList.copyOf(partitionKeys);
     this.orderKeys = ImmutableList.copyOf(orderKeys);
-    this.lowerBound = Objects.requireNonNull(lowerBound, "lowerBound");
-    this.upperBound = Objects.requireNonNull(upperBound, "upperBound");
+    this.lowerBound = requireNonNull(lowerBound, "lowerBound");
+    this.upperBound = requireNonNull(upperBound, "upperBound");
     this.exclude = exclude;
     this.isRows = isRows;
     this.nodeCount = computeCodeCount();
     this.digest = computeDigest();
     checkArgument(
-        !(lowerBound.isUnbounded() && lowerBound.isPreceding()
-            && upperBound.isUnbounded() && upperBound.isFollowing() && isRows),
+        !(lowerBound.isUnboundedPreceding()
+            && upperBound.isUnboundedFollowing()
+            && isRows),
         "use RANGE for unbounded, not ROWS");
   }
 
@@ -157,16 +159,18 @@ public class RexWindow {
     // There are 3 reasons to skip the ROWS/RANGE clause.
     // 1. If this window is being used with a RANK-style function that does not
     //    allow framing, or
-    // 2. If there is no ORDER BY (in which case a frame is invalid), or
-    // 3. If the ROWS/RANGE clause is the default, "RANGE BETWEEN UNBOUNDED
-    //    PRECEDING AND CURRENT ROW"
+    // 2. If it is RANGE without ORDER BY (in which case all frames yield all
+    //    rows),
+    // 3. If it is an unbounded range
+    //    ("RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
+    //    or "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING")
+    //    with no ORDER BY.
     if (!allowFraming // 1
-        || orderKeys.isEmpty() // 2
-        || (lowerBound.isUnbounded() // 3
-            && lowerBound.isPreceding()
-            && upperBound.isCurrentRow()
-            && !isRows)) {
-      // No ROWS or RANGE clause
+        || (!isRows && orderKeys.isEmpty()) // 2
+        || (orderKeys.isEmpty()
+            && lowerBound.isUnboundedPreceding() // 3
+            && upperBound.isUnboundedFollowing())) {
+      // Don't print a ROWS or RANGE clause
     } else if (upperBound.isCurrentRow()) {
       // Per MSSQL: If ROWS/RANGE is specified and <window frame preceding>
       // is used for <window frame extent> (short syntax) then this
@@ -175,18 +179,25 @@ public class RexWindow {
       // "ROWS 5 PRECEDING" is equal to "ROWS BETWEEN 5 PRECEDING AND CURRENT
       // ROW".
       //
-      // By similar reasoning to (3) above, we print the shorter option if it 
is
+      // We print the shorter option if it is
       // the default. If the RexWindow is, say, "ROWS BETWEEN 5 PRECEDING AND
       // CURRENT ROW", we output "ROWS 5 PRECEDING" because it is equivalent 
and
       // is shorter.
-      sb.append(sb.length() > initialLength
-          ? (isRows ? " ROWS " : " RANGE ")
-          : (isRows ? "ROWS " : "RANGE "))
-          .append(lowerBound);
+      if (!isRows && lowerBound.isUnboundedPreceding()) {
+        // OVER (ORDER BY x)
+        // OVER (ORDER BY x RANGE UNBOUNDED PRECEDING)
+        // OVER (ORDER BY x RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
+        // are equivalent, so print the first (i.e. nothing).
+      } else {
+        sb.append(sb.length() > initialLength ? " " : "")
+            .append(isRows ? "ROWS" : "RANGE")
+            .append(' ')
+            .append(lowerBound);
+      }
     } else {
-      sb.append(sb.length() > initialLength
-          ? (isRows ? " ROWS BETWEEN " : " RANGE BETWEEN ")
-          : (isRows ? "ROWS BETWEEN " : "RANGE BETWEEN "))
+      sb.append(sb.length() > initialLength ? " " : "")
+          .append(isRows ? "ROWS" : "RANGE")
+          .append(" BETWEEN ")
           .append(lowerBound)
           .append(" AND ")
           .append(upperBound);
diff --git a/core/src/main/java/org/apache/calcite/rex/RexWindowBound.java 
b/core/src/main/java/org/apache/calcite/rex/RexWindowBound.java
index e2d0d7fd2a..0072db9c1a 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexWindowBound.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexWindowBound.java
@@ -48,6 +48,16 @@ public abstract class RexWindowBound {
     return false;
   }
 
+  /** Returns whether the bound is {@code UNBOUNDED PRECEDING}. */
+  public final boolean isUnboundedPreceding() {
+    return isUnbounded() && isPreceding();
+  }
+
+  /** Returns whether the bound is {@code UNBOUNDED FOLLOWING}. */
+  public final boolean isUnboundedFollowing() {
+    return isUnbounded() && isFollowing();
+  }
+
   /**
    * Returns if the bound is PRECEDING.
    *
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java 
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index e32c48f756..d88daa6318 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -1720,8 +1720,8 @@ public class SqlToRelConverter {
       SqlNodeList selectList = select.getSelectList();
       SqlNodeList groupList = select.getGroup();
 
-      if ((selectList.size() == 1)
-          && ((groupList == null) || (groupList.size() == 0))) {
+      if (selectList.size() == 1
+          && (groupList == null || groupList.isEmpty())) {
         SqlNode selectExpr = selectList.get(0);
         if (selectExpr instanceof SqlCall) {
           SqlCall selectExprCall = (SqlCall) selectExpr;
@@ -2284,8 +2284,9 @@ public class SqlToRelConverter {
         // ROW_NUMBER() expects specific kind of framing.
         rows = true;
       }
-    } else if (orderList.size() == 0) {
-      // Without ORDER BY, there must be no bracketing.
+    } else if (orderList.isEmpty() && !rows) {
+      // In RANGE without ORDER BY, all rows are equivalent, so bracketing has
+      // no effect.
       sqlLowerBound = SqlWindow.createUnboundedPreceding(SqlParserPos.ZERO);
       sqlUpperBound = SqlWindow.createUnboundedFollowing(SqlParserPos.ZERO);
     } else if (sqlLowerBound == null && sqlUpperBound == null) {
@@ -2307,7 +2308,7 @@ public class SqlToRelConverter {
         bb.convertExpression(requireNonNull(sqlLowerBound, "sqlLowerBound"));
     final RexNode upperBound =
         bb.convertExpression(requireNonNull(sqlUpperBound, "sqlUpperBound"));
-    if (orderList.size() == 0 && !rows) {
+    if (orderList.isEmpty() && !rows) {
       // A logical range requires an ORDER BY clause. Use the implicit
       // ordering of this relation. There must be one, otherwise it would
       // have failed validation.
@@ -2745,7 +2746,7 @@ public class SqlToRelConverter {
     // 3. Gather columns used as arguments to aggregate functions.
     pivotBb.agg = aggConverter;
     final List<@Nullable String> aggAliasList = new ArrayList<>();
-    assert aggConverter.aggCalls.size() == 0;
+    assert aggConverter.aggCalls.isEmpty();
     pivot.forEachAgg((alias, call) -> {
       call.accept(aggConverter);
       aggAliasList.add(alias);
@@ -3404,7 +3405,7 @@ public class SqlToRelConverter {
     bb.setRoot(ImmutableList.of(leftRel, rightRel));
     replaceSubQueries(bb, condition, RelOptUtil.Logic.UNKNOWN_AS_FALSE);
     final RelNode newRightRel =
-        bb.root == null || bb.registered.size() == 0
+        bb.root == null || bb.registered.isEmpty()
             ? rightRel
             : bb.reRegister(rightRel);
     bb.setRoot(ImmutableList.of(leftRel, newRightRel));
@@ -4638,7 +4639,7 @@ public class SqlToRelConverter {
       joinList.add(relBuilder.build());
     }
 
-    if (joinList.size() == 0) {
+    if (joinList.isEmpty()) {
       joinList.add(lastList);
     }
 
diff --git a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java 
b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
index fd9499510e..39ea28a1a1 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 package org.apache.calcite.plan;
+
 import org.apache.calcite.adapter.java.ReflectiveSchema;
 import org.apache.calcite.avatica.util.TimeUnit;
 import org.apache.calcite.plan.volcano.VolcanoPlanner;
@@ -114,6 +115,7 @@ import static java.util.Objects.requireNonNull;
 /**
  * Unit test for {@link org.apache.calcite.rel.externalize.RelJson}.
  */
+@SuppressWarnings("ConcatenationWithEmptyString")
 class RelWriterTest {
   public static final String XX = "{\n"
       + "  \"rels\": [\n"
@@ -521,11 +523,11 @@ class RelWriterTest {
         Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
           rootSchema.add("hr",
               new ReflectiveSchema(new HrSchema()));
+          final RelOptTable table =
+              requireNonNull(
+                  relOptSchema.getTableForMember(Arrays.asList("hr", "emps")));
           LogicalTableScan scan =
-              LogicalTableScan.create(cluster,
-                  relOptSchema.getTableForMember(
-                      Arrays.asList("hr", "emps")),
-                  ImmutableList.of());
+              LogicalTableScan.create(cluster, table, ImmutableList.of());
           final RexBuilder rexBuilder = cluster.getRexBuilder();
           LogicalFilter filter =
               LogicalFilter.create(scan,
@@ -568,11 +570,11 @@ class RelWriterTest {
         Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
           rootSchema.add("hr",
               new ReflectiveSchema(new HrSchema()));
+          final RelOptTable table =
+              requireNonNull(
+                  relOptSchema.getTableForMember(Arrays.asList("hr", "emps")));
           LogicalTableScan scan =
-              LogicalTableScan.create(cluster,
-                  relOptSchema.getTableForMember(
-                      Arrays.asList("hr", "emps")),
-                  ImmutableList.of());
+              LogicalTableScan.create(cluster, table, ImmutableList.of());
           final RexBuilder rexBuilder = cluster.getRexBuilder();
           final RelDataType bigIntType =
               cluster.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
@@ -956,7 +958,7 @@ class RelWriterTest {
     rel.explain(jsonWriter);
     final String relJson = jsonWriter.asString();
     String s = deserializeAndDump(getSchema(rel), relJson, format);
-    String expected = null;
+    final String expected;
     switch (format) {
     case TEXT:
       expected = ""
@@ -975,6 +977,8 @@ class RelWriterTest {
           + "$5\\n\" [label=\"0\"]\n"
           + "}\n";
       break;
+    default:
+      throw new AssertionError();
     }
     assertThat(s, isLinux(expected));
   }
@@ -1216,8 +1220,7 @@ class RelWriterTest {
     final Function<RelBuilder, RelNode> relFn = b ->
         mockCountOver(b, "EMP", ImmutableList.of(), 
ImmutableList.of("DEPTNO"));
     final String expected = ""
-        + "LogicalProject($f0=[COUNT() OVER (ORDER BY $7 NULLS LAST "
-        + "ROWS UNBOUNDED PRECEDING)])\n"
+        + "LogicalProject($f0=[COUNT() OVER (ORDER BY $7 NULLS LAST)])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
     relFn(relFn)
         .assertThatPlan(isLinux(expected));
@@ -1355,7 +1358,7 @@ class RelWriterTest {
             return super.visit(scan);
           }
         });
-    return schemaHolder.get();
+    return requireNonNull(schemaHolder.get());
   }
 
   /**
@@ -1427,7 +1430,7 @@ class RelWriterTest {
     for (String orderKeyName : orderKeyNames) {
       orderKeys.add(new RexFieldCollation(builder.field(orderKeyName), 
ImmutableSet.of()));
     }
-    final RelNode rel = builder
+    return builder
         .project(
             rexBuilder.makeOver(
                 type,
@@ -1437,9 +1440,8 @@ class RelWriterTest {
                 ImmutableList.copyOf(orderKeys),
                 RexWindowBounds.UNBOUNDED_PRECEDING,
                 RexWindowBounds.CURRENT_ROW,
-                true, true, false, false, false))
+                false, true, false, false, false))
         .build();
-    return rel;
   }
 
   @Test void testHashDistributionWithoutKeys() {
@@ -1462,11 +1464,10 @@ class RelWriterTest {
       final RelDataTypeFactory typeFactory = relBuilder.getTypeFactory();
       final RelDataType intType = 
typeFactory.createSqlType(SqlTypeName.INTEGER);
       final RexDynamicParam dynamicParam = 
rexBuilder.makeDynamicParam(intType, 0);
-      final RelNode relNode = relBuilder
+      return relBuilder
           .scan("EMP")
           .sortLimit(null, dynamicParam, 
relBuilder.fields(RelCollations.EMPTY))
           .build();
-      return relNode;
     };
 
     final String expectedJson = "{\n"
@@ -1528,10 +1529,14 @@ class RelWriterTest {
         .project(b.fields(), ImmutableList.of(), true)
             .let(b2 -> {
               final RelNode input = b2.build();
-              final RelOptTable table = input.getInput(0).getTable();
+              final RelOptTable table =
+                  requireNonNull(input.getInput(0).getTable());
+              final Prepare.CatalogReader schema =
+                  (Prepare.CatalogReader)
+                      requireNonNull(table.getRelOptSchema());
               final LogicalTableModify modify =
                   LogicalTableModify.create(table,
-                      (Prepare.CatalogReader) table.getRelOptSchema(),
+                      schema,
                       input,
                       TableModify.Operation.INSERT,
                       null,
@@ -1556,10 +1561,14 @@ class RelWriterTest {
                 b.equals(b.field("JOB"), b.literal("c")))
             .let(b2 -> {
               final RelNode filter = b2.build();
-              final RelOptTable table = filter.getInput(0).getTable();
+              final RelOptTable table =
+                  requireNonNull(filter.getInput(0).getTable());
+              final Prepare.CatalogReader schema =
+                  (Prepare.CatalogReader)
+                      requireNonNull(table.getRelOptSchema());
               final LogicalTableModify modify =
                   LogicalTableModify.create(table,
-                      (Prepare.CatalogReader) table.getRelOptSchema(),
+                      schema,
                       filter,
                       TableModify.Operation.UPDATE,
                       ImmutableList.of("ENAME"),
@@ -1583,10 +1592,14 @@ class RelWriterTest {
             .filter(b.equals(b.field("JOB"), b.literal("c")))
             .let(b2 -> {
               final RelNode filter = b2.build();
-              final RelOptTable table = filter.getInput(0).getTable();
+              final RelOptTable table =
+                  requireNonNull(filter.getInput(0).getTable());
+              final Prepare.CatalogReader schema =
+                  (Prepare.CatalogReader)
+                      requireNonNull(table.getRelOptSchema());
               LogicalTableModify modify =
                   LogicalTableModify.create(table,
-                      (Prepare.CatalogReader) table.getRelOptSchema(),
+                      schema,
                       filter,
                       TableModify.Operation.DELETE,
                       null,
@@ -1647,9 +1660,12 @@ class RelWriterTest {
               //     INSERT VALUES (0, 'x', 'x', 0, '20200501 10:00:00',
               //         0, 0, 0, 0)
               final RelNode project = b.build();
+              final Prepare.CatalogReader schema =
+                  (Prepare.CatalogReader)
+                      requireNonNull(emp.get().getRelOptSchema());
               LogicalTableModify modify =
                   LogicalTableModify.create(emp.get(),
-                      (Prepare.CatalogReader) emp.get().getRelOptSchema(),
+                      schema,
                       project,
                       TableModify.Operation.MERGE,
                       ImmutableList.of("ENAME"),
@@ -1716,6 +1732,7 @@ class RelWriterTest {
       return this;
     }
 
+    @SuppressWarnings("UnusedReturnValue")
     Fixture assertThatPlan(Matcher<String> matcher) {
       final FrameworkConfig config = RelBuilderTest.config().build();
       final RelBuilder b = RelBuilder.create(config);
diff --git a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java 
b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
index a0b983fdd7..ddb66a2af6 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
@@ -3616,12 +3616,12 @@ class RexProgramTest extends RexProgramTestBase {
     final RelDataType bigintType = 
typeFactory.createSqlType(SqlTypeName.BIGINT);
     final RexCall countCall =
         new RexCall(bigintType, SqlStdOperatorTable.COUNT, ImmutableList.of());
-    checkSimplify(m2v(v2m(countCall)), "COUNT() OVER ()");
+    checkSimplify(m2v(v2m(countCall)), "COUNT() OVER (ROWS CURRENT ROW)");
     // "m2v(v2m(sum($0))" -> "sum($0) over (rows current row)"
     final RexInputRef i0 = rexBuilder.makeInputRef(bigintType, 0);
     final RexCall sumCall =
         new RexCall(bigintType, SqlStdOperatorTable.SUM, ImmutableList.of(i0));
-    checkSimplify(m2v(v2m(sumCall)), "SUM($0) OVER ()");
+    checkSimplify(m2v(v2m(sumCall)), "SUM($0) OVER (ROWS CURRENT ROW)");
   }
 
   @Test void testSimplifyUnaryMinus() {
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 02980adc35..47efce21c6 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -2544,6 +2544,30 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
     sql(sql).ok();
   }
 
+  /** As {@link #testOverDefaultBracket()}, but no {@code ORDER BY},
+   * which makes more things equivalent. */
+  @Test void testOverDefaultBracketNoOrderBy() {
+    // c2 is invalid (therefore commented out);
+    // c3, c6, c7 are equivalent to c1;
+    // c5 is equivalent to c4.
+    final String sql = "select\n"
+        + "  count(*) over () c1,\n"
+        + "--count(*) over (\n"
+        + "--  range unbounded preceding) c2,\n"
+        + "  count(*) over (\n"
+        + "    range between unbounded preceding and current row) c3,\n"
+        + "  count(*) over (\n"
+        + "    rows unbounded preceding) c4,\n"
+        + "  count(*) over (\n"
+        + "    rows between unbounded preceding and current row) c5,\n"
+        + "  count(*) over (\n"
+        + "    range between unbounded preceding and unbounded following) 
c6,\n"
+        + " count(*) over (\n"
+        + "    rows between unbounded preceding and unbounded following) c7\n"
+        + "from emp";
+    sql(sql).ok();
+  }
+
   /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-750";>[CALCITE-750]
    * Allow windowed aggregate on top of regular aggregate</a>. */
diff --git 
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml 
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 01aa3af1ff..c3ee9f7c66 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -5272,8 +5272,8 @@ LogicalProject(EMPNO=[$0], DEPTNO=[$1], W_COUNT=[$2])
       <![CDATA[
 LogicalProject(EMPNO=[$0], DEPTNO=[$1], W_COUNT=[$2])
   LogicalFilter(condition=[IS NULL($2)])
-    LogicalProject(EMPNO=[$0], DEPTNO=[$7], $2=[$9])
-      LogicalWindow(window#0=[window(aggs [COUNT($0)])])
+    LogicalWindow(window#0=[window(rows between $2 PRECEDING and $3 PRECEDING 
aggs [COUNT($0)])])
+      LogicalProject(EMPNO=[$0], DEPTNO=[$7])
         LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
@@ -6794,7 +6794,7 @@ from empm]]>
     </Resource>
     <Resource name="planBefore">
       <![CDATA[
-LogicalProject(DEPTNO=[$7], AVGSAL=[CAST(/(SUM($5) OVER (), COUNT($5) OVER 
())):INTEGER NOT NULL])
+LogicalProject(DEPTNO=[$7], AVGSAL=[CAST(/(SUM($5) OVER (ROWS CURRENT ROW), 
COUNT($5) OVER (ROWS CURRENT ROW))):INTEGER NOT NULL])
   LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
@@ -6825,7 +6825,7 @@ order by 2 desc limit 3]]>
 LogicalProject(DEPTNO=[$0], AVGSAL=[$1])
   LogicalProject(DEPTNO=[$0], AVGSAL=[M2V($1)], EXPR$2=[$2])
     LogicalSort(sort0=[$2], dir0=[DESC], fetch=[3])
-      LogicalProject(DEPTNO=[$7], AVGSAL=[V2M(CAST(/(SUM($5), 
COUNT($5))):INTEGER NOT NULL)], EXPR$2=[CAST(/(SUM($5) OVER (), COUNT($5) OVER 
())):INTEGER NOT NULL])
+      LogicalProject(DEPTNO=[$7], AVGSAL=[V2M(CAST(/(SUM($5), 
COUNT($5))):INTEGER NOT NULL)], EXPR$2=[CAST(/(SUM($5) OVER (ROWS CURRENT ROW), 
COUNT($5) OVER (ROWS CURRENT ROW))):INTEGER NOT NULL])
         LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
@@ -6833,7 +6833,7 @@ LogicalProject(DEPTNO=[$0], AVGSAL=[$1])
       <![CDATA[
 LogicalProject(DEPTNO=[$0], AVGSAL=[$3])
   LogicalSort(sort0=[$2], dir0=[ASC], fetch=[3])
-    LogicalProject(DEPTNO=[$7], AVGSAL=[V2M(CAST(/(SUM($5), 
COUNT($5))):INTEGER NOT NULL)], EXPR$2=[CAST(/(SUM($5) OVER (), COUNT($5) OVER 
())):INTEGER NOT NULL], $f3=[CAST(/(SUM($5) OVER (), COUNT($5) OVER 
())):INTEGER NOT NULL])
+    LogicalProject(DEPTNO=[$7], AVGSAL=[V2M(CAST(/(SUM($5), 
COUNT($5))):INTEGER NOT NULL)], EXPR$2=[CAST(/(SUM($5) OVER (ROWS CURRENT ROW), 
COUNT($5) OVER (ROWS CURRENT ROW))):INTEGER NOT NULL], $f3=[CAST(/(SUM($5) OVER 
(ROWS CURRENT ROW), COUNT($5) OVER (ROWS CURRENT ROW))):INTEGER NOT NULL])
       LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
diff --git 
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml 
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 60b1ce73e6..5d6f6f2f0f 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -4657,7 +4657,7 @@ from (
     </Resource>
     <Resource name="plan">
       <![CDATA[
-LogicalProject(DEPTNO=[$7], COUNT_PLUS_10=[+(COUNT() OVER (), 10)])
+LogicalProject(DEPTNO=[$7], COUNT_PLUS_10=[+(COUNT() OVER (ROWS CURRENT ROW), 
10)])
   LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
@@ -4670,7 +4670,7 @@ from (select deptno, job, 1 as measure uno from emp) as 
t]]>
     </Resource>
     <Resource name="plan">
       <![CDATA[
-LogicalProject(DEPTNO=[$7], C=[COUNT() OVER ()], UNO=[1], TWO=[2])
+LogicalProject(DEPTNO=[$7], C=[COUNT() OVER (ROWS CURRENT ROW)], UNO=[1], 
TWO=[2])
   LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
@@ -5882,6 +5882,31 @@ from emp]]>
       <![CDATA[
 LogicalProject(C1=[COUNT() OVER (ORDER BY $7)], C2=[COUNT() OVER (ORDER BY 
$7)], C3=[COUNT() OVER (ORDER BY $7)], C4=[COUNT() OVER (ORDER BY $7 ROWS 
UNBOUNDED PRECEDING)], C5=[COUNT() OVER (ORDER BY $7 ROWS UNBOUNDED 
PRECEDING)], C6=[COUNT() OVER (ORDER BY $7 RANGE BETWEEN UNBOUNDED PRECEDING 
AND UNBOUNDED FOLLOWING)], C7=[COUNT() OVER (ORDER BY $7 RANGE BETWEEN 
UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)])
   LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testOverDefaultBracketNoOrderBy">
+    <Resource name="sql">
+      <![CDATA[select
+  count(*) over () c1,
+--count(*) over (
+--  range unbounded preceding) c2,
+  count(*) over (
+    range between unbounded preceding and current row) c3,
+  count(*) over (
+    rows unbounded preceding) c4,
+  count(*) over (
+    rows between unbounded preceding and current row) c5,
+  count(*) over (
+    range between unbounded preceding and unbounded following) c6,
+ count(*) over (
+    rows between unbounded preceding and unbounded following) c7
+from emp]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalProject(C1=[COUNT() OVER ()], C3=[COUNT() OVER ()], C4=[COUNT() OVER 
(ROWS UNBOUNDED PRECEDING)], C5=[COUNT() OVER (ROWS UNBOUNDED PRECEDING)], 
C6=[COUNT() OVER ()], C7=[COUNT() OVER ()])
+  LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
   </TestCase>
diff --git a/core/src/test/resources/sql/winagg.iq 
b/core/src/test/resources/sql/winagg.iq
index ddd6dfaae1..64e4a0d26c 100644
--- a/core/src/test/resources/sql/winagg.iq
+++ b/core/src/test/resources/sql/winagg.iq
@@ -78,6 +78,187 @@ from emp;
 
 !ok
 
+# [CALCITE-6538] OVER (ROWS CURRENT ROW) should return a window with one row, 
not all rows
+# (RANGE CURRENT ROW returns all rows, and is already correct.)
+select ename,
+  sal,
+  sum(sal) over (rows current row) as row_sum_sal,
+  sum(sal) over (range current row) as range_sum_sal
+from emp
+where job = 'MANAGER';
++-------+---------+-------------+---------------+
+| ENAME | SAL     | ROW_SUM_SAL | RANGE_SUM_SAL |
++-------+---------+-------------+---------------+
+| BLAKE | 2850.00 |     2850.00 |       8275.00 |
+| CLARK | 2450.00 |     2450.00 |       8275.00 |
+| JONES | 2975.00 |     2975.00 |       8275.00 |
++-------+---------+-------------+---------------+
+(3 rows)
+
+!ok
+
+# [CALCITE-5883] ROWS window aggregates ignore frames when there is no ORDER 
BY clause
+# Other windows with ROWS and no ORDER BY.
+select count(*) over (rows between 3 preceding and 2 preceding) as c3p2p,
+  sum(two) over (rows between 3 preceding and 2 preceding) as s3p2p,
+  sum(two) over (rows 1 preceding) as s1p,
+  sum(two) over (rows between 1 preceding and 1 following) as s1p1f,
+  sum(two) over (rows between current row and 2 following) as s2f
+from (select *, 2 as two from emp)
+order by 1;
++-------+-------+-----+-------+-----+
+| C3P2P | S3P2P | S1P | S1P1F | S2F |
++-------+-------+-----+-------+-----+
+|     0 |       |   2 |     4 |   6 |
+|     0 |       |   4 |     6 |   6 |
+|     1 |     2 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   6 |
+|     2 |     4 |   4 |     6 |   4 |
+|     2 |     4 |   4 |     4 |   2 |
++-------+-------+-----+-------+-----+
+(14 rows)
+
+!ok
+
+# Various combinations of ROWS, RANGE, UNBOUNDED PRECEDING,
+# UNBOUNDED FOLLOWING, and CURRENT ROW.
+#
+# 'OVER (ROWS CURRENT ROW)' includes one row;
+#
+# 'OVER (ROWS UNBOUNDED PRECEDING)' includes the previous rows;
+#
+# 'OVER ()' includes all rows and is equivalent to
+# 'OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)' and
+# 'OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)' and
+# 'OVER (RANGE UNBOUNDED PRECEDING)'.
+# Checked on Postgres.
+select count(*) over (rows current row) as "rows",
+  count(*) over (rows unbounded preceding) as "rowsUp",
+  count(*) over (range unbounded preceding) as "rangeUp",
+  count(*) over () as "empty",
+  count(*) over (rows between unbounded preceding and unbounded following) as 
"rowsUpUf",
+  count(*) over (range between unbounded preceding and unbounded following) as 
"rangeUpUf",
+  count(*) over (range unbounded preceding) as "rangeUp"
+from emp
+order by 3;
++------+--------+---------+-------+----------+-----------+---------+
+| rows | rowsUp | rangeUp | empty | rowsUpUf | rangeUpUf | rangeUp |
++------+--------+---------+-------+----------+-----------+---------+
+|    1 |      1 |      14 |    14 |       14 |        14 |      14 |
+|    1 |      2 |      14 |    14 |       14 |        14 |      14 |
+|    1 |      3 |      14 |    14 |       14 |        14 |      14 |
+|    1 |      4 |      14 |    14 |       14 |        14 |      14 |
+|    1 |      5 |      14 |    14 |       14 |        14 |      14 |
+|    1 |      6 |      14 |    14 |       14 |        14 |      14 |
+|    1 |      7 |      14 |    14 |       14 |        14 |      14 |
+|    1 |      8 |      14 |    14 |       14 |        14 |      14 |
+|    1 |      9 |      14 |    14 |       14 |        14 |      14 |
+|    1 |     10 |      14 |    14 |       14 |        14 |      14 |
+|    1 |     11 |      14 |    14 |       14 |        14 |      14 |
+|    1 |     12 |      14 |    14 |       14 |        14 |      14 |
+|    1 |     13 |      14 |    14 |       14 |        14 |      14 |
+|    1 |     14 |      14 |    14 |       14 |        14 |      14 |
++------+--------+---------+-------+----------+-----------+---------+
+(14 rows)
+
+!ok
+
+# Various combinations of ROWS, RANGE, UNBOUNDED PRECEDING,
+# UNBOUNDED FOLLOWING, and CURRENT ROW, with PARTITION BY.
+# The equivalence sets are the same the previous query (without PARTITION BY).
+# Checked on Postgres.
+select deptno,
+  count(*) over (partition by deptno rows current row) as "rows",
+  count(*) over (partition by deptno rows unbounded preceding) as "rowsUp",
+  count(*) over (partition by deptno range unbounded preceding) as "rangeUp",
+  count(*) over (partition by deptno) as "empty",
+  count(*) over (partition by deptno rows between unbounded preceding and 
unbounded following) as "rowsUpUf",
+  count(*) over (partition by deptno range between unbounded preceding and 
unbounded following) as "rangeUpUf",
+  count(*) over (partition by deptno range unbounded preceding) as "rangeUp"
+from emp
+order by 1, 4;
++--------+------+--------+---------+-------+----------+-----------+---------+
+| DEPTNO | rows | rowsUp | rangeUp | empty | rowsUpUf | rangeUpUf | rangeUp |
++--------+------+--------+---------+-------+----------+-----------+---------+
+|     10 |    1 |      1 |       3 |     3 |        3 |         3 |       3 |
+|     10 |    1 |      2 |       3 |     3 |        3 |         3 |       3 |
+|     10 |    1 |      3 |       3 |     3 |        3 |         3 |       3 |
+|     20 |    1 |      1 |       5 |     5 |        5 |         5 |       5 |
+|     20 |    1 |      2 |       5 |     5 |        5 |         5 |       5 |
+|     20 |    1 |      3 |       5 |     5 |        5 |         5 |       5 |
+|     20 |    1 |      4 |       5 |     5 |        5 |         5 |       5 |
+|     20 |    1 |      5 |       5 |     5 |        5 |         5 |       5 |
+|     30 |    1 |      1 |       6 |     6 |        6 |         6 |       6 |
+|     30 |    1 |      2 |       6 |     6 |        6 |         6 |       6 |
+|     30 |    1 |      3 |       6 |     6 |        6 |         6 |       6 |
+|     30 |    1 |      4 |       6 |     6 |        6 |         6 |       6 |
+|     30 |    1 |      5 |       6 |     6 |        6 |         6 |       6 |
+|     30 |    1 |      6 |       6 |     6 |        6 |         6 |       6 |
++--------+------+--------+---------+-------+----------+-----------+---------+
+(14 rows)
+
+!ok
+
+# As previous, with PARTITION BY and ORDER BY.
+#
+# 'OVER (... ROWS CURRENT ROW)' (rows) includes one row;
+#
+# 'OVER (...)' (empty),
+# 'OVER (... RANGE UNBOUNDED PRECEDING)' (rangeUp) and
+# 'OVER (... RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)' (rangeUpC)
+# are equivalent to each other,
+#
+# 'OVER (... ROWS UNBOUNDED PRECEDING)' (rowsUp) and
+# 'OVER (... ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)' (rowsUpC)
+# are equivalent to each other and include N rows,
+# and are similar to empty/rangeUp/rangeUpC except when there are ties (WARD, 
SCOTT);
+#
+# 'OVER (... ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)' 
(rowsUpUf) and
+# 'OVER (... RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)' 
(rangeUpUf)
+# are equivalent and return all rows.
+#
+# Checked on Postgres.
+select deptno, sal, ename,
+  count(*) over (partition by deptno order by sal rows current row) as "rows",
+  count(*) over (partition by deptno order by sal) as "empty",
+  count(*) over (partition by deptno order by sal range unbounded preceding) 
as "rangeUp",
+  count(*) over (partition by deptno order by sal range between unbounded 
preceding and current row) as "rangeUpC",
+  count(*) over (partition by deptno order by sal rows unbounded preceding) as 
"rowsUp",
+  count(*) over (partition by deptno order by sal rows between unbounded 
preceding and current row) as "rowsUpC",
+  count(*) over (partition by deptno order by sal range between unbounded 
preceding and unbounded following) as "rangeUpUf",
+  count(*) over (partition by deptno order by sal rows between unbounded 
preceding and unbounded following) as "rowsUpUf"
+from emp
+order by 1, 2;
++--------+---------+--------+------+-------+---------+----------+--------+---------+-----------+----------+
+| DEPTNO | SAL     | ENAME  | rows | empty | rangeUp | rangeUpC | rowsUp | 
rowsUpC | rangeUpUf | rowsUpUf |
++--------+---------+--------+------+-------+---------+----------+--------+---------+-----------+----------+
+|     10 | 1300.00 | MILLER |    1 |     1 |       1 |        1 |      1 |     
  1 |         3 |        3 |
+|     10 | 2450.00 | CLARK  |    1 |     2 |       2 |        2 |      2 |     
  2 |         3 |        3 |
+|     10 | 5000.00 | KING   |    1 |     3 |       3 |        3 |      3 |     
  3 |         3 |        3 |
+|     20 |  800.00 | SMITH  |    1 |     1 |       1 |        1 |      1 |     
  1 |         5 |        5 |
+|     20 | 1100.00 | ADAMS  |    1 |     2 |       2 |        2 |      2 |     
  2 |         5 |        5 |
+|     20 | 2975.00 | JONES  |    1 |     3 |       3 |        3 |      3 |     
  3 |         5 |        5 |
+|     20 | 3000.00 | SCOTT  |    1 |     5 |       5 |        5 |      4 |     
  4 |         5 |        5 |
+|     20 | 3000.00 | FORD   |    1 |     5 |       5 |        5 |      5 |     
  5 |         5 |        5 |
+|     30 |  950.00 | JAMES  |    1 |     1 |       1 |        1 |      1 |     
  1 |         6 |        6 |
+|     30 | 1250.00 | WARD   |    1 |     3 |       3 |        3 |      2 |     
  2 |         6 |        6 |
+|     30 | 1250.00 | MARTIN |    1 |     3 |       3 |        3 |      3 |     
  3 |         6 |        6 |
+|     30 | 1500.00 | TURNER |    1 |     4 |       4 |        4 |      4 |     
  4 |         6 |        6 |
+|     30 | 1600.00 | ALLEN  |    1 |     5 |       5 |        5 |      5 |     
  5 |         6 |        6 |
+|     30 | 2850.00 | BLAKE  |    1 |     6 |       6 |        6 |      6 |     
  6 |         6 |        6 |
++--------+---------+--------+------+-------+---------+----------+--------+---------+-----------+----------+
+(14 rows)
+
+!ok
+
 # STDDEV applied to nullable column
 select empno,
   stddev(CAST(comm AS DECIMAL(12, 4))) over (order by empno rows unbounded 
preceding) as stdev
@@ -875,6 +1056,70 @@ order by 1;
 
 !ok
 
+select
+  count(*) over (order by "one" rows between 2 preceding and 5 following 
exclude group) as c2p5fxg,
+  count(*) over (order by "one" rows between 2 preceding and 5 following) as 
c2p5f,
+  count(*) over (order by "one" rows 20 preceding) as c20p,
+  sum("one") over (order by "one" rows between 2 preceding and unbounded 
following exclude ties) as s2pxt,
+  sum("one") over (order by "one" rows between unbounded preceding and 
unbounded following exclude no others) as sxn,
+  sum("one") over (order by "one" rows between unbounded preceding and 
unbounded following) as s
+from (select *, 1 as "one" from emp) as e
+order by c20p;
++---------+-------+------+-------+-----+----+
+| C2P5FXG | C2P5F | C20P | S2PXT | SXN | S  |
++---------+-------+------+-------+-----+----+
+|       0 |     6 |    1 |     1 |  14 | 14 |
+|       0 |     7 |    2 |     1 |  14 | 14 |
+|       0 |     8 |    3 |     1 |  14 | 14 |
+|       0 |     8 |    4 |     1 |  14 | 14 |
+|       0 |     8 |    5 |     1 |  14 | 14 |
+|       0 |     8 |    6 |     1 |  14 | 14 |
+|       0 |     8 |    7 |     1 |  14 | 14 |
+|       0 |     8 |    8 |     1 |  14 | 14 |
+|       0 |     8 |    9 |     1 |  14 | 14 |
+|       0 |     7 |   10 |     1 |  14 | 14 |
+|       0 |     6 |   11 |     1 |  14 | 14 |
+|       0 |     5 |   12 |     1 |  14 | 14 |
+|       0 |     4 |   13 |     1 |  14 | 14 |
+|       0 |     3 |   14 |     1 |  14 | 14 |
++---------+-------+------+-------+-----+----+
+(14 rows)
+
+!ok
+
+# As above, without ORDER BY. Results are the same, because 'one' has
+# the same value for all rows.
+select
+  count(*) over (rows between 2 preceding and 5 following exclude group) as 
c2p5fxg,
+  count(*) over (rows between 2 preceding and 5 following) as c2p5f,
+  count(*) over (rows between 20 preceding and current row) as c20p,
+  sum("one") over (rows between 2 preceding and unbounded following exclude 
ties) as s2pxt,
+  sum("one") over (rows between unbounded preceding and unbounded following 
exclude no others) as sxn,
+  sum("one") over (rows between unbounded preceding and unbounded following) 
as s
+from (select *, 1 as "one" from emp) as e
+order by c20p;
++---------+-------+------+-------+-----+----+
+| C2P5FXG | C2P5F | C20P | S2PXT | SXN | S  |
++---------+-------+------+-------+-----+----+
+|       0 |     6 |    1 |     1 |  14 | 14 |
+|       0 |     7 |    2 |     1 |  14 | 14 |
+|       0 |     8 |    3 |     1 |  14 | 14 |
+|       0 |     8 |    4 |     1 |  14 | 14 |
+|       0 |     8 |    5 |     1 |  14 | 14 |
+|       0 |     8 |    6 |     1 |  14 | 14 |
+|       0 |     8 |    7 |     1 |  14 | 14 |
+|       0 |     8 |    8 |     1 |  14 | 14 |
+|       0 |     8 |    9 |     1 |  14 | 14 |
+|       0 |     7 |   10 |     1 |  14 | 14 |
+|       0 |     6 |   11 |     1 |  14 | 14 |
+|       0 |     5 |   12 |     1 |  14 | 14 |
+|       0 |     4 |   13 |     1 |  14 | 14 |
+|       0 |     3 |   14 |     1 |  14 | 14 |
++---------+-------+------+-------+-----+----+
+(14 rows)
+
+!ok
+
 select empno,
   stddev(comm) over (order by empno rows between 2 preceding and 5 following 
exclude current row) as e1,
   count(*) over (order by empno rows between 2 preceding and 5 following 
exclude group) as e2,
diff --git 
a/piglet/src/main/java/org/apache/calcite/piglet/PigRelToSqlConverter.java 
b/piglet/src/main/java/org/apache/calcite/piglet/PigRelToSqlConverter.java
index 2b6ea23de2..bba1f44baa 100644
--- a/piglet/src/main/java/org/apache/calcite/piglet/PigRelToSqlConverter.java
+++ b/piglet/src/main/java/org/apache/calcite/piglet/PigRelToSqlConverter.java
@@ -105,14 +105,21 @@ public class PigRelToSqlConverter extends 
RelToSqlConverter {
         orderList.add(builder.context.toSql(orderKey));
       }
 
+      SqlNode lowerBound = builder.context.toSql(winGroup.lowerBound);
+      SqlNode upperBound = builder.context.toSql(winGroup.upperBound);
+      if (orderList.isEmpty() && !winGroup.isRows) {
+        // With no ORDER BY, all RANGE windows are equivalent to OVER (),
+        // so simplify.
+        lowerBound = upperBound = null;
+      }
       final SqlNode sqlWindow =
           SqlWindow.create(null, // Window declaration name
               null, // Window reference name
               new SqlNodeList(partitionList, POS),
               new SqlNodeList(orderList, POS),
               SqlLiteral.createBoolean(winGroup.isRows, POS),
-              builder.context.toSql(winGroup.lowerBound),
-              builder.context.toSql(winGroup.upperBound),
+              lowerBound,
+              upperBound,
               null, // allowPartial
               builder.context.toSql(winGroup.exclude),
               POS);
diff --git a/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java 
b/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
index 4e00b561c3..9b5709184a 100644
--- a/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
+++ b/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
@@ -1667,8 +1667,7 @@ class PigRelOpTest extends PigRelTestBase {
         + "    LogicalTableScan(table=[[emp1]])\n";
 
     final String sql = "SELECT w0$o0 AS rank_A, id, name, age, city\n"
-        + "FROM (SELECT id, name, age, city, RANK() OVER (RANGE BETWEEN "
-        + "UNBOUNDED PRECEDING AND CURRENT ROW)\n"
+        + "FROM (SELECT id, name, age, city, RANK() OVER ()\n"
         + "    FROM emp1) AS t\n"
         + "WHERE w0$o0 > 1";
     pig(script).assertRel(hasTree(plan))

Reply via email to