Skip to content

Commit 875d5a8

Browse files
rojiCopilot
andcommitted
Move join-lifting from postprocessor to TranslateExecuteUpdate
Move the SQLite UPDATE join-lifting logic from a separate ExpressionVisitor in the postprocessor to an override of TranslateExecuteUpdate in SqliteQueryableMethodTranslatingExpressionVisitor. This runs the fix earlier during translation and only for ExecuteUpdate queries, rather than as a full tree walk over all queries. Delete SqliteUpdateJoinLiftingVisitor.cs as it is no longer needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 388aee9 commit 875d5a8

4 files changed

Lines changed: 79 additions & 102 deletions

File tree

src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryTranslationPostprocessor.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ public override Expression Process(Expression query)
4040
{
4141
var result = base.Process(query);
4242
_applyValidator.Visit(result);
43-
result = new SqliteUpdateJoinLiftingVisitor(RelationalQueryCompilationContext.SqlAliasManager).Visit(result);
4443

4544
return result;
4645
}

src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,83 @@ protected SqliteQueryableMethodTranslatingExpressionVisitor(
8484
protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVisitor()
8585
=> new SqliteQueryableMethodTranslatingExpressionVisitor(this);
8686

87+
/// <summary>
88+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
89+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
90+
/// any release. You should only use it directly in your code with extreme caution and knowing that
91+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
92+
/// </summary>
93+
protected override UpdateExpression TranslateExecuteUpdate(ShapedQueryExpression source, IReadOnlyList<ExecuteUpdateSetter> setters)
94+
{
95+
var updateExpression = base.TranslateExecuteUpdate(source, setters);
96+
97+
// SQLite doesn't support referencing the UPDATE target table alias in JOIN ON clauses.
98+
// Detect such joins and replace them with cross joins, lifting their predicates to WHERE.
99+
var selectExpression = updateExpression.SelectExpression;
100+
if (selectExpression.Tables.Count <= 1)
101+
{
102+
return updateExpression;
103+
}
104+
105+
var targetAlias = updateExpression.Table.Alias;
106+
var tables = selectExpression.Tables.ToList();
107+
var predicate = selectExpression.Predicate;
108+
var changed = false;
109+
110+
for (var i = 0; i < tables.Count; i++)
111+
{
112+
if (tables[i] is InnerJoinExpression innerJoin
113+
&& ContainsTargetReference(innerJoin.JoinPredicate, targetAlias))
114+
{
115+
predicate = predicate == null
116+
? innerJoin.JoinPredicate
117+
: new SqlBinaryExpression(
118+
ExpressionType.AndAlso,
119+
predicate,
120+
innerJoin.JoinPredicate,
121+
typeof(bool),
122+
predicate.TypeMapping);
123+
124+
tables[i] = new CrossJoinExpression(innerJoin.Table);
125+
changed = true;
126+
}
127+
}
128+
129+
if (!changed)
130+
{
131+
return updateExpression;
132+
}
133+
134+
#pragma warning disable EF1001 // Internal EF Core API usage.
135+
var newSelect = new SelectExpression(
136+
selectExpression.Alias,
137+
tables,
138+
predicate,
139+
selectExpression.GroupBy,
140+
selectExpression.Having,
141+
selectExpression.Projection,
142+
selectExpression.IsDistinct,
143+
selectExpression.Orderings,
144+
selectExpression.Offset,
145+
selectExpression.Limit,
146+
_sqlAliasManager,
147+
(IReadOnlySet<string>)selectExpression.Tags);
148+
#pragma warning restore EF1001 // Internal EF Core API usage.
149+
150+
return updateExpression.Update(newSelect, updateExpression.ColumnValueSetters);
151+
152+
static bool ContainsTargetReference(SqlExpression expression, string targetAlias)
153+
=> expression switch
154+
{
155+
ColumnExpression col => col.TableAlias == targetAlias,
156+
SqlBinaryExpression binary
157+
=> ContainsTargetReference(binary.Left, targetAlias)
158+
|| ContainsTargetReference(binary.Right, targetAlias),
159+
SqlUnaryExpression unary => ContainsTargetReference(unary.Operand, targetAlias),
160+
_ => false
161+
};
162+
}
163+
87164
/// <summary>
88165
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
89166
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

src/EFCore.Sqlite.Core/Query/Internal/SqliteUpdateJoinLiftingVisitor.cs

Lines changed: 0 additions & 101 deletions
This file was deleted.

test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ public override async Task Update_main_table_in_entity_with_entity_splitting(boo
133133
"""
134134
UPDATE "Blogs" AS "b"
135135
SET "CreationTimestamp" = '2020-01-01 00:00:00'
136+
FROM "BlogsPart1" AS "b0"
137+
WHERE "b"."Id" = "b0"."Id"
136138
""");
137139
}
138140

0 commit comments

Comments
 (0)