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

Support member access on parameter and constant introduced in entity equality #16469

Merged
merged 1 commit into from
Jul 9, 2019

Conversation

roji
Copy link
Member

@roji roji commented Jul 5, 2019

Entity equality introduces member access expressions on what may be a
parameter or a constant. Identify these cases and transform them into
a new parameter (for access of a parameter) or evaluate the constant.

Fixes #15855
Fixes #14645
Fixes #14644

Notes (additional comments inline):

  • For now the logic is implemented in RelationalSqlTranslatingEV. In theory it should be moved to an earlier, independent visitor in the optimization phase, this way this can be used for non-relational. However, that would require distinguishing between ParameterExpressions that correspond to closures (which we're looking for) and all other lambda ParameterExpressions (which we don't care about). In other words, ParameterExtractingEV made our job difficult by replacing constant expressions with TypeAttributes.NestedPrivate to ParameterExpression. We could go around this by having ParameterExtractingEV inject a special, custom ParameterExpression which we could easily identify later - not sure if that's a good idea. We can always revisit all this later.
  • For now, when a member access is encountered on a parameter, we generate a new parameter appending _<member name>, e.g. @__local_0_CustomerID. We could go the extra mile to make this @__local_CustomerID_x if it's important - ideally that would mean the QCC has access to the QueryContext (to see how many parameters are on it), or we can code-generate calculating it in runtime, which seems bad.
  • In theory we could encounter nested member access on parameter (e.g. captured.x.y), but I don't think anything in our pipeline can currently generate this. Entity equality will always tack key properties on an entity that was parameterized up-front by ParameterExpression (so one level only). Assuming global filters are only parameterized for a simple tenant ID on the context, it's simlarly impossible. So I'm leaving this problem out of scope unless you have an example where it's important.
  • The current ParameterExtractingEV, as it's used by Fix to #15264 - QueryRewrite: incorporate query filters into nav rewrite #16327 for query filters, seems like it could use some refactoring :) Basically the same visitor is used in two modes - when used in the frontend _generateContextAccessors is false and it produces actual parameter value. When used for query filters, that flag is true and instead it produces lambdas... This is why Fix to #15264 - QueryRewrite: incorporate query filters into nav rewrite #16327 added Dictionary<string, object> to QCC but it always contains lambda values. I'd like to take a look and see if it could be cleaner, i.e. two subclasses - one for the frontend and another for the filters.

@roji roji requested a review from smitpatel July 5, 2019 22:39
@@ -1994,7 +2010,7 @@ where es.SingleOrDefault(e2 => e2.EmployeeID == 42) == new Employee()
select e1);
}

[ConditionalFact]
[ConditionalFact(Skip = "Does not throw")]
Copy link
Member Author

@roji roji Jul 5, 2019

Choose a reason for hiding this comment

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

Seems like we have a bug here with Single in subquery. Let me know if I should open a new issue or point me to an existing.

Copy link
Contributor

Choose a reason for hiding this comment

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

We stopped throwing behavior for Single in subquery. See #15559
Though it should generate invalid SQL for now since single should generate Top (2) in subquery which is wrong.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this is very similar to Where_query_composition_entity_equality_multiple_elements_SingleOrDefault from the comment above, except that this test doesn't generate any rows at all and SqlServer is OK with that... I will also reference #15559 in the skip message.

@roji
Copy link
Member Author

roji commented Jul 5, 2019

Need to update PR for Cosmos (always forget that), will do that tomorrow.

Copy link
Contributor

@smitpatel smitpatel left a comment

Choose a reason for hiding this comment

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

  • The logic must be in core optimization phase. This expression is side-effect of optimization of tree in entity equality so it is not way related to translation pipeline or relational.
  • Parameter created by front-end are prefixed by CompiledQueryCache.CompiledQueryParameterPrefix (Query filter has slightly different prefix). So it is easy to identify the case. Prefix can still have clash in edge case but it has worked in past and we live with it.
  • QCC should not have direct access to QC. For that purpose, we use different prefix. QueryFilter got own prefix. Since the functionality of runtime parameters from query filter and EE is merging, we can determine a runtime parameter prefix and use it. The numbering will auto-adjust since QCC has size.
  • Nested is out of scope since EE doesn't/can't generate that.
  • Refactoring has low value. When it produces lambdas for query filter, it still evaluate partial expressions to generate constants. So evaluating subtree part is not going away regardless of it generating contextAccessor

