You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by mb...@apache.org on 2026/08/02 17:44:53 UTC

(calcite) 01/02: Stronger validation for JOIN UNNEST

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 <mb...@feldera.com>
AuthorDate: Fri Jun 19 00:01:30 2026 -0700

    Stronger validation for JOIN UNNEST
    
    Signed-off-by: Mihai Budiu <mb...@feldera.com>
---
 .../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");