Skip to content
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

Fix to #30575 - Multiple LeftJoins (GroupJoins) lead to GroupJoin Exception when the same where is used twice #30594

Merged
merged 1 commit into from
Apr 26, 2023

Conversation

maumar
Copy link
Contributor

@maumar maumar commented Mar 29, 2023

Problem is in QueryableMethodNormalizingExpressionVisitor and specifically part where we convert from GroupJoin-SelectMany-DefaultIfEmpty into left join (SelectManyVerifyingExpressionVisitor). We check if the collection selector is correlated, and we do that by looking at parameters in that lambda. Problem is that the affected queries reference outside variable that gets parameterized and that breaks the correlation finding logic. Fix is to add a step that scans entire query and identifies external parameters before we try to normalize GJSMDIE into LeftJoins, so that those external parameters are not counted as correlated.

Fixes #30575

@maumar
Copy link
Contributor Author

maumar commented Apr 15, 2023

@roji

@@ -37,6 +37,10 @@ public QueryableMethodNormalizingExpressionVisitor(QueryCompilationContext query
/// </summary>
public virtual Expression Normalize(Expression expression)
{
var externalParametersDetector = new ExternalParametersDetector();
externalParametersDetector.Visit(expression);
_selectManyVerifyingExpressionVisitor = new(externalParametersDetector.ExternalParameters);
Copy link
Member

@roji roji Apr 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: instead of reinstantiating SelectManyVerifyingExpressionVisitor every time (allocations etc.), we can just pass the allowed parameters to VerifyCollectionSelector

Copy link
Member

@roji roji left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, the problem here is that a captured lambda variable is mistakenly identified as a sever-correlated ParameterExpression, and so instead of LEFT JOIN we get OUTER APPLY, right?

If so, and we need to identify captured variables to avoid marking the tree as correlated, then I think the standard way of doing that is checking if the parameter name starts with QueryCompilationContext.QueryParameterPrefix; we already do that in various other contexts.

If that's all correct, then instead of an additional visitation for all queries in order to identify captured variables, I think we can just check the name in SelectManyVerifyingExpressionVisitor.VisitParameter:

if (_allowedParameters.Contains(parameterExpression)
    || parameterExpression.Name?.StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal) == true)
{
    return parameterExpression;
}

In confirmed that just doing this makes your new tests pass.

{
if (!ExternalParameters.Contains(parameterExpression))
{
ExternalParameters.Add(parameterExpression);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that all lambda parameters are returned in the allowed, and their scope is "valid" throughout the entire query tree; this is unlike the current implementation, where we remove the lambda parameters from the allow list when we exit the lambda. I don't think it's a problem, since such a parameter could probably only appear outside of its lambda's scope in malformed expression tree.

Another point is that we add the lambda parameters here, but then there's still the code in SelectManyVerifyingExpressionVisitor which adds and removes them again...

To make this simpler/safer, we could have this visitor only add non-lambda parameters, and leave the lambda parameter management to SelectManyVerifyingExpressionVisitor as today. So we'd just provide an externally-provided list of non-lambda parameters.

…eption when the same where is used twice

Problem is in QueryableMethodNormalizingExpressionVisitor and specifically part where we convert from GroupJoin-SelectMany-DefaultIfEmpty into left join (SelectManyVerifyingExpressionVisitor). We check if the collection selector is correlated, and we do that by looking at parameters in that lambda. Problem is that the affected queries reference outside variable that gets parameterized and that breaks the correlation finding logic.
Fix is to check if a parameter has a prefix associated with external parameter when we count correlated parameters.

Fixes #30575
@roji
Copy link
Member

roji commented Apr 26, 2023

Should consider for patching...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Multiple LeftJoins (GroupJoins) lead to GroupJoin Exception when the same where is used twice
2 participants