@roji roji changed the title Support member access on parameter and constant introduced in pipeline Support member access on parameter and constant introduced in entity equality Jul 9, 2019
@roji roji force-pushed the ConstantAndParameterProperties branch from 19b0a36 to 75f5e86 Compare July 9, 2019 16:40
@roji
Copy link
Member Author

roji commented Jul 9, 2019

Thanks for guidance @smitpatel, all makes sense - was mainly confused around identifying the parameter, and about the possible need to perform the logic generically for other possible visitors which may produce property access. The new version performs everything with EE.

QCC should not have direct access to QC. For that purpose, we use different prefix. QueryFilter got own prefix. Since the functionality of runtime parameters from query filter and EE is merging, we can determine a runtime parameter prefix and use it. The numbering will auto-adjust since QCC has size.

Makes sense. I still propose to keep the EE and query filter parameter prefixes distinct, to help debugging where the parameters came from. We can keep "__" for regular parameters, and have "EE" and "QF" for entity equality and query filters, respectively. How does that sound?

Refactoring has low value. When it produces lambdas for query filter, it still evaluate partial expressions to generate constants. So evaluating subtree part is not going away regardless of it generating contextAccessor

My problem here is mainly with the fact that ParameterExtractingEV populates parameterValues sometimes with values, and sometimes with lambdas. I can take a quick look at this when I start looking at #16327, no need to discuss this now.

@smitpatel smitpatel dismissed their stale review July 9, 2019 17:25

Feedback addressed.

@smitpatel
Copy link
Contributor

Also fixes #14645

@smitpatel
Copy link
Contributor

Also resolves #14644 since parameters are added to parameter values before we go through translation/SQL generation phase.

@roji roji force-pushed the ConstantAndParameterProperties branch from 75f5e86 to ae4a9ed Compare July 9, 2019 18:51
Copy link
Contributor

@smitpatel smitpatel left a comment

Choose a reason for hiding this comment

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

some tiny bits to fix.

@roji roji force-pushed the ConstantAndParameterProperties branch from ae4a9ed to 87777ba Compare July 9, 2019 19:30
@roji
Copy link
Member Author

roji commented Jul 9, 2019

Want to give this a last quick look @smitpatel? If not I'll merge.

Entity equality introduces member access expressions on what may be a
parameter or a constant. Identify these cases and generate a new
parameter (for access of a parameter) or evaluate the constant.

Fixes #15855
Fixes #14645
Fixes #14644
@roji roji force-pushed the ConstantAndParameterProperties branch from 87777ba to 7b2cc0d Compare July 9, 2019 19:49
@roji
Copy link
Member Author

roji commented Jul 9, 2019

Windows build failed with the following, merging:

      System.InvalidOperationException : Internal connection fatal error.
      Stack Trace:
           at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
           at Microsoft.Data.SqlClient.SqlDataReader.TryCloseInternal(Boolean closeReader)
           at Microsoft.Data.SqlClient.SqlDataReader.Close()
        /_/src/EFCore.Relational/Storage/RelationalDataReader.cs(134,0): at Microsoft.EntityFrameworkCore.Storage.RelationalDataReader.Dispose()
        /_/src/EFCore.Relational/Query/Pipeline/QueryingEnumerable.cs(172,0): at Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.RelationalShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.Dispose()
           at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
           at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
        /_/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs(403,0): at Microsoft.EntityFrameworkCore.TestUtilities.QueryAsserter`1.AssertQuery[TItem1,TItem2](Func`3 actualQuery, Func`3 expectedQuery, Func`2 elementSorter, Action`2 elementAsserter, Boolean assertOrder, Int32 entryCount, Boolean isAsync, String testMethodName)
        /_/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs(1057,0): at Microsoft.EntityFrameworkCore.Query.SimpleQuerySqlServerTest.Where_not_in_optimization3(Boolean isAsync)
        --- End of stack trace from previous location where exception was thrown ---

@roji roji merged commit b5cee2d into master Jul 9, 2019
@ghost ghost deleted the ConstantAndParameterProperties branch July 9, 2019 20:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants