From: <fab...@us...> - 2011-04-25 21:13:29
|
Revision: 5762 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=5762&view=rev Author: fabiomaulo Date: 2011-04-25 21:13:23 +0000 (Mon, 25 Apr 2011) Log Message: ----------- Fix NH-2505 Modified Paths: -------------- trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj Added Paths: ----------- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2505/ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2505/Fixture.cs Modified: trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs 2011-04-25 13:48:32 UTC (rev 5761) +++ trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs 2011-04-25 21:13:23 UTC (rev 5762) @@ -213,17 +213,7 @@ // Need to check for boolean equality if (lhs is HqlBooleanExpression || rhs is HqlBooleanExpression) { - lhs = - _hqlTreeBuilder.Case( - new [] { _hqlTreeBuilder.When(lhs, _hqlTreeBuilder.Constant(true)) }, - _hqlTreeBuilder.Constant(false)); - - rhs = - _hqlTreeBuilder.Case( - new [] { _hqlTreeBuilder.When(rhs, _hqlTreeBuilder.Constant(true)) }, - _hqlTreeBuilder.Constant(false)); - - return _hqlTreeBuilder.Equality(lhs, rhs); + return ResolveBooleanEquality(expression, lhs, rhs, (l, r) => _hqlTreeBuilder.Equality(l, r)); } // Check for nulls on left or right. @@ -246,23 +236,12 @@ case ExpressionType.NotEqual: // Need to check for boolean in-equality - if (lhs is HqlBooleanExpression || rhs is HqlBooleanExpression) - { - lhs = - _hqlTreeBuilder.Case( - new [] { _hqlTreeBuilder.When(lhs, _hqlTreeBuilder.Constant(true)) }, - _hqlTreeBuilder.Constant(false)); + if (lhs is HqlBooleanExpression || rhs is HqlBooleanExpression) + { + return ResolveBooleanEquality(expression, lhs, rhs, (l, r) => _hqlTreeBuilder.Inequality(l, r)); + } - rhs = - _hqlTreeBuilder.Case( - new [] { _hqlTreeBuilder.When(rhs, _hqlTreeBuilder.Constant(true)) }, - _hqlTreeBuilder.Constant(false)); - - return _hqlTreeBuilder.Inequality(lhs, rhs); - - } - - // Check for nulls on left or right. + // Check for nulls on left or right. if (expression.Right is ConstantExpression && expression.Right.Type.IsNullableOrReference() && ((ConstantExpression)expression.Right).Value == null) @@ -330,7 +309,49 @@ throw new InvalidOperationException(); } - protected HqlTreeNode VisitUnaryExpression(UnaryExpression expression) + private HqlTreeNode ResolveBooleanEquality(BinaryExpression expression, HqlExpression lhs, HqlExpression rhs, + Func<HqlExpression, HqlExpression, HqlTreeNode> applyResultExpressions) + { + if (!(lhs is HqlBooleanExpression) && !(rhs is HqlBooleanExpression)) + { + throw new InvalidOperationException("Invalid operators for ResolveBooleanEquality, this may indicate a bug in NHibernate"); + } + + HqlExpression leftHqlExpression = GetExpressionForBooleanEquality(expression.Left, lhs); + HqlExpression rightHqlExpression = GetExpressionForBooleanEquality(expression.Right, rhs); + return applyResultExpressions(leftHqlExpression, rightHqlExpression); + } + + private HqlExpression GetExpressionForBooleanEquality(Expression @operator, HqlExpression original) + { + //When the expression is a constant then use the constant + var operandEx = @operator as ConstantExpression; + if (operandEx != null) + { + NamedParameter namedParameter; + if (_parameters.ConstantToParameterMap.TryGetValue(operandEx, out namedParameter)) + { + _parameters.RequiredHqlParameters.Add(new NamedParameterDescriptor(namedParameter.Name, null, new[] { _parameters.RequiredHqlParameters.Count + 1 }, false)); + return _hqlTreeBuilder.Parameter(namedParameter.Name).AsExpression(); + } + + return _hqlTreeBuilder.Constant(operandEx.Value); + } + + //When the expression is a member-access not nullable then use the HbmDot + var memberAccessExpression = @operator as MemberExpression; + if (ExpressionType.MemberAccess.Equals(@operator.NodeType) && memberAccessExpression != null && typeof(bool).Equals(memberAccessExpression.Type)) + { + // this case make the difference when the property "Value" of a nullable type is used (ignore the null since the user is explicity checking the Value) + return original; + } + + //When the expression is a member-access nullable then use the "case" clause to transform it to boolean (to use always .NET meaning instead leave the DB the behavior for null) + //When the expression is a complex-expression then use the "case" clause to transform it to boolean + return _hqlTreeBuilder.Case(new[] {_hqlTreeBuilder.When(original, _hqlTreeBuilder.Constant(true))}, _hqlTreeBuilder.Constant(false)); + } + + protected HqlTreeNode VisitUnaryExpression(UnaryExpression expression) { switch (expression.NodeType) { Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2505/Fixture.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2505/Fixture.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2505/Fixture.cs 2011-04-25 21:13:23 UTC (rev 5762) @@ -0,0 +1,175 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; +using SharpTestsEx; + +namespace NHibernate.Test.NHSpecificTest.NH2505 +{ + public class MyClass + { + public virtual Guid Id { get; set; } + public virtual bool Alive { get; set; } + public virtual bool? MayBeAlive { get; set; } + public virtual int Something { get; set; } + } + public class Fixture: TestCaseMappingByCode + { + private Regex caseClause = new Regex("case",RegexOptions.IgnoreCase); + protected override HbmMapping GetMappings() + { + var mapper = new ConventionModelMapper(); + mapper.BeforeMapClass += (mi, t, x) => x.Id(map=> map.Generator(Generators.Guid)); + return mapper.CompileMappingFor(new[] { typeof(MyClass) }); + } + + private class Scenario: IDisposable + { + private readonly ISessionFactory factory; + + public Scenario(ISessionFactory factory) + { + this.factory = factory; + using (var session= factory.OpenSession()) + { + session.Save(new MyClass { Alive = true }); + session.Save(new MyClass { Alive = false, MayBeAlive = true }); + session.Flush(); + } + } + + public void Dispose() + { + using (var session = factory.OpenSession()) + { + session.CreateQuery("delete from MyClass").ExecuteUpdate(); + session.Flush(); + } + } + } + + [Test] + public void WhenQueryConstantEqualToMemberThenDoesNotUseTheCaseConstructor() + { + using (new Scenario(Sfi)) + { + using (var session = OpenSession()) + { + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => x.Alive == false).Should().Have.Count.EqualTo(1); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(0); + } + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => true == x.Alive).Should().Have.Count.EqualTo(1); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(0); + } + } + } + } + + [Test] + public void WhenQueryConstantNotEqualToMemberThenDoesNotUseTheCaseConstructor() + { + using (new Scenario(Sfi)) + { + using (var session = OpenSession()) + { + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => x.Alive != false).Should().Have.Count.EqualTo(1); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(0); + } + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => true != x.Alive).Should().Have.Count.EqualTo(1); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(0); + } + } + } + } + + [Test] + public void WhenQueryComplexEqualToComplexThentUseTheCaseConstructorForBoth() + { + using (new Scenario(Sfi)) + { + using (var session = OpenSession()) + { + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => (5 > x.Something) == (x.Something < 10)).ToList(); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(2); + } + } + } + } + + [Test] + public void WhenQueryConstantEqualToNullableMemberThenUseTheCaseConstructorForMember() + { + using (new Scenario(Sfi)) + { + using (var session = OpenSession()) + { + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => x.MayBeAlive == false).Should().Have.Count.EqualTo(1); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(1); + } + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => true == x.MayBeAlive).Should().Have.Count.EqualTo(1); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(1); + } + } + } + } + + [Test] + public void WhenQueryConstantEqualToNullableMemberValueThenDoesNotUseTheCaseConstructorForMember() + { + using (new Scenario(Sfi)) + { + using (var session = OpenSession()) + { + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => x.MayBeAlive.Value == false).ToList(); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(1); + } + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => true == x.MayBeAlive.Value).ToList(); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(1); + } + } + } + } + + [Test] + public void WhenQueryConstantNotEqualToNullableMemberThenUseTheCaseConstructorForMember() + { + using (new Scenario(Sfi)) + { + using (var session = OpenSession()) + { + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => x.MayBeAlive != false).Should().Have.Count.EqualTo(1); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(1); + } + using (var sqls = new SqlLogSpy()) + { + session.Query<MyClass>().Where(x => true != x.MayBeAlive).Should().Have.Count.EqualTo(1); + caseClause.Matches(sqls.GetWholeLog()).Count.Should().Be(1); + } + } + } + } + } +} \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2011-04-25 13:48:32 UTC (rev 5761) +++ trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2011-04-25 21:13:23 UTC (rev 5762) @@ -763,6 +763,7 @@ <Compile Include="NHSpecificTest\NH2490\Fixture.cs" /> <Compile Include="NHSpecificTest\NH2490\Model.cs" /> <Compile Include="NHSpecificTest\NH2491\Fixture.cs" /> + <Compile Include="NHSpecificTest\NH2505\Fixture.cs" /> <Compile Include="NHSpecificTest\NH2507\Animal.cs" /> <Compile Include="NHSpecificTest\NH2507\Fixture.cs" /> <Compile Include="NHSpecificTest\NH2527\FixtureWithNoBatcher.cs" /> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |