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

mbudiu 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 a56d069f76 [CALCITE-6221] JDBC adapter generates invalid query when 
the same table is joined multiple times
a56d069f76 is described below

commit a56d069f76b26ef9619968846313d00a34ab8d1d
Author: Ulrich Kramer <[email protected]>
AuthorDate: Thu Feb 1 14:52:51 2024 +0100

    [CALCITE-6221] JDBC adapter generates invalid query when the same table is 
joined multiple times
---
 .../calcite/rel/rel2sql/RelToSqlConverter.java     |  34 +++++-
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java | 118 ++++++++++++++++++++-
 .../org/apache/calcite/test/JdbcAdapterTest.java   |  46 ++++++++
 3 files changed, 195 insertions(+), 3 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java 
b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
index f80c358bac..353e2fa3a4 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
@@ -110,6 +110,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -121,6 +122,7 @@ import java.util.stream.Stream;
 import static com.google.common.base.Preconditions.checkArgument;
 
 import static org.apache.calcite.rex.RexLiteral.stringValue;
+import static org.apache.calcite.util.Util.last;
 
 import static java.util.Objects.requireNonNull;
 
@@ -441,11 +443,41 @@ public class RelToSqlConverter extends SqlImplementor
       final Result x = visitInput(e, 0, Clause.WHERE);
       parseCorrelTable(e, x);
       final Builder builder = x.builder(e);
+      if (input instanceof Join) {
+        final Context context = x.qualifiedContext();
+        if (selectListRequired(context)) {
+          final ImmutableList.Builder<SqlNode> selectList = 
ImmutableList.builder();
+          // Fieldnames are unique since they are created by 
SqlValidatorUtil.deriveJoinRowType()
+          final List<String> uniqueFieldNames = 
input.getRowType().getFieldNames();
+          for (int i = 0; i < context.fieldCount; i++) {
+            final SqlNode field = context.field(i);
+            final String fieldName = uniqueFieldNames.get(i);
+            selectList.add(
+                SqlStdOperatorTable.AS.createCall(POS, field,
+                new SqlIdentifier(fieldName, POS)));
+          }
+          builder.setSelect(new SqlNodeList(selectList.build(), POS));
+        }
+      }
       builder.setWhere(builder.context.toSql(null, e.getCondition()));
       return builder.result();
     }
   }
 
