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

xiedeyantu 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 ea98b94f39 [CALCITE-7597] Support ORDER BY ALL
ea98b94f39 is described below

commit ea98b94f396456ebe89b29fc56d8011b39a1fbd2
Author: Tisya Bhatia <[email protected]>
AuthorDate: Mon Jun 22 10:03:01 2026 -0500

    [CALCITE-7597] Support ORDER BY ALL
---
 core/src/main/codegen/templates/Parser.jj          | 31 ++++++++++--
 .../apache/calcite/runtime/CalciteResource.java    |  3 ++
 .../main/java/org/apache/calcite/sql/SqlKind.java  |  4 ++
 .../calcite/sql/fun/SqlInternalOperators.java      | 12 +++++
 .../calcite/sql/validate/SqlValidatorImpl.java     | 57 ++++++++++++++++++++++
 .../calcite/runtime/CalciteResource.properties     |  1 +
 .../apache/calcite/sql/test/SqlAdvisorTest.java    | 12 +++--
 .../org/apache/calcite/test/SqlValidatorTest.java  | 24 +++++++++
 core/src/test/resources/sql/sort.iq                | 14 ++++++
 site/_docs/reference.md                            |  8 ++-
 .../apache/calcite/sql/parser/SqlParserTest.java   | 30 ++++++++++++
 11 files changed, 188 insertions(+), 8 deletions(-)

diff --git a/core/src/main/codegen/templates/Parser.jj 
b/core/src/main/codegen/templates/Parser.jj
index 077481c262..5519dd566d 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -3093,6 +3093,7 @@ SqlNodeList OrderBy(boolean accept) :
 {
     final List<SqlNode> list = new ArrayList<SqlNode>();
     final Span s;
+    SqlNode all;
 }
 {
     <ORDER> {
@@ -3104,10 +3105,32 @@ SqlNodeList OrderBy(boolean accept) :
             throw SqlUtil.newContextException(s.pos(), 
RESOURCE.illegalOrderBy());
         }
     }
