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

Set operations: cleanup #16249

Merged
merged 1 commit into from
Jun 28, 2019
Merged

Set operations: cleanup #16249

merged 1 commit into from
Jun 28, 2019

Conversation

roji
Copy link
Member

@roji roji commented Jun 25, 2019

NOTE: This PR was changed to only cleanup some aspects, other parts were split out


  • Include/nav rewriting is now supported.
  • We now push down to subquery when OrderBy, Take or Skip are applied to set operations, to avoid using the hack where ColumnExpression with no table alias was used. Set operations: translate OrderBy/Take/Skip directly on set operation instead of pushing down #16244 was opened to track for post-3.0.
  • Added missing support for union over subselect projection mappings.
  • Added Other type to SetOperationType so that providers can define extra set operations (e.g. PostgreSQL INTERSECT ALL, EXCEPT ALL).

Completes #6812
Fixes #13196
Fixes #16065
Fixes #16165

if (commonParentEntityType != projection1.EntityType)
{
// The first source has been up-cast by the set operation, so we also need to change the shaper expression.
var entityShaperExpression =
Copy link
Member Author

Choose a reason for hiding this comment

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

@smitpatel I know remove the cast only if it was casting to our common parent type, for which we're setting the new shaper anyway. This code doesn't handle all cases - it's possible there will be two cast nodes, in which case this will not work (but I've seen this pattern in other places in the code). I'm still not exactly sure what we expect to happen, but this may be enough for now. Let me know if you want us to do something else here.

// Find that.
var clrType1 = preProcessResult1.state.CurrentParameter.Type;
var clrType2 = preProcessResult2.state.CurrentParameter.Type;
var parentState = clrType1.IsAssignableFrom(clrType2) ? preProcessResult1.state : preProcessResult2.state;
Copy link
Contributor

Choose a reason for hiding this comment

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

this way we only propagate include information applied on less derived type. e.g.
customers.Include(c => c.Nav1).Concat(customers.OfType().Include(c => c.Nav2)), we would end up including only Nav1. We either need to merge include information (which is embedded in NavigationTreeNode on the NavigationExpansionState) or detect when the include paths on both sides are different and throw - can probably be done in a future PR as the scenario is somewhat contrived.

Copy link
Contributor

Choose a reason for hiding this comment

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

you can use IncludeHelpers.CopyIncludeInformation

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I thought about this a bit. I think that trying to do a set operation with differing includes should throw, since merging includes doesn't correspond to the presumed user expectation, which would be to only include each nav the set operand it's specified. This can't be done in SQL (i.e. unless we fetch the information and don't materialize it).

If users want merged includes, they can simply write the following themselves:

customers.Union(customers.OfType<...>())
    .Include(c => c.Nav1)
    .Include(c => c.Nav2)

We may want to discuss this in design (@divega @ajcvickers). For now I've implemented a NotSupportedException for this (@maumar hope you can take a look to make sure I did it right).

var select2 = otherSelectExpression;

if (_projection.Any())
{
throw new NotImplementedException("Set operation on SelectExpression with populated _projection");
throw new NotSupportedException("Can't process set operations after client evaluation, consider moving the operation before the last Select() call (see issue #16243)");
Copy link
Contributor

Choose a reason for hiding this comment

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

Which exception type to use? @ajcvickers
We had some recent discussion around this but I don't remember the outcome.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor

@divega divega Jun 26, 2019

Choose a reason for hiding this comment

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

I don't remember that discussion 😄 But I am leaning towards NotImplementedException InvalidOperationException and simplifying the message, but that is because I think #16243 is by design (of course I could be wrong and I may need someone to explain to me why it isn't).

Copy link
Contributor

Choose a reason for hiding this comment

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

FYI, I created https://go.microsoft.com/fwlink/?linkid=2097910 for exceptions we throw when something would require client evaluation. We tweak what it points to in the future.

Copy link
Member Author

Choose a reason for hiding this comment

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

@divega I don't have the full picture, but @smitpatel seemed to indicate this should be possible by us moving the union before the client evaluation.

I'm leaning towards NotSupportedException (in the sense of not supported in this version), or to a less extent NotImplementedException (although that's usually used as a very temporary marker for a dev to remember they still have to do something). InvalidOperationException seems used more because an object is in a wrong state, which doesn't seem to be the case here.

