-
Notifications
You must be signed in to change notification settings - Fork 2.5k
[CALCITE-6242] Enhance lambda closure #4926
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3300,10 +3300,9 @@ private void registerQuery( | |
| alias, | ||
| lambdaNamespace, | ||
| forceNullable); | ||
| operands = call.getOperandList(); | ||
| for (int i = 0; i < operands.size(); i++) { | ||
| registerOperandSubQueries(parentScope, call, i); | ||
| } | ||
| // Register sub-queries inside the body under lambdaScope, so that | ||
| // nested lambdas can resolve outer lambda parameters. | ||
| registerOperandSubQueries(lambdaScope, call, 1); | ||
| break; | ||
|
|
||
| case WITH: | ||
|
|
@@ -6426,6 +6425,30 @@ public void setOriginal(SqlNode expr, SqlNode original) { | |
| final LambdaNamespace ns = | ||
| getNamespaceOrThrow(lambdaExpr).unwrap(LambdaNamespace.class); | ||
|
|
||
| // Check for duplicate lambda parameter names | ||
| final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); | ||
| final Set<String> seen = nameMatcher.createSet(); | ||
| for (SqlNode param : lambdaExpr.getParameters()) { | ||
| final String name = ((SqlIdentifier) param).getSimple(); | ||
| if (!seen.add(name)) { | ||
| throw newValidationError(param, | ||
| RESOURCE.duplicateLambdaParameter(name)); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we write something like:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
done,thanks |
||
| // Check against enclosing lambda scopes: x -> ... x -> ... | ||
| SqlValidatorScope parentScope = scope.getParent(); | ||
| while (parentScope instanceof DelegatingScope) { | ||
| if (parentScope instanceof SqlLambdaScope) { | ||
| final SqlLambdaScope parentLambda = (SqlLambdaScope) parentScope; | ||
| if (parentLambda.getParameterTypes().keySet().stream() | ||
| .anyMatch(p -> nameMatcher.matches(p, name))) { | ||
| throw newValidationError(param, | ||
| RESOURCE.duplicateLambdaParameter(name)); | ||
| } | ||
| } | ||
| parentScope = ((DelegatingScope) parentScope).getParent(); | ||
| } | ||
| } | ||
|
|
||
| deriveType(scope, lambdaExpr.getExpression()); | ||
| RelDataType type = deriveTypeImpl(scope, lambdaExpr); | ||
| setValidatedNodeType(lambdaExpr, type); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2362,6 +2362,13 @@ private RexNode convertLambda(Blackboard bb, SqlNode node) { | |
| final SqlLambdaScope scope = (SqlLambdaScope) validator().getLambdaScope(call); | ||
|
|
||
| final Map<String, RexNode> nameToNodeMap = new HashMap<>(); | ||
| // For nested lambdas, inherit the parent blackboard's nameToNodeMap so that | ||
| // the inner lambda can resolve references to outer lambda parameters. | ||
| // e.g., in x -> EXISTS(arr, y -> x + y = 4), the inner lambda's blackboard | ||
| // needs access to "X" from the outer lambda's nameToNodeMap. | ||
| if (bb.nameToNodeMap != null) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. speaking of this, I suspect we should reject lambdas with the same parameter used twice
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I completely agree. I have added new test cases for this |
||
| nameToNodeMap.putAll(bb.nameToNodeMap); | ||
| } | ||
| final List<RexLambdaRef> parameters = new ArrayList<>(scope.getParameterTypes().size()); | ||
| final Map<String, RelDataType> parameterTypes = scope.getParameterTypes(); | ||
|
|
||
|
|
@@ -5563,11 +5570,16 @@ void setRoot(List<RelNode> inputs) { | |
| SqlQualified qualified) { | ||
| if (nameToNodeMap != null && qualified.prefixLength == 1) { | ||
| RexNode node = nameToNodeMap.get(qualified.identifier.names.get(0)); | ||
| if (node == null) { | ||
| if (node != null) { | ||
| return Pair.of(node, null); | ||
| } | ||
| // If the identifier is not found in nameToNodeMap and the current scope | ||
| // is a lambda scope, fall through to standard scope resolution to allow | ||
| // external references (e.g., t2.v in a JOIN ON lambda expression). | ||
| if (!(scope instanceof SqlLambdaScope)) { | ||
| throw new AssertionError("Unknown identifier '" + qualified.identifier | ||
| + "' encountered while expanding expression"); | ||
| } | ||
| return Pair.of(node, null); | ||
| } | ||
| final SqlNameMatcher nameMatcher = | ||
| scope.getValidator().getCatalogReader().nameMatcher(); | ||
|
|
@@ -5586,6 +5598,13 @@ void setRoot(List<RelNode> inputs) { | |
| // preserved. | ||
| final SqlValidatorScope ancestorScope = resolve.scope; | ||
| boolean isParent = ancestorScope != scope; | ||
| // When in a lambda scope, external references to tables that are part | ||
| // of the current blackboard's inputs should be resolved locally, not | ||
| // as correlation variables. The lambda blackboard inherits inputs from | ||
| // its parent blackboard. | ||
| if (isParent && scope instanceof SqlLambdaScope && inputs != null) { | ||
| isParent = false; | ||
| } | ||
| if ((inputs != null) && !isParent) { | ||
| final LookupContext rels = | ||
| new LookupContext(this, inputs, systemFieldList.size()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could add some comments here to explain the reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, thanks