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

dianfu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new b3f60e1eb14 [FLINK-40753][table-planner] Fix decorrelation of 
subqueries over VALUES
b3f60e1eb14 is described below

commit b3f60e1eb14ab38ae3fe391a0d6528561cd2a11e
Author: SulphurFH <[email protected]>
AuthorDate: Thu Sep 24 09:55:18 2026 +0800

    [FLINK-40753][table-planner] Fix decorrelation of subqueries over VALUES
    
    This closes #29275.
---
 .../plan/rules/logical/SubQueryDecorrelator.java   |   9 +-
 .../plan/rules/logical/SubQueryValuesTest.java     |  71 ++++++++
 .../plan/rules/logical/SubQueryValuesTest.xml      | 189 +++++++++++++++++++++
 3 files changed, 266 insertions(+), 3 deletions(-)

diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/SubQueryDecorrelator.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/SubQueryDecorrelator.java
index 9ac57e63973..035d76e667f 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/SubQueryDecorrelator.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/SubQueryDecorrelator.java
@@ -485,7 +485,10 @@ public class SubQueryDecorrelator extends RelShuttleImpl {
                 }
             }
             RelNode newProject = RelOptUtil.createProject(newInput, projects, 
false);
-            newProject = ((LogicalProject) 
newProject).withHints(rel.getHints());
+            // The projection may be simplified to a Values node.
+            if (newProject instanceof Hintable) {
+                newProject = ((Hintable) newProject).withHints(rel.getHints());
+            }
 
             final RexNode newCorCondition;
             if (frame.c != null) {
@@ -897,8 +900,8 @@ public class SubQueryDecorrelator extends RelShuttleImpl {
          * @param rel Values to be rewritten
          */
         public Frame decorrelateRel(Values rel) {
-            // There are no inputs, so rel does not need to be changed.
-            return null;
+            // Preserve the Values node while allowing its parents to be 
decorrelated.
+            return new Frame(rel, rel, null, 
identityMap(rel.getRowType().getFieldCount()));
         }
 
         public Frame decorrelateRel(LogicalCorrelate rel) {
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/rules/logical/SubQueryValuesTest.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/rules/logical/SubQueryValuesTest.java
new file mode 100644
index 00000000000..9733c02e40c
--- /dev/null
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/rules/logical/SubQueryValuesTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.table.planner.plan.rules.logical;
+
+import org.apache.flink.table.api.TableConfig;
+import org.apache.flink.table.planner.plan.optimize.program.FlinkBatchProgram;
+import org.apache.flink.table.planner.utils.BatchTableTestUtil;
+import org.apache.flink.table.planner.utils.TableTestBase;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/** Plan tests for correlated subqueries over SQL VALUES. */
+class SubQueryValuesTest extends TableTestBase {
+
+    private BatchTableTestUtil util;
+
+    @BeforeEach
+    void setup() {
+        util = batchTestUtil(TableConfig.getDefault());
+        util.buildBatchProgram(FlinkBatchProgram.DEFAULT_REWRITE());
+    }
+
+    @ParameterizedTest(name = "{0}")
+    @ValueSource(strings = {"EXISTS", "NOT EXISTS"})
+    void testValues(String predicate) {
+        util.verifyRelPlan(query(predicate));
+    }
+
+    @ParameterizedTest(name = "{0}")
+    @ValueSource(strings = {"EXISTS", "NOT EXISTS"})
+    void testProjectAndFilter(String predicate) {
+        util.verifyRelPlan(queryWithProjectAndFilter(predicate));
+    }
+
+    private static String query(String predicate) {
+        return "SELECT l.id FROM (VALUES (1), (2)) AS l(id) WHERE "
+                + predicate
+                + " (SELECT 1 FROM (VALUES (1), (2)) AS r(right_id) WHERE l.id 
= r.right_id)";
+    }
+
+    private static String queryWithProjectAndFilter(String predicate) {
+        return "SELECT l.id, l.amount "
+                + "FROM (VALUES (1, 10), (1, 30), (2, 20), (3, 40), (CAST(NULL 
AS INT), 50)) "
+                + "AS l(id, amount) "
+                + "WHERE "
+                + predicate
+                + " (SELECT 1 FROM ("
+                + "SELECT raw_id + 1 AS id, limit_value "
+                + "FROM (VALUES (25, 0), (10, 1), (CAST(NULL AS INT), 2)) AS 
v(limit_value, raw_id) "
+                + "WHERE limit_value IS NOT NULL) AS r "
+                + "WHERE l.id = r.id AND l.amount < r.limit_value)";
+    }
+}
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/rules/logical/SubQueryValuesTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/rules/logical/SubQueryValuesTest.xml
new file mode 100644
index 00000000000..971d553f6b1
--- /dev/null
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/rules/logical/SubQueryValuesTest.xml
@@ -0,0 +1,189 @@
+<?xml version="1.0" ?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to you under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<Root>
+  <TestCase name="testProjectAndFilter[EXISTS]">
+    <Resource name="sql">
+      <![CDATA[SELECT l.id, l.amount FROM (VALUES (1, 10), (1, 30), (2, 20), 
(3, 40), (CAST(NULL AS INT), 50)) AS l(id, amount) WHERE EXISTS (SELECT 1 FROM 
(SELECT raw_id + 1 AS id, limit_value FROM (VALUES (25, 0), (10, 1), (CAST(NULL 
AS INT), 2)) AS v(limit_value, raw_id) WHERE limit_value IS NOT NULL) AS r 
WHERE l.id = r.id AND l.amount < r.limit_value)]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(id=[$0], amount=[$1])
++- LogicalFilter(condition=[EXISTS({
+LogicalFilter(condition=[AND(=($cor0.id, $0), <($cor0.amount, $1))])
+  LogicalProject(id=[+($1, 1)], limit_value=[$0])
+    LogicalFilter(condition=[IS NOT NULL($0)])
+      LogicalProject(limit_value=[$0], raw_id=[$1])
+        LogicalUnion(all=[true])
+          LogicalProject(EXPR$0=[25], EXPR$1=[0])
+            LogicalValues(tuples=[[{ 0 }]])
+          LogicalProject(EXPR$0=[10], EXPR$1=[1])
+            LogicalValues(tuples=[[{ 0 }]])
+          LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[2])
+            LogicalValues(tuples=[[{ 0 }]])
+})], variablesSet=[[$cor0]])
+   +- LogicalProject(id=[$0], amount=[$1])
+      +- LogicalUnion(all=[true])
+         :- LogicalProject(EXPR$0=[1], EXPR$1=[10])
+         :  +- LogicalValues(tuples=[[{ 0 }]])
+         :- LogicalProject(EXPR$0=[1], EXPR$1=[30])
+         :  +- LogicalValues(tuples=[[{ 0 }]])
+         :- LogicalProject(EXPR$0=[2], EXPR$1=[20])
+         :  +- LogicalValues(tuples=[[{ 0 }]])
+         :- LogicalProject(EXPR$0=[3], EXPR$1=[40])
+         :  +- LogicalValues(tuples=[[{ 0 }]])
+         +- LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[50])
+            +- LogicalValues(tuples=[[{ 0 }]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+LogicalProject(id=[$0], amount=[$1])
++- LogicalJoin(condition=[AND(=($0, $2), <($1, $3))], joinType=[semi])
+   :- LogicalProject(id=[$0], amount=[$1])
+   :  +- LogicalUnion(all=[true])
+   :     :- LogicalProject(EXPR$0=[1], EXPR$1=[10])
+   :     :  +- LogicalValues(tuples=[[{ 0 }]])
+   :     :- LogicalProject(EXPR$0=[1], EXPR$1=[30])
+   :     :  +- LogicalValues(tuples=[[{ 0 }]])
+   :     :- LogicalProject(EXPR$0=[2], EXPR$1=[20])
+   :     :  +- LogicalValues(tuples=[[{ 0 }]])
+   :     :- LogicalProject(EXPR$0=[3], EXPR$1=[40])
+   :     :  +- LogicalValues(tuples=[[{ 0 }]])
+   :     +- LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[50])
+   :        +- LogicalValues(tuples=[[{ 0 }]])
+   +- LogicalFilter(condition=[true])
+      +- LogicalProject(id=[+($1, 1)], limit_value=[$0])
+         +- LogicalFilter(condition=[IS NOT NULL($0)])
+            +- LogicalProject(limit_value=[$0], raw_id=[$1])
+               +- LogicalUnion(all=[true])
+                  :- LogicalValues(tuples=[[{ 25, 0 }]])
+                  :- LogicalValues(tuples=[[{ 10, 1 }]])
+                  +- LogicalValues(tuples=[[{ null, 2 }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testProjectAndFilter[NOT EXISTS]">
+    <Resource name="sql">
+      <![CDATA[SELECT l.id, l.amount FROM (VALUES (1, 10), (1, 30), (2, 20), 
(3, 40), (CAST(NULL AS INT), 50)) AS l(id, amount) WHERE NOT EXISTS (SELECT 1 
FROM (SELECT raw_id + 1 AS id, limit_value FROM (VALUES (25, 0), (10, 1), 
(CAST(NULL AS INT), 2)) AS v(limit_value, raw_id) WHERE limit_value IS NOT 
NULL) AS r WHERE l.id = r.id AND l.amount < r.limit_value)]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(id=[$0], amount=[$1])
++- LogicalFilter(condition=[NOT(EXISTS({
+LogicalFilter(condition=[AND(=($cor0.id, $0), <($cor0.amount, $1))])
+  LogicalProject(id=[+($1, 1)], limit_value=[$0])
+    LogicalFilter(condition=[IS NOT NULL($0)])
+      LogicalProject(limit_value=[$0], raw_id=[$1])
+        LogicalUnion(all=[true])
+          LogicalProject(EXPR$0=[25], EXPR$1=[0])
+            LogicalValues(tuples=[[{ 0 }]])
+          LogicalProject(EXPR$0=[10], EXPR$1=[1])
+            LogicalValues(tuples=[[{ 0 }]])
+          LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[2])
+            LogicalValues(tuples=[[{ 0 }]])
+}))], variablesSet=[[$cor0]])
+   +- LogicalProject(id=[$0], amount=[$1])
+      +- LogicalUnion(all=[true])
+         :- LogicalProject(EXPR$0=[1], EXPR$1=[10])
+         :  +- LogicalValues(tuples=[[{ 0 }]])
+         :- LogicalProject(EXPR$0=[1], EXPR$1=[30])
+         :  +- LogicalValues(tuples=[[{ 0 }]])
+         :- LogicalProject(EXPR$0=[2], EXPR$1=[20])
+         :  +- LogicalValues(tuples=[[{ 0 }]])
+         :- LogicalProject(EXPR$0=[3], EXPR$1=[40])
+         :  +- LogicalValues(tuples=[[{ 0 }]])
+         +- LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[50])
+            +- LogicalValues(tuples=[[{ 0 }]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+LogicalProject(id=[$0], amount=[$1])
++- LogicalJoin(condition=[AND(=($0, $2), <($1, $3))], joinType=[anti])
+   :- LogicalProject(id=[$0], amount=[$1])
+   :  +- LogicalUnion(all=[true])
+   :     :- LogicalProject(EXPR$0=[1], EXPR$1=[10])
+   :     :  +- LogicalValues(tuples=[[{ 0 }]])
+   :     :- LogicalProject(EXPR$0=[1], EXPR$1=[30])
+   :     :  +- LogicalValues(tuples=[[{ 0 }]])
+   :     :- LogicalProject(EXPR$0=[2], EXPR$1=[20])
+   :     :  +- LogicalValues(tuples=[[{ 0 }]])
+   :     :- LogicalProject(EXPR$0=[3], EXPR$1=[40])
+   :     :  +- LogicalValues(tuples=[[{ 0 }]])
+   :     +- LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[50])
+   :        +- LogicalValues(tuples=[[{ 0 }]])
+   +- LogicalFilter(condition=[true])
+      +- LogicalProject(id=[+($1, 1)], limit_value=[$0])
+         +- LogicalFilter(condition=[IS NOT NULL($0)])
+            +- LogicalProject(limit_value=[$0], raw_id=[$1])
+               +- LogicalUnion(all=[true])
+                  :- LogicalValues(tuples=[[{ 25, 0 }]])
+                  :- LogicalValues(tuples=[[{ 10, 1 }]])
+                  +- LogicalValues(tuples=[[{ null, 2 }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testValues[EXISTS]">
+    <Resource name="sql">
+      <![CDATA[SELECT l.id FROM (VALUES (1), (2)) AS l(id) WHERE EXISTS 
(SELECT 1 FROM (VALUES (1), (2)) AS r(right_id) WHERE l.id = r.right_id)]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(id=[$0])
++- LogicalFilter(condition=[EXISTS({
+LogicalFilter(condition=[=($cor0.id, $0)])
+  LogicalValues(tuples=[[{ 1 }, { 2 }]])
+})], variablesSet=[[$cor0]])
+   +- LogicalValues(tuples=[[{ 1 }, { 2 }]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+LogicalProject(id=[$0])
++- LogicalJoin(condition=[=($0, $1)], joinType=[semi])
+   :- LogicalValues(tuples=[[{ 1 }, { 2 }]])
+   +- LogicalFilter(condition=[true])
+      +- LogicalValues(tuples=[[{ 1 }, { 2 }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testValues[NOT EXISTS]">
+    <Resource name="sql">
+      <![CDATA[SELECT l.id FROM (VALUES (1), (2)) AS l(id) WHERE NOT EXISTS 
(SELECT 1 FROM (VALUES (1), (2)) AS r(right_id) WHERE l.id = r.right_id)]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(id=[$0])
++- LogicalFilter(condition=[NOT(EXISTS({
+LogicalFilter(condition=[=($cor0.id, $0)])
+  LogicalValues(tuples=[[{ 1 }, { 2 }]])
+}))], variablesSet=[[$cor0]])
+   +- LogicalValues(tuples=[[{ 1 }, { 2 }]])
+]]>
+    </Resource>
+    <Resource name="optimized rel plan">
+      <![CDATA[
+LogicalProject(id=[$0])
++- LogicalJoin(condition=[=($0, $1)], joinType=[anti])
+   :- LogicalValues(tuples=[[{ 1 }, { 2 }]])
+   +- LogicalFilter(condition=[true])
+      +- LogicalValues(tuples=[[{ 1 }, { 2 }]])
+]]>
+    </Resource>
+  </TestCase>
+</Root>

Reply via email to