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

Query: Introduce client projections in SelectExpression #24675

Merged
1 commit merged into from
Apr 20, 2021
Merged

Query: Introduce client projections in SelectExpression #24675

1 commit merged into from
Apr 20, 2021

Conversation

smitpatel
Copy link
Contributor

  • ClientProjections is alternate when ProjectionMember binding is not possible using indexes
  • ClientProjections is separate from SelectExpression.Projection where former holds complex client side mapped projections and later holds only scalars
  • What it enables
    • Single result subquery is not joined right away
    • pendingCollections are removed
    • Ability to bind with above subquery translations after projection has converted to index based binding (enabling pending selector future change)
    • Ability to bind client projections smoothly like ProjectionMember binding so suppose translations post-client eval where it is supported
    • Grouping.FirstOrDefault can add subquery projection which applies separately so we can combine aggregate/non-aggregate projection on grouping
  • Introduce CollectionResultExpression which holds info on how to create collection for a subquery
  • ApplyProjection converts projectionMember/Index based bindings to actual scalar projection and updates shaper in same pass
  • Unique collectionId are assigned by shaper
  • Change in flow to projection to let sqlTranslator determines translatibility before processing for client side
  • Regression in collection over single result subquery
    • Earlier we applied single result right away copying over pending collection to outer which allowed single result subquery to convert apply to join
    • Now all client projections apply at same time so pending collection (equivalent subquery) is applied inside single result subquery so we fail to convert apply to join

Ground work for #20291, #12088, #13805
Resolves #23571

@@ -391,525 +393,622 @@ public void ApplyDistinct()
/// <summary>
/// Adds expressions from projection mapping to projection if not done already.
/// </summary>
public void ApplyProjection()
public Expression ApplyProjection(Expression shaperExpression, QuerySplittingBehavior querySplittingBehavior)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No way to preserve old method with new behavior.

/// <summary>
/// Clears all existing projections.
/// </summary>
public void ClearProjection()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Behavior has diverged so there is no good new behavior. Obsoleted to point to how to remove client projections.
(We can add method in future to add scalar projections if we get feedback for it)

/// Replaces current projection mapping with a new one to change what is being projected out from this <see cref="SelectExpression" />.
/// </summary>
/// <param name="projectionMapping"> A new projection mapping. </param>
public void ReplaceProjectionMapping(IDictionary<ProjectionMember, Expression> projectionMapping)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

obsoleted

/// </summary>
/// <param name="projectionMember"> A projection member to search in the mapping. </param>
/// <returns> The mapped projection for given projection member. </returns>
public Expression GetMappedProjection(ProjectionMember projectionMember)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Obsoleted

/// </summary>
/// <param name="entityProjection"> An entity projection to add. </param>
/// <returns> A dictionary of <see cref="IProperty" /> to int indicating properties and their corresponding indexes in the projection list. </returns>
public IReadOnlyDictionary<IProperty, int> AddToProjection(EntityProjectionExpression entityProjection)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No way to preserve old behavior

/// </summary>
/// <param name="sqlExpression"> An expression to add. </param>
/// <returns> An int value indicating the index at which the expression was added in the projection list. </returns>
public int AddToProjection(SqlExpression sqlExpression)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This still exists below.

/// </summary>
/// <param name="shapedQueryExpression"> A shaped query expression for the subquery producing single non-scalar result. </param>
/// <returns> A shaper expression to shape the result of this projection. </returns>
public Expression AddSingleProjection(ShapedQueryExpression shapedQueryExpression)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No way to preserve old behavior

