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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreatePolicyCommand.java:
##########
@@ -159,12 +162,56 @@ private void validate(ConnectContext ctx) throws 
AnalysisException {
                             throw new 
org.apache.doris.nereids.exceptions.AnalysisException(
                                     "column not exist: " + slot.getName());
                         }
+                    } else if (expr instanceof SubqueryExpr) {
+                        // Exists/InSubquery/ScalarSubquery are leaf 
expressions: their subquery
+                        // plan isn't a child in the expression tree, so the 
foreach above never
+                        // looks inside it. A subquery that reaches back into 
tableIf (the row
+                        // policy's own table) is a correlated subquery, and 
this command has no
+                        // way to keep that reference resolvable once the 
policy is stored and
+                        // re-parsed at query time, so reject it here instead 
of silently
+                        // dropping the policy later.
+                        rejectCorrelatedSubquery((SubqueryExpr) expr, tableIf);

Review Comment:
   [P1] Cover persisted and mixed-version policy state as well as live DDL. 
`RowPolicy.gsonPostProcess` reparses `originStmt` without this validation, 
`PolicyMgr.read` then installs every image policy through 
`updateTablePolicies`, and edit-log replay goes directly through `replayCreate 
-> unprotectedAdd`. Therefore an existing correlated policy, or one accepted 
and journaled by an old leader during rolling upgrade, remains active after 
restart/failover and preserves the silent row-policy failure this PR is meant 
to close. Please define a fail-closed, observable behavior for 
image/replay/consumption and test old-record and mixed-version paths.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreatePolicyCommand.java:
##########
@@ -159,12 +162,56 @@ private void validate(ConnectContext ctx) throws 
AnalysisException {
                             throw new 
org.apache.doris.nereids.exceptions.AnalysisException(
                                     "column not exist: " + slot.getName());
                         }
+                    } else if (expr instanceof SubqueryExpr) {
+                        // Exists/InSubquery/ScalarSubquery are leaf 
expressions: their subquery
+                        // plan isn't a child in the expression tree, so the 
foreach above never
+                        // looks inside it. A subquery that reaches back into 
tableIf (the row
+                        // policy's own table) is a correlated subquery, and 
this command has no
+                        // way to keep that reference resolvable once the 
policy is stored and
+                        // re-parsed at query time, so reject it here instead 
of silently
+                        // dropping the policy later.
+                        rejectCorrelatedSubquery((SubqueryExpr) expr, tableIf);
                     }
                 });
 
         }
     }
 
+    // ponytail: name-based, not a real bind. A subquery column that merely 
shares a name with
+    // an outer-table column gets rejected even when the subquery has its own 
local relation
+    // that would actually resolve it (e.g. a self-join on the policy's own 
table). That's the
+    // deliberate trade-off of Option A from apache/doris#62729: false 
rejections are safe here,
+    // silently dropping a policy is not. Upgrade to real correlation 
detection (Option B) if
+    // this starts blocking legitimate policies.
+    private static void rejectCorrelatedSubquery(SubqueryExpr subquery, 
TableIf outerTable) {
+        subquery.getQueryPlan().foreach(node -> {
+            for (Expression expr : ((Plan) node).getExpressions()) {
+                checkNoOuterReference(expr, outerTable);
+            }
+        });
+    }
+
+    private static void checkNoOuterReference(Expression expr, TableIf 
outerTable) {
+        if (expr instanceof UnboundSlot) {
+            // Use the last name part, not getName(): for a qualified 
reference like
+            // "main_table.ref_id", getName() returns the whole dotted string, 
which would
+            // never match a bare column name.
+            List<String> nameParts = ((UnboundSlot) expr).getNameParts();

Review Comment:
   [P1] Do not infer the outer column from the final name token. For a target 
column `payload STRUCT<ref_id:INT>`, the correlated reference 
`main_table.payload.ref_id` has name parts `[main_table, payload, ref_id]`, so 
this looks up top-level `ref_id` and accepts the policy when only `payload` 
exists. Other raw carriers are missed altogether: `UnboundStar` is a leaf and 
keeps `REPLACE` expressions outside `children()`, so `SELECT l.* 
REPLACE(main_table.ref_id AS ref_id)` also escapes this walk. These forms 
preserve the fail-open behavior being fixed. Please classify correlation after 
scope-aware binding and add nested-field/non-slot coverage.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreatePolicyCommand.java:
##########
@@ -159,12 +162,56 @@ private void validate(ConnectContext ctx) throws 
AnalysisException {
                             throw new 
org.apache.doris.nereids.exceptions.AnalysisException(
                                     "column not exist: " + slot.getName());
                         }
+                    } else if (expr instanceof SubqueryExpr) {
+                        // Exists/InSubquery/ScalarSubquery are leaf 
expressions: their subquery
+                        // plan isn't a child in the expression tree, so the 
foreach above never
+                        // looks inside it. A subquery that reaches back into 
tableIf (the row
+                        // policy's own table) is a correlated subquery, and 
this command has no
+                        // way to keep that reference resolvable once the 
policy is stored and
+                        // re-parsed at query time, so reject it here instead 
of silently
+                        // dropping the policy later.
+                        rejectCorrelatedSubquery((SubqueryExpr) expr, tableIf);
                     }
                 });
 
         }
     }
 
