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

zhenchen 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 1a4b00b77a [CALCITE-7311] Support the syntax ROW(*) to create a nested 
ROW type with all columns
1a4b00b77a is described below

commit 1a4b00b77a1cff4f8dd3f16834a743b209f02659
Author: Zhen Chen <[email protected]>
AuthorDate: Thu Dec 18 16:46:01 2025 +0800

    [CALCITE-7311] Support the syntax ROW(*) to create a nested ROW type with 
all columns
---
 core/src/main/codegen/templates/Parser.jj          | 52 ++++++++++++++-----
 .../apache/calcite/runtime/CalciteResource.java    |  3 ++
 .../calcite/sql/validate/SqlValidatorImpl.java     | 60 +++++++++++++++++++++-
 .../calcite/runtime/CalciteResource.properties     |  1 +
 .../org/apache/calcite/test/SqlValidatorTest.java  |  9 ++++
 core/src/test/resources/sql/struct.iq              | 39 ++++++++++++++
 6 files changed, 151 insertions(+), 13 deletions(-)

diff --git a/core/src/main/codegen/templates/Parser.jj 
b/core/src/main/codegen/templates/Parser.jj
index ee3c1c759a..2069882cc2 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -170,6 +170,20 @@ public class ${parser.class} extends SqlAbstractParserImpl
     private Casing quotedCasing;
     private int identifierMaxLength;
     private SqlConformance conformance;