/// <param name="navigation"> A navigation associated with this collection, if any. </param>
/// <param name="elementType"> The type of the element in the collection. </param>
/// <returns> A <see cref="CollectionShaperExpression" /> which represents shaping of this collection. </returns>
public CollectionShaperExpression AddCollectionProjection(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No way to preserve old behavior

/// <param name="elementType"> The type of the element in the collection. </param>
/// <param name="splitQuery"> A value indicating whether the collection query would be run with a different DbCommand. </param>
/// <returns> An expression which represents shaping of this collection. </returns>
public Expression? ApplyCollectionJoin(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No way to preserve old behavior

@smitpatel smitpatel requested a review from a team April 16, 2021 20:14
@roji
Copy link
Member

roji commented Apr 16, 2021

/cc @Emill, according to @smitpatel this should help with your work on array-based includes. Might be better to wait until this is merged before tackling the EF side...

@smitpatel
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@Emill
Copy link

Emill commented Apr 17, 2021

I ran through the new code using the debugger, and from what I see it seems very nice indeed as a base for the collection-as-array approach!

I guess what simply has to be done now is to skip most of the code in ApplyProjection that transforms the collection into a join, and replace it with an array of rows projection instead. The materializer code should be quite similar to what I've already done, but hopefully more clean when reading out the projection items.

Not sure though how the ROW(...) expression is best reflected as an SqlExpression. The ROW(...) itself wraps multiple columns into one column (which must be done because an array cannot contain multiple columns per item), but for the projection references, we would probably still want to treat it as multiple columns, i.e. as if the ROW(...) wrapping never happened.

@roji
Copy link
Member

roji commented Apr 17, 2021

Not sure though how the ROW(...) expression is best reflected as an SqlExpression. The ROW(...) itself wraps multiple columns into one column (which must be done because an array cannot contain multiple columns per item), but for the projection references, we would probably still want to treat it as multiple columns, i.e. as if the ROW(...) wrapping never happened.

Let's have this discussion on the EFCore.PG repo, to avoid create PG-specific noise here (unless you think it could impact the design in this PR in some way).

@Emill
Copy link

Emill commented Apr 17, 2021

Sure, just brought up the topic here since it relates to the change in this PR how to reference a specific projection with the new "client projection" index thing.

@smitpatel
Copy link
Contributor Author

@Emill - The rough idea is that an enumerable subquery which pg specifically want to convert to a different SQL, now can be intercepted in ShapedQueryExpression before projection is applied as whole. So you can get that associated projection out and modify it as needed to form the custom expression and replace CollectionResultExpression to whatever suitable expression which can generate results from custom SQL.

@maumar
Copy link
Contributor

maumar commented Apr 20, 2021

:shipit:

- ClientProjections is alternate when ProjectionMember binding is not possible using indexes
- ClientProjections is separate from SelectExpression.Projection where former holds complex client side mapped projections and later holds only scalars
- What it enables
  - Single result subquery is not joined right away
  - pendingCollections are removed
  - Ability to bind with above subquery translations after projection has converted to index based binding (enabling pending selector future change)
  - Ability to bind client projections smoothly like ProjectionMember binding so suppose translations post-client eval where it is supported
  - Grouping.FirstOrDefault can add subquery projection which applies separately so we can combine aggregate/non-aggregate projection on grouping
- Introduce CollectionResultExpression which holds info on how to create collection for a subquery
- ApplyProjection converts projectionMember/Index based bindings to actual scalar projection and updates shaper in same pass
- Unique collectionId are assigned by shaper
- Change in flow to projection to let sqlTranslator determines translatibility before processing for client side
- Regression in collection over single result subquery
  - Earlier we applied single result right away copying over pending collection to outer which allowed single result subquery to convert apply to join
  - Now all client projections apply at same time so pending collection (equivalent subquery) is applied inside single result subquery so we fail to convert apply to join

Ground work for #20291, #12088, #13805
Resolves #23571
@ghost
Copy link

ghost commented Apr 20, 2021

Hello @smitpatel!

Because this pull request has the auto-merge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost
Copy link

ghost commented Apr 20, 2021

Apologies, while this PR appears ready to be merged, I've been configured to only merge when all checks have explicitly passed. The following integrations have not reported any progress on their checks and are blocking auto-merge:

  1. Azure Pipelines

These integrations are possibly never going to report a check, and unblocking auto-merge likely requires a human being to update my configuration to exempt these integrations from requiring a passing check.

Give feedback on this
From the bot dev team

We've tried to tune the bot such that it posts a comment like this only when auto-merge is blocked for exceptional, non-intuitive reasons. When the bot's auto-merge capability is properly configured, auto-merge should operate as you would intuitively expect and you should not see any spurious comments.

Please reach out to us at fabricbotservices@microsoft.com to provide feedback if you believe you're seeing this comment appear spuriously. Please note that we usually are unable to update your bot configuration on your team's behalf, but we're happy to help you identify your bot admin.

@ghost ghost merged commit 7e2c5ce into main Apr 20, 2021
@ghost ghost deleted the smit/Abc branch April 20, 2021 15:20
smitpatel added a commit that referenced this pull request Apr 21, 2021
… nested

This fixes slight regression which was introduced in #24675
We need to lift projections for single result since if there is a split query on it then we need it joined to top level to get proper parent query to clone
smitpatel added a commit that referenced this pull request Apr 21, 2021
… nested

This fixes slight regression which was introduced in #24675
We need to lift projections for single result since if there is a split query on it then we need it joined to top level to get proper parent query to clone
ghost pushed a commit that referenced this pull request Apr 21, 2021
… nested (#24715)

This fixes slight regression which was introduced in #24675
We need to lift projections for single result since if there is a split query on it then we need it joined to top level to get proper parent query to clone
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Query: Make pending collections non-pending
4 participants