+    // ponytail: name-based, not a real bind. A subquery column that merely 
shares a name with
+    // an outer-table column gets rejected even when the subquery has its own 
local relation
+    // that would actually resolve it (e.g. a self-join on the policy's own 
table). That's the
+    // deliberate trade-off of Option A from apache/doris#62729: false 
rejections are safe here,
+    // silently dropping a policy is not. Upgrade to real correlation 
detection (Option B) if
+    // this starts blocking legitimate policies.
+    private static void rejectCorrelatedSubquery(SubqueryExpr subquery, 
TableIf outerTable) {
+        subquery.getQueryPlan().foreach(node -> {
+            for (Expression expr : ((Plan) node).getExpressions()) {
+                checkNoOuterReference(expr, outerTable);
+            }
+        });
+    }
+
+    private static void checkNoOuterReference(Expression expr, TableIf 
outerTable) {
+        if (expr instanceof UnboundSlot) {
+            // Use the last name part, not getName(): for a qualified 
reference like
+            // "main_table.ref_id", getName() returns the whole dotted string, 
which would
+            // never match a bare column name.
+            List<String> nameParts = ((UnboundSlot) expr).getNameParts();
+            String columnName = nameParts.get(nameParts.size() - 1);
+            if (outerTable.getColumn(columnName) != null) {
+                throw new 
org.apache.doris.nereids.exceptions.AnalysisException(

Review Comment:
   [P2] Return this expected validation failure through the declared user-error 
path. This throws `org.apache.doris.nereids.exceptions.AnalysisException` (a 
`RuntimeException`), while `validate` declares 
`org.apache.doris.common.AnalysisException` (a `UserException`). `StmtExecutor` 
therefore handles every new rejection in its generic `Maybe our bug` branch and 
emits an INFO stack trace for routine invalid input; direct callers also cannot 
rely on the public method's declared exception contract. Please surface the 
common `AnalysisException` outside the traversal callback and exercise the real 
command path in the test.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreatePolicyCommand.java:
##########
@@ -159,12 +162,56 @@ private void validate(ConnectContext ctx) throws 
AnalysisException {
                             throw new 
org.apache.doris.nereids.exceptions.AnalysisException(
                                     "column not exist: " + slot.getName());
                         }
+                    } else if (expr instanceof SubqueryExpr) {
+                        // Exists/InSubquery/ScalarSubquery are leaf 
expressions: their subquery
+                        // plan isn't a child in the expression tree, so the 
foreach above never
+                        // looks inside it. A subquery that reaches back into 
tableIf (the row
+                        // policy's own table) is a correlated subquery, and 
this command has no
+                        // way to keep that reference resolvable once the 
policy is stored and
+                        // re-parsed at query time, so reject it here instead 
of silently
+                        // dropping the policy later.
+                        rejectCorrelatedSubquery((SubqueryExpr) expr, tableIf);
                     }
                 });
 
         }
     }
 
