Skip to content

NH-3889 - Fixed weird extra joins in subqueries #482

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

Merged
merged 1 commit into from
Feb 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3889/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;

namespace NHibernate.Test.NHSpecificTest.NH3889
{
public class TimeRecord
{
public virtual Guid Id { get; set; }
public virtual Project Project { get; set; }
public virtual Job ActualJob { get; set; }
public virtual decimal Hours { get; set; }

public virtual TimeSetting Setting { get; set; }
}

public class TimeSetting
{
public virtual Guid Id { get; set; }
public virtual TimeInclude Include { get; set; }
}

public class TimeInclude
{
public virtual Guid Id { get; set; }
public virtual bool Flag { get; set; }
}

public class Project
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual Job Job { get; set; }
}

public class Job
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
}
265 changes: 265 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3889/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
using System;
using System.Linq;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Dialect;
using NHibernate.Linq;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.NH3889
{
public class ByCodeFixture : TestCaseMappingByCode
{
protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<Job>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
});
mapper.Class<Project>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
rc.ManyToOne(x => x.Job, map =>
{
map.Column("JobId");
map.NotNullable(true);
});
});
mapper.Class<TimeRecord>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Hours);
rc.ManyToOne(x => x.Project, map =>
{
map.Column("ProjectId");
map.NotNullable(true);
});
rc.ManyToOne(x => x.ActualJob, map =>
{
map.Column("ActualJobId");
});
rc.ManyToOne(x => x.Setting, map =>
{
map.Column("SettingId");
});
});

mapper.Class<TimeSetting>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.ManyToOne(x => x.Include, map =>
{
map.Column("IncludeId");
});
});

mapper.Class<TimeInclude>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Flag);
});

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

protected override void OnSetUp()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var job_a = new Job { Name = "Big Job" };
session.Save(job_a);
var job_b = new Job { Name = "Small Job" };
session.Save(job_b);

var project_a = new Project { Job = job_a, Name = "Big Job - Part A" };
session.Save(project_a);
var project_b = new Project { Job = job_a, Name = "Big Job - Part B" };
session.Save(project_b);
var project_c = new Project { Job = job_b, Name = "Small Job - Rework" };
session.Save(project_c);

var include = new TimeInclude { Flag = true };
session.Save(include);
var setting = new TimeSetting { Include = include };
session.Save(setting);

session.Save(new TimeRecord {Project = project_a, Hours = 2, Setting = setting }/*.AddTime(2)*/);
session.Save(new TimeRecord {Project = project_a, Hours = 3, Setting = setting }/*.AddTime(3)*/);
session.Save(new TimeRecord {Project = project_b, Hours = 5, Setting = setting }/*.AddTime(2).AddTime(3)*/);
session.Save(new TimeRecord {Project = project_b, Hours = 2, Setting = setting }/*.AddTime(1).AddTime(1)*/);
session.Save(new TimeRecord {Project = project_c, Hours = 7, Setting = setting }/*.AddTime(2).AddTime(3).AddTime(2)*/);
session.Save(new TimeRecord {Project = project_c, ActualJob = job_a, Hours = 3, Setting = setting }/*.AddTime(1).AddTime(1).AddTime(1)*/);

session.Flush();
transaction.Commit();
}
}

protected override void OnTearDown()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
session.Delete("from TimeRecord");
session.Delete("from TimeInclude");
session.Delete("from TimeSetting");
session.Delete("from Project");
session.Delete("from Job");

session.Flush();
transaction.Commit();
}
}

[Test]
public void CoalesceOnEntitySum()
{
using (var session = OpenSession())
using (session.BeginTransaction())
{
var job_a = session.Query<Job>().Single(j => j.Name == "Big Job");
var job_a_hours = session.Query<TimeRecord>()
.Where(t => (t.ActualJob ?? t.Project.Job) == job_a)
.Sum(t => t.Hours);
Assert.That(job_a_hours, Is.EqualTo(15));

var job_b = session.Query<Job>().Single(j => j.Name == "Small Job");
var job_b_hours = session.Query<TimeRecord>()
.Where(t => (t.ActualJob ?? t.Project.Job) == job_b)
.Sum(t => t.Hours);
Assert.That(job_b_hours, Is.EqualTo(7));
}
}

[Test]
public void CoalesceOnEntitySumWithExtraJoin()
{
using (var session = OpenSession())
using (session.BeginTransaction())
{
var include = session.Query<TimeInclude>().Single();

var job_a = session.Query<Job>().Single(j => j.Name == "Big Job");
var job_a_hours = session.Query<TimeRecord>()
.Where(t => (t.ActualJob ?? t.Project.Job) == job_a)
.Where(t => t.Setting.Include == include)
.Sum(t => t.Hours);
Assert.That(job_a_hours, Is.EqualTo(15));

var job_b = session.Query<Job>().Single(j => j.Name == "Small Job");
var job_b_hours = session.Query<TimeRecord>()
.Where(t => (t.ActualJob ?? t.Project.Job) == job_b)
.Where(t => t.Setting.Include == include)
.Sum(t => t.Hours);
Assert.That(job_b_hours, Is.EqualTo(7));
}
}

