-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
ExecuteUpdate: Allow using other tables in the query to generate result set #28731
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 | ||||
---|---|---|---|---|---|---|
|
@@ -1130,6 +1130,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp | |||||
foreach (var (propertyExpression, valueExpression) in propertyValueLambdaExpressions) | ||||||
{ | ||||||
var left = RemapLambdaBody(source, propertyExpression); | ||||||
left = left.UnwrapTypeConversion(out _); | ||||||
if (!IsValidPropertyAccess(left, out var ese)) | ||||||
{ | ||||||
AddTranslationErrorDetails(RelationalStrings.InvalidPropertyInSetProperty(propertyExpression.Print())); | ||||||
|
@@ -1148,6 +1149,10 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp | |||||
} | ||||||
|
||||||
var right = RemapLambdaBody(source, valueExpression); | ||||||
if (right.Type != left.Type) | ||||||
{ | ||||||
right = Expression.Convert(right, left.Type); | ||||||
} | ||||||
// We generate equality between property = value while translating sothat value infer tye type mapping from property correctly. | ||||||
// Later we decompose it back into left/right components so that the equality is not in the tree which can get affected by | ||||||
// null semantics or other visitor. | ||||||
|
@@ -1305,7 +1310,7 @@ static bool IsValidPropertyAccess(Expression expression, [NotNullWhen(true)] out | |||||
/// <param name="selectExpression">The select expression to validate.</param> | ||||||
/// <param name="entityShaperExpression">The entity shaper expression on which the delete operation is being applied.</param> | ||||||
/// <param name="tableExpression">The table expression from which rows are being deleted.</param> | ||||||
/// <returns> das </returns> | ||||||
/// <returns>Returns <see langword="true" /> if the current select expression can be used for delete as-is, <see langword="false" /> otherwise.</returns> | ||||||
protected virtual bool IsValidSelectExpressionForExecuteDelete( | ||||||
SelectExpression selectExpression, | ||||||
EntityShaperExpression entityShaperExpression, | ||||||
|
@@ -1330,13 +1335,12 @@ protected virtual bool IsValidSelectExpressionForExecuteDelete( | |||||
return false; | ||||||
} | ||||||
|
||||||
// TODO: Update this documentation. | ||||||
/// <summary> | ||||||
/// Validates if the current select expression can be used for execute update operation or it requires to be pushed into a subquery. | ||||||
/// Validates if the current select expression can be used for execute update operation or it requires to be joined as a subquery. | ||||||
/// </summary> | ||||||
/// <remarks> | ||||||
/// <para> | ||||||
/// By default, only single-table select expressions are supported, and optionally with a predicate. | ||||||
/// By default, only muli-table select expressions are supported, and optionally with a predicate. | ||||||
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.
Suggested change
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 will make this change in next PR to get this PR to pass and merge. Not confident that if I push another commit here, the mighty build infra will make it pass before deadline. |
||||||
/// </para> | ||||||
/// <para> | ||||||
/// Providers can override this to allow more select expression features to be supported without pushing down into a subquery. | ||||||
|
@@ -1347,7 +1351,7 @@ protected virtual bool IsValidSelectExpressionForExecuteDelete( | |||||
/// <param name="selectExpression">The select expression to validate.</param> | ||||||
/// <param name="entityShaperExpression">The entity shaper expression on which the update operation is being applied.</param> | ||||||
/// <param name="tableExpression">The table expression from which rows are being deleted.</param> | ||||||
/// <returns> das </returns> | ||||||
/// <returns>Returns <see langword="true" /> if the current select expression can be used for update as-is, <see langword="false" /> otherwise.</returns> | ||||||
protected virtual bool IsValidSelectExpressionForExecuteUpdate( | ||||||
SelectExpression selectExpression, | ||||||
EntityShaperExpression entityShaperExpression, | ||||||
|
@@ -1359,13 +1363,30 @@ protected virtual bool IsValidSelectExpressionForExecuteUpdate( | |||||
&& (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null) | ||||||
&& selectExpression.GroupBy.Count == 0 | ||||||
&& selectExpression.Having == null | ||||||
&& selectExpression.Orderings.Count == 0 | ||||||
&& selectExpression.Tables.Count == 1 | ||||||
&& selectExpression.Tables[0] is TableExpression expression) | ||||||
&& selectExpression.Orderings.Count == 0) | ||||||
{ | ||||||
tableExpression = expression; | ||||||
TableExpressionBase table; | ||||||
if (selectExpression.Tables.Count == 1) | ||||||
{ | ||||||
table = selectExpression.Tables[0]; | ||||||
} | ||||||
else | ||||||
{ | ||||||
var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; | ||||||
var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression); | ||||||
var column = entityProjectionExpression.BindProperty(entityShaperExpression.EntityType.GetProperties().First()); | ||||||
table = column.Table; | ||||||
if (table is JoinExpressionBase joinExpressionBase) | ||||||
{ | ||||||
table = joinExpressionBase.Table; | ||||||
} | ||||||
} | ||||||
|
||||||
return true; | ||||||
if (table is TableExpression te) | ||||||
{ | ||||||
tableExpression = te; | ||||||
return true; | ||||||
} | ||||||
} | ||||||
|
||||||
tableExpression = null; | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,15 +80,14 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression) | |
var selectExpression = updateExpression.SelectExpression; | ||
|
||
if (selectExpression.Offset == null | ||
&& selectExpression.Limit == null | ||
&& selectExpression.Having == null | ||
&& selectExpression.Orderings.Count == 0 | ||
&& selectExpression.GroupBy.Count == 0 | ||
&& selectExpression.Tables.Count == 1 | ||
&& selectExpression.Tables[0] == updateExpression.Table | ||
&& selectExpression.Projection.Count == 0) | ||
{ | ||
Sql.Append("UPDATE "); | ||
GenerateTop(selectExpression); | ||
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. 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 want you to change destiny of people but only x number of them. Rest can suffer their entire life! 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. Wait, actually.... just to confirm... If the user does OrderBy and then Take, will it translate correctly? I mean, if you push both operators in the the subquery it's fine, but if the TOP is at the top-level and the OrderBy is in the query, then the translation is bad... 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. (removing auto-merge just to be on the safe side) 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. existing order by causes subquery and top goes inside the subquery. 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. Perfect 👍 |
||
|
||
Sql.AppendLine($"{Dependencies.SqlGenerationHelper.DelimitIdentifier(updateExpression.Table.Alias)}"); | ||
using (Sql.Indent()) | ||
{ | ||
|
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.
For this kind of SQL manipulation, remember I moved the logic for PG DELETE to a separate visitor at the end of the pipeline, to keep SQL generation simple/clean. We could do something similar here, though of course it's not strictly necessary (it's just a pretty unique case where we're doing that in SQL generation).
I'll probably just do use the same visitor for update in PG 😈
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 considered it but there were few differences in that case. Using doesn't allow using any joins so you would need to move everything. Here it is just 1 table which needs to changed that way so I just inlined here. Also this is relational which SqlServer overrides, changing expression here means providers other than Sqliite/Postgre need to block the conversion. Which makes task more overall.