+    // ponytail: name-based, not a real bind. A subquery column that merely 
shares a name with
+    // an outer-table column gets rejected even when the subquery has its own 
local relation
+    // that would actually resolve it (e.g. a self-join on the policy's own 
table). That's the
+    // deliberate trade-off of Option A from apache/doris#62729: false 
rejections are safe here,
+    // silently dropping a policy is not. Upgrade to real correlation 
detection (Option B) if
+    // this starts blocking legitimate policies.
+    private static void rejectCorrelatedSubquery(SubqueryExpr subquery, 
TableIf outerTable) {
+        subquery.getQueryPlan().foreach(node -> {

Review Comment:
   [P1] Traverse CTE definitions, not only ordinary plan children. `LogicalCTE` 
stores `WITH` definitions in `aliasQueries` exposed through `extraPlans()`, 
while `TreeNode.foreach` recurses only through `children()`. Thus `EXISTS (WITH 
x AS (SELECT 1 FROM lookup_table l WHERE l.ref_id = main_table.ref_id) SELECT 1 
FROM x)` never visits the correlated slot and the unsupported policy is still 
stored. Please cover syntax-bearing extra plans (or use analyzer-driven 
correlation detection) and add a `WITH`-clause regression.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreatePolicyCommand.java:
##########
@@ -159,12 +162,56 @@ private void validate(ConnectContext ctx) throws 
AnalysisException {
                             throw new 
org.apache.doris.nereids.exceptions.AnalysisException(
                                     "column not exist: " + slot.getName());
                         }
+                    } else if (expr instanceof SubqueryExpr) {
+                        // Exists/InSubquery/ScalarSubquery are leaf 
expressions: their subquery
+                        // plan isn't a child in the expression tree, so the 
foreach above never
+                        // looks inside it. A subquery that reaches back into 
tableIf (the row
+                        // policy's own table) is a correlated subquery, and 
this command has no
+                        // way to keep that reference resolvable once the 
policy is stored and
+                        // re-parsed at query time, so reject it here instead 
of silently
+                        // dropping the policy later.
+                        rejectCorrelatedSubquery((SubqueryExpr) expr, tableIf);
                     }
                 });
 
         }
     }
 
+    // ponytail: name-based, not a real bind. A subquery column that merely 
shares a name with
+    // an outer-table column gets rejected even when the subquery has its own 
local relation
+    // that would actually resolve it (e.g. a self-join on the policy's own 
table). That's the
+    // deliberate trade-off of Option A from apache/doris#62729: false 
rejections are safe here,
+    // silently dropping a policy is not. Upgrade to real correlation 
detection (Option B) if
+    // this starts blocking legitimate policies.
+    private static void rejectCorrelatedSubquery(SubqueryExpr subquery, 
TableIf outerTable) {
+        subquery.getQueryPlan().foreach(node -> {
+            for (Expression expr : ((Plan) node).getExpressions()) {
+                checkNoOuterReference(expr, outerTable);
+            }
+        });
+    }
+
+    private static void checkNoOuterReference(Expression expr, TableIf 
outerTable) {
+        if (expr instanceof UnboundSlot) {
+            // Use the last name part, not getName(): for a qualified 
reference like
+            // "main_table.ref_id", getName() returns the whole dotted string, 
which would
+            // never match a bare column name.
+            List<String> nameParts = ((UnboundSlot) expr).getNameParts();
+            String columnName = nameParts.get(nameParts.size() - 1);
+            if (outerTable.getColumn(columnName) != null) {

Review Comment:
   [P1] Resolve inner slots before classifying correlation. With the schemas in 
this test, a valid uncorrelated predicate such as `EXISTS (SELECT 1 FROM 
lookup_table l WHERE l.ref_id = 10)` is rejected here: `l.ref_id` has name 
parts `[l, ref_id]`, this drops `l`, and `main_table` also has `ref_id`. Normal 
analysis binds the qualified slot in the inner scope first, so it is not 
correlated. The new positive test avoids this collision, and the negative test 
would already throw on its local `l.ref_id` even without `main_table.ref_id`. 
Please use the analyzer's actual correlated-slot result and add a same-name 
local-column case.



-- 
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