[Test]
public void CoalesceOnEntitySubselectSum()
{
AssertDialect();
using (var session = OpenSession())
using (session.BeginTransaction())
{
var query = session.Query<Job>()
.Select(j => new
{
Job = j,
Hours = session.Query<TimeRecord>()
.Where(t => (t.ActualJob ?? t.Project.Job) == j)
.Sum(t => (decimal?)t.Hours) ?? 0
});
var results = query.ToList();

Assert.That(results.Count, Is.EqualTo(2));
Assert.That(results.Single(x => x.Job.Name == "Big Job").Hours, Is.EqualTo(15));
Assert.That(results.Single(x => x.Job.Name == "Small Job").Hours, Is.EqualTo(7));
}
}

[Test]
public void CoalesceOnEntitySubselectSumWithExtraJoin()
{
AssertDialect();
using (var session = OpenSession())
using (session.BeginTransaction())
{
var include = session.Query<TimeInclude>().Single();

var query = session.Query<Job>()
.Select(j => new
{
Job = j,
Hours = session.Query<TimeRecord>()
.Where(t => (t.ActualJob ?? t.Project.Job) == j)
.Where(t => t.Setting.Include == include)
.Sum(t => (decimal?)t.Hours) ?? 0
});
var results = query.ToList();

Assert.That(results.Count, Is.EqualTo(2));
Assert.That(results.Single(x => x.Job.Name == "Big Job").Hours, Is.EqualTo(15));
Assert.That(results.Single(x => x.Job.Name == "Small Job").Hours, Is.EqualTo(7));
}
}

[Test]
public void CoalesceOnIdSubselectSum()
{
AssertDialect();
using (var session = OpenSession())
using (session.BeginTransaction())
{
var query = session.Query<Job>()
.Select(j => new
{
Job = j,
Hours = session.Query<TimeRecord>()
.Where(t => ((Guid?)t.ActualJob.Id ?? t.Project.Job.Id) == j.Id)
.Sum(t => (decimal?)t.Hours) ?? 0
});
var results = query.ToList();

Assert.That(results.Count, Is.EqualTo(2));
Assert.That(results.Single(x => x.Job.Name == "Big Job").Hours, Is.EqualTo(15));
Assert.That(results.Single(x => x.Job.Name == "Small Job").Hours, Is.EqualTo(7));
}
}

[Test]
public void CoalesceOnIdSubselectSumWithExtraJoin()
{
AssertDialect();
using (var session = OpenSession())
using (session.BeginTransaction())
{
var include = session.Query<TimeInclude>().Single();

var query = session.Query<Job>()
.Select(j => new
{
Job = j,
Hours = session.Query<TimeRecord>()
.Where(t => ((Guid?)t.ActualJob.Id ?? t.Project.Job.Id) == j.Id)
.Where(t => t.Setting.Include == include)
.Sum(t => (decimal?)t.Hours) ?? 0
});
var results = query.ToList();

Assert.That(results.Count, Is.EqualTo(2));
Assert.That(results.Single(x => x.Job.Name == "Big Job").Hours, Is.EqualTo(15));
Assert.That(results.Single(x => x.Job.Name == "Small Job").Hours, Is.EqualTo(7));
}
}

void AssertDialect()
{
if (Dialect is MsSqlCeDialect) Assert.Ignore(Dialect.GetType() + " does not support this type of query");
}
}
}
2 changes: 2 additions & 0 deletions src/NHibernate.Test/NHibernate.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,8 @@
<Compile Include="NHSpecificTest\NH3912\ReusableBatcherFixture.cs" />
<Compile Include="NHSpecificTest\NH3918\Model.cs" />
<Compile Include="NHSpecificTest\NH3918\FixtureByCode.cs" />
<Compile Include="NHSpecificTest\NH3889\Entity.cs" />
<Compile Include="NHSpecificTest\NH3889\FixtureByCode.cs" />
<Compile Include="NHSpecificTest\NH3414\Entity.cs" />
<Compile Include="NHSpecificTest\NH3414\FixtureByCode.cs" />
<Compile Include="NHSpecificTest\NH2218\Fixture.cs" />
Expand Down
8 changes: 6 additions & 2 deletions src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,16 @@ query
// The query / subquery rule. Pops the current 'from node' context
// (list of aliases).
unionedQuery!
@init{
bool oldInSelect = _inSelect;
_inSelect = false;
}
@after {
// Antlr note: #x_in refers to the input AST, #x refers to the output AST
BeforeStatementCompletion( "select" );
ProcessQuery( $s.tree, $unionedQuery.tree );
AfterStatementCompletion( "select" );
_inSelect = oldInSelect;
}
: ^( QUERY { BeforeStatement( "select", SELECT ); }
// The first phase places the FROM first to make processing the SELECT simpler.
Expand Down Expand Up @@ -189,11 +194,10 @@ selectClause!
;

selectExprList @init{
bool oldInSelect = _inSelect;
_inSelect = true;
}
: ( selectExpr | aliasedSelectExpr )+ {
_inSelect = oldInSelect;
_inSelect = false;
}
;

Expand Down