From: <pa...@us...> - 2010-11-25 03:34:12
|
Revision: 5269 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=5269&view=rev Author: patearl Date: 2010-11-25 03:34:05 +0000 (Thu, 25 Nov 2010) Log Message: ----------- Utilize simple SQL equality in expressions that may involve null values. (NH-2402) Fixed missing cases in Linq null (in)equality handling. Fixed Linq query caching when the same variable alternates between null and non-null values. (part of NH-2397) Modified Paths: -------------- trunk/nhibernate/src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs trunk/nhibernate/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql trunk/nhibernate/src/NHibernate.Test/Linq/Entities/AnotherEntity.cs trunk/nhibernate/src/NHibernate.Test/Linq/Mappings/AnotherEntity.hbm.xml trunk/nhibernate/src/NHibernate.Test/Linq/NorthwindDbCreator.cs trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj Modified: trunk/nhibernate/src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs 2010-11-21 17:36:14 UTC (rev 5268) +++ trunk/nhibernate/src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs 2010-11-25 03:34:05 UTC (rev 5269) @@ -77,7 +77,11 @@ if (_constantToParameterMap.TryGetValue(expression, out param)) { - _string.Append(param.Name); + // Nulls generate different query plans. X = variable generates a different query depending on if variable is null or not. + if (param.Value == null) + _string.Append("NULL"); + else + _string.Append(param.Name); } else { Modified: trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs 2010-11-21 17:36:14 UTC (rev 5268) +++ trunk/nhibernate/src/NHibernate/Linq/Visitors/HqlGeneratorExpressionTreeVisitor.cs 2010-11-25 03:34:05 UTC (rev 5269) @@ -226,34 +226,22 @@ return _hqlTreeBuilder.Equality(lhs, rhs); } - // Also check for nullability - if (expression.Left.Type.IsNullableOrReference() || expression.Right.Type.IsNullableOrReference()) + // Check for nulls on left or right. + if (expression.Right is ConstantExpression + && expression.Right.Type.IsNullableOrReference() + && ((ConstantExpression)expression.Right).Value == null) { - // TODO - yuck. This clone is needed because the AST tree nodes are not immutable, - // and sharing nodes between multiple branches will cause issues in the hqlSqlWalker phase - - // a node, x, gets visited during the walk and updated to refer to a real property. Later in - // the walk, x get revisited (since we copied it here), but now the type doesn't match what - // the parser expects. So we can't share. Implementing Clone() on HqlTreeNode would be better - // that doing a full visit of the Expression tree. Allowing shared nodes in the AST would be better - // still, but might be more work - var lhs2 = VisitExpression(expression.Left).AsExpression(); - var rhs2 = VisitExpression(expression.Right).AsExpression(); - - if (expression.Right is ConstantExpression - && expression.Right.Type.IsNullableOrReference() - && ((ConstantExpression)expression.Right).Value == null) - { - return _hqlTreeBuilder.IsNull(lhs2); - } - - return _hqlTreeBuilder.BooleanOr( - _hqlTreeBuilder.BooleanAnd( - _hqlTreeBuilder.IsNull(lhs), - _hqlTreeBuilder.IsNull(rhs)), - _hqlTreeBuilder.Equality(lhs2, rhs2) - ); + return _hqlTreeBuilder.IsNull(lhs); } + if (expression.Left is ConstantExpression + && expression.Left.Type.IsNullableOrReference() + && ((ConstantExpression)expression.Left).Value == null) + { + return _hqlTreeBuilder.IsNull(rhs); + } + + // Nothing was null, use standard equality. return _hqlTreeBuilder.Equality(lhs, rhs); case ExpressionType.NotEqual: @@ -274,36 +262,24 @@ } - // Also check for nullability - if (expression.Left.Type.IsNullableOrReference() || expression.Right.Type.IsNullableOrReference()) + // Check for nulls on left or right. + if (expression.Right is ConstantExpression + && expression.Right.Type.IsNullableOrReference() + && ((ConstantExpression)expression.Right).Value == null) { - var lhs2 = VisitExpression(expression.Left).AsExpression(); - var rhs2 = VisitExpression(expression.Right).AsExpression(); - var lhs3 = VisitExpression(expression.Left).AsExpression(); - var rhs3 = VisitExpression(expression.Right).AsExpression(); - - if (expression.Right is ConstantExpression - && expression.Right.Type.IsNullableOrReference() - && ((ConstantExpression)expression.Right).Value == null) - { - return _hqlTreeBuilder.IsNotNull(lhs2); - } - - return - _hqlTreeBuilder.BooleanOr( - _hqlTreeBuilder.BooleanOr( - _hqlTreeBuilder.BooleanAnd( - _hqlTreeBuilder.IsNull(lhs), - _hqlTreeBuilder.IsNotNull(rhs)), - _hqlTreeBuilder.BooleanAnd( - _hqlTreeBuilder.IsNotNull(lhs2), - _hqlTreeBuilder.IsNull(rhs2)) - ), - _hqlTreeBuilder.Inequality(lhs3, rhs3)); + return _hqlTreeBuilder.IsNotNull(lhs); } - return _hqlTreeBuilder.Inequality(lhs, rhs); + if (expression.Left is ConstantExpression + && expression.Left.Type.IsNullableOrReference() + && ((ConstantExpression)expression.Left).Value == null) + { + return _hqlTreeBuilder.IsNotNull(rhs); + } + // Nothing was null, use standard inequality. + return _hqlTreeBuilder.Inequality(lhs, rhs); + case ExpressionType.And: return _hqlTreeBuilder.BitwiseAnd(lhs, rhs); Modified: trunk/nhibernate/src/NHibernate.Test/DbScripts/MsSql2008DialectLinqReadonlyCreateScript.sql =================================================================== (Binary files differ) Modified: trunk/nhibernate/src/NHibernate.Test/Linq/Entities/AnotherEntity.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/Linq/Entities/AnotherEntity.cs 2010-11-21 17:36:14 UTC (rev 5268) +++ trunk/nhibernate/src/NHibernate.Test/Linq/Entities/AnotherEntity.cs 2010-11-25 03:34:05 UTC (rev 5269) @@ -4,5 +4,6 @@ { public virtual int Id { get; set; } public virtual string Output { get; set; } + public virtual string Input { get; set; } } } \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate.Test/Linq/Mappings/AnotherEntity.hbm.xml =================================================================== --- trunk/nhibernate/src/NHibernate.Test/Linq/Mappings/AnotherEntity.hbm.xml 2010-11-21 17:36:14 UTC (rev 5268) +++ trunk/nhibernate/src/NHibernate.Test/Linq/Mappings/AnotherEntity.hbm.xml 2010-11-25 03:34:05 UTC (rev 5269) @@ -5,5 +5,6 @@ <generator class="native" /> </id> <property name="Output" /> + <property name="Input" /> </class> </hibernate-mapping> Modified: trunk/nhibernate/src/NHibernate.Test/Linq/NorthwindDbCreator.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/Linq/NorthwindDbCreator.cs 2010-11-21 17:36:14 UTC (rev 5268) +++ trunk/nhibernate/src/NHibernate.Test/Linq/NorthwindDbCreator.cs 2010-11-25 03:34:05 UTC (rev 5269) @@ -20,7 +20,7 @@ IsActive = true, Entity = new AnotherEntity() { - Output = "this is output..." + Output = "output" } }, new Role() @@ -143,6 +143,16 @@ animals[1].Children = new[] { animals[5] }.ToList(); + List<AnotherEntity> otherEntities = new List<AnotherEntity>(); + // AnotherEntity with only Output set is created above. + otherEntities.Add(new AnotherEntity { Input = "input" }); + otherEntities.Add(new AnotherEntity { Input = "i/o", Output = "i/o" }); + otherEntities.Add(new AnotherEntity()); // Input and Output both null. + otherEntities.Add(new AnotherEntity { Input = "input", Output = "output" }); + + foreach (AnotherEntity otherEntity in otherEntities) + session.Save(otherEntity); + foreach (Role role in roles) session.Save(role); Modified: trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2010-11-21 17:36:14 UTC (rev 5268) +++ trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2010-11-25 03:34:05 UTC (rev 5269) @@ -448,6 +448,7 @@ <Compile Include="Linq\MethodCallTests.cs" /> <Compile Include="Linq\MiscellaneousTextFixture.cs" /> <Compile Include="Linq\NorthwindDbCreator.cs" /> + <Compile Include="Linq\NullComparisonTests.cs" /> <Compile Include="Linq\ObjectDumper.cs" /> <Compile Include="Linq\OrderByTests.cs" /> <Compile Include="Linq\PagingTests.cs" /> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |