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

mihaibudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 0de42de0006b911f107ba31df20edb6b60f00873
Author: Mihai Budiu <[email protected]>
AuthorDate: Fri Jun 19 00:01:30 2026 -0700

    Stronger validation for JOIN UNNEST
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../apache/calcite/runtime/CalciteResource.java    |  3 +++
 .../calcite/sql/validate/SqlValidatorImpl.java     | 30 ++++++++++++++++++++++
 .../calcite/runtime/CalciteResource.properties     |  1 +
 .../org/apache/calcite/test/SqlValidatorTest.java  | 24 +++++++++++++++++
 4 files changed, 58 insertions(+)

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 c6e1a4dbdc..c5047574a3 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -337,6 +337,9 @@ ExInst<CalciteException> invalidCompare(String a0, String 
a1, String a2,
   @BaseMessage("Cannot specify condition (NATURAL keyword, or ON or USING 
clause) following CROSS JOIN")
   ExInst<SqlValidatorException> crossJoinDisallowsCondition();
 
+  @BaseMessage("UNNEST is only supported with INNER, LEFT, CROSS, or COMMA 
join, not ''{0}''")
+  ExInst<SqlValidatorException> unnestInvalidJoinType(String a0);
+
   @BaseMessage("Cannot specify NATURAL keyword with ON or USING clause")
   ExInst<SqlValidatorException> naturalDisallowsOnOrUsing();
 
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 023de1b77f..7021696b67 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
@@ -4116,6 +4116,20 @@ protected void validateJoin(SqlJoin join, 
SqlValidatorScope scope) {
       }
     }
 
+    // UNNEST on the right side is only meaningful with INNER, LEFT, CROSS, or 
COMMA.
+    if (isUnnestNode(right)) {
+      switch (joinType) {
+      case INNER:
+      case LEFT:
+      case CROSS:
+      case COMMA:
+        break;
+      default:
+        throw newValidationError(join.getJoinTypeNode(),
+            RESOURCE.unnestInvalidJoinType(joinType.name()));
+      }
+    }
+
     // Which join types require/allow a ON/USING condition, or allow
     // a NATURAL keyword?
     switch (joinType) {
@@ -4192,6 +4206,22 @@ protected void validateJoin(SqlJoin join, 
SqlValidatorScope scope) {
     }
   }
 
+  /**
+   * Returns whether {@code node} is (or wraps, via AS or LATERAL) an
+   * {@code UNNEST} call.
+   */
+  private static boolean isUnnestNode(SqlNode node) {
+    switch (node.getKind()) {
+    case UNNEST:
+      return true;
+    case AS:
+    case LATERAL:
+      return isUnnestNode(((SqlCall) node).operand(0));
+    default:
+      return false;
+    }
+  }
+
   /**
    * Shuttle which determines whether all SqlCalls that are
    * comparisons are comparing columns from both namespaces.
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 f4f16d7326..a90099d7cb 100644
--- 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -115,6 +115,7 @@ AliasListDuplicate=Duplicate name ''{0}'' in column alias 
list
 JoinRequiresCondition=INNER, LEFT, RIGHT, FULL, or ASOF join requires a 
condition (NATURAL keyword or ON or USING clause)
 DisallowsQualifyingCommonColumn=Cannot qualify common column ''{0}''
 CrossJoinDisallowsCondition=Cannot specify condition (NATURAL keyword, or ON 
or USING clause) following CROSS JOIN
+UnnestInvalidJoinType=UNNEST is only supported with INNER, LEFT, CROSS, or 
COMMA join, not ''{0}''
 NaturalDisallowsOnOrUsing=Cannot specify NATURAL keyword with ON or USING 
clause
 ColumnInUsingNotUnique=Column name ''{0}'' in NATURAL join or USING clause is 
not unique on one side of join
 NaturalOrUsingColumnNotCompatible=Column ''{0}'' matched using NATURAL keyword 
or USING clause has incompatible types: cannot compare ''{1}'' to ''{2}''
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 8f95099ab9..018c27e898 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -9855,6 +9855,30 @@ void testGroupExpressionEquivalenceParams() {
         .fails("Column 'ORDINALITY' not found in any table");
   }
 
+  /** UNNEST is valid with INNER, LEFT, CROSS, and COMMA joins;
+   *  all other join kinds must be rejected by the validator. */
+  @Test void testUnnestJoinType() {
+    // Allowed join kinds — these must all validate without error.
+    sql("select * from dept inner join unnest(array[1, 2]) as u(x) on 
true").ok();
+    sql("select * from dept left join unnest(array[1, 2]) as u(x) on 
true").ok();
+    sql("select * from dept cross join unnest(array[1, 2]) as u(x)").ok();
+    sql("select * from dept, unnest(array[1, 2]) as u(x)").ok();
+
+    // LATERAL wrapping must also be allowed for valid join kinds.
+    sql("select * from dept cross join lateral unnest(array[1, 2]) as 
u(x)").ok();
+    sql("select * from dept left join lateral unnest(array[1, 2]) as u(x) on 
true").ok();
+
+    // Disallowed join kinds — validator must reject these.
+    sql("select * from dept right ^join^ unnest(array[1, 2]) as u(x) on true")
+        .fails("UNNEST is only supported with INNER, LEFT, CROSS, or COMMA 
join, not 'RIGHT'");
+    sql("select * from dept full ^join^ unnest(array[1, 2]) as u(x) on true")
+        .fails("UNNEST is only supported with INNER, LEFT, CROSS, or COMMA 
join, not 'FULL'");
+
+    // LATERAL wrapping must also be rejected for invalid join kinds.
+    sql("select * from dept right ^join^ lateral unnest(array[1, 2]) as u(x) 
on true")
+        .fails("UNNEST is only supported with INNER, LEFT, CROSS, or COMMA 
join, not 'RIGHT'");
+  }
+
   @Test void unnestMapMustNameColumnsKeyAndValueWhenNotAliased() {
     sql("select * from unnest(map[1, 12, 2, 22])")
         .type("RecordType(INTEGER NOT NULL KEY, INTEGER NOT NULL VALUE) NOT 
NULL");

Reply via email to