github-actions[bot] commented on code in PR #67713:
URL: https://github.com/apache/doris/pull/67713#discussion_r4069618865
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java:
##########
@@ -511,6 +516,36 @@ protected boolean shouldPrioritizeRelationQualifier() {
return lambdaAnalyzer.analyze(lambdaFunction, context);
}
+ /**
+ * Build the scope of a lambda body. The lambda arguments shadow the
same-named slots that are visible
+ * to the enclosing expression, and the enclosing scope becomes the outer
scope. Slot binding only looks
+ * one level up, so when this analyzer itself analyzes a lambda body
(nested high-order functions), the
+ * scope of this analyzer only holds the enclosing lambda arguments: merge
them into the new scope and keep
+ * the plan scope as the outer scope, so that the columns captured by the
nested lambda body stay bindable.
+ */
+ private Scope newLambdaScope(List<Slot> lambdaArgumentSlots) {
Review Comment:
[P2] Reconcile the scope fix after the base merge
`newLambdaScope` has no call sites on this head, and
`isLambdaBodyAnalyzer()` is only read inside this unused helper.
`analyzeLambdaFunction` still passes `new Scope(Optional.of(getScope()),
boundedSlots)` directly, so these production additions have no effect. The
positive cases can now bind because the newer base recursively delegates misses
through `enclosingAnalyzer`, not because this helper runs. Please either remove
the dead helper/override and present this as test-only coverage, or wire it
only for a case the base delegation demonstrably does not handle.
##########
fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindFunctionTest.java:
##########
@@ -74,4 +89,63 @@ void testJoinBindFunction() {
).when(join -> join.getHashJoinConjuncts().size() == 1)
);
}
+
+ @Test
+ void testNestedLambdaCapturesOuterColumn() {
+ List<String> sqls = ImmutableList.of(
+ "SELECT array_map(a -> array_sum(array_map(b -> if(flag, b,
0), arr2)), arr1) FROM t_arr",
+ "SELECT array_map(a -> array_sum(array_sortby(b -> if(flag, b,
0), arr2)), arr1) FROM t_arr",
+ "SELECT array_map(a -> array_map(b -> array_map(c -> if(flag,
b + c, id), arr2), arr2), arr1)"
+ + " FROM t_arr"
+ );
+ for (String sql : sqls) {
+ Lambda innermostLambda =
innermostLambda(PlanChecker.from(connectContext).analyze(sql).getPlan());
+ // getInputSlots() never contains the lambda argument slots
+ Set<String> capturedColumns =
innermostLambda.getLambdaFunction().getInputSlots().stream()
+ .map(Slot::getName)
+ .collect(Collectors.toSet());
+ Assertions.assertTrue(capturedColumns.contains("flag"), sql);
+ }
+ }
+
+ @Test
+ void testNestedLambdaSeesAllEnclosingLambdaArguments() {
+ String sql = "SELECT array_map(a -> array_map(b -> array_map(c ->
concat(a, b, c), arr1), arr1), arr1)"
+ + " FROM t_arr";
+ Lambda innermostLambda =
innermostLambda(PlanChecker.from(connectContext).analyze(sql).getPlan());
+ Set<ArrayItemSlot> lambdaArguments =
innermostLambda.getLambdaFunction()
+ .collect(ArrayItemSlot.class::isInstance);
+ Assertions.assertEquals(ImmutableList.of("a", "b", "c"),
+
lambdaArguments.stream().map(Slot::getName).sorted().collect(Collectors.toList()));
+ }
+
+ @Test
+ void testNestedLambdaArgumentShadowsEnclosingArgument() {
+ String sql = "SELECT array_map(x -> array_map(x -> x + 1, arr2), arr1)
FROM t_arr";
+ Lambda innermostLambda =
innermostLambda(PlanChecker.from(connectContext).analyze(sql).getPlan());
+ Set<ArrayItemSlot> bodySlots =
innermostLambda.getLambdaFunction().collect(ArrayItemSlot.class::isInstance);
+ Assertions.assertEquals(1, bodySlots.size());
+
Assertions.assertEquals(innermostLambda.getLambdaArgument(0).getExprId(),
+ bodySlots.iterator().next().getExprId());
+
Assertions.assertTrue(innermostLambda.getLambdaFunction().getInputSlots().isEmpty());
+ }
+
+ @Test
+ void testNestedLambdaUnknownSlot() {
+ String sql = "SELECT array_map(a -> array_map(b -> unknown_col + b,
arr2), arr1) FROM t_arr";
+ AnalysisException exception =
Assertions.assertThrows(AnalysisException.class,
+ () -> PlanChecker.from(connectContext).analyze(sql));
+ Assertions.assertTrue(exception.getMessage().contains("Unknown lambda
slot 'unknown_col"),
Review Comment:
[P1] Match the unknown-slot assertions to the reachable diagnostic
On this head an unresolved inner-lambda name is delegated through each
`enclosingAnalyzer` to the query analyzer, whose `couldNotFoundColumn` emits
`Unknown column 'unknown_col'...`. There is no production occurrence of
`Unknown lambda slot`; that text exists only in this assertion and the new
Groovy expectation at line 144. Therefore both negative tests fail when run.
Please update both expectations to the current diagnostic, or restore a
reachable lambda-specific diagnostic deliberately.
--
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]