Skip to content

Commit a1af241

Browse files
author
Emil Cicos
committed
CodePlex#2296 and CodePlex#2256 - Joins not being eliminated
JoinElimination and PostJoinElimination transformations were run only twice. In addition the Deffered transformations were run after JoinElimination. The IQT can be simplified further in some cases by repeatedly running JoinElimination and PostJoinElimination transformations until the tree remains un-modified. The change removes the Deferred transformation rule group and updates the PostJoinElimination rule group to include ApplyOpRules. Then it runs JoinElimination and PostJoinElimination transformations until the IQT does not change anymore, for a maximum of 10 iterations. Included two unit tests for two scenarios described in the bugs.
1 parent 06cb32c commit a1af241

File tree

4 files changed

+181
-49
lines changed

4 files changed

+181
-49
lines changed

src/EntityFramework/Core/Query/PlanCompiler/PlanCompiler.cs

+19-23
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,10 @@ private void Compile(
267267
var beforeTransformationRules1 = String.Empty;
268268
var beforeProjectionPruning3 = String.Empty;
269269
var beforeTransformationRules2 = String.Empty;
270-
var beforeJoinElimination1 = String.Empty;
270+
var beforeNullSemantics = String.Empty;
271271
var beforeTransformationRules3 = String.Empty;
272-
var beforeJoinElimination2 = String.Empty;
272+
var beforeJoinElimination = String.Empty;
273273
var beforeTransformationRules4 = String.Empty;
274-
var beforeNullSemantics = String.Empty;
275-
var beforeTransformationRules5 = String.Empty;
276-
var beforeTransformationRules6 = String.Empty;
277274
var beforeCodeGen = String.Empty;
278275

279276
//
@@ -364,25 +361,27 @@ private void Compile(
364361
// Join elimination
365362
if (IsPhaseNeeded(PlanCompilerPhase.JoinElimination))
366363
{
367-
beforeJoinElimination1 = SwitchToPhase(PlanCompilerPhase.JoinElimination);
368-
var modified = JoinElimination.Process(this);
369-
if (modified)
364+
const int maxIterations = 10;
365+
366+
for (var i = 0; i < maxIterations; i++)
370367
{
371-
ApplyTransformations(ref beforeTransformationRules4, TransformationRulesGroup.PostJoinElimination);
372-
beforeJoinElimination2 = SwitchToPhase(PlanCompilerPhase.JoinElimination);
373-
modified = JoinElimination.Process(this);
374-
if (modified)
368+
beforeJoinElimination = SwitchToPhase(PlanCompilerPhase.JoinElimination);
369+
370+
var modified = JoinElimination.Process(this);
371+
372+
if (modified || TransformationsDeferred)
375373
{
376-
ApplyTransformations(ref beforeTransformationRules5, TransformationRulesGroup.PostJoinElimination);
374+
TransformationsDeferred = false;
375+
376+
ApplyTransformations(ref beforeTransformationRules4, TransformationRulesGroup.PostJoinElimination);
377+
}
378+
else
379+
{
380+
break;
377381
}
378382
}
379383
}
380384

381-
if (TransformationsDeferred)
382-
{
383-
ApplyTransformations(ref beforeTransformationRules6, TransformationRulesGroup.Deferred);
384-
}
385-
386385
// Code generation
387386
beforeCodeGen = SwitchToPhase(PlanCompilerPhase.CodeGen);
388387
CodeGen.Process(this, out providerCommands, out resultColumnMap, out columnCount);
@@ -399,13 +398,10 @@ private void Compile(
399398
size = beforeTransformationRules1.Length;
400399
size = beforeProjectionPruning3.Length;
401400
size = beforeTransformationRules2.Length;
402-
size = beforeJoinElimination1.Length;
401+
size = beforeNullSemantics.Length;
403402
size = beforeTransformationRules3.Length;
404-
size = beforeJoinElimination2.Length;
403+
size = beforeJoinElimination.Length;
405404
size = beforeTransformationRules4.Length;
406-
size = beforeNullSemantics.Length;
407-
size = beforeTransformationRules5.Length;
408-
size = beforeTransformationRules6.Length;
409405
size = beforeCodeGen.Length;
410406
#endif
411407
}

src/EntityFramework/Core/Query/PlanCompiler/TransformationRules.cs

+1-24
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ internal static class TransformationRules
5656
internal static readonly ReadOnlyCollection<ReadOnlyCollection<Rule>> NullSemanticsRulesTable =
5757
BuildLookupTableForRules(NullSemanticsRules);
5858

59-
internal static readonly ReadOnlyCollection<ReadOnlyCollection<Rule>> DeferredRulesTable =
60-
BuildLookupTableForRules(DeferredRules);
61-
6259
#region private state maintenance
6360

6461
private static List<Rule> allRules;
@@ -99,6 +96,7 @@ private static List<Rule> PostJoinEliminationRules
9996
//these don't use key info per-se, but can help after the distinct op rules.
10097
postJoinEliminationRules.AddRange(DistinctOpRules.Rules);
10198
postJoinEliminationRules.AddRange(FilterOpRules.Rules);
99+
postJoinEliminationRules.AddRange(ApplyOpRules.Rules);
102100
postJoinEliminationRules.AddRange(JoinOpRules.Rules);
103101
postJoinEliminationRules.AddRange(NullabilityRules);
104102
}
@@ -150,24 +148,6 @@ private static List<Rule> NullSemanticsRules
150148
}
151149
}
152150