-    <BY> OrderItemList(list)
-    {
-        return new SqlNodeList(list, s.addAll(list).pos());
-    }
+    <BY>
+    (
+        <ALL> { all = SqlInternalOperators.ORDER_BY_ALL.createCall(getPos()); }
+        (
+            <ASC>
+        |   <DESC> { all = SqlStdOperatorTable.DESC.createCall(getPos(), all); 
}
+        )?
+        (
+            LOOKAHEAD(2)
+            <NULLS> <FIRST> {
+                all = SqlStdOperatorTable.NULLS_FIRST.createCall(getPos(), 
all);
+            }
+        |
+            <NULLS> <LAST> {
+                all = SqlStdOperatorTable.NULLS_LAST.createCall(getPos(), all);
+            }
+        )?
+        {
+            list.add(all);
+            return new SqlNodeList(list, s.addAll(list).pos());
+        }
+    |
+        OrderItemList(list) {
+            return new SqlNodeList(list, s.addAll(list).pos());
+        }
+    )
 }
 
 <#if parser.includeSelectBy!false>
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java 
b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index 171a8da68a..ac2ed85019 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -777,6 +777,9 @@ ExInst<CalciteException> 
illegalArgumentForTableFunctionCall(String a0,
   @BaseMessage("Streaming ORDER BY must start with monotonic expression")
   ExInst<SqlValidatorException> streamMustOrderByMonotonic();
 
+  @BaseMessage("ORDER BY ALL requires an explicit SELECT list; ''*'' is not 
supported")
+  ExInst<SqlValidatorException> orderByAllRequiresExplicitSelectList();
+
   @BaseMessage("Set operator cannot combine streaming and non-streaming 
inputs")
   ExInst<SqlValidatorException> streamSetOpInconsistentInputs();
 
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java 
b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index 680c833c17..a70c71df76 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -195,6 +195,9 @@ public enum SqlKind {
    */
   ORDER_BY,
 
+  /** The ALL keyword of the ORDER BY clause. */
+  ORDER_BY_ALL,
+
   /** WITH clause. */
   WITH,
 
@@ -1482,6 +1485,7 @@ public enum SqlKind {
                   TIMESTAMP_ADD, TIMESTAMP_DIFF, TIMESTAMP_SUB,
                   EXTRACT, INTERVAL,
                   LITERAL_CHAIN, JDBC_FN, PRECEDING, FOLLOWING, ORDER_BY,
+                  ORDER_BY_ALL,
                   NULLS_FIRST, NULLS_LAST, COLLECTION_TABLE, TABLESAMPLE,
                   VALUES, WITH, WITH_ITEM, ITEM, SKIP_TO_FIRST, SKIP_TO_LAST,
                   JSON_VALUE_EXPRESSION, UNNEST),
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
index 8757e1e177..d1429f501b 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
@@ -208,6 +208,18 @@ private SqlInternalOperators() {
   public static final SqlInternalOperator GROUP_BY_ALL =
       new SqlRollupOperator("GROUP BY ALL", SqlKind.GROUP_BY_ALL);
 
+  /** {@code ORDER BY ALL}, a placeholder expanded during validation into
+   * a standard {@code ORDER BY}. */
+  public static final SqlInternalOperator ORDER_BY_ALL =
+      // High precedence so the placeholder is never wrapped in parentheses 
when it
+      // appears alone or inside DESC / NULLS FIRST | LAST in an 
always-parentheses writer
+      new SqlInternalOperator("ORDER BY ALL", SqlKind.ORDER_BY_ALL, 100) {
+        @Override public void unparse(SqlWriter writer, SqlCall call,
+            int leftPrec, int rightPrec) {
+          writer.keyword("ALL");
+        }
+      };
+
   /** Fetch operator is ONLY used for its precedence during unparsing. */
   public static final SqlOperator FETCH =
       SqlBasicOperator.create("FETCH")
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index a41be8c67b..37f6712e90 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -5209,6 +5209,7 @@ protected void validateOrderList(SqlSelect select) {
     // ORDER BY is validated in a scope where aliases in the SELECT clause
     // are visible. For example, "SELECT empno AS x FROM emp ORDER BY x"
     // is valid.
+    rewriteOrderByAll(select);
     SqlNodeList orderList = select.getOrderList();
     if (orderList == null) {
       return;
@@ -5236,6 +5237,62 @@ protected void validateOrderList(SqlSelect select) {
     }
   }
 
+  protected void rewriteOrderByAll(SqlSelect select) {
+    final SqlNodeList orderList = select.getOrderList();
+    if (orderList == null || orderList.size() != 1) {
+      return;
+    }
+
+    SqlNode node = orderList.get(0);
+    boolean desc = false;
+    SqlKind nulls = null;
+
+    while (node instanceof SqlCall) {
+      final SqlKind kind = node.getKind();
+      if (kind == SqlKind.NULLS_FIRST || kind == SqlKind.NULLS_LAST) {
+        nulls = kind;
+        node = ((SqlCall) node).operand(0);
+      } else if (kind == SqlKind.DESCENDING) {
+        desc = true;
+        node = ((SqlCall) node).operand(0);
+      } else {
+        break;
+      }
+    }
+
+    if (node.getKind() != SqlKind.ORDER_BY_ALL) {
+      return;
+    }
+    final SqlParserPos pos = orderList.getParserPosition();
+    final List<SqlNode> keys = new ArrayList<>();
+
+    for (SqlNode selectItem : select.getSelectList()) {
+      final SqlNode expr = SqlUtil.stripAs(selectItem);
+      if (expr instanceof SqlIdentifier && ((SqlIdentifier) expr).isStar()) {
+        throw newValidationError(expr,
+            RESOURCE.orderByAllRequiresExplicitSelectList());
+      }
+      keys.add(applyOrderByAllDirection(expr, desc, nulls, pos));
+    }
+    select.setOrderBy(new SqlNodeList(keys, pos));
+  }
+
+  /** Wraps a single ORDER BY ALL key with the optional descending direction
+   * and null-ordering that apply to every expanded key. */
+  private static SqlNode applyOrderByAllDirection(SqlNode key, boolean desc,
+      @Nullable SqlKind nulls, SqlParserPos pos) {
+    SqlNode result = key;
+    if (desc) {
+      result = SqlStdOperatorTable.DESC.createCall(pos, result);
+    }
+    if (nulls == SqlKind.NULLS_FIRST) {
+      result = SqlStdOperatorTable.NULLS_FIRST.createCall(pos, result);
+    } else if (nulls == SqlKind.NULLS_LAST) {
+      result = SqlStdOperatorTable.NULLS_LAST.createCall(pos, result);
+    }
+    return result;
+  }
+
   /**
    * Validates an item in the GROUP BY clause of a SELECT statement.
    *
diff --git 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 4096537b7f..0799d8d6cc 100644
--- 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -256,6 +256,7 @@ CannotConvertToStream=Cannot convert table ''{0}'' to stream
 CannotConvertToRelation=Cannot convert stream ''{0}'' to relation
 StreamMustGroupByMonotonic=Streaming aggregation requires at least one 
monotonic expression in GROUP BY clause
 StreamMustOrderByMonotonic=Streaming ORDER BY must start with monotonic 
expression
+OrderByAllRequiresExplicitSelectList=ORDER BY ALL requires an explicit SELECT 
list; ''*'' is not supported
 StreamSetOpInconsistentInputs=Set operator cannot combine streaming and 
non-streaming inputs
 CannotStreamValues=Cannot stream VALUES
 CyclicDefinition=Cannot resolve ''{0}''; it references view ''{1}'', whose 
definition is cyclic
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java 
b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index 94cbc6fb9e..fad2778a38 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -71,6 +71,10 @@ class SqlAdvisorTest extends SqlValidatorTestCase {
       Collections.singletonList(
           "KEYWORD(*)");
 
+  private static final List<String> ORDER_BY_ALL_KEYWORD =
+      Collections.singletonList(
+          "KEYWORD(ALL)");
+
   protected static final List<String> FROM_KEYWORDS =
       Arrays.asList(
           "KEYWORD(()",
@@ -744,10 +748,12 @@ protected List<String> getJoinKeywords() {
     String sql;
 
     sql = "select emp.empno from sales.emp where empno=1 order by ^dummy";
-    f.withSql(sql).assertHint(EXPR_KEYWORDS, EMP_COLUMNS, EMP_TABLE);
+    f.withSql(sql).assertHint(EXPR_KEYWORDS, ORDER_BY_ALL_KEYWORD, EMP_COLUMNS,
+        EMP_TABLE);
 
     sql = "select emp.empno from sales.emp where empno=1 order by ^";
-    f.withSql(sql).assertComplete(EXPR_KEYWORDS, EMP_COLUMNS, EMP_TABLE);
+    f.withSql(sql).assertComplete(EXPR_KEYWORDS, ORDER_BY_ALL_KEYWORD,
+        EMP_COLUMNS, EMP_TABLE);
 
     sql =
         "select emp.empno\n"
@@ -755,7 +761,7 @@ protected List<String> getJoinKeywords() {
             + "  mpno,name,ob,gr,iredate,al,omm,eptno,lacker)\n"
             + "where e.mpno=1 order by ^";
     f.withSql(sql)
-        .assertComplete(EXPR_KEYWORDS,
+        .assertComplete(EXPR_KEYWORDS, ORDER_BY_ALL_KEYWORD,
             Arrays.asList("COLUMN(MPNO)",
                 "COLUMN(NAME)",
                 "COLUMN(OB)",
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index c4e8993839..f85c6c75bf 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -7324,6 +7324,30 @@ public boolean isBangEqualAllowed() {
         .ok();
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7597";>[CALCITE-7597]
+   * Support ORDER BY ALL</a>. */
+  @Test void testOrderByAll() {
+    // expands to all SELECT items, validates fine
+    sql("select deptno, sal from emp order by all").ok();
+    // direction applies to every expanded key
+    sql("select deptno, sal from emp order by all desc").ok();
+    // SELECT * can't be expanded here
+    sql("select ^*^ from emp order by all")
+        .fails("(?s).*ORDER BY ALL requires an explicit SELECT list.*");
+    // Aliases that shadow other column names must not confuse expansion
+    sql("select empno as deptno, deptno as empno from emp order by all").ok();
+    // verify "x" still resolves and the two features coexist
+    sql("select sal as x, x + 1 as y from emp order by all")
+        .withValidatorIdentifierExpansion(true)
+        .withConformance(SqlConformanceEnum.BABEL)
+        .ok();
+    sql("select sal as x, x + 1 as y from emp order by all desc")
+        .withValidatorIdentifierExpansion(true)
+        .withConformance(SqlConformanceEnum.BABEL)
+        .ok();
+  }
+
   @Test void testOrder() {
     final SqlConformance conformance = fixture().conformance();
     sql("select empno as x from emp order by empno").ok();
diff --git a/core/src/test/resources/sql/sort.iq 
b/core/src/test/resources/sql/sort.iq
index b7df52bff2..f9149a5482 100644
--- a/core/src/test/resources/sql/sort.iq
+++ b/core/src/test/resources/sql/sort.iq
@@ -549,4 +549,18 @@ order by au."books"[1]."title";
 
 !ok
 
+# [CALCITE-7597] ORDER BY ALL sorts by every SELECT expression, in list order
+select x, y from (values (2, 'b'), (1, 'a'), (1, 'c')) as t(x, y)
+order by all;
++---+---+
+| X | Y |
++---+---+
+| 1 | a |
+| 1 | c |
+| 2 | b |
++---+---+
+(3 rows)
+
+!ok
+
 # End sort.iq
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index b6ecb6b52b..6250a6d639 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -191,7 +191,7 @@ ## Grammar
       |   query MINUS [ ALL | DISTINCT ] query
       |   query INTERSECT [ ALL | DISTINCT ] query
       }
-      [ ORDER BY orderItem [, orderItem ]* ]
+      [ ORDER BY { ALL [ ASC | DESC ] [ NULLS FIRST | NULLS LAST ] | orderItem 
[, orderItem]* } ]
       [ LIMIT [ start, ] { count | ALL } ]
       [ OFFSET start { ROW | ROWS } ]
       [ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY ]
@@ -421,6 +421,12 @@ ## Grammar
 In *orderItem*, if *expression* is a positive integer *n*, it denotes
 the <em>n</em>th item in the SELECT clause.
 
+`ORDER BY ALL` sorts by every expression in the SELECT clause,
+in the order that they appear in the list; for example:
+"SELECT x, y FROM t ORDER BY ALL" is equivalent to
+"SELECT x, y FROM t ORDER BY x, y"
+An optional trailing ASC / DESC and NULLS FIRST / NULLS LAST applies to all 
keys.
+
 In *query*, *count* and *start* may each be either an unsigned integer literal
 or a dynamic parameter whose value is an integer.
 
diff --git 
a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java 
b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 0fa8c43fd9..591278ebfa 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -3947,6 +3947,36 @@ void checkPeriodPredicate(Checker checker) {
             + "ORDER BY `EMPNO`, `GENDER` DESC, `DEPTNO`, `EMPNO`, `NAME` 
DESC");
   }
 
+  @Test void testOrderByAll() {
+    final String sql = "select x, y from t\n"
+        + "order by all";
+    final String expected = "SELECT `X`, `Y`\n"
+        + "FROM `T`\n"
+        + "ORDER BY ALL";
+    sql(sql).ok(expected);
+
+    final String sql1 = "select x, y from t\n"
+        + "order by all desc";
+    final String expected1 = "SELECT `X`, `Y`\n"
+        + "FROM `T`\n"
+        + "ORDER BY ALL DESC";
+    sql(sql1).ok(expected1);
+
+    final String sql2 = "select x, y from t\n"
+        + "order by all desc nulls last";
+    final String expected2 = "SELECT `X`, `Y`\n"
+        + "FROM `T`\n"
+        + "ORDER BY ALL DESC NULLS LAST";
+    sql(sql2).ok(expected2);
+
+    final String sql3 = "select x, y from t\n"
+        + "order by all nulls first";
+    final String expected3 = "SELECT `X`, `Y`\n"
+        + "FROM `T`\n"
+        + "ORDER BY ALL NULLS FIRST";
+    sql(sql3).ok(expected3);
+  }
+
   @Test void testOrderNullsFirst() {
     final String sql = "select * from emp\n"
         + "order by gender desc nulls last,\n"

Reply via email to