Skip to content

Commit

Permalink
Merge pull request #522 from PleasantD/NH-3918
Browse files Browse the repository at this point in the history
NH-3918 - Nominate equality expressions in SELECT clauses
  • Loading branch information
oskarb committed Nov 26, 2016
2 parents a1e0eb3 + 130ae83 commit 6214054
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 3 deletions.
29 changes: 29 additions & 0 deletions src/NHibernate.Test/Linq/SelectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,35 @@ public void CanSelectConditionalEntity()
Assert.That(fatherInsteadOfChild, Has.Exactly(2).With.Property("SerialNumber").EqualTo("5678"));
}

[Test]
public void CanSelectConditionalEntityWithCast()
{
var fatherInsteadOfChild = db.Mammals.Select(a => a.Father.SerialNumber == "5678" ? (object)a.Father : (object)a).ToList();
Assert.That(fatherInsteadOfChild, Has.Exactly(2).With.Property("SerialNumber").EqualTo("5678"));
}

[Test]
public void CanSelectConditionalEntityValue()
{
var fatherInsteadOfChild = db.Animals.Select(a => a.Father.SerialNumber == "5678" ? a.Father.SerialNumber : a.SerialNumber).ToList();
Assert.That(fatherInsteadOfChild, Has.Exactly(2).EqualTo("5678"));
}

[Test]
public void CanSelectConditionalEntityValueWithEntityComparison()
{
var father = db.Animals.Single(a => a.SerialNumber == "5678");
var fatherInsteadOfChild = db.Animals.Select(a => a.Father == father ? a.Father.SerialNumber : a.SerialNumber).ToList();
Assert.That(fatherInsteadOfChild, Has.Exactly(2).EqualTo("5678"));
}

[Test]
public void CanSelectConditionalEntityValueWithEntityComparisonRepeat()
{
// Check again in the same ISessionFactory to ensure caching doesn't cause failures
CanSelectConditionalEntityValueWithEntityComparison();
}

[Test]
public void CanSelectConditionalObject()
{
Expand Down
127 changes: 127 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3918/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Linq;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.NH3918
{
public class ByCodeFixture : TestCaseMappingByCode
{
protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<Owner>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
});
mapper.Class<Entity>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
rc.ManyToOne(m => m.Owner, m =>
{
m.Column("OwnerId");
});
});

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

protected override void OnSetUp()
{
using (ISession session = OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
var bob = CreateOwner(session, "Bob");
var carl = CreateOwner(session, "Carl");
var doug = CreateOwner(session, "Doug");

CreateEntity(session, "Test 1", bob);
CreateEntity(session, "Test 2", carl);
CreateEntity(session, "Test 3", doug);
CreateEntity(session, "Test 4", bob);
CreateEntity(session, "Test 5", carl);
CreateEntity(session, "Test 6", doug);

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

protected Owner CreateOwner(ISession session, string name)
{
var t = new Owner { Name = name };
session.Save(t);
return t;
}

protected void CreateEntity(ISession session, string name, Owner owner)
{
var t = new Entity
{
Name = name,
Owner = owner,
};
session.Save(t);
}

protected override void OnTearDown()
{
using (ISession session = OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
session.Delete("from Entity");
session.Delete("from Owner");

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

[Test]
public void EntityComparisonTest()
{
using (ISession session = OpenSession())
using (session.BeginTransaction())
{
var bob = session.Query<Owner>().Single(o => o.Name == "Bob");

var queryWithWhere = session.Query<Entity>()
.Where(WhereExpression(bob))
.Select(e => e.Name);
var queryWithSelect = session.Query<Entity>()
.Select(SelectExpression(bob));

var resultsFromWhere = queryWithWhere.ToList();
var resultsFromSelect = queryWithSelect.ToList();

Assert.That(resultsFromSelect.Where(x => (bool)x[1]).Select(x => (string)x[0]), Is.EquivalentTo(resultsFromWhere));
}
}

[Test]
public void EntityComparisonTestAgain()
{
// When the entire fixture is run this will execute the test again within the same ISessionFactory which will test caching
EntityComparisonTest();
}

protected Expression<Func<Entity, bool>> WhereExpression(Owner owner)
{
return e => e.Owner == owner;
}

protected Expression<Func<Entity, object[]>> SelectExpression(Owner owner)
{
return e => new object[]
{
e.Name,
e.Owner == owner
};
}
}
}
25 changes: 25 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3918/Model.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;

namespace NHibernate.Test.NHSpecificTest.NH3918
{
public interface IModelObject
{
Guid Id { get; set; }
string Name { get; set; }
}

public class Entity : IModelObject
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }

public virtual Owner Owner { get; set; }
}

public class Owner : IModelObject
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
}
2 changes: 2 additions & 0 deletions src/NHibernate.Test/NHibernate.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,8 @@
<Compile Include="NHSpecificTest\NH2204\Fixture.cs" />
<Compile Include="NHSpecificTest\NH3912\BatcherLovingEntity.cs" />
<Compile Include="NHSpecificTest\NH3912\ReusableBatcherFixture.cs" />
<Compile Include="NHSpecificTest\NH3918\Model.cs" />
<Compile Include="NHSpecificTest\NH3918\FixtureByCode.cs" />
<Compile Include="NHSpecificTest\NH3414\Entity.cs" />
<Compile Include="NHSpecificTest\NH3414\FixtureByCode.cs" />
<Compile Include="NHSpecificTest\NH2218\Fixture.cs" />
Expand Down
15 changes: 12 additions & 3 deletions src/NHibernate/Linq/Visitors/SelectClauseNominator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public override Expression VisitExpression(Expression expression)
return innerExpression;
}

var projectConstantsInHql = _stateStack.Peek() || IsRegisteredFunction(expression);
var projectConstantsInHql = _stateStack.Peek() || expression.NodeType == ExpressionType.Equal || IsRegisteredFunction(expression);

// Set some flags, unless we already have proper values for them:
// projectConstantsInHql if they are inside a method call executed server side.
Expand Down Expand Up @@ -153,8 +153,17 @@ private bool CanBeEvaluatedInHqlSelectStatement(Expression expression, bool proj
if (expression.NodeType == ExpressionType.Call)
{
// Depends if it's in the function registry
if (!IsRegisteredFunction(expression))
return false;
return IsRegisteredFunction(expression);
}

if (expression.NodeType == ExpressionType.Conditional)
{
// Theoretically, any conditional that returns a CAST-able primitive should be constructable in HQL.
// The type needs to be CAST-able because HQL wraps the CASE clause in a CAST and only supports
// certain types (as defined by the HqlIdent constructor that takes a System.Type as the second argument).
// However, this may still not cover all cases, so to limit the nomination of conditional expressions,
// we will only consider those which are already getting constants projected into them.
return projectConstantsInHql;
}

// Assume all is good
Expand Down

0 comments on commit 6214054

Please sign in to comment.