153-
private static List<Rule> deferredRules;
154-
155-
private static List<Rule> DeferredRules
156-
{
157-
get
158-
{
159-
if (deferredRules == null)
160-
{
161-
deferredRules = new List<Rule>();
162-
deferredRules.Add(FilterOpRules.Rule_FilterOverLeftOuterJoin);
163-
deferredRules.Add(FilterOpRules.Rule_FilterOverOuterApply);
164-
deferredRules.AddRange(ApplyOpRules.Rules);
165-
deferredRules.AddRange(JoinOpRules.Rules);
166-
}
167-
return deferredRules;
168-
}
169-
}
170-
171151
private static ReadOnlyCollection<ReadOnlyCollection<Rule>> BuildLookupTableForRules(IEnumerable<Rule> rules)
172152
{
173153
var NoRules = new ReadOnlyCollection<Rule>(new Rule[0]);
@@ -254,9 +234,6 @@ internal static bool Process(PlanCompiler compilerState, TransformationRulesGrou
254234
case TransformationRulesGroup.NullSemantics:
255235
rulesTable = NullSemanticsRulesTable;
256236
break;
257-
case TransformationRulesGroup.Deferred:
258-
rulesTable = DeferredRulesTable;
259-
break;
260237
}
261238

262239
// If any rule has been applied after which reapplying nullability rules may be useful,

src/EntityFramework/Core/Query/PlanCompiler/TransformationRulesGroup.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ internal enum TransformationRulesGroup
1010
All,
1111
Project,
1212
PostJoinElimination,
13-
NullSemantics,
14-
Deferred
13+
NullSemantics
1514
}
1615
}

test/EntityFramework/FunctionalTests/Query/JoinEliminationTests.cs