+    private int rowValueStarCount;
+
+    private void pushRowValueStar() {
+        rowValueStarCount++;
+    }
+
+    private void popRowValueStar() {
+        assert rowValueStarCount > 0;
+        rowValueStarCount--;
+    }
+
+    private boolean allowRowValueStar() {
+        return rowValueStarCount > 0;
+    }
 
     /**
      * {@link SqlParserImplFactory} implementation for creating parser.
@@ -4040,27 +4054,38 @@ SqlNode Expression3(ExprContext exprContext) :
     LOOKAHEAD(3)
     <ROW> {
         s = span();
+        pushRowValueStar();
     }
     list = ParenthesizedQueryOrCommaList(exprContext) {
-        if (exprContext != ExprContext.ACCEPT_ALL
-            && exprContext != ExprContext.ACCEPT_CURSOR
-            && !this.conformance.allowExplicitRowValueConstructor())
-        {
-            throw SqlUtil.newContextException(s.end(list),
-                RESOURCE.illegalRowExpression());
+        try {
+            if (exprContext != ExprContext.ACCEPT_ALL
+                && exprContext != ExprContext.ACCEPT_CURSOR
+                && !this.conformance.allowExplicitRowValueConstructor())
+            {
+                throw SqlUtil.newContextException(s.end(list),
+                    RESOURCE.illegalRowExpression());
+            }
+            return SqlStdOperatorTable.ROW.createCall(list);
+        } finally {
+            popRowValueStar();
         }
-        return SqlStdOperatorTable.ROW.createCall(list);
     }
 |
     (
-        <ROW> { rowSpan = span(); }
+        <ROW> { rowSpan = span(); pushRowValueStar(); }
     |   { rowSpan = null; }
     )
     list1 = ParenthesizedQueryOrCommaList(exprContext) {
-        if (rowSpan != null) {
-            // interpret as row constructor
-            return SqlStdOperatorTable.ROW.createCall(rowSpan.end(list1),
-                (List<SqlNode>) list1);
+        try {
+            if (rowSpan != null) {
+                // interpret as row constructor
+                return SqlStdOperatorTable.ROW.createCall(rowSpan.end(list1),
+                    (List<SqlNode>) list1);
+            }
+        } finally {
+            if (rowSpan != null) {
+                popRowValueStar();
+            }
         }
     }
     [
@@ -4391,6 +4416,9 @@ SqlNode AtomicRowExpression() :
         e = ContextVariable()
     |
         e = CompoundIdentifier()
+    |
+        LOOKAHEAD({ allowRowValueStar() })
+        <STAR> { return SqlIdentifier.star(getPos()); }
     |
         e = NewSpecification()
     |
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 33fd588a02..e3bc081496 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -1169,6 +1169,9 @@ ExInst<RuntimeException> 
multipleCapturingGroupsForRegexpFunctions(String value,
   @BaseMessage("Unequal number of entries in ROW expressions")
   ExInst<SqlValidatorException> unequalRowSizes();
 
+  @BaseMessage("Star is not allowed in ROW constructor outside of query 
context")
+  ExInst<SqlValidatorException> rowStarNotAllowed();
+
   @BaseMessage("Cannot infer return type for {0}; operand types: {1}")
   ExInst<SqlValidatorException> cannotInferReturnType(String operator, String 
types);
 
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 1f9415ff75..0fc14cf6dc 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
@@ -7412,11 +7412,69 @@ public static boolean isAmbiguousException(Exception 
ex) {
       CallCopyingArgHandler argHandler =
           new CallCopyingArgHandler(call, false);
       call.getOperator().acceptCall(this, call, true, argHandler);
-      final SqlNode result = argHandler.result();
+      final SqlNode result = expandStarInRow(argHandler.result());
       validator.setOriginal(result, call);
       return result;
     }
 
+    /**
+     * Expands star (*) within ROW constructors.
+     * For example, transforms {@code ROW(*)} or {@code ROW(t.*)} into
+     * {@code ROW(col1, col2, ...)} based on available columns in scope.
+     *
+     * @param node Node to potentially expand
+     * @return Original node if not a ROW with stars, otherwise expanded ROW
+     */
+    private SqlNode expandStarInRow(SqlNode node) {
+      if (!(node instanceof SqlCall)) {
+        return node;
+      }
+      final SqlCall call = (SqlCall) node;
+      if (call.getKind() != SqlKind.ROW) {
+        return node;
+      }
+      final SqlValidatorScope scope = getScope();
+      if (!(scope instanceof SelectScope)) {
+        // Check if any operand is a star identifier before throwing error
+        for (SqlNode operand : call.getOperandList()) {
+          if (operand instanceof SqlIdentifier
+              && ((SqlIdentifier) operand).isStar()) {
+            throw validator.newValidationError(node,
+                RESOURCE.rowStarNotAllowed());
+          }
+        }
+        return node;
+      }
+      final SelectScope selectScope = (SelectScope) scope;
+      final List<SqlNode> expandedOperands = new ArrayList<>();
+      boolean expanded = false;
+      for (SqlNode operand : call.getOperandList()) {
+        if (operand instanceof SqlIdentifier) {
+          final SqlIdentifier identifier = (SqlIdentifier) operand;
+          if (identifier.isStar()) {
+            final boolean expandedStar =
+                validator.expandStar(expandedOperands,
+                    validator.catalogReader.nameMatcher().createSet(),
+                    PairList.of(),
+                    false,
+                    selectScope,
+                    identifier);
+            if (!expandedStar) {
+              throw new AssertionError("Row star expansion failed for " + 
identifier);
+            }
+            expanded = true;
+            continue;
+          }
+        }
+        expandedOperands.add(operand);
+      }
+      if (!expanded) {
+        return node;
+      }
+      return SqlStdOperatorTable.ROW.createCall(
+          call.getParserPosition(), expandedOperands);
+    }
+
     protected SqlNode expandDynamicStar(SqlIdentifier id, SqlIdentifier fqId) {
       if (DynamicRecordType.isDynamicStarColName(Util.last(fqId.names))
           && !DynamicRecordType.isDynamicStarColName(Util.last(id.names))) {
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 6cff623598..cf600b98cf 100644
--- 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -381,6 +381,7 @@ AsofCannotBeCorrelated=ASOF JOIN does not support 
correlated subqueries
 UnknownRowField=ROW type does not have a field named ''{0}'': {1}
 IllegalRowIndexValue=ROW type does not have a field with index {0,number}; 
legal range is 1 to {1,number}
 UnequalRowSizes=Unequal number of entries in ROW expressions
+RowStarNotAllowed=Star is not allowed in ROW constructor outside of query 
context
 IllegalRowIndex=Index in ROW type does not have a constant integer or string 
value
 CannotInferReturnType=Cannot infer return type for {0}; operand types: {1}
 SelectByCannotWithGroupBy=SELECT BY cannot be used with GROUP BY
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 8a6877193d..98c7ea665e 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -2101,6 +2101,15 @@ void testLikeAndSimilarFails() {
         .columnType("INTEGER NOT NULL");
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7311";>[CALCITE-7311]
+   * Support the syntax ROW(*) to create a nested ROW type with all 
columns</a>. */
+  @Test void testRowWildcardExpansion() {
+    sql("select row(*) from emp").ok();
+    sql("select row(emp.*) from emp").ok();
+    sql("select row(emp.*, dept.*) from emp join dept on emp.deptno = 
dept.deptno").ok();
+  }
+
   @Test void testRowWithValidDot() {
     sql("select ((1,2),(3,4,5)).\"EXPR$1\".\"EXPR$2\"\n from dept")
         .columnType("INTEGER NOT NULL");
diff --git a/core/src/test/resources/sql/struct.iq 
b/core/src/test/resources/sql/struct.iq
index 0706980cc4..61c8921573 100644
--- a/core/src/test/resources/sql/struct.iq
+++ b/core/src/test/resources/sql/struct.iq
@@ -146,6 +146,45 @@ select
 
 !ok
 
+# [CALCITE-7311] Support the syntax ROW(*) to create a nested ROW type with 
all columns
+select row(*) from emp limit 1;
++----------------------------------------------------------+
+| EXPR$0                                                   |
++----------------------------------------------------------+
+| {7369, SMITH, CLERK, 7902, 1980-12-17, 800.00, null, 20} |
++----------------------------------------------------------+
+(1 row)
+
+!ok
+
+select row(emp.*) from emp limit 1;
++----------------------------------------------------------+
+| EXPR$0                                                   |
++----------------------------------------------------------+
+| {7369, SMITH, CLERK, 7902, 1980-12-17, 800.00, null, 20} |
++----------------------------------------------------------+
+(1 row)
+
+!ok
 
+select row(emp.*, dept.*) from emp join dept on emp.deptno = dept.deptno limit 
1;
++---------------------------------------------------------------------------------------+
+| EXPR$0                                                                       
         |
++---------------------------------------------------------------------------------------+
+| {7782, CLARK, MANAGER, 7839, 1981-06-09, 2450.00, null, 10, 10, ACCOUNTING, 
NEW YORK} |
++---------------------------------------------------------------------------------------+
+(1 row)
+
+!ok
+
+select row(d.*, row(d.*)) from dept d limit 1;
++--------------------------------------------------------+
+| EXPR$0                                                 |
++--------------------------------------------------------+
+| {10, ACCOUNTING, NEW YORK, {10, ACCOUNTING, NEW YORK}} |
++--------------------------------------------------------+
+(1 row)
+
+!ok
 
 # End struct.iq

Reply via email to