github-actions[bot] commented on code in PR #63763:
URL: https://github.com/apache/doris/pull/63763#discussion_r3490531919


##########
fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/AggScalarSubQueryToWindowFunctionTest.java:
##########
@@ -338,6 +362,1213 @@ public void testNotMatchTheRule() {
         }
     }
 
+    @Test
+    public void testWindowPartitionsByOuterOnlyRelationSlots() throws 
Exception {
+        // Use TPC-H Q17: correlated table is part (p_partkey is unique via 
constraint),
+        // fact table is lineitem. The window PARTITION BY should contain all 
output
+        // columns of the correlated table (part).
+        String sql = TPCHUtils.Q17;
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .applyTopDown(new PushDownFilterThroughProject())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        List<LogicalWindow<Plan>> windows = 
plan.collectToList(LogicalWindow.class::isInstance);
+        Assertions.assertEquals(1, windows.size());
+
+        LogicalWindow<Plan> window = windows.get(0);
+        List<NamedExpression> windowExpressions = 
window.getWindowExpressions();
+        Assertions.assertEquals(1, windowExpressions.size());
+
+        WindowExpression windowExpression = (WindowExpression) 
windowExpressions.get(0).child(0);
+        Set<String> partitionKeys = 
windowExpression.getPartitionKeys().stream()
+                .map(Expression.class::cast)
+                .filter(Slot.class::isInstance)
+                .map(Slot.class::cast)
+                .map(Slot::getName)
+                .collect(Collectors.toSet());
+        // The window PARTITION BY should include the correlated column 
(p_partkey)
+        Assertions.assertTrue(partitionKeys.contains("p_partkey"),
+                "Expected partition keys to contain p_partkey, got: " + 
partitionKeys);
+    }
+
+    @Test
+    public void testSharedTablePredicatesStayAboveWindow() throws Exception {
+        createTable("CREATE TABLE fact_split (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_split (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        // Add UNIQUE constraint so the rule matches
+        addConstraint("alter table dim_split add constraint uq_dim_split_k 
unique (k)");
+
+        // Query with extra predicate on shared table (f.v > 6).
+        // This predicate must stay ABOVE the window, otherwise the window
+        // function would aggregate fewer rows than the original scalar 
subquery.
+        String sql = "SELECT d.did, d.k, d.tag, f.id, f.v "
+                + "FROM fact_split f, dim_split d "
+                + "WHERE f.k = d.k "
+                + "  AND f.v > 6"
+                + "  AND f.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_split f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .applyTopDown(new PushDownFilterThroughProject())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule should match and produce a window
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rule should produce a window for this query");
+
+        // Collect the ExprIds of the shared table (fact_split – appears in 
both
+        // outer and inner plans).  Predicates that reference ONLY these 
ExprIds
+        // (shared-table-only filters) must stay ABOVE the window, otherwise 
the
+        // window function would see fewer rows than the original scalar 
subquery.
+        List<CatalogRelation> rels = 
plan.collectToList(CatalogRelation.class::isInstance);
+        Set<ExprId> sharedExprIds = rels.stream()
+                .filter(r -> r.getTable().getName().equals("fact_split"))
+                .flatMap(r -> r.getOutputExprIdSet().stream())
+                .collect(Collectors.toSet());
+
+        // Verify that no filter below the window contains a predicate whose
+        // input ExprIds are all from the shared table.  Such predicates
+        // (e.g. f.v > 6) must have been placed above the window.
+        List<LogicalWindow<Plan>> windows = 
plan.collectToList(LogicalWindow.class::isInstance);
+        LogicalWindow<Plan> window = windows.get(0);
+        Plan belowWindow = window.child(0);
+        List<LogicalFilter<Plan>> belowFilters = belowWindow
+                .collectToList(LogicalFilter.class::isInstance);
+        for (LogicalFilter<Plan> f : belowFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                Set<ExprId> conjExprIds = conj.getInputSlotExprIds();
+                if (!conjExprIds.isEmpty() && 
sharedExprIds.containsAll(conjExprIds)) {
+                    Assertions.fail(
+                            "Shared-table-only predicate should not be below 
window: " + conj.toSql());
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testMixedSharedOuterPredicatesStayAboveWindow() throws 
Exception {
+        // Mixed predicates that reference both shared-table and 
outer-only-table
+        // columns (e.g. f.v > d.tag) must stay ABOVE the window.  Pushing them
+        // below would restrict the rows seen by the window function, producing
+        // a different aggregate than the original scalar subquery.
+        //
+        // Input plan shape:
+        //   Filter(f.v > d.tag, f.v * 2 > sum_alias)       ← mixed + 
correlated
+        //     Apply(correlation: d.k)
+        //       CrossJoin
+        //         Scan fact f
+        //         Scan dim d   -- d.k is unique (constraint)
+        //       Aggregate(sum(f2.v) AS sum_alias)
+        //         Filter(f2.k = d.k)
+        //           Scan fact f2
+        //
+        // Output plan shape:
+        //   Filter(f.v > d.tag, f.v * 2 > sum_over_window)  ← mixed stays 
ABOVE
+        //     Window(sum(v) OVER (PARTITION BY d.k))
+        //       Filter(f.k = d.k)                           ← join cond BELOW
+        //         CrossJoin
+        //           Scan fact f
+        //           Scan dim d
+        createTable("CREATE TABLE fact_mixed (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_mixed (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        addConstraint("alter table dim_mixed add constraint uq_dim_mixed_k 
unique (k)");
+
+        // Mixed predicate: f.v > d.tag references both shared (f.v) and
+        // outer-only (d.tag) columns.  It must stay ABOVE the window.
+        String sql = "SELECT d.did, d.k, d.tag, f.id, f.v "
+                + "FROM fact_mixed f, dim_mixed d "
+                + "WHERE f.k = d.k "
+                + "  AND f.v > d.tag"
+                + "  AND f.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_mixed f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .applyTopDown(new PushDownFilterThroughProject())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule should match and produce a window
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rule should produce a window for this query");
+
+        // Collect shared table (fact_mixed) ExprIds.
+        // A mixed predicate like f.v > d.tag references BOTH shared and
+        // outer-only sets.
+        List<CatalogRelation> rels = 
plan.collectToList(CatalogRelation.class::isInstance);
+        Set<ExprId> sharedExprIds = rels.stream()
+                .filter(r -> r.getTable().getName().equals("fact_mixed"))
+                .flatMap(r -> r.getOutputExprIdSet().stream())
+                .collect(Collectors.toSet());
+
+        // Verify: the mixed predicate f.v > d.tag must NOT be below the 
window.
+        // A mixed predicate references slots from BOTH shared and outer-only
+        // tables.  We detect it by checking that at least one ExprId is in the
+        // shared set AND at least one is outside the shared set.
+        List<LogicalWindow<Plan>> windows = 
plan.collectToList(LogicalWindow.class::isInstance);
+        LogicalWindow<Plan> window = windows.get(0);
+        Plan belowWindow = window.child(0);
+        List<LogicalFilter<Plan>> belowFilters = belowWindow
+                .collectToList(LogicalFilter.class::isInstance);
+        for (LogicalFilter<Plan> f : belowFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                Set<ExprId> conjExprIds = conj.getInputSlotExprIds();
+                if (conjExprIds.isEmpty()) {
+                    continue;
+                }
+                boolean hasShared = false;
+                boolean hasNonShared = false;
+                for (ExprId id : conjExprIds) {
+                    if (sharedExprIds.contains(id)) {
+                        hasShared = true;
+                    } else {
+                        hasNonShared = true;
+                    }
+                }
+                if (hasShared && hasNonShared) {
+                    // Join conditions (f.k = d.k) are expected below the
+                    // window — they are matched from the inner filter and
+                    // needed for the join.  Only flag non-equality mixed
+                    // predicates like f.v > d.tag.
+                    if (!(conj instanceof 
org.apache.doris.nereids.trees.expressions.EqualPredicate)) {
+                        Assertions.fail(
+                                "Mixed shared+outer predicate should not be 
below window: "
+                                + conj.toSql());
+                    }
+                }
+                if (!hasNonShared) {
+                    // Shared-table-only predicate also belongs ABOVE.
+                    Assertions.fail(
+                            "Shared-table-only predicate should not be below 
window: "
+                            + conj.toSql());
+                }
+            }
+        }
+
+        // Verify the mixed predicate IS present in a filter ABOVE the window.
+        // Collect all filters above the window by excluding below-window 
filters.
+        List<LogicalFilter<Plan>> allFilters = plan
+                .collectToList(LogicalFilter.class::isInstance);
+        List<LogicalFilter<Plan>> aboveFilters = allFilters.stream()
+                .filter(f -> !belowFilters.contains(f))
+                .collect(Collectors.toList());
+        boolean foundMixedAbove = false;
+        for (LogicalFilter<Plan> f : aboveFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                Set<ExprId> conjExprIds = conj.getInputSlotExprIds();
+                boolean hasShared = false;
+                boolean hasNonShared = false;
+                for (ExprId id : conjExprIds) {
+                    if (sharedExprIds.contains(id)) {
+                        hasShared = true;
+                    } else {
+                        hasNonShared = true;
+                    }
+                }
+                if (hasShared && hasNonShared) {
+                    foundMixedAbove = true;
+                }
+            }
+        }
+        Assertions.assertTrue(foundMixedAbove,
+                "Mixed predicate f.v > d.tag should be above the window");
+    }
+
+    @Test
+    public void testEnsureProjectOutputExpandsPrunedColumn() throws Exception {
+        // When a nested shared-table filter is extracted and hoisted above the
+        // window, any pruning project inside apply.left() that dropped a 
column
+        // referenced by the extracted conjunct must be expanded by
+        // ensureProjectOutput().  Otherwise the reinserted predicate would 
have
+        // a dangling slot reference.
+        //
+        // Plan shape:
+        //   Filter(sf.k = d.k, sf.k * 2 > sum_alias)     ← sf.k comparison
+        //     Apply(correlation: d.k)
+        //       CrossJoin
+        //         SubQueryAlias sf
+        //           Project(k)                             ← prunes v
+        //             Filter(v > 6)                        ← nested shared 
filter
+        //               Scan fact(k, v)
+        //         Scan dim_unique d
+        //       Aggregate(SUM(f2.k) AS sum_alias)
+        //         Filter(f2.k = d.k)
+        //           Scan fact f2
+        //
+        // The subquery SELECTs only k; the outer query uses sf.k for both join
+        // and comparison.  The column v appears ONLY in the nested filter v > 
6.
+        // After extracting v > 6, ensureProjectOutput() must expand Project(k)
+        // to Project(k, v) so the hoisted predicate has access to v.
+        createTable("CREATE TABLE fact_ensure_proj (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_ensure_proj (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        addConstraint("alter table dim_ensure_proj add constraint uq_dim_ep_k 
unique (k)");
+
+        // The subquery SELECTs only k, pruning v.  v > 6 is a nested 
shared-table
+        // filter that must be extracted.  The rule must produce a window with
+        // v > 6 hoisted above it, and ensureProjectOutput must expand the
+        // pruning project to carry v through.
+        String sql = "SELECT sf.k, d.did "
+                + "FROM (SELECT k FROM fact_ensure_proj WHERE v > 6) sf, "
+                + "    dim_ensure_proj d "
+                + "WHERE sf.k = d.k "
+                + "  AND sf.k * 2 > ("
+                + "    SELECT SUM(f2.k) "
+                + "    FROM fact_ensure_proj f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule must match and produce a window.
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rule must produce a window for ensureProjectOutput test");
+
+        // Walk every filter in the plan and verify that every slot referenced
+        // by each conjunct is produced by the filter's child.  If
+        // ensureProjectOutput() did not expand the project, the reinserted
+        // predicate v > 6 would have a dangling reference to v.
+        List<LogicalFilter<Plan>> allFilters = plan
+                .collectToList(LogicalFilter.class::isInstance);
+        for (LogicalFilter<Plan> f : allFilters) {
+            Set<ExprId> childOutput = f.child().getOutputExprIdSet();
+            for (Expression conj : f.getConjuncts()) {
+                for (ExprId id : conj.getInputSlotExprIds()) {
+                    Assertions.assertTrue(childOutput.contains(id),
+                            "Filter conjunct slot " + id
+                            + " must be produced by filter's child. "
+                            + "ensureProjectOutput() may not have expanded "
+                            + "a pruning project. Conjunct: " + conj.toSql());
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testNotMatchWhenCorrelatedTableNotUnique() throws Exception {
+        createTable("CREATE TABLE tpch.fact_dup (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE tpch.dim_dup (\n"
+                + "  did INT,\n"
+                + "  k INT,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+
+        String sql = "SELECT d.did, d.k, d.tag, f.id, f.v "
+                + "FROM fact_dup f, dim_dup d "
+                + "WHERE f.k = d.k "
+                + "  AND f.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_dup f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .applyTopDown(new PushDownFilterThroughProject())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // DUPLICATE KEY table does not guarantee uniqueness, rule should not 
match
+        Assertions.assertFalse(plan.anyMatch(LogicalWindow.class::isInstance));
+    }
+
+    @Test
+    public void testUniqueKeyModelTriggersRewrite() throws Exception {
+        // UNIQUE KEY model tables guarantee uniqueness + non-null on the key
+        // column.  DataTrait recognizes this even without an explicit
+        // ADD CONSTRAINT, so the rule should fire.
+        createTable("CREATE TABLE tpch.fact_ukey (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        // dim_ukey has UNIQUE KEY(k) with k INT NOT NULL — this implies
+        // unique + non-null without needing an explicit constraint.
+        createTable("CREATE TABLE tpch.dim_ukey (\n"
+                + "  k INT NOT NULL,\n"
+                + "  did INT,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "UNIQUE KEY(k)\n"
+                + "DISTRIBUTED BY HASH(k) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+
+        String sql = "SELECT d.did, d.k, d.tag, f.id, f.v "
+                + "FROM fact_ukey f, dim_ukey d "
+                + "WHERE f.k = d.k "
+                + "  AND f.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_ukey f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .applyTopDown(new PushDownFilterThroughProject())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // UNIQUE KEY model alone provides uniqueness + non-null.
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "UNIQUE KEY model should trigger the window rewrite");
+    }
+
+    @Test
+    public void testNotMatchWhenOuterOnlyRelationOutputIsPruned() throws 
Exception {
+        createTable("CREATE TABLE tpch.fact_window_pruned (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE tpch.dim_window_pruned (\n"
+                + "  did INT,\n"
+                + "  k INT,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+
+        String sql = "SELECT t.id, t.k, t.v "
+                + "FROM ("
+                + "    SELECT f.id, d.k, f.v "
+                + "    FROM fact_window_pruned f, dim_window_pruned d "
+                + "    WHERE f.k = d.k"
+                + ") t "
+                + "WHERE t.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_window_pruned f2 "
+                + "    WHERE f2.k = t.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .applyTopDown(new PushDownFilterThroughProject())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        Assertions.assertFalse(plan.anyMatch(LogicalWindow.class::isInstance));
+    }
+
+    @Test
+    public void testNestedOuterFilterHoistedAboveWindow() throws Exception {
+        // When the outer child of Apply contains a nested LogicalFilter
+        // (e.g. a filter pushed into the FROM subquery like
+        // FROM (SELECT * FROM fact WHERE v > 6) sf), the rule must extract
+        // the nested conjuncts, classify them, and hoist shared-table
+        // predicates ABOVE the window.  Otherwise the window would aggregate
+        // over a filtered subset, while the original scalar subquery computes
+        // over ALL fact rows for the key.
+        createTable("CREATE TABLE fact_nested (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_nested (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        addConstraint("alter table dim_nested add constraint uq_dim_nested_k 
unique (k)");
+
+        // The FROM-subquery with WHERE v > 6 produces a LogicalFilter(f.v > 6)
+        // nested inside apply.left() under the LogicalSubQueryAlias.
+        String sql = "SELECT d.did, sf.id, sf.k, sf.v "
+                + "FROM (SELECT id, k, v FROM fact_nested WHERE v > 6) sf, 
dim_nested d "
+                + "WHERE sf.k = d.k "
+                + "  AND sf.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_nested f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule should match and produce a window
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rule should produce a window when nested outer filter is 
present");
+
+        // The nested shared-table predicate (v > 6) must be hoisted ABOVE the
+        // window.  Verify no shared-table-only predicate is below the window.
+        List<CatalogRelation> rels = 
plan.collectToList(CatalogRelation.class::isInstance);
+        Set<ExprId> factExprIds = rels.stream()
+                .filter(r -> r.getTable().getName().equals("fact_nested"))
+                .flatMap(r -> r.getOutputExprIdSet().stream())
+                .collect(Collectors.toSet());
+        List<LogicalWindow<Plan>> windows = 
plan.collectToList(LogicalWindow.class::isInstance);
+        LogicalWindow<Plan> window = windows.get(0);
+        Plan belowWindow = window.child(0);
+        List<LogicalFilter<Plan>> belowFilters = belowWindow
+                .collectToList(LogicalFilter.class::isInstance);
+        for (LogicalFilter<Plan> f : belowFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                Set<ExprId> conjExprIds = conj.getInputSlotExprIds();
+                if (!conjExprIds.isEmpty() && 
factExprIds.containsAll(conjExprIds)) {
+                    Assertions.fail(
+                            "Nested shared-table predicate should be hoisted 
above window: "
+                            + conj.toSql());
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testVolatilePredicateStaysAboveWindow() throws Exception {
+        // Volatile predicates like random() > 0.5 have no table column
+        // references but are non-deterministic.  They must stay ABOVE the
+        // window, otherwise the window function would aggregate over a
+        // different set of rows per partition than the original scalar
+        // subquery.  This follows the same principle as
+        // PushDownFilterThroughWindow.canPushDown().
+        createTable("CREATE TABLE fact_volatile (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_volatile (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        addConstraint("alter table dim_volatile add constraint 
uq_dim_volatile_k unique (k)");
+
+        // random() > 0.5 is a volatile predicate with no input slots.
+        // It must be kept ABOVE the window.
+        String sql = "SELECT d.did, f.id, f.k, f.v "
+                + "FROM fact_volatile f, dim_volatile d "
+                + "WHERE f.k = d.k "
+                + "  AND random() > 0.5"
+                + "  AND f.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_volatile f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule should match and produce a window
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rule should produce a window for volatile predicate query");
+
+        // Verify the volatile predicate is NOT below the window.
+        List<LogicalWindow<Plan>> windows = 
plan.collectToList(LogicalWindow.class::isInstance);
+        LogicalWindow<Plan> window = windows.get(0);
+        Plan belowWindow = window.child(0);
+        List<LogicalFilter<Plan>> belowFilters = belowWindow
+                .collectToList(LogicalFilter.class::isInstance);
+        for (LogicalFilter<Plan> f : belowFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                Assertions.assertFalse(conj.containsVolatileExpression(),
+                        "Volatile predicate should stay above window: " + 
conj.toSql());
+            }
+        }
+    }
+
+    @Test
+    public void testInnerFilterConjunctsStayBelowWindow() throws Exception {
+        // Regression test: shared-table predicates that were matched against
+        // inner subquery filter conjuncts must stay BELOW the window.
+        //
+        // Without the matchedInnerFilterConjuncts tracking, f.v < 10 would be
+        // classified as shared-table-only and placed ABOVE the window, letting
+        // the window aggregate over rows the original scalar subquery 
excluded.
+        createTable("CREATE TABLE fact_inner_filter (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_inner_filter (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        // UNIQUE constraint so the rule matches
+        addConstraint("alter table dim_inner_filter add constraint uq_dim_if_k 
unique (k)");
+
+        // Inner subquery has f2.v < 10 as a filter.  After checkFilter matches
+        // it against outer f.v < 10, the outer conjunct must go BELOW the 
window
+        // because it is semantically part of the inner aggregate's filter.
+        String sql = "SELECT d.did, f.id, f.k, f.v "
+                + "FROM fact_inner_filter f, dim_inner_filter d "
+                + "WHERE f.k = d.k "
+                + "  AND f.v < 10"
+                + "  AND f.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_inner_filter f2 "
+                + "    WHERE f2.k = d.k"
+                + "      AND f2.v < 10"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule should match and produce a window
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rule should produce a window for this query");
+
+        // Collect the ExprIds of the shared table (fact_inner_filter).
+        List<CatalogRelation> allRels = 
plan.collectToList(CatalogRelation.class::isInstance);
+        Set<ExprId> sharedExprIds = allRels.stream()
+                .filter(r -> 
r.getTable().getName().equals("fact_inner_filter"))
+                .flatMap(r -> r.getOutputExprIdSet().stream())
+                .collect(Collectors.toSet());
+
+        // The conjunct f.v < 10, which was matched from the inner filter, must
+        // appear in a filter BELOW the window (it is part of the aggregate
+        // computation).  Verify it exists there and is NOT above.
+        List<LogicalWindow<Plan>> windows = 
plan.collectToList(LogicalWindow.class::isInstance);
+        LogicalWindow<Plan> window = windows.get(0);
+
+        // Check below-window filters: there MUST be at least one 
shared-table-only
+        // conjunct (f.v < 10) — this is the matched inner-filter predicate.
+        Plan belowWindow = window.child(0);
+        List<LogicalFilter<Plan>> belowFilters = belowWindow
+                .collectToList(LogicalFilter.class::isInstance);
+        boolean foundSharedOnlyBelow = false;
+        for (LogicalFilter<Plan> f : belowFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                Set<ExprId> conjExprIds = conj.getInputSlotExprIds();
+                if (!conjExprIds.isEmpty() && 
sharedExprIds.containsAll(conjExprIds)) {
+                    foundSharedOnlyBelow = true;
+                }
+            }
+        }
+        Assertions.assertTrue(foundSharedOnlyBelow,
+                "Matched inner-filter conjunct f.v < 10 must be below the 
window");
+
+        // Check above-window filters: there should NOT be a shared-table-only
+        // conjunct that is NOT the window comparison.  f.v < 10 should not 
leak
+        // above.
+        List<LogicalFilter<Plan>> allFilters = plan
+                .collectToList(LogicalFilter.class::isInstance);
+        List<LogicalFilter<Plan>> aboveFilters = allFilters.stream()
+                .filter(f -> !belowFilters.contains(f))
+                .collect(Collectors.toList());
+        for (LogicalFilter<Plan> f : aboveFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                Set<ExprId> conjExprIds = conj.getInputSlotExprIds();
+                if (!conjExprIds.isEmpty() && 
sharedExprIds.containsAll(conjExprIds)) {
+                    Assertions.fail(
+                            "Unexpected shared-table-only predicate above 
window: " + conj.toSql());
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testSplitInnerFilterFromPushDown() throws Exception {
+        // Regression: PushDownFilterThroughProject splits the inner WHERE 
clause
+        // into multiple LogicalFilter nodes when a correlated predicate cannot
+        // be pushed through a project (references a correlated slot not in the
+        // project output) but a non-correlated predicate can.
+        //
+        // Before the fix, checkFilter() required exactly one inner filter and
+        // would reject plans where the filter was split.  Now it collects
+        // conjuncts from ALL inner filters.
+        createTable("CREATE TABLE fact_split2 (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_split2 (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        addConstraint("alter table dim_split2 add constraint uq_dim_split2_k 
unique (k)");
+
+        String sql = "SELECT d.did, f.id, f.k, f.v "
+                + "FROM fact_split2 f, dim_split2 d "
+                + "WHERE f.k = d.k "
+                + "  AND f.v < 10 "
+                + "  AND f.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_split2 f2 "
+                + "    WHERE f2.k = d.k"
+                + "      AND f2.v < 10"
+                + "  )";
+
+        // Full regression-style pipeline: PushDownFilterThroughProject splits
+        // the inner filter, MergeFilters merges adjacent filters, then the
+        // custom rule rewrites.
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyTopDown(new PushDownFilterThroughProject())
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .applyBottomUp(new MergeFilters())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule should match and produce a window
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rule should produce a window even when inner filter is split 
by "
+                + "PushDownFilterThroughProject");
+
+        // Verify f.v < 10 (matched from inner filter) is below the window
+        List<LogicalWindow<Plan>> windows = 
plan.collectToList(LogicalWindow.class::isInstance);
+        LogicalWindow<Plan> window = windows.get(0);
+        Plan belowWindow = window.child(0);
+        List<LogicalFilter<Plan>> belowFilters = belowWindow
+                .collectToList(LogicalFilter.class::isInstance);
+        boolean found = false;
+        for (LogicalFilter<Plan> f : belowFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                if (conj.toSql().contains("v < 10")) {
+                    found = true;
+                }
+            }
+        }
+        Assertions.assertTrue(found,
+                "Matched inner-filter conjunct f.v < 10 must be below the 
window");
+    }
+
+    @Test
+    public void testNestedVolatilePredicateStaysInPlace() throws Exception {
+        // Volatile predicates in nested filters (inside a FROM subquery like
+        // (SELECT * FROM dim WHERE random() > 0.5)) must NOT be hoisted above
+        // the window or join.  Moving a volatile predicate across a join
+        // changes its evaluation frequency:
+        //
+        //   CrossJoin(fact, (SELECT * FROM dim WHERE random()>0.5) d)
+        //
+        // originally evaluates random() once per dim row BEFORE the join.
+        // Hoisting it above the window/join evaluates random() once per
+        // joined fact row — one dim row with two matching fact rows can
+        // now keep one row instead of both or none.  We must keep volatile
+        // nested-filter predicates at their original child position while
+        // only extracting deterministic predicates.
+        createTable("CREATE TABLE fact_nested_vol (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_nested_vol (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        addConstraint("alter table dim_nested_vol add constraint 
uq_dim_nested_vol_k unique (k)");
+
+        // random() > 0.5 is nested inside the FROM subquery on dim_nested_vol.
+        // It must stay at its original position, NOT appear in the top filter
+        // above the window.
+        String sql = "SELECT d.did, f.id, f.k, f.v "
+                + "FROM fact_nested_vol f, "
+                + "    (SELECT * FROM dim_nested_vol WHERE random() > 0.5) d "
+                + "WHERE f.k = d.k "
+                + "  AND f.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_nested_vol f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule should match and produce a window
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rule should produce a window when nested volatile filter is 
present");
+
+        // The volatile predicate must NOT be above the window, AND must
+        // still be present below the window (proving it was preserved, not
+        // silently dropped by stripOuterFilters or a later change).
+        List<LogicalWindow<Plan>> windows = 
plan.collectToList(LogicalWindow.class::isInstance);
+        LogicalWindow<Plan> window = windows.get(0);
+        Plan belowWindow = window.child(0);
+        List<LogicalFilter<Plan>> belowFilters = belowWindow
+                .collectToList(LogicalFilter.class::isInstance);
+        List<LogicalFilter<Plan>> allFilters = plan
+                .collectToList(LogicalFilter.class::isInstance);
+        List<LogicalFilter<Plan>> aboveFilters = allFilters.stream()
+                .filter(f -> !belowFilters.contains(f))
+                .collect(Collectors.toList());
+        for (LogicalFilter<Plan> f : aboveFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                Assertions.assertFalse(conj.containsVolatileExpression(),
+                        "Nested volatile predicate should stay at its original 
position, "
+                        + "not be hoisted above the window: " + conj.toSql());
+            }
+        }
+        // Prove the volatile predicate was preserved below the window.
+        boolean foundVolatileBelow = false;
+        for (LogicalFilter<Plan> f : belowFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                if (conj.containsVolatileExpression()) {
+                    foundVolatileBelow = true;
+                }
+            }
+        }
+        Assertions.assertTrue(foundVolatileBelow,
+                "Nested volatile predicate must still exist below the window — 
"
+                + "it should have been preserved at its original position, "
+                + "not silently dropped");
+    }
+
+    @Test
+    public void testVolatileNestedOnSharedTableRejected() throws Exception {
+        // A volatile predicate nested on a SHARED table (fact, which appears
+        // in both outer and inner plans) must cause the rewrite to be 
REJECTED.
+        //
+        // Keeping the volatile filter in place below the window would let the
+        // window aggregate over fewer fact rows than the original scalar
+        // subquery (which sums ALL fact f2 rows per key).  The same hazard
+        // does not apply to volatile filters on the outer-only table because
+        // the original scalar subquery does not touch that table.
+        //
+        // Plan shape that would be unsafe:
+        //   CrossJoin
+        //     Filter(random() > 0.5)       ← volatile on shared table fact
+        //       Scan fact sf
+        //     Scan dim_shared_vol d         ← outer-only, unique k
+        //
+        // After rewrite, the window would compute SUM(sf.v) OVER(PARTITION BY
+        // d.k) only over random-surviving fact rows, but the original scalar
+        // subquery SELECT SUM(f2.v) FROM fact f2 WHERE f2.k = d.k does NOT
+        // have a random filter — it always sees all fact rows.
+        createTable("CREATE TABLE fact_shared_vol (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_shared_vol (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        addConstraint("alter table dim_shared_vol add constraint 
uq_dim_shared_vol_k unique (k)");
+
+        // random() > 0.5 is on the shared table fact_shared_vol (inside the
+        // FROM subquery on the SHARED table).  The rewrite must be rejected.
+        String sql = "SELECT d.did, sf.id, sf.k, sf.v "
+                + "FROM (SELECT * FROM fact_shared_vol WHERE random() > 0.5) 
sf, "
+                + "    dim_shared_vol d "
+                + "WHERE sf.k = d.k "
+                + "  AND sf.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_shared_vol f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule must NOT match — volatile on shared table is unsafe
+        Assertions.assertFalse(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rewrite must be rejected when volatile predicate is on the "
+                + "shared table (fact), otherwise the window would compute "
+                + "over fewer rows than the original scalar subquery");
+    }
+
+    @Test
+    public void testNonMovableNestedOnOuterOnlyKeptInPlace() throws Exception {
+        // NoneMovableFunction predicates like assert_true() have side effects
+        // and must not be moved from their original branch position.  When
+        // placed on the outer-only table, preserving them in place is safe.
+        createTable("CREATE TABLE fact_nm_outer (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_nm_outer (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        addConstraint("alter table dim_nm_outer add constraint 
uq_dim_nm_outer_k unique (k)");
+
+        // assert_true(tag > 0, 'bad') is on the outer-only table dim_nm_outer.
+        // It must stay at its original position below the window.
+        String sql = "SELECT d.did, f.id, f.k, f.v "
+                + "FROM fact_nm_outer f, "
+                + "    (SELECT * FROM dim_nm_outer WHERE assert_true(tag > 0, 
'bad')) d "
+                + "WHERE f.k = d.k "
+                + "  AND f.v * 2 > ("
+                + "    SELECT SUM(f2.v) "
+                + "    FROM fact_nm_outer f2 "
+                + "    WHERE f2.k = d.k"
+                + "  )";
+
+        Plan plan = PlanChecker.from(createCascadesContext(sql))
+                .analyze(sql)
+                .applyBottomUp(new PullUpProjectUnderApply())
+                .customRewrite(new EliminateUnnecessaryProject())
+                .customRewrite(new AggScalarSubQueryToWindowFunction())
+                .getPlan();
+
+        // Rule should match — NoneMovable on outer-only is safe to keep in 
place
+        Assertions.assertTrue(plan.anyMatch(LogicalWindow.class::isInstance),
+                "Rewrite should succeed when NoneMovableFunction predicate is "
+                + "on the outer-only table");
+
+        // The assert_true predicate must NOT be hoisted above the window,
+        // AND must still be present below the window (proving it was
+        // preserved, not silently dropped).
+        List<LogicalWindow<Plan>> windows = 
plan.collectToList(LogicalWindow.class::isInstance);
+        LogicalWindow<Plan> window = windows.get(0);
+        Plan belowWindow = window.child(0);
+        List<LogicalFilter<Plan>> belowFilters = belowWindow
+                .collectToList(LogicalFilter.class::isInstance);
+        List<LogicalFilter<Plan>> allFilters = plan
+                .collectToList(LogicalFilter.class::isInstance);
+        List<LogicalFilter<Plan>> aboveFilters = allFilters.stream()
+                .filter(f -> !belowFilters.contains(f))
+                .collect(Collectors.toList());
+        for (LogicalFilter<Plan> f : aboveFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                Assertions.assertFalse(
+                        conj.containsType(
+                                
org.apache.doris.nereids.trees.expressions.functions
+                                        .NoneMovableFunction.class),
+                        "NoneMovableFunction predicate should stay at its 
original "
+                        + "position, not be hoisted above the window: "
+                        + conj.toSql());
+            }
+        }
+        // Prove the NoneMovableFunction predicate was preserved below the 
window.
+        boolean foundNoneMovableBelow = false;
+        for (LogicalFilter<Plan> f : belowFilters) {
+            for (Expression conj : f.getConjuncts()) {
+                if (conj.containsType(
+                        org.apache.doris.nereids.trees.expressions.functions
+                                .NoneMovableFunction.class)) {
+                    foundNoneMovableBelow = true;
+                }
+            }
+        }
+        Assertions.assertTrue(foundNoneMovableBelow,
+                "NoneMovableFunction predicate must still exist below the 
window — "
+                + "it should have been preserved at its original position, "
+                + "not silently dropped");
+    }
+
+    @Test
+    public void testNonMovableNestedOnSharedTableRejected() throws Exception {
+        // NoneMovableFunction predicates like assert_true() must not be moved
+        // from their original position.  When placed on a shared table, 
keeping
+        // them in place would restrict the window's input relative to the
+        // original scalar subquery (same hazard as volatile on shared).
+        createTable("CREATE TABLE fact_nm_shared (\n"
+                + "  id INT,\n"
+                + "  k INT,\n"
+                + "  v INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(id)\n"
+                + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        createTable("CREATE TABLE dim_nm_shared (\n"
+                + "  did INT,\n"
+                + "  k INT NOT NULL,\n"
+                + "  tag INT\n"
+                + ") ENGINE=OLAP\n"
+                + "DUPLICATE KEY(did)\n"
+                + "DISTRIBUTED BY HASH(did) BUCKETS 1\n"
+                + "PROPERTIES ('replication_num' = '1')");
+        addConstraint("alter table dim_nm_shared add constraint 
uq_dim_nm_shared_k unique (k)");
+
+        // assert_true(f.v > 0, 'bad') is on the shared table fact_nm_shared.
+        // The rewrite must be rejected because the window would compute over
+        // a subset of fact rows, while the original scalar subquery sees all.

Review Comment:
   This test still passes without exercising the shared-table 
`NoneMovableFunction` filter guard it is documenting. The query puts 
`assert_true(v > 0, 'bad')` in the derived table SELECT list, so it creates a 
non-slot project expression; `checkProject()` rejects that shape before 
`rewrite()` reaches the `nestedOuterFilters` loop that checks 
`conj.containsType(NoneMovableFunction.class)` on shared-table filters.
   
   Please make this a nested filter, for example `FROM (SELECT * FROM 
fact_nm_shared WHERE assert_true(v > 0, 'bad')) sf`, and keep the `no 
LogicalWindow` assertion. That way the test fails if the shared-table 
`NoneMovableFunction` rejection branch is removed or regresses.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to