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

snuyanzin 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 5ff1668728 [CALCITE-7217] `LATERAL` is lost after validation
5ff1668728 is described below

commit 5ff16687289b6279b6b5bcf12d2e3ddd12a2c9e9
Author: Sergey Nuyanzin <[email protected]>
AuthorDate: Wed Oct 8 01:34:19 2025 +0200

    [CALCITE-7217] `LATERAL` is lost after validation
---
 .../calcite/sql/validate/SqlValidatorImpl.java     | 10 ++++-
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  9 ++++
 .../org/apache/calcite/test/SqlValidatorTest.java  | 51 ++++++++++++++++++++++
 3 files changed, 68 insertions(+), 2 deletions(-)

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 f930243f35..512898f36d 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
@@ -1363,6 +1363,7 @@ private SqlValidatorScope getScopeOrThrow(SqlNode node) {
       }
       // fall through
     case TABLE_REF:
+    case LATERAL:
     case SNAPSHOT:
     case OVER:
     case COLLECTION_TABLE:
@@ -2621,8 +2622,8 @@ private SqlNode registerFrom(
       return newNode;
 
     case LATERAL:
-      return registerFrom(
-          parentScope,
+      SqlBasicCall sbc = (SqlBasicCall) node;
+      registerFrom(parentScope,
           usingScope,
           register,
           ((SqlCall) node).operand(0),
@@ -2631,6 +2632,11 @@ private SqlNode registerFrom(
           extendList,
           forceNullable,
           true);
+      // Put the usingScope which is a JoinScope,
+      // in order to make visible the left items
+      // of the JOIN tree.
+      scopes.put(node, usingScope);
+      return sbc;
 
     case COLLECTION_TABLE:
       call = (SqlCall) node;
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java 
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 6b0c67dc13..87d2b47201 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -2508,6 +2508,15 @@ protected void convertFrom(
       convertCollectionTable(bb, call2);
       return;
 
+    case LATERAL:
+      call = (SqlCall) from;
+
+      // Extract and analyze lateral part of join call.
+      assert call.getOperandList().size() == 1;
+      final SqlCall callLateral = call.operand(0);
+      convertFrom(bb, callLateral, fieldNames);
+      return;
+
     default:
       throw new AssertionError("not a join operator " + from);
     }
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 b1c7642109..e353447d3c 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -9107,6 +9107,57 @@ void testGroupExpressionEquivalenceParams() {
         .fails("Duplicate relation name 'EMP' in FROM clause");
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7217";>[CALCITE-7217]
+   * LATERAL is lost after validation</a>. */
+  @Test void testCollectionTableWithLateralRewrite() {
+    sql("select * from emp, lateral table(ramp(emp.deptno)), dept")
+        .rewritesTo("SELECT *\n"
+            + "FROM `EMP`,\n"
+            + "LATERAL TABLE(RAMP(`EMP`.`DEPTNO`)),\n"
+            + "`DEPT`");
+    // As above, with alias
+    sql("select * from emp, lateral table(ramp(emp.deptno)) as t(a), dept")
+        .rewritesTo("SELECT *\n"
+            +  "FROM `EMP`,\n"
+            +  "LATERAL TABLE(RAMP(`EMP`.`DEPTNO`)) AS `T` (`A`),\n"
+            +  "`DEPT`");
+    sql("select *\n"
+        + "from dept,\n"
+        + "  lateral table(ramp(deptno))\n"
+        + "  cross join (values ('A'), ('B'))")
+        .rewritesTo("SELECT *\n"
+            +  "FROM `DEPT`,\n"
+            +  "LATERAL TABLE(RAMP(`DEPTNO`))\n"
+            +  "CROSS JOIN (VALUES ROW('A'),\n"
+            +  "ROW('B'))");
+    // As above, using NATURAL JOIN
+    sql("select *\n"
+        + "from dept,\n"
+        + "  lateral table(ramp(deptno))\n"
+        + "  natural join emp")
+        .rewritesTo("SELECT *\n"
+            +  "FROM `DEPT`,\n"
+            +  "LATERAL TABLE(RAMP(`DEPTNO`))\n"
+            +  "NATURAL INNER JOIN `EMP`");
+    // As above, using comma
+    sql("select *\n"
+        + "from emp,\n"
+        + "  lateral (select * from dept where dept.deptno = emp.deptno),\n"
+        + "  emp as e2")
+        .rewritesTo("SELECT *\n"
+            +  "FROM `EMP`,\n"
+            +  "LATERAL (SELECT *\n"
+            +  "FROM `DEPT`\n"
+            +  "WHERE `DEPT`.`DEPTNO` = `EMP`.`DEPTNO`),\n"
+            +  "`EMP` AS `E2`");
+    // LATERAL in left part of join
+    sql("select * from lateral table(ramp(1234)), emp")
+        .rewritesTo("SELECT *\n"
+            +  "FROM LATERAL TABLE(RAMP(1234)),\n"
+            +  "`EMP`");
+  }
+
   @Test void testCollectionTableWithCursorParam() {
     sql("select * from table(dedup(cursor(select * from emp),'ename'))")
         .type("RecordType(VARCHAR(1024) NOT NULL NAME) NOT NULL");

Reply via email to