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