+  private static boolean selectListRequired(Context context) {
+    Set<String> uniqueFieldNames = new HashSet<>();
+    for (SqlNode node : context.fieldList()) {
+      if (node instanceof SqlIdentifier) {
+        SqlIdentifier field = (SqlIdentifier) node;
+        if (uniqueFieldNames.contains(last(field.names))) {
+          return true;
+        }
+        uniqueFieldNames.add(last(field.names));
+      }
+    }
+    return false;
+  }
+
   /** Visits a Project; called by {@link #dispatch} via reflection. */
   public Result visit(Project e) {
     // If the input is a Sort, wrap SELECT is not required.
@@ -1114,7 +1146,7 @@ public class RelToSqlConverter extends SqlImplementor
       final int nUpdateFiled = updateColumnList.size();
       if (nUpdateFiled != 0) {
         final SqlNodeList expressionList =
-            Util.last(selectList, nUpdateFiled).stream()
+            last(selectList, nUpdateFiled).stream()
                 .collect(SqlNodeList.toList());
         update =
             new SqlUpdate(POS, sqlTargetTable,
diff --git 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index a9701591a9..2349380ed2 100644
--- 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -3871,7 +3871,64 @@ class RelToSqlConverterTest {
         + "  on p.\"product_class_id\" = pc.\"product_class_id\"\n"
         + "where c.\"city\" = 'San Francisco'\n"
         + "and pc.\"product_department\" = 'Snacks'\n";
-    final String expected = "SELECT *\n"
+    final String expected = "SELECT "
+        + "\"sales_fact_1997\".\"product_id\" AS \"product_id\", "
+        + "\"sales_fact_1997\".\"time_id\" AS \"time_id\", "
+        + "\"sales_fact_1997\".\"customer_id\" AS \"customer_id\", "
+        + "\"sales_fact_1997\".\"promotion_id\" AS \"promotion_id\", "
+        + "\"sales_fact_1997\".\"store_id\" AS \"store_id\", "
+        + "\"sales_fact_1997\".\"store_sales\" AS \"store_sales\", "
+        + "\"sales_fact_1997\".\"store_cost\" AS \"store_cost\", "
+        + "\"sales_fact_1997\".\"unit_sales\" AS \"unit_sales\", "
+        + "\"customer\".\"customer_id\" AS \"customer_id0\", "
+        + "\"customer\".\"account_num\" AS \"account_num\", "
+        + "\"customer\".\"lname\" AS \"lname\", "
+        + "\"customer\".\"fname\" AS \"fname\", "
+        + "\"customer\".\"mi\" AS \"mi\", "
+        + "\"customer\".\"address1\" AS \"address1\", "
+        + "\"customer\".\"address2\" AS \"address2\", "
+        + "\"customer\".\"address3\" AS \"address3\", "
+        + "\"customer\".\"address4\" AS \"address4\", "
+        + "\"customer\".\"city\" AS \"city\", "
+        + "\"customer\".\"state_province\" AS \"state_province\", "
+        + "\"customer\".\"postal_code\" AS \"postal_code\", "
+        + "\"customer\".\"country\" AS \"country\", "
+        + "\"customer\".\"customer_region_id\" AS \"customer_region_id\", "
+        + "\"customer\".\"phone1\" AS \"phone1\", "
+        + "\"customer\".\"phone2\" AS \"phone2\", "
+        + "\"customer\".\"birthdate\" AS \"birthdate\", "
+        + "\"customer\".\"marital_status\" AS \"marital_status\", "
+        + "\"customer\".\"yearly_income\" AS \"yearly_income\", "
+        + "\"customer\".\"gender\" AS \"gender\", "
+        + "\"customer\".\"total_children\" AS \"total_children\", "
+        + "\"customer\".\"num_children_at_home\" AS \"num_children_at_home\", "
+        + "\"customer\".\"education\" AS \"education\", "
+        + "\"customer\".\"date_accnt_opened\" AS \"date_accnt_opened\", "
+        + "\"customer\".\"member_card\" AS \"member_card\", "
+        + "\"customer\".\"occupation\" AS \"occupation\", "
+        + "\"customer\".\"houseowner\" AS \"houseowner\", "
+        + "\"customer\".\"num_cars_owned\" AS \"num_cars_owned\", "
+        + "\"customer\".\"fullname\" AS \"fullname\", "
+        + "\"product\".\"product_class_id\" AS \"product_class_id\", "
+        + "\"product\".\"product_id\" AS \"product_id0\", "
+        + "\"product\".\"brand_name\" AS \"brand_name\", "
+        + "\"product\".\"product_name\" AS \"product_name\", "
+        + "\"product\".\"SKU\" AS \"SKU\", "
+        + "\"product\".\"SRP\" AS \"SRP\", "
+        + "\"product\".\"gross_weight\" AS \"gross_weight\", "
+        + "\"product\".\"net_weight\" AS \"net_weight\", "
+        + "\"product\".\"recyclable_package\" AS \"recyclable_package\", "
+        + "\"product\".\"low_fat\" AS \"low_fat\", "
+        + "\"product\".\"units_per_case\" AS \"units_per_case\", "
+        + "\"product\".\"cases_per_pallet\" AS \"cases_per_pallet\", "
+        + "\"product\".\"shelf_width\" AS \"shelf_width\", "
+        + "\"product\".\"shelf_height\" AS \"shelf_height\", "
+        + "\"product\".\"shelf_depth\" AS \"shelf_depth\", "
+        + "\"product_class\".\"product_class_id\" AS \"product_class_id0\", "
+        + "\"product_class\".\"product_subcategory\" AS 
\"product_subcategory\", "
+        + "\"product_class\".\"product_category\" AS \"product_category\", "
+        + "\"product_class\".\"product_department\" AS \"product_department\", 
"
+        + "\"product_class\".\"product_family\" AS \"product_family\"\n"
         + "FROM \"foodmart\".\"sales_fact_1997\"\n"
         + "INNER JOIN \"foodmart\".\"customer\" "
         + "ON \"sales_fact_1997\".\"customer_id\" = \"customer\""
@@ -5800,7 +5857,64 @@ class RelToSqlConverterTest {
         + "      up as up.\"net_weight\" > prev(up.\"net_weight\")\n"
         + "  ) mr order by MR.\"net_weight\"";
     final String expected = "SELECT *\n"
-        + "FROM (SELECT *\n"
+        + "FROM (SELECT "
+        + "\"sales_fact_1997\".\"product_id\" AS \"product_id\", "
+        + "\"sales_fact_1997\".\"time_id\" AS \"time_id\", "
+        + "\"sales_fact_1997\".\"customer_id\" AS \"customer_id\", "
+        + "\"sales_fact_1997\".\"promotion_id\" AS \"promotion_id\", "
+        + "\"sales_fact_1997\".\"store_id\" AS \"store_id\", "
+        + "\"sales_fact_1997\".\"store_sales\" AS \"store_sales\", "
+        + "\"sales_fact_1997\".\"store_cost\" AS \"store_cost\", "
+        + "\"sales_fact_1997\".\"unit_sales\" AS \"unit_sales\", "
+        + "\"customer\".\"customer_id\" AS \"customer_id0\", "
+        + "\"customer\".\"account_num\" AS \"account_num\", "
+        + "\"customer\".\"lname\" AS \"lname\", "
+        + "\"customer\".\"fname\" AS \"fname\", "
+        + "\"customer\".\"mi\" AS \"mi\", "
+        + "\"customer\".\"address1\" AS \"address1\", "
+        + "\"customer\".\"address2\" AS \"address2\", "
+        + "\"customer\".\"address3\" AS \"address3\", "
+        + "\"customer\".\"address4\" AS \"address4\", "
+        + "\"customer\".\"city\" AS \"city\", "
+        + "\"customer\".\"state_province\" AS \"state_province\", "
+        + "\"customer\".\"postal_code\" AS \"postal_code\", "
+        + "\"customer\".\"country\" AS \"country\", "
+        + "\"customer\".\"customer_region_id\" AS \"customer_region_id\", "
+        + "\"customer\".\"phone1\" AS \"phone1\", "
+        + "\"customer\".\"phone2\" AS \"phone2\", "
+        + "\"customer\".\"birthdate\" AS \"birthdate\", "
+        + "\"customer\".\"marital_status\" AS \"marital_status\", "
+        + "\"customer\".\"yearly_income\" AS \"yearly_income\", "
+        + "\"customer\".\"gender\" AS \"gender\", "
+        + "\"customer\".\"total_children\" AS \"total_children\", "
+        + "\"customer\".\"num_children_at_home\" AS \"num_children_at_home\", "
+        + "\"customer\".\"education\" AS \"education\", "
+        + "\"customer\".\"date_accnt_opened\" AS \"date_accnt_opened\", "
+        + "\"customer\".\"member_card\" AS \"member_card\", "
+        + "\"customer\".\"occupation\" AS \"occupation\", "
+        + "\"customer\".\"houseowner\" AS \"houseowner\", "
+        + "\"customer\".\"num_cars_owned\" AS \"num_cars_owned\", "
+        + "\"customer\".\"fullname\" AS \"fullname\", "
+        + "\"product\".\"product_class_id\" AS \"product_class_id\", "
+        + "\"product\".\"product_id\" AS \"product_id0\", "
+        + "\"product\".\"brand_name\" AS \"brand_name\", "
+        + "\"product\".\"product_name\" AS \"product_name\", "
+        + "\"product\".\"SKU\" AS \"SKU\", "
+        + "\"product\".\"SRP\" AS \"SRP\", "
+        + "\"product\".\"gross_weight\" AS \"gross_weight\", "
+        + "\"product\".\"net_weight\" AS \"net_weight\", "
+        + "\"product\".\"recyclable_package\" AS \"recyclable_package\", "
+        + "\"product\".\"low_fat\" AS \"low_fat\", "
+        + "\"product\".\"units_per_case\" AS \"units_per_case\", "
+        + "\"product\".\"cases_per_pallet\" AS \"cases_per_pallet\", "
+        + "\"product\".\"shelf_width\" AS \"shelf_width\", "
+        + "\"product\".\"shelf_height\" AS \"shelf_height\", "
+        + "\"product\".\"shelf_depth\" AS \"shelf_depth\", "
+        + "\"product_class\".\"product_class_id\" AS \"product_class_id0\", "
+        + "\"product_class\".\"product_subcategory\" AS 
\"product_subcategory\", "
+        + "\"product_class\".\"product_category\" AS \"product_category\", "
+        + "\"product_class\".\"product_department\" AS \"product_department\", 
"
+        + "\"product_class\".\"product_family\" AS \"product_family\"\n"
         + "FROM \"foodmart\".\"sales_fact_1997\"\n"
         + "INNER JOIN \"foodmart\".\"customer\" "
         + "ON \"sales_fact_1997\".\"customer_id\" = 
\"customer\".\"customer_id\"\n"
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java 
b/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
index 171dea060c..7a35870d12 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
@@ -1378,6 +1378,52 @@ class JdbcAdapterTest {
     });
   }
 
+  /**
+   * Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6221";>[CALCITE-6221]</a>.*/
+  @Test void testUnknownColumn() {
+    CalciteAssert.model(JdbcTest.SCOTT_MODEL)
+        .query("SELECT\n"
+          + "    \"content-format-owner\",\n"
+          + "    \"content-owner\"\n"
+          + "FROM\n"
+          + "    (\n"
+          + "        SELECT\n"
+          + "            d1.dname AS \"content-format-owner\",\n"
+          + "            d2.dname || ' ' AS \"content-owner\"\n"
+          + "        FROM\n"
+          + "            scott.emp e1\n"
+          + "            left outer join scott.dept d1 on e1.deptno = 
d1.deptno\n"
+          + "            left outer join scott.dept d2 on e1.deptno = 
d2.deptno\n"
+          + "            left outer join scott.emp e2 on e1.deptno = 
e2.deptno\n"
+          + "        GROUP BY\n"
+          + "            d1.dname,\n"
+          + "            d2.dname\n"
+          + "    )\n"
+          + "WHERE\n"
+          + "    \"content-owner\" IN (?)")
+        .planHasSql("SELECT "
+            + "\"t2\".\"DNAME\" AS \"content-format-owner\", "
+            + "\"t2\".\"DNAME0\" || ' ' AS \"content-owner\"\n"
+            + "FROM (SELECT \"t\".\"DEPTNO\" AS \"DEPTNO\", "
+            + "\"t0\".\"DEPTNO\" AS \"DEPTNO0\", "
+            + "\"t0\".\"DNAME\" AS \"DNAME\", "
+            + "\"t1\".\"DEPTNO\" AS \"DEPTNO1\", "
+            + "\"t1\".\"DNAME\" AS \"DNAME0\"\n"
+            + "FROM (SELECT \"DEPTNO\"\n"
+            + "FROM \"SCOTT\".\"EMP\") AS \"t\"\n"
+            + "LEFT JOIN (SELECT \"DEPTNO\", \"DNAME\"\n"
+            + "FROM \"SCOTT\".\"DEPT\") AS \"t0\" ON \"t\".\"DEPTNO\" = 
\"t0\".\"DEPTNO\"\n"
+            + "LEFT JOIN (SELECT \"DEPTNO\", \"DNAME\"\n"
+            + "FROM \"SCOTT\".\"DEPT\") AS \"t1\" "
+            + "ON \"t\".\"DEPTNO\" = \"t1\".\"DEPTNO\"\n"
+            + "WHERE \"t1\".\"DNAME\" || ' ' = ?) AS \"t2\"\n"
+            + "LEFT JOIN (SELECT \"DEPTNO\"\n"
+            + "FROM \"SCOTT\".\"EMP\") AS \"t3\" "
+            + "ON \"t2\".\"DEPTNO\" = \"t3\".\"DEPTNO\"\n"
+            + "GROUP BY \"t2\".\"DNAME\", \"t2\".\"DNAME0\"")
+        .runs();
+  }
   /** Acquires a lock, and releases it when closed. */
   static class LockWrapper implements AutoCloseable {
     private final Lock lock;

Reply via email to