|
From: <pa...@us...> - 2011-04-15 04:10:59
|
Revision: 5705
http://nhibernate.svn.sourceforge.net/nhibernate/?rev=5705&view=rev
Author: patearl
Date: 2011-04-15 04:10:51 +0000 (Fri, 15 Apr 2011)
Log Message:
-----------
Harald Mueller's great contribution that fixes joins for member expression chains, especially where || operators are involved. This is basically the original patch. (NH-2583)
Modified Paths:
--------------
trunk/nhibernate/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs
trunk/nhibernate/src/NHibernate/NHibernate.csproj
trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj
Added Paths:
-----------
trunk/nhibernate/src/NHibernate/Linq/Clauses/NhJoinClause.cs
trunk/nhibernate/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs
trunk/nhibernate/src/NHibernate/Linq/Visitors/AbstractJoinDetector.cs
trunk/nhibernate/src/NHibernate/Linq/Visitors/SelectAndOrderByJoinDetector.cs
trunk/nhibernate/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/AbstractMassTestingFixture.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/Domain.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/ManualTestFixture.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/Mappings.hbm.xml
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingNotAndDeMorganFixture.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingOneOrTreeFixture.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingOrderByFixture.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingSelectFixture.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingThreeOrTreesSideBySideFixture.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/MassTestingTwoOrTreesSideBySideFixture.cs
trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/SelfJoinTestFixture.cs
Added: trunk/nhibernate/src/NHibernate/Linq/Clauses/NhJoinClause.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Linq/Clauses/NhJoinClause.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate/Linq/Clauses/NhJoinClause.cs 2011-04-15 04:10:51 UTC (rev 5705)
@@ -0,0 +1,23 @@
+using System.Linq.Expressions;
+using Remotion.Linq.Clauses;
+using Remotion.Linq.Clauses;
+
+namespace NHibernate.Linq.Visitors
+{
+ /// <summary>
+ /// All joins are created as outer joins. An optimization in <see cref="WhereJoinDetector"/> finds
+ /// joins that may be inner joined and calls <see cref="MakeInner"/> on them.
+ /// <see cref="QueryModelVisitor"/>'s <see cref="QueryModelVisitor.VisitAdditionalFromClause"/> will
+ /// then emit the correct HQL join.
+ /// </summary>
+ public class NhJoinClause : AdditionalFromClause
+ {
+ public bool IsInner { get; private set; }
+ public void MakeInner() {
+ IsInner = true;
+ }
+ public NhJoinClause(string itemName, System.Type itemType, Expression fromExpression) : base(itemName, itemType, fromExpression) {
+ IsInner = false;
+ }
+ }
+}
Added: trunk/nhibernate/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs 2011-04-15 04:10:51 UTC (rev 5705)
@@ -0,0 +1,105 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using NHibernate.Linq.Visitors;
+using Remotion.Linq;
+using Remotion.Linq.Clauses;
+using Remotion.Linq.Clauses.Expressions;
+
+namespace NHibernate.Linq.ReWriters
+{
+ internal interface IIsEntityDecider
+ {
+ bool IsEntity(System.Type type);
+ }
+
+ public class AddJoinsReWriter : QueryModelVisitorBase, IIsEntityDecider
+ {
+ private readonly Dictionary<string, NhJoinClause> _joins = new Dictionary<string, NhJoinClause>();
+ private readonly Dictionary<MemberExpression, QuerySourceReferenceExpression> _expressionMap = new Dictionary<MemberExpression, QuerySourceReferenceExpression>();
+ private readonly NameGenerator _nameGenerator;
+ private readonly ISessionFactory _sessionFactory;
+
+ private AddJoinsReWriter(NameGenerator nameGenerator, ISessionFactory sessionFactory)
+ {
+ _nameGenerator = nameGenerator;
+ _sessionFactory = sessionFactory;
+ }
+
+ public static void ReWrite(QueryModel queryModel, ISessionFactory sessionFactory)
+ {
+ new AddJoinsReWriter(new NameGenerator(queryModel), sessionFactory).ReWrite(queryModel);
+ }
+
+ private void ReWrite(QueryModel queryModel)
+ {
+ VisitQueryModel(queryModel);
+
+ if (_joins.Count > 0)
+ {
+ MemberExpressionSwapper swap = new MemberExpressionSwapper(_expressionMap);
+ queryModel.TransformExpressions(swap.VisitExpression);
+
+ foreach (var join in _joins.Values)
+ {
+ queryModel.BodyClauses.Add(join);
+ }
+ }
+ }
+
+ public override void VisitSelectClause(SelectClause selectClause, QueryModel queryModel)
+ {
+ new SelectAndOrderByJoinDetector(_nameGenerator, this, _joins, _expressionMap).VisitExpression(selectClause.Selector);
+ }
+
+ public override void VisitWhereClause(WhereClause whereClause, QueryModel queryModel, int index)
+ {
+ WhereJoinDetector.Find(whereClause.Predicate,_nameGenerator,
+ this,
+ _joins,
+ _expressionMap);
+ }
+
+ public override void VisitOrderByClause(OrderByClause orderByClause, QueryModel queryModel, int index)
+ {
+ var joinDetector = new SelectAndOrderByJoinDetector(_nameGenerator, this, _joins, _expressionMap);
+ foreach (Ordering ordering in orderByClause.Orderings)
+ {
+ joinDetector.VisitExpression(ordering.Expression);
+ }
+ }
+
+ public bool IsEntity(System.Type type)
+ {
+ return _sessionFactory.GetClassMetadata(type) != null;
+ }
+ }
+
+ public class MemberExpressionSwapper : NhExpressionTreeVisitor
+ {
+ private readonly Dictionary<MemberExpression, QuerySourceReferenceExpression> _expressionMap;
+
+ public MemberExpressionSwapper(Dictionary<MemberExpression, QuerySourceReferenceExpression> expressionMap)
+ {
+ _expressionMap = expressionMap;
+ }
+
+ protected override Expression VisitMemberExpression(MemberExpression expression)
+ {
+ if (expression == null)
+ {
+ return null;
+ }
+
+ QuerySourceReferenceExpression replacement;
+
+ if (_expressionMap.TryGetValue(expression, out replacement))
+ {
+ return replacement;
+ }
+ else
+ {
+ return base.VisitMemberExpression(expression);
+ }
+ }
+ }
+}
Added: trunk/nhibernate/src/NHibernate/Linq/Visitors/AbstractJoinDetector.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Linq/Visitors/AbstractJoinDetector.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate/Linq/Visitors/AbstractJoinDetector.cs 2011-04-15 04:10:51 UTC (rev 5705)
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using NHibernate.Linq.ReWriters;
+using Remotion.Linq.Clauses.Expressions;
+
+namespace NHibernate.Linq.Visitors
+{
+ public abstract class AbstractJoinDetector : NhExpressionTreeVisitor
+ {
+ private readonly NameGenerator _nameGenerator;
+ internal readonly IIsEntityDecider _isEntityDecider;
+ protected readonly Dictionary<string, NhJoinClause> _joins;
+ protected readonly Dictionary<MemberExpression, QuerySourceReferenceExpression> _expressionMap;
+
+ internal AbstractJoinDetector(NameGenerator nameGenerator, IIsEntityDecider isEntityDecider, Dictionary<string, NhJoinClause> joins, Dictionary<MemberExpression, QuerySourceReferenceExpression> expressionMap)
+ {
+ _nameGenerator = nameGenerator;
+ _expressionMap = expressionMap;
+ _joins = joins;
+ _isEntityDecider = isEntityDecider;
+ }
+
+ protected internal Expression AddJoin(MemberExpression expression)
+ {
+ string key = ExpressionKeyVisitor.Visit(expression, null);
+ NhJoinClause join;
+
+ if (!_joins.TryGetValue(key, out join))
+ {
+ join = new NhJoinClause(_nameGenerator.GetNewName(), expression.Type, expression);
+ _joins.Add(key, join);
+ }
+
+ QuerySourceReferenceExpression newExpr = new QuerySourceReferenceExpression(join);
+
+ if (!_expressionMap.ContainsKey(expression))
+ _expressionMap.Add(expression, newExpr);
+
+ return newExpr;
+ }
+
+ protected void MakeInnerIfJoined(string memberExpressionKey)
+ {
+ // memberExpressionKey is not joined if it occurs only at tails of expressions, e.g.
+ // a.B == null, a.B != null, a.B == c.D etc.
+ if (_joins.ContainsKey(memberExpressionKey))
+ {
+ _joins[memberExpressionKey].MakeInner();
+ }
+ }
+ }
+}
Modified: trunk/nhibernate/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs 2011-04-14 21:27:16 UTC (rev 5704)
+++ trunk/nhibernate/src/NHibernate/Linq/Visitors/QueryModelVisitor.cs 2011-04-15 04:10:51 UTC (rev 5705)
@@ -41,8 +41,8 @@
// Flatten pointless subqueries
QueryReferenceExpressionFlattener.ReWrite(queryModel);
- // Add left joins for references
- AddLeftJoinsReWriter.ReWrite(queryModel, parameters.SessionFactory);
+ // Add joins for references
+ AddJoinsReWriter.ReWrite(queryModel, parameters.SessionFactory);
// Move OrderBy clauses to end
MoveOrderByToEndRewriter.ReWrite(queryModel);
@@ -202,12 +202,20 @@
public override void VisitAdditionalFromClause(AdditionalFromClause fromClause, QueryModel queryModel, int index)
{
- if (fromClause is LeftJoinClause)
+ if (fromClause is NhJoinClause)
{
- // It's a left join
- _hqlTree.AddFromClause(_hqlTree.TreeBuilder.LeftJoin(
- HqlGeneratorExpressionTreeVisitor.Visit(fromClause.FromExpression, VisitorParameters).AsExpression(),
- _hqlTree.TreeBuilder.Alias(fromClause.ItemName)));
+ if (((NhJoinClause)fromClause).IsInner)
+ {
+ _hqlTree.AddFromClause(_hqlTree.TreeBuilder.Join(
+ HqlGeneratorExpressionTreeVisitor.Visit(fromClause.FromExpression, VisitorParameters).AsExpression(),
+ _hqlTree.TreeBuilder.Alias(fromClause.ItemName)));
+ }
+ else
+ {
+ _hqlTree.AddFromClause(_hqlTree.TreeBuilder.LeftJoin(
+ HqlGeneratorExpressionTreeVisitor.Visit(fromClause.FromExpression, VisitorParameters).AsExpression(),
+ _hqlTree.TreeBuilder.Alias(fromClause.ItemName)));
+ }
}
else if (fromClause.FromExpression is MemberExpression)
{
Added: trunk/nhibernate/src/NHibernate/Linq/Visitors/SelectAndOrderByJoinDetector.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Linq/Visitors/SelectAndOrderByJoinDetector.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate/Linq/Visitors/SelectAndOrderByJoinDetector.cs 2011-04-15 04:10:51 UTC (rev 5705)
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using NHibernate.Linq.ReWriters;
+using Remotion.Linq.Clauses.Expressions;
+
+namespace NHibernate.Linq.Visitors
+{
+ public class SelectAndOrderByJoinDetector : AbstractJoinDetector
+ {
+ internal SelectAndOrderByJoinDetector(NameGenerator nameGenerator, IIsEntityDecider isEntityDecider, Dictionary<string, NhJoinClause> joins, Dictionary<MemberExpression, QuerySourceReferenceExpression> expressionMap)
+ : base(nameGenerator, isEntityDecider, joins, expressionMap)
+ {
+ }
+
+ protected override Expression VisitMemberExpression(MemberExpression expression)
+ {
+ if (expression.Type.IsNonPrimitive() && _isEntityDecider.IsEntity(expression.Type)) {
+ return AddJoin(expression);
+ }
+
+ return base.VisitMemberExpression(expression);
+ }
+ }
+}
Added: trunk/nhibernate/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate/Linq/Visitors/WhereJoinDetector.cs 2011-04-15 04:10:51 UTC (rev 5705)
@@ -0,0 +1,340 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using NHibernate.Linq.ReWriters;
+using Remotion.Linq.Clauses.Expressions;
+using Remotion.Linq.Utilities;
+
+namespace NHibernate.Linq.Visitors
+{
+ /// <summary>
+ /// The WhereJoinDetector creates the joins for the where clause, including
+ /// optimizations for inner joins. The algorithms are explained in a text
+ /// attached to JIRA entry NH-2583.
+ /// </summary>
+ internal class WhereJoinDetector : AbstractJoinDetector
+ {
+ // Possible results of a condition when emptily outer joined.
+ private const int T = 1;
+ private const int N = 2;
+ private const int TN = T | N;
+ private const int F = 4;
+ private const int TF = T | F;
+ private const int NF = N | F;
+ private const int TNF = T | N | F;
+
+ // Composition rules for possible results when emptily outer joined
+ // for &&, ||, !.
+ private static readonly int[,] AND = new int[8, 8];
+ private static readonly int[,] OR = new int[8, 8];
+ private static readonly int[] NOT = new int[8];
+
+ /// <summary>
+ /// Setup of <see cref="AND"/>, <see cref="OR"/>, <see cref="NOT"/>.
+ /// </summary>
+ static WhereJoinDetector()
+ {
+ // Setup of simple values according to SQL 3-valued logic.
+ NOT[T] = F;
+ NOT[N] = N;
+ NOT[F] = T;
+
+ foreach (var p in new[] { T, N, F })
+ {
+ OR[p, p] = AND[p, p] = p;
+ AND[p, F] = AND[F, p] = F;
+ OR[p, T] = OR[T, p] = T;
+ }
+ AND[T, N] = AND[N, T] = N;
+ OR[F, N] = OR[N, F] = N;
+
+ // Setup of compound values. Idea: Split each
+ // compound value to simple values, compute results
+ // for simple values and or them together.
+ var allValues = new[] { T, N, TN, F, TF, NF, TNF };
+
+ // How compound values are split up into simple values.
+ var split = new int[8][];
+ split[T] = new[] { T };
+ split[N] = new[] { N };
+ split[TN] = new[] { T, N };
+ split[F] = new[] { F };
+ split[TF] = new[] { T, F };
+ split[NF] = new[] { N, F };
+ split[TNF] = new[] { T, N, F };
+
+ foreach (var p in allValues)
+ {
+ int[] splitP = split[p];
+ // We only need to compute NOT for compound values.
+ if (splitP.Length > 1)
+ {
+ int notResult = 0;
+ foreach (var p0 in splitP)
+ {
+ notResult |= NOT[p0];
+ }
+ NOT[p] = notResult;
+ }
+ foreach (var q in allValues)
+ {
+ int[] splitQ = split[q];
+ // We must compute AND and OR if both values are compound,
+ // *but also* if one is compound and the other is simple
+ // (e.g. T and TNF).
+ if (splitP.Length > 1 || splitQ.Length > 1)
+ {
+ int andResult = 0;
+ int orResult = 0;
+ foreach (var p0 in splitP)
+ {
+ foreach (var q0 in splitQ)
+ {
+ andResult |= AND[p0, q0];
+ orResult |= OR[p0, q0];
+ }
+ }
+ AND[p, q] = andResult;
+ OR[p, q] = orResult;
+ }
+ }
+ }
+ }
+
+ // The following is used for all *condition* traversal (but not *expressions* that are not conditions).
+ // This is the "mapping" described in the text at NH-2583.
+ private Dictionary<string, int> _memberExpressionMapping = new Dictionary<string, int>();
+
+ // The following two are used for member expressions traversal.
+ private HashSet<string> _collectedPathMemberExpressionsInExpression = new HashSet<string>();
+ private int _memberExpressionDepth = 0;
+
+ internal
+ WhereJoinDetector(NameGenerator nameGenerator, IIsEntityDecider isEntityDecider, Dictionary<string, NhJoinClause> joins, Dictionary<MemberExpression, QuerySourceReferenceExpression> expressionMap)
+ : base(nameGenerator, isEntityDecider, joins, expressionMap)
+ {
+ }
+
+ protected override Expression VisitBinaryExpression(BinaryExpression expression)
+ {
+ ArgumentUtility.CheckNotNull("expression", expression);
+
+ Expression baseResult = expression;
+ if (expression.NodeType == ExpressionType.AndAlso && expression.Type == typeof(bool))
+ {
+ // Case (a) from the text at NH-2583.
+ var newLeft = VisitExpression(expression.Left);
+
+ var leftMapping = _memberExpressionMapping;
+
+ var newRight = VisitExpression(expression.Right);
+
+ var rightMapping = _memberExpressionMapping;
+
+ _memberExpressionMapping = BinaryMapping(leftMapping, rightMapping, AND);
+
+ // The following is copy-pasted from Relinq's visitor, as I had to split the code above.
+ var newConversion = (LambdaExpression)VisitExpression(expression.Conversion);
+ if (newLeft != expression.Left || newRight != expression.Right || newConversion != expression.Conversion)
+ baseResult = Expression.MakeBinary(expression.NodeType, newLeft, newRight, expression.IsLiftedToNull, expression.Method, newConversion);
+ }
+ else if (expression.NodeType == ExpressionType.OrElse && expression.Type == typeof(bool))
+ {
+ // Case (b)
+ var newLeft = VisitExpression(expression.Left);
+
+ var leftMapping = _memberExpressionMapping;
+
+ var newRight = VisitExpression(expression.Right);
+
+ var rightMapping = _memberExpressionMapping;
+
+ _memberExpressionMapping = BinaryMapping(leftMapping, rightMapping, OR);
+
+ // Again, the following is copy-pasted from Relinq's visitor, as I had to split the code above.
+ var newConversion = (LambdaExpression)VisitExpression(expression.Conversion);
+ if (newLeft != expression.Left || newRight != expression.Right || newConversion != expression.Conversion)
+ baseResult = Expression.MakeBinary(expression.NodeType, newLeft, newRight, expression.IsLiftedToNull, expression.Method, newConversion);
+ }
+ else if (expression.Type == typeof(bool)
+ && (expression.NodeType == ExpressionType.Equal && !IsNullConstantExpression(expression.Right) && !IsNullConstantExpression(expression.Left)
+ || expression.NodeType == ExpressionType.NotEqual && !IsNullConstantExpression(expression.Right) && !IsNullConstantExpression(expression.Left)
+ || expression.NodeType == ExpressionType.LessThan
+ || expression.NodeType == ExpressionType.LessThanOrEqual
+ || expression.NodeType == ExpressionType.GreaterThan
+ || expression.NodeType == ExpressionType.GreaterThanOrEqual))
+ {
+ // Cases (e), (f).2, (g).2
+ _collectedPathMemberExpressionsInExpression = new HashSet<string>();
+
+ baseResult = base.VisitBinaryExpression(expression);
+
+ _memberExpressionMapping = FixedMapping(_collectedPathMemberExpressionsInExpression, N);
+ }
+ else if (expression.Type == typeof(bool)
+ && expression.NodeType == ExpressionType.NotEqual)
+ {
+ // Case (h)
+ _collectedPathMemberExpressionsInExpression = new HashSet<string>();
+
+ baseResult = base.VisitBinaryExpression(expression);
+
+ _memberExpressionMapping = FixedMapping(_collectedPathMemberExpressionsInExpression, F);
+ }
+ else if (expression.Type == typeof(bool)
+ && expression.NodeType == ExpressionType.Equal)
+ {
+ // Case (i)
+ _collectedPathMemberExpressionsInExpression = new HashSet<string>();
+
+ baseResult = base.VisitBinaryExpression(expression);
+
+ _memberExpressionMapping = FixedMapping(_collectedPathMemberExpressionsInExpression, T);
+ }
+ else // +, * etc.
+ {
+ // Case (j)
+ _collectedPathMemberExpressionsInExpression = new HashSet<string>();
+
+ baseResult = base.VisitBinaryExpression(expression);
+
+ _memberExpressionMapping = FixedMapping(_collectedPathMemberExpressionsInExpression, TNF);
+ }
+ return baseResult;
+ }
+
+ private static Dictionary<string, int> FixedMapping(IEnumerable<string> collectedPathMemberExpressionsInExpression, int value)
+ {
+ var memberExpressionMapping = new Dictionary<string, int>();
+ foreach (var me in collectedPathMemberExpressionsInExpression)
+ {
+ memberExpressionMapping.Add(me, value);
+ }
+ return memberExpressionMapping;
+ }
+
+ private static Dictionary<string, int> BinaryMapping(Dictionary<string, int> leftMapping, Dictionary<string, int> rightMapping, int[,] op)
+ {
+ var result = new Dictionary<string, int>();
+ // Compute mapping for all member expressions in leftMapping. If the member expression is missing
+ // in rightMapping, use TNF as a "pessimistic approximation" instead (inside the ?: operator). See
+ // the text for an explanation of this.
+ foreach (var lhs in leftMapping)
+ {
+ result.Add(lhs.Key, op[lhs.Value, rightMapping.ContainsKey(lhs.Key) ? rightMapping[lhs.Key] : TNF]);
+ }
+ // Compute mapping for all member expressions *only* in rightMapping (we did the common ones above).
+ // Again, use TNF as pessimistic approximation to result of left subcondition.
+ foreach (var rhs in rightMapping)
+ {
+ if (!leftMapping.ContainsKey(rhs.Key))
+ {
+ result[rhs.Key] = op[rhs.Value, TNF];
+ }
+ }
+ return result;
+ }
+
+ private static bool IsNullConstantExpression(Expression expression)
+ {
+ if (expression is ConstantExpression)
+ {
+ var constant = (ConstantExpression)expression;
+ return constant.Value == null;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ protected override Expression VisitUnaryExpression(UnaryExpression expression)
+ {
+ ArgumentUtility.CheckNotNull("expression", expression);
+
+ Expression baseResult;
+ if (expression.NodeType == ExpressionType.Not && expression.Type == typeof(bool))
+ {
+ // Case (c) from text at NH-2583.
+ baseResult = VisitExpression(expression.Operand);
+
+ var opMapping = _memberExpressionMapping;
+ _memberExpressionMapping = new Dictionary<string, int>();
+
+ foreach (var m in opMapping)
+ {
+ _memberExpressionMapping.Add(m.Key, NOT[m.Value]);
+ }
+ }
+ else
+ {
+ baseResult = base.VisitUnaryExpression(expression);
+ }
+ return baseResult;
+ }
+
+ //protected override Expression VisitConditionalExpression(ConditionalExpression expression)
+ //{
+ // ArgumentUtility.CheckNotNull("expression", expression);
+
+ // VisitExpression(expression.Test);
+ // // If the ?: returns bool, it is (most probably ...) a condition which may require outer joins.
+ // // TODO: Check check whether HQL accepts ?: conditions; if not, should be rewritten it as (a && b || !a && c).
+ // if (expression.Type == typeof(bool))
+ // {
+ // ...
+ // }
+ // return expression;
+ //}
+
+ protected override Expression VisitMemberExpression(MemberExpression expression)
+ {
+ ArgumentUtility.CheckNotNull("expression", expression);
+
+ Expression newExpression;
+ try
+ {
+ _memberExpressionDepth++;
+ newExpression = base.VisitExpression(expression.Expression);
+ }
+ finally
+ {
+ _memberExpressionDepth--;
+ }
+ bool isEntity = _isEntityDecider.IsEntity(expression.Type);
+
+ if (isEntity)
+ {
+ // See (h) why we do not check for _memberExpressionDepth here!
+ _collectedPathMemberExpressionsInExpression.Add(ExpressionKeyVisitor.Visit(expression, null));
+ }
+
+ if (_memberExpressionDepth > 0 && isEntity)
+ {
+ return AddJoin(expression);
+ }
+ else
+ {
+ if (newExpression != expression.Expression)
+ return Expression.MakeMemberAccess(newExpression, expression.Member);
+ return expression;
+ }
+ }
+
+ internal static void Find(Expression expression, NameGenerator nameGenerator, IIsEntityDecider isEntityDecider, Dictionary<string, NhJoinClause> joins, Dictionary<MemberExpression, QuerySourceReferenceExpression> expressionMap)
+ {
+ WhereJoinDetector f = new WhereJoinDetector(nameGenerator, isEntityDecider, joins, expressionMap);
+
+ f.VisitExpression(expression);
+
+ foreach (var mapping in f._memberExpressionMapping)
+ {
+ // If outer join can never produce true, we can safely inner join.
+ if ((mapping.Value & T) == 0)
+ {
+ f.MakeInnerIfJoined(mapping.Key);
+ }
+ }
+ }
+
+ }
+}
Modified: trunk/nhibernate/src/NHibernate/NHibernate.csproj
===================================================================
--- trunk/nhibernate/src/NHibernate/NHibernate.csproj 2011-04-14 21:27:16 UTC (rev 5704)
+++ trunk/nhibernate/src/NHibernate/NHibernate.csproj 2011-04-15 04:10:51 UTC (rev 5705)
@@ -266,12 +266,17 @@
<Compile Include="ISessionFactory.cs" />
<Compile Include="ITransaction.cs" />
<Compile Include="LazyInitializationException.cs" />
+ <Compile Include="Linq\Clauses\NhJoinClause.cs" />
<Compile Include="Linq\Functions\DictionaryGenerator.cs" />
+ <Compile Include="Linq\ReWriters\AddJoinsReWriter.cs" />
<Compile Include="Linq\ReWriters\MoveOrderByToEndRewriter.cs" />
<Compile Include="Linq\ReWriters\ResultOperatorRewriter.cs" />
<Compile Include="Linq\ReWriters\ResultOperatorRewriterResult.cs" />
+ <Compile Include="Linq\Visitors\AbstractJoinDetector.cs" />
<Compile Include="Linq\Visitors\ExpressionTreeVisitor.cs" />
<Compile Include="Linq\Visitors\ResultOperatorProcessors\ProcessAggregateFromSeed.cs" />
+ <Compile Include="Linq\Visitors\SelectAndOrderByJoinDetector.cs" />
+ <Compile Include="Linq\Visitors\WhereJoinDetector.cs" />
<Compile Include="Loader\Loader.cs" />
<Compile Include="Loader\OuterJoinLoader.cs" />
<Compile Include="LockMode.cs" />
@@ -892,7 +897,6 @@
<Compile Include="Linq\Expressions\NhMinExpression.cs" />
<Compile Include="Linq\Expressions\NhSumExpression.cs" />
<Compile Include="Impl\ExpressionQueryImpl.cs" />
- <Compile Include="Linq\ReWriters\AddLeftJoinsReWriter.cs" />
<Compile Include="Linq\Visitors\IHqlExpressionVisitor.cs" />
<Compile Include="Linq\GroupJoin\IsAggregatingResults.cs" />
<Compile Include="Linq\GroupJoin\GroupJoinAggregateDetectionVisitor.cs" />
@@ -908,7 +912,6 @@
<Compile Include="Linq\Functions\StringGenerator.cs" />
<Compile Include="Linq\Functions\QueryableGenerator.cs" />
<Compile Include="Linq\ReWriters\RemoveUnnecessaryBodyOperators.cs" />
- <Compile Include="Linq\Clauses\LeftJoinClause.cs" />
<Compile Include="Linq\IntermediateHqlTree.cs" />
<Compile Include="Linq\Visitors\ResultOperatorProcessors\ProcessCast.cs" />
<Compile Include="Linq\Visitors\ResultOperatorProcessors\ProcessOfType.cs" />
@@ -922,7 +925,6 @@
<Compile Include="Linq\Visitors\ResultOperatorProcessors\ResultOperatorProcessorBase.cs" />
<Compile Include="Linq\Visitors\ResultOperatorProcessors\ProcessResultOperatorReturn.cs" />
<Compile Include="Linq\Visitors\ResultOperatorProcessors\IResultOperatorProcessor.cs" />
- <Compile Include="Linq\Visitors\LeftJoinDetector.cs" />
<Compile Include="Linq\Visitors\NameGenerator.cs" />
<Compile Include="Linq\Visitors\ResultOperatorProcessors\ProcessAggregate.cs" />
<Compile Include="Linq\Visitors\ResultOperatorProcessors\ProcessAll.cs" />
Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/AbstractMassTestingFixture.cs
===================================================================
--- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/AbstractMassTestingFixture.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/AbstractMassTestingFixture.cs 2011-04-15 04:10:51 UTC (rev 5705)
@@ -0,0 +1,254 @@
+using NHibernate.Linq;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace NHibernate.Test.NHSpecificTest.NH2583
+{
+ public abstract class AbstractMassTestingFixture : BugTestCase
+ {
+ private class ValueTuple<T1, T2, T3, T4, T5, T6, T7>
+ {
+ public T1 Item1;
+ public T2 Item2;
+ public T3 Item3;
+ public T4 Item4;
+ public T5 Item5;
+ public T6 Item6;
+ public T7 Item7;
+ }
+
+ private static IEnumerable<ValueTuple<T1, T2, T3, T4, T5, T6, T7>> GetAllTestCases<T1, T2, T3, T4, T5, T6, T7>()
+ {
+ foreach (T1 v1 in Enum.GetValues(typeof(T1)))
+ {
+ foreach (T2 v2 in Enum.GetValues(typeof(T2)))
+ {
+ foreach (T3 v3 in Enum.GetValues(typeof(T3)))
+ {
+ foreach (T4 v4 in Enum.GetValues(typeof(T4)))
+ {
+ foreach (T5 v5 in Enum.GetValues(typeof(T5)))
+ {
+ foreach (T6 v6 in Enum.GetValues(typeof(T6)))
+ {
+ foreach (T7 v7 in Enum.GetValues(typeof(T7)))
+ {
+ yield return
+ new ValueTuple<T1, T2, T3, T4, T5, T6, T7> { Item1 = v1, Item2 = v2, Item3 = v3, Item4 = v4, Item5 = v5, Item6 = v6, Item7 = v7 };
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public class SetterTuple<T1, T2, T3, T4, T5, T6, T7>
+ {
+ private readonly Action<MyBO, ISession, T1> _set1;
+ private readonly Action<MyBO, ISession, T2> _set2;
+ private readonly Action<MyBO, ISession, T3> _set3;
+ private readonly Action<MyBO, ISession, T4> _set4;
+ private readonly Action<MyBO, ISession, T5> _set5;
+ private readonly Action<MyBO, ISession, T6> _set6;
+ private readonly Action<MyBO, ISession, T7> _set7;
+
+ public SetterTuple(Action<MyBO, ISession, T1> set1,
+ Action<MyBO, ISession, T2> set2,
+ Action<MyBO, ISession, T3> set3,
+ Action<MyBO, ISession, T4> set4,
+ Action<MyBO, ISession, T5> set5,
+ Action<MyBO, ISession, T6> set6,
+ Action<MyBO, ISession, T7> set7)
+ {
+ _set1 = set1;
+ _set2 = set2;
+ _set3 = set3;
+ _set4 = set4;
+ _set5 = set5;
+ _set6 = set6;
+ _set7 = set7;
+ }
+
+ public void Set(MyBO bo, ISession s, T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
+ {
+ if (_set1 != null) { _set1(bo, s, item1); }
+ if (_set2 != null) { _set2(bo, s, item2); }
+ if (_set3 != null) { _set3(bo, s, item3); }
+ if (_set4 != null) { _set4(bo, s, item4); }
+ if (_set5 != null) { _set5(bo, s, item5); }
+ if (_set6 != null) { _set6(bo, s, item6); }
+ if (_set7 != null) { _set7(bo, s, item7); }
+ }
+ }
+
+
+ protected void RunTest<T1, T2, T3, T4, T5, T6, T7>(Expression<Func<MyBO, bool>> condition, SetterTuple<T1, T2, T3, T4, T5, T6, T7> setters)
+ {
+ if (condition == null)
+ {
+ throw new ArgumentNullException("condition");
+ }
+ if (setters == null)
+ {
+ throw new ArgumentNullException("setters");
+ }
+ IEnumerable<int> expectedIds;
+
+ // Setup
+ using (var session = OpenSession())
+ {
+ using (session.BeginTransaction())
+ {
+ using (var tx = session.BeginTransaction())
+ {
+ expectedIds = CreateObjects(session, setters, condition.Compile());
+ tx.Commit();
+ }
+ }
+ }
+
+ try
+ {
+ // Test
+ using (var session = OpenSession())
+ {
+ using (session.BeginTransaction())
+ {
+ TestAndAssert(condition, session, expectedIds);
+ }
+ }
+
+ }
+ finally
+ {
+ // Teardown
+ using (var session = OpenSession())
+ {
+ using (var tx = session.BeginTransaction())
+ {
+ DeleteAll<MyBO>(session);
+ DeleteAll<MyRef1>(session);
+ DeleteAll<MyRef2>(session);
+ DeleteAll<MyRef3>(session);
+ tx.Commit();
+ }
+ }
+ }
+ }
+
+ protected abstract void TestAndAssert(Expression<Func<MyBO, bool>> condition, ISession session, IEnumerable<int> expectedIds);
+
+ protected static SetterTuple<T1, T2, T3, T4, T5, T6, T7> Setters<T1, T2, T3, T4, T5, T6, T7>(Action<MyBO, ISession, T1> set1,
+ Action<MyBO, ISession, T2> set2,
+ Action<MyBO, ISession, T3> set3,
+ Action<MyBO, ISession, T4> set4,
+ Action<MyBO, ISession, T5> set5,
+ Action<MyBO, ISession, T6> set6,
+ Action<MyBO, ISession, T7> set7)
+ {
+ return new SetterTuple<T1, T2, T3, T4, T5, T6, T7>(set1, set2, set3, set4, set5, set6, set7);
+ }
+
+ protected static SetterTuple<T1, T2, T3, T4, T5, T6, Ignore> Setters<T1, T2, T3, T4, T5, T6>(Action<MyBO, ISession, T1> set1,
+ Action<MyBO, ISession, T2> set2,
+ Action<MyBO, ISession, T3> set3,
+ Action<MyBO, ISession, T4> set4,
+ Action<MyBO, ISession, T5> set5,
+ Action<MyBO, ISession, T6> set6)
+ {
+ return new SetterTuple<T1, T2, T3, T4, T5, T6, Ignore>(set1, set2, set3, set4, set5, set6, null);
+ }
+
+ protected static SetterTuple<T1, T2, T3, T4, T5, Ignore, Ignore> Setters<T1, T2, T3, T4, T5>(Action<MyBO, ISession, T1> set1,
+ Action<MyBO, ISession, T2> set2,
+ Action<MyBO, ISession, T3> set3,
+ Action<MyBO, ISession, T4> set4,
+ Action<MyBO, ISession, T5> set5)
+ {
+ return new SetterTuple<T1, T2, T3, T4, T5, Ignore, Ignore>(set1, set2, set3, set4, set5, null, null);
+ }
+
+ protected static SetterTuple<T1, T2, T3, T4, Ignore, Ignore, Ignore> Setters<T1, T2, T3, T4>(Action<MyBO, ISession, T1> set1,
+ Action<MyBO, ISession, T2> set2,
+ Action<MyBO, ISession, T3> set3,
+ Action<MyBO, ISession, T4> set4)
+ {
+ return new SetterTuple<T1, T2, T3, T4, Ignore, Ignore, Ignore>(set1, set2, set3, set4, null, null, null);
+ }
+
+ protected static SetterTuple<T1, T2, T3, Ignore, Ignore, Ignore, Ignore> Setters<T1, T2, T3>(Action<MyBO, ISession, T1> set1,
+ Action<MyBO, ISession, T2> set2,
+ Action<MyBO, ISession, T3> set3)
+ {
+ return new SetterTuple<T1, T2, T3, Ignore, Ignore, Ignore, Ignore>(set1, set2, set3,null, null, null, null);
+ }
+
+ protected static SetterTuple<T1, T2, Ignore, Ignore, Ignore, Ignore, Ignore> Setters<T1, T2>(Action<MyBO, ISession, T1> set1,
+ Action<MyBO, ISession, T2> set2)
+ {
+ return new SetterTuple<T1, T2, Ignore, Ignore, Ignore, Ignore, Ignore>(set1, set2, null, null, null, null, null);
+ }
+
+ protected static SetterTuple<T1, Ignore, Ignore, Ignore, Ignore, Ignore, Ignore> Setters<T1>(Action<MyBO, ISession, T1> set1)
+ {
+ return new SetterTuple<T1, Ignore, Ignore, Ignore, Ignore, Ignore, Ignore>(set1, null, null, null, null, null, null);
+ }
+
+ private static void DeleteAll<T>(ISession session)
+ {
+ foreach (var bo in session.Query<T>())
+ {
+ session.Delete(bo);
+ }
+ }
+
+ private static IEnumerable<int> CreateObjects<T1, T2, T3, T4, T5, T6, T7>(ISession session, SetterTuple<T1, T2, T3, T4, T5, T6, T7> setters, Func<MyBO, bool> condition)
+ {
+ var expectedIds = new List<int>();
+ bool thereAreSomeWithTrue = false;
+ bool thereAreSomeWithFalse = false;
+ foreach (var q in GetAllTestCases<T1, T2, T3, T4, T5, T6, T7>())
+ {
+ MyBO bo = new MyBO();
+ setters.Set(bo, session, q.Item1, q.Item2, q.Item3, q.Item4, q.Item5, q.Item6, q.Item7);
+ try
+ {
+ if (condition(bo))
+ {
+ expectedIds.Add(bo.Id);
+ thereAreSomeWithTrue = true;
+ }
+ else
+ {
+ thereAreSomeWithFalse = true;
+ }
+ session.Save(bo);
+ }
+ catch (NullReferenceException)
+ {
+ // ignore - we only check consistency with Linq2Objects in non-failing cases;
+ // emulating the outer-join logic for exceptional cases in Lin2Objects is IMO very hard.
+ }
+ }
+ if (!thereAreSomeWithTrue)
+ {
+ throw new ArgumentException("Condition is false for all - not a good test", "condition");
+ }
+ if (!thereAreSomeWithFalse)
+ {
+ throw new ArgumentException("Condition is true for all - not a good test", "condition");
+ }
+ return expectedIds;
+ }
+
+ protected static void AreEqual(IEnumerable<int> expectedIds, IEnumerable<int> actualList)
+ {
+ Assert.That(() => actualList.ToList(), Is.EquivalentTo(expectedIds));
+ }
+ }
+}
Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/Domain.cs
===================================================================
--- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/Domain.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/Domain.cs 2011-04-15 04:10:51 UTC (rev 5705)
@@ -0,0 +1,261 @@
+namespace NHibernate.Test.NHSpecificTest.NH2583
+{
+ public class MyRef1
+ {
+ private static int _idCt = 1000;
+ private int _id;
+
+ public MyRef1()
+ {
+ _id = ++_idCt;
+ }
+
+ public virtual int Id {
+ get { return _id; }
+ set { _id = value; }
+ }
+
+ public virtual int? I1 { get; set; }
+ public virtual int I2 { get; set; }
+ public virtual int I3 { get; set; }
+
+ public virtual MyRef2 BO2 { get; set; }
+ public virtual MyRef3 BO3 { get; set; }
+
+ public virtual MyRef2 GetOrCreateBO2(ISession s)
+ {
+ if (BO2 == null)
+ {
+ BO2 = new MyRef2();
+ s.Save(BO2);
+ }
+ return BO2;
+ }
+
+ public virtual MyRef3 GetOrCreateBO3(ISession s)
+ {
+ if (BO3 == null)
+ {
+ BO3 = new MyRef3();
+ s.Save(BO3);
+ }
+ return BO3;
+ }
+ }
+
+ public class MyRef2
+ {
+ private static int _idCt = 1000;
+ private int _id;
+
+ public MyRef2()
+ {
+ _id = ++_idCt;
+ }
+
+ public virtual int Id {
+ get { return _id; }
+ set { _id = value; }
+ }
+
+ public virtual int? J1 { get; set; }
+ public virtual int J2 { get; set; }
+ public virtual int J3 { get; set; }
+ }
+
+ public class MyRef3
+ {
+ private static int _idCt = 3000;
+ private int _id;
+
+ public MyRef3()
+ {
+ _id = ++_idCt;
+ }
+
+ public virtual int Id {
+ get { return _id; }
+ set { _id = value; }
+ }
+
+ public virtual int L1 { get; set; }
+ }
+
+ public enum Ignore { Ignore }
+ public enum TK { Zero, One }
+ public enum TBO1_I { Null, Zero, One }
+ public enum TBO2_J { Null, Zero, One }
+ public enum TBO1_BO2_J { Null, BO1, Zero, One }
+ public enum TBO1_BO3_L { Null, BO1, Zero, One }
+
+ public class MyBO
+ {
+ private static int _idCt = 0;
+ private int _id;
+
+ public MyBO()
+ {
+ _id = ++_idCt;
+ }
+
+ public virtual int Id {
+ get { return _id; }
+ set { _id = value; }
+ }
+
+ public virtual string Name { get; set; }
+ public virtual MyBO LeftSon { get; set; }
+ public virtual MyBO RightSon { get; set; }
+ public virtual MyRef1 BO1 { get; set; }
+ public virtual MyRef1 OtherBO1 { get; set; }
+ public virtual MyRef2 BO2 { get; set; }
+ public virtual int? K1 { get; set; }
+ public virtual int K2 { get; set; }
+ public virtual int K3 { get; set; }
+
+ private MyRef1 GetOrCreateBO1(ISession s)
+ {
+ if (BO1 == null)
+ {
+ BO1 = new MyRef1();
+ s.Save(BO1);
+ }
+ return BO1;
+ }
+
+ private MyRef2 GetOrCreateBO2(ISession s)
+ {
+ if (BO2 == null)
+ {
+ BO2 = new MyRef2();
+ s.Save(BO2);
+ }
+ return BO2;
+ }
+
+ public static void SetK1(MyBO bo, ISession s, TK value)
+ {
+ bo.K1 = value == TK.One ? 1 : 0;
+ }
+
+ public static void SetK2(MyBO bo, ISession s, TK value)
+ {
+ bo.K2 = value == TK.One ? 1 : 0;
+ }
+
+ public static void SetK3(MyBO bo, ISession s, TK value)
+ {
+ bo.K3 = value == TK.One ? 1 : 0;
+ }
+
+ private static void SetBO1_I(MyBO bo, ISession s, TBO1_I value, System.Action<MyRef1, int> set)
+ {
+ switch (value)
+ {
+ case TBO1_I.Null:
+ bo.BO1 = null;
+ break;
+ case TBO1_I.One:
+ set(bo.GetOrCreateBO1(s), 1);
+ break;
+ case TBO1_I.Zero:
+ set(bo.GetOrCreateBO1(s), 0);
+ break;
+ }
+ }
+
+ public static void SetBO1_I1(MyBO bo, ISession s, TBO1_I value)
+ {
+ SetBO1_I(bo, s, value, (b, i) => b.I1 = i);
+ }
+
+ public static void SetBO1_I2(MyBO bo, ISession s, TBO1_I value)
+ {
+ SetBO1_I(bo, s, value, (b, i) => b.I2 = i);
+ }
+
+ public static void SetBO1_I3(MyBO bo, ISession s, TBO1_I value)
+ {
+ SetBO1_I(bo, s, value, (b, i) => b.I3 = i);
+ }
+
+ private static void SetBO2_J(MyBO bo, ISession s, TBO2_J value, System.Action<MyRef2, int> set)
+ {
+ switch (value)
+ {
+ case TBO2_J.Null:
+ bo.BO2 = null;
+ break;
+ case TBO2_J.One:
+ set(bo.GetOrCreateBO2(s), 1);
+ break;
+ case TBO2_J.Zero:
+ set(bo.GetOrCreateBO2(s), 0);
+ break;
+ }
+ }
+
+ public static void SetBO2_J1(MyBO bo, ISession s, TBO2_J value)
+ {
+ SetBO2_J(bo, s, value, (b, i) => b.J1 = i);
+ }
+
+ public static void SetBO2_J2(MyBO bo, ISession s, TBO2_J value)
+ {
+ SetBO2_J(bo, s, value, (b, i) => b.J2 = i);
+ }
+
+ public static void SetBO2_J3(MyBO bo, ISession s, TBO2_J value)
+ {
+ SetBO2_J(bo, s, value, (b, i) => b.J3 = i);
+ }
+
+ private static void SetBO1_BO2_J(MyBO bo, ISession s, TBO1_BO2_J value, System.Action<MyRef2, int> set)
+ {
+ switch (value)
+ {
+ case TBO1_BO2_J.Null:
+ bo.BO1 = null;
+ break;
+ case TBO1_BO2_J.BO1:
+ bo.GetOrCreateBO1(s).BO2 = null;
+ break;
+ case TBO1_BO2_J.Zero:
+ set(bo.GetOrCreateBO1(s).GetOrCreateBO2(s), 0);
+ break;
+ case TBO1_BO2_J.One:
+ set(bo.GetOrCreateBO1(s).GetOrCreateBO2(s), 1);
+ break;
+ }
+ }
+
+ public static void SetBO1_BO2_J1(MyBO bo, ISession s, TBO1_BO2_J value)
+ {
+ SetBO1_BO2_J(bo, s, value, (b, i) => b.J1 = i);
+ }
+
+ public static void Set_BO1_BO2_J2(MyBO bo, ISession s, TBO1_BO2_J value)
+ {
+ SetBO1_BO2_J(bo, s, value, (b, i) => b.J2 = i);
+ }
+
+ public static void SetBO1_BO3_L1(MyBO bo, ISession s, TBO1_BO3_L value)
+ {
+ switch (value)
+ {
+ case TBO1_BO3_L.Null:
+ bo.BO1 = null;
+ break;
+ case TBO1_BO3_L.BO1:
+ bo.GetOrCreateBO1(s).BO3 = null;
+ break;
+ case TBO1_BO3_L.Zero:
+ bo.GetOrCreateBO1(s).GetOrCreateBO3(s).L1 = 0;
+ break;
+ case TBO1_BO3_L.One:
+ bo.GetOrCreateBO1(s).GetOrCreateBO3(s).L1 = 1;
+ break;
+ }
+ }
+ }
+}
Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/ManualTestFixture.cs
===================================================================
--- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/ManualTestFixture.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2583/ManualTestFixture.cs 2011-04-15 04:10:51 UTC (rev 5705)
@@ -0,0 +1,339 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using NHibernate.Linq;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.NH2583
+{
+ public class ManualTestFixture : BugTestCase
+ {
+ /// <summary>
+ /// This setup is used in most tests in here - but not all; and we might want to
+ /// twist it for special tests. Therefore, the OnSetUp mechanism is not used.
+ /// </summary>
+ [Test]
+ private void StandardSetUp()
+ {
+ using (var session = OpenSession())
+ {
+ using (var tx = session.BeginTransaction())
+ {
+ var i1001 = new MyRef1 { Id = 1001, I1 = null, I2 = 101 };
+ var i1101 = new MyRef1 { Id = 1101, I1 = null, I2 = 111 };
+ var i1002 = new MyRef1 { Id = 1002, I1 = 12, I2 = 102 };
+ var i1003 = new MyRef1 { Id = 1003, I1 = 13, I2 = 103 };
+ session.Save(i1001);
+ session.Save(i1101);
+ session.Save(i1002);
+ session.Save(i1003);
+
+ var j2001 = new MyRef2 { Id = 2001, J1 = null, J2 = 201 };
+ var j2002 = new MyRef2 { Id = 2002, J1 = 22, J2 = 202 };
+ var j2003 = new MyRef2 { Id = 2003, J1 = null, J2 = 203 };
+ session.Save(j2001);
+ session.Save(j2002);
+ session.Save(j2003);
+
+ var b10 = new MyBO { Id = 10, Name = "1:1001,o1:NULL,2:NULL", BO1 = i1001, OtherBO1 = null, BO2 = null };
+ var b11 = new MyBO { Id = 11, Name = "1:1001,o1:1101,2:NULL", BO1 = i1001, OtherBO1 = i1101, BO2 = null };
+ var b20 = new MyBO { Id = 20, Name = "1:1002,o1:NULL,2:2002", BO1 = i1002, OtherBO1 = null, BO2 = j2002 };
+ var b30 = new MyBO { Id = 30, Name = "1:NULL,o1:NULL,2:2003", BO1 = null, OtherBO1 = null, BO2 = j2003 };
+ session.Save(b10);
+ session.Save(b11);
+ session.Save(b20);
+ session.Save(b30);
+ tx.Commit();
+ }
+ }
+ }
+
+ public void OrWithTrueShouldBeEqualToTrue()
+ {
+ StandardSetUp();
+
+ int compareCt;
+ using (var session = OpenSession())
+ {
+ var result = session.Query<MyBO>();
+ // 3.1.0/2011-03-19: OK - select mybo0_.Id as Id0_, mybo0_.Name as Name0_, mybo0_.BO1Key as BO3_0_, mybo0_.OtherBO1Key as OtherBO4_0_, mybo0_.BO2Key as BO5_0_ from MyBO mybo0_
+ var resultList = result.ToList();
+ compareCt = resultList.Count;
+ Assert.IsTrue(compareCt > 0);
+ }
+ using (var session = OpenSession())
+ {
+ var result = session.Query<MyBO>()
+ .Where(bo => true);
+ // 3.1.0/2011-03-19: OK - exec sp_executesql N'select mybo0_.Id as Id0_, mybo0_.Name as Name0_, mybo0_.BO1Key as BO3_0_, mybo0_.OtherBO1Key as OtherBO4_0_, mybo0_.BO2Key as BO5_0_ from MyBO mybo0_ where @p0=1',N'@p0 bit',@p0=1
+ var resultList = result.ToList();
+ Assert.AreEqual(compareCt, resultList.Count);
+ }
+ using (var session = OpenSession())
+ {
+ var result = session.Query<MyBO>()
+ .Where(bo =>
+ bo.BO1 != null && bo.BO1.I2 == 101
+ || true
+ );
+ // 3.1.0/2011-03-19: WRONG - exec sp_executesql N'select mybo0_.Id as Id0_, mybo0_.Name as Name0_, mybo0_.BO1Key as BO3_0_, mybo0_.OtherBO1Key as OtherBO4_0_, mybo0_.BO2Key as BO5_0_ from MyBO mybo0_, MyRef1 myref1x1_ where mybo0_.BO1Key=myref1x1_.Id and ((mybo0_.BO1Key is not null) and myref1x1_.I2=@p0 or @p1=1)',N'@p0 int,@p1 bit',@p0=101,@p1=1
+ var resultList = result.ToList();
+ Assert.AreEqual(compareCt, resultList.Count);
+ }
+ using (var session = OpenSession())
+ {
+ var result = session.Query<MyBO>()
+ .Where(bo =>
+ bo.BO1 != null && bo.BO1.I2 == 101
+ || bo.Id == bo.Id + 0
+ );
+ // 3.1.0/2011-03-19: WRONG - exec sp_executesql N'select mybo0_.Id as Id0_, mybo0_.Name as Name0_, mybo0_.BO1Key as BO3_0_, mybo0_.OtherBO1Key as OtherBO4_0_, mybo0_.BO2Key as BO5_0_ from MyBO mybo0_, MyRef1 myref1x1_ where mybo0_.BO1Key=myref1x1_.Id and ((mybo0_.BO1Key is not null) and myref1x1_.I2=@p0 or mybo0_.Id=mybo0_.Id+@p1)',N'@p0 int,@p1 int',@p0=101,@p1=0
+ var resultList = result.ToList();
+ Assert.AreEqual(compareCt, resultList.Count);
+ }
+ }
+
+ [Test]
+ public void OrAndNavigationsShouldUseOuterJoins()
+ {
+ StandardSetUp();
+
+ using (var session = OpenSession())
+ {
+ var result = session.Query<MyBO>()
+ .Where(bo =>
+ bo.BO1 != null && bo.BO1.I2 == 101
+ // || bo.BO2 != null && bo.BO2.J2 == 203 - is added below!
+ );
+ var resultList = result.ToList();
+ Assert.IsTrue(resultList.Count > 0);
+ }
+ using (var session = OpenSession())
+ {
+ var result = session.Query<MyBO>()
+ .Where(bo =>
+ bo.BO1 != null && bo.BO1.I2 == 101
+ || bo.BO2 != null && bo.BO2.J2 == 203
+ );
+ var resultList = result.ToList();
+ Assert.IsTrue(resultList.Count > 0);
+ }
+ using (var session = OpenSession())
+ {
+ var result = session.Query<MyBO>()
+ .Where(bo =>
+ bo.BO1.I2 == 101 && bo.BO1 != null
+ || bo.BO2.J2 == 203 && bo.BO2 != null
+ );
+ var resultList = result.ToList();
+ Assert.IsTrue(resultList.Count > 0);
+ }
+ }
+
+ [Test]
+ public void OrShouldBeCompatibleWithAdditionForNullReferences()
+ {
+ // This tests against the "outer join anomaly" of (||-3) semantics.
+ StandardSetUp();
+
+ using (var session = OpenSession())
+ {
+ List<MyBO> leftResult;
+ List<MyBO> rightResult;
+ List<MyBO> orResult;
+ TestCoreOrShouldBeCompatibleWithSum(session,
+ bo => bo.BO1.I2 == null,
+ bo => bo.BO2.J2 == null,
+ bo => bo.BO1.I2 == null || bo.BO2.J2 == null, out leftResult, out rightResult, out orResult);
+ //Assert.AreEqual(0, leftResult.Count);
+ //Assert.AreEqual(0, rightResult.Count);
+ //Assert.AreEqual(0, orResult.Count);
+
+ }
+ }
+
+ private static void TestCoreOrShouldBeCompatibleWithSum(ISession session,
+ Expression<Func<MyBO, bool>> left,
+ Expression<Func<MyBO, bool>> right,
+ Expression<Func<MyBO, bool>> both,
+ out List<MyBO> leftResult,
+ out List<MyBO> rightResult,
+ out List<MyBO> orResult)
+ {
+ leftResult = session.Query<MyBO>()
+ .Where(left
+ ).ToList();
+
+ rightResult = session.Query<MyBO>()
+ .Where(right
+ ).ToList();
+
+ orResult = session.Query<MyBO>()
+ .Where(both
+ ).ToList();
+ Assert.IsTrue(orResult.Count <= leftResult.Count + rightResult.Count);
+ }
+
+ [Test]
+ public void OrShouldBeCompatibleWithAdditionForNonNullReferences()
+ {
+ // This tests one case of existing references - so that we do not fall
+ // into trap of testing only empty results that might be empty for other reasons.
+ StandardSetUp();
+
+ using (var session = OpenSession())
+ {
+ List<MyBO> leftResult;
+ List<MyBO> rightResult;
+ List<MyBO> orResult;
+ TestCoreOrShouldBeCompatibleWithSum(session,
+ bo => bo.BO1.I1 == null,
+ bo => bo.BO2.J1 == null,
+ bo => bo.BO1.I1 == null || bo.BO2.J1 == null, out leftResult, out rightResult, out orResult);
+ Assert.That(() => leftResult.Count, Is.GreaterThan(0));
+ Assert.That(() => rightResult.Count, Is.GreaterThan(0));
+ Assert.That(() => orResult.Count, Is.GreaterThan(0));
+ }
+ }
+
+ [Test, Ignore("Pure Outer Join semantics has projection anomaly!")]
+ public void ProjectionDoesNotChangeResult()
+ {
+ // This tests against the "projection anomaly" of (||-4) semantics.
+ using (var session = OpenSession())
+ {
+ using (var tx = session.BeginTransaction())
+ {
+ var i1001 = new MyRef1 { Id = 1001, I1 = null, I2 = 101 };
+ var i1101 = new MyRef1 { Id = 1101, I1 = null, I2 = 111 };
+ session.Save(i1001);
+ session.Save(i1101);
+
+ var b1 = new MyBO { Id = 1, Name = "1:1001", BO1 = i1001 };
+ var b2 = new MyBO { Id = 2, Name = "2:1101", BO1 = i1101 };
+ var b3 = new MyBO { Id = 3, Name = "3:1101", BO1 = i1101 };
+ var b4 = new MyBO { Id = 4, Name = "4:NULL", BO1 = null };
+ session.Save(b1);
+ session.Save(b2);
+ session.Save(b3);
+ session.Save(b4);
+ tx.Commit();
+ }...
[truncated message content] |