+160
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,165 @@ FROM [dbo].[Strings] AS [Extent1]
211211
QueryTestHelpers.VerifyQuery(query, expectedSqlStrings);
212212
}
213213
}
214+
215+
public class CodePlex2296 : FunctionalTestBase
216+
{
217+
public class MyBase
218+
{
219+
public int Id { get; set; }
220+
}
221+
222+
public class MyDerived1 : MyBase
223+
{
224+
public string Name { get; set; }
225+
public MyDerived2 Derived2 { get; set; }
226+
}
227+
228+
public class MyDerived2 : MyBase
229+
{
230+
public string Name { get; set; }
231+
public MyDerived1 Derived1 { get; set; }
232+
}
233+
234+
public class MyContext : DbContext
235+
{
236+
static MyContext()
237+
{
238+
Database.SetInitializer<MyContext>(null);
239+
}
240+
241+
public DbSet<MyBase> Bases { get; set; }
242+
243+
protected override void OnModelCreating(DbModelBuilder builder)
244+
{
245+
builder.Entity<MyBase>().ToTable("MyBase");
246+
builder.Entity<MyDerived2>().HasOptional(e => e.Derived1).WithOptionalDependent(e => e.Derived2);
247+
builder.Entity<MyDerived1>().ToTable("Derived1");
248+
builder.Entity<MyDerived2>().ToTable("Derived2");
249+
}
250+
}
251+
252+
[Fact]
253+
public void Joins_are_eliminated_and_expression_is_simplified()
254+
{
255+
using (var context = new MyContext())
256+
{
257+
var query = context.Bases.OfType<MyDerived2>().Where(e => e.Derived1.Derived2.Name == "Foo");
258+
259+
QueryTestHelpers.VerifyQuery(
260+
query,
261+
@"SELECT
262+
[Extent1].[Id] AS [Id],
263+
'0X0X' AS [C1],
264+
[Extent1].[Name] AS [Name],
265+
CAST(NULL AS varchar(1)) AS [C2],
266+
[Extent1].[Derived1_Id] AS [Derived1_Id]
267+
FROM [dbo].[Derived2] AS [Extent1]
268+
INNER JOIN [dbo].[Derived2] AS [Extent2] ON ([Extent2].[Derived1_Id] = [Extent1].[Derived1_Id]) AND ([Extent1].[Derived1_Id] IS NOT NULL)
269+
WHERE ([Extent2].[Derived1_Id] IS NOT NULL) AND (N'Foo' = [Extent2].[Name])");
270+
}
271+
}
272+
}
273+
274+
public class CodePlex2256 : FunctionalTestBase
275+
{
276+
public class A
277+
{
278+
public int Id { get; set; }
279+
280+
public B B { get; set; }
281+
}
282+
283+
public class B
284+
{
285+
public B()
286+
{
287+
As = new List<A>();
288+
}
289+
290+
public int Id { get; set; }
291+
292+
public C C { get; set; }
293+
public ICollection<A> As { get; set; }
294+
}
295+
296+
public class C
297+
{
298+
public C()
299+
{
300+
Bs = new List<B>();
301+
}
302+
303+
public int Id { get; set; }
304+
305+
public D D { get; set; }
306+
public ICollection<B> Bs { get; set; }
307+
}
308+
309+
public class D
310+
{
311+
public D()
312+
{
313+
Cs = new List<C>();
314+
}
315+
316+
public int Id { get; set; }
317+
318+
public E E { get; set; }
319+
public ICollection<C> Cs { get; set; }
320+
}
321+
322+
public class E
323+
{
324+
public E()
325+
{
326+
Ds = new List<D>();
327+
}
328+
329+
public int Id { get; set; }
330+
331+
public ICollection<D> Ds { get; set; }
332+
}
333+
334+
public class MyContext : DbContext
335+
{
336+
static MyContext()
337+
{
338+
Database.SetInitializer<MyContext>(null);
339+
}
340+
341+
public DbSet<A> As { get; set; }
342+
343+
protected override void OnModelCreating(DbModelBuilder modelBuilder)
344+
{
345+
modelBuilder.Entity<A>().HasRequired(e => e.B).WithMany(e => e.As);
346+
modelBuilder.Entity<B>().HasRequired(e => e.C).WithMany(e => e.Bs);
347+
modelBuilder.Entity<C>().HasRequired(e => e.D).WithMany(e => e.Cs);
348+
modelBuilder.Entity<D>().HasRequired(e => e.E).WithMany(e => e.Ds);
349+
}
350+
}
351+
352+
[Fact]
353+
public void Joins_are_eliminated_and_expression_is_simplified()
354+
{
355+
using (var context = new MyContext())
356+
{
357+
var query = context.As.Include(a => a.B.C.D.E);
358+
359+
QueryTestHelpers.VerifyQuery(
360+
query,
361+
@"SELECT
362+
[Extent1].[Id] AS [Id],
363+
[Extent2].[Id] AS [Id1],
364+
[Extent3].[Id] AS [Id2],
365+
[Extent4].[Id] AS [Id3],
366+
[Extent4].[E_Id] AS [E_Id]
367+
FROM [dbo].[A] AS [Extent1]
368+
INNER JOIN [dbo].[B] AS [Extent2] ON [Extent1].[B_Id] = [Extent2].[Id]
369+
INNER JOIN [dbo].[C] AS [Extent3] ON [Extent2].[C_Id] = [Extent3].[Id]
370+
INNER JOIN [dbo].[D] AS [Extent4] ON [Extent3].[D_Id] = [Extent4].[Id]");
371+
}
372+
}
373+
}
214374
}
215375
}

0 commit comments

Comments
 (0)