However, I'm changing to InvalidOperationException for now we we really want this PR to get merged in time for preview7.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see. That is a good idea. I guess it would only about lifting the client eval part and not the actual projection, because lifting the projection over Union() would not be correct in all cases. I personally would love to do something when we can, but I suspect it is going to take some time for this to get to the top of the list.

Re the exception type, I actually don't feel strongly about which one would be the most correct to throw, but what I heard from @ajcvickers and @smitpatel is that keeping the set of exception types customers should catch beats being absolutely correct.

{
shaperExpression = new EntityShaperExpression(
commonParentEntityType, entityShaperExpression.ValueBufferExpression, entityShaperExpression.Nullable);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What is else result? Just ignore?

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, I went with a different approach - I now replace the EntityShaperExpression, and if it's wrapped by Convert nodes I recreate those exactly as they previously were. It's effectively as if the entity was originally queried as a less derived type as what it is.

Sorry this part is taking so many iterations, please let me know if it's still not the way you want - I still don't feel like I understand all the possibilities here 100%.

@roji
Copy link
Member Author

roji commented Jun 26, 2019

Pushed a new commit with various fixes.

@smitpatel
Copy link
Contributor

If navigation part of set operation is going to take time then can we split out, push down for order by in separate PR and get it merged sooner?

@smitpatel
Copy link
Contributor

Also set operations with fromsql.

@roji
Copy link
Member Author

roji commented Jun 26, 2019

If navigation part of set operation is going to take time then can we split out, push down for order by in separate PR and get it merged sooner?

I'm going to take a quick stab at it - I have to leave very soon anyway.

In any case I will look at it later tonight. @smitpatel, maybe comment on what you consider OK (e.g. there's still the convert nodes when replacing the EntityShaperExpression). If necessary I can split out the problematic parts.

Finally, even if the PR isn't perfect as-is, we can also decide to merge and I'll fix anything right afterwards (i.e. tomorrow during my day). Up to you.

@smitpatel
Copy link
Contributor

smitpatel commented Jun 26, 2019

private ColumnExpression(string name, TableExpressionBase table, Type type, RelationalTypeMapping typeMapping, bool nullable)
            : base(type, typeMapping)
        {
// Add this line
            Check.NotEmpty(Table.Alias);
            Name = name;
            Table = table;
            Nullable = nullable;
        }

Fix whatever fails with that, (disable/pushdown). (Apparently this PR does not update code about null check). Split out above in a new PR.

@roji
Copy link
Member Author

roji commented Jun 27, 2019

@smitpatel, removed support entirely for set operations over operands of different entity types - scoped this out to #16298 (this is what the ColumnExpression was for). We can look at this later, hopefully (but not 100% necessarily for 3.0).

@maumar implemented the better way to compare includes on both sides, you can take a look.

Let's try to get it into preview7.

@maumar
Copy link
Contributor

maumar commented Jun 27, 2019

@roji nav rewrite changes look good!

@roji
Copy link
Member Author

roji commented Jun 28, 2019

Addressed @maumar's comments and rebased on latest master.

@roji roji mentioned this pull request Jun 28, 2019
67 tasks
@smitpatel
Copy link
Contributor

Can you split out PR in 2 parts?

  • Fixing issue with Table without alias
  • Nav expansion part of set operations

Since @maumar is gone for vacation, it may take some time to understand nav expansion changes.

@roji
Copy link
Member Author

roji commented Jun 28, 2019

Done, this PR now only fixes the ColumnExpression alias issue. Once it's merged I'll submit a separate one for nav/include support.

* We no longer allow ColumnExpression without table alias
* Set operations over operands with different types have been disabled
  for now.
* We now push down set operations into a subquery on Orderby, Skip or
  Take, which shouldn't be necessary (#16244).
* Some test consolidation and cleanup.

Continues #6812
@roji roji merged commit a2ce460 into master Jun 28, 2019
@ghost ghost deleted the StateOfTheUnion2 branch June 28, 2019 16:23
@roji roji changed the title Set operations: finalization Set operations: cleanup Jun 28, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants