From: <fab...@us...> - 2011-05-30 18:24:36
|
Revision: 5889 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=5889&view=rev Author: fabiomaulo Date: 2011-05-30 18:24:30 +0000 (Mon, 30 May 2011) Log Message: ----------- Fix NH-2703 thanks to Roy Jacobs Modified Paths: -------------- trunk/nhibernate/src/NHibernate/Loader/JoinWalker.cs trunk/nhibernate/src/NHibernate/Loader/OuterJoinableAssociation.cs trunk/nhibernate/src/NHibernate/NHibernate.csproj trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj Added Paths: ----------- trunk/nhibernate/src/NHibernate/Loader/TopologicalSorter.cs trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Fixture.cs trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Mappings.hbm.xml trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Model.cs Modified: trunk/nhibernate/src/NHibernate/Loader/JoinWalker.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Loader/JoinWalker.cs 2011-05-30 17:31:39 UTC (rev 5888) +++ trunk/nhibernate/src/NHibernate/Loader/JoinWalker.cs 2011-05-30 18:24:30 UTC (rev 5889) @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; using Iesi.Collections.Generic; using NHibernate.Collection; using NHibernate.Engine; @@ -18,6 +19,7 @@ private readonly ISet<AssociationKey> visitedAssociationKeys = new HashedSet<AssociationKey>(); private readonly IDictionary<string, IFilter> enabledFilters; private readonly IDictionary<string, IFilter> enabledFiltersForManyToOne; + private readonly Regex aliasRegex = new Regex(@"([\w]+)\.", RegexOptions.IgnoreCase | RegexOptions.Compiled); private string[] suffixes; private string[] collectionSuffixes; @@ -110,6 +112,14 @@ get { return false; } } + public class DependentAlias + { + public string Alias { get; set; } + public string[] DependsOn { get; set; } + } + + readonly List<DependentAlias> _dependentAliases = new List<DependentAlias>(); + protected JoinWalker(ISessionFactoryImplementor factory, IDictionary<string, IFilter> enabledFilters) { this.factory = factory; @@ -149,7 +159,7 @@ OuterJoinableAssociation assoc = new OuterJoinableAssociation(type, alias, aliasedLhsColumns, subalias, joinType, GetWithClause(path), Factory, enabledFilters); assoc.ValidateJoin(path); - associations.Add(assoc); + AddAssociation(subalias, assoc); int nextDepth = currentDepth + 1; @@ -167,7 +177,65 @@ } } + private static int[] GetTopologicalSortOrder(List<DependentAlias> fields) + { + TopologicalSorter g = new TopologicalSorter(fields.Count); + Dictionary<string, int> _indexes = new Dictionary<string, int>(); + + // add vertices + for (int i = 0; i < fields.Count; i++) + { + _indexes[fields[i].Alias.ToLower()] = g.AddVertex(i); + } + + // add edges + for (int i = 0; i < fields.Count; i++) + { + if (fields[i].DependsOn != null) + { + for (int j = 0; j < fields[i].DependsOn.Length; j++) + { + var dependentField = fields[i].DependsOn[j].ToLower(); + if (_indexes.ContainsKey(dependentField)) + { + g.AddEdge(i, _indexes[dependentField]); + } + } + } + } + + return g.Sort(); + } + /// <summary> + /// Adds an association and extracts the aliases the association's 'with clause' is dependent on + /// </summary> + private void AddAssociation(string subalias, OuterJoinableAssociation association) + { + subalias = subalias.ToLower(); + + var dependencies = new List<string>(); + var on = association.On.ToString(); + if (!String.IsNullOrEmpty(on)) + { + foreach (Match match in aliasRegex.Matches(on)) + { + string alias = match.Groups[1].Value; + if (alias == subalias) continue; + dependencies.Add(alias.ToLower()); + } + } + + _dependentAliases.Add(new DependentAlias + { + Alias = subalias, + DependsOn = dependencies.ToArray() + }); + + associations.Add(association); + } + + /// <summary> /// For an entity class, return a list of associations to be fetched by outerjoin /// </summary> protected void WalkEntityTree(IOuterJoinLoadable persister, string alias) @@ -559,10 +627,18 @@ /// </summary> protected JoinFragment MergeOuterJoins(IList<OuterJoinableAssociation> associations) { + IList<OuterJoinableAssociation> sortedAssociations = new List<OuterJoinableAssociation>(); + + var indices = GetTopologicalSortOrder(_dependentAliases); + for (int index = indices.Length - 1; index >= 0; index--) + { + sortedAssociations.Add(associations[indices[index]]); + } + JoinFragment outerjoin = Dialect.CreateOuterJoinFragment(); OuterJoinableAssociation last = null; - foreach (OuterJoinableAssociation oj in associations) + foreach (OuterJoinableAssociation oj in sortedAssociations) { if (last != null && last.IsManyToManyWith(oj)) { Modified: trunk/nhibernate/src/NHibernate/Loader/OuterJoinableAssociation.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Loader/OuterJoinableAssociation.cs 2011-05-30 17:31:39 UTC (rev 5888) +++ trunk/nhibernate/src/NHibernate/Loader/OuterJoinableAssociation.cs 2011-05-30 18:24:30 UTC (rev 5889) @@ -48,6 +48,11 @@ get { return rhsAlias; } } + public SqlString On + { + get { return on; } + } + private bool IsOneToOne { get Added: trunk/nhibernate/src/NHibernate/Loader/TopologicalSorter.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Loader/TopologicalSorter.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Loader/TopologicalSorter.cs 2011-05-30 18:24:30 UTC (rev 5889) @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +// Algorithm from: http://tawani.blogspot.com/2009/02/topological-sorting-and-cyclic.html +namespace NHibernate.Loader +{ + class TopologicalSorter + { + #region - Private Members - + + private readonly int[] _vertices; // list of vertices + private readonly int[,] _matrix; // adjacency matrix + private int _numVerts; // current number of vertices + private readonly int[] _sortedArray; + + #endregion + + #region - CTors - + + public TopologicalSorter(int size) + { + _vertices = new int[size]; + _matrix = new int[size, size]; + _numVerts = 0; + for (int i = 0; i < size; i++) + for (int j = 0; j < size; j++) + _matrix[i, j] = 0; + _sortedArray = new int[size]; // sorted vert labels + } + + #endregion + + #region - Public Methods - + + public int AddVertex(int vertex) + { + _vertices[_numVerts++] = vertex; + return _numVerts - 1; + } + + public void AddEdge(int start, int end) + { + _matrix[start, end] = 1; + } + + public int[] Sort() // toplogical sort + { + while (_numVerts > 0) // while vertices remain, + { + // get a vertex with no successors, or -1 + int currentVertex = noSuccessors(); + if (currentVertex == -1) // must be a cycle + throw new Exception("Graph has cycles"); + + // insert vertex label in sorted array (start at end) + _sortedArray[_numVerts - 1] = _vertices[currentVertex]; + + deleteVertex(currentVertex); // delete vertex + } + + // vertices all gone; return sortedArray + return _sortedArray; + } + + #endregion + + #region - Private Helper Methods - + + // returns vert with no successors (or -1 if no such verts) + private int noSuccessors() + { + for (int row = 0; row < _numVerts; row++) + { + bool isEdge = false; // edge from row to column in adjMat + for (int col = 0; col < _numVerts; col++) + { + if (_matrix[row, col] > 0) // if edge to another, + { + isEdge = true; + break; // this vertex has a successor try another + } + } + if (!isEdge) // if no edges, has no successors + return row; + } + return -1; // no + } + + private void deleteVertex(int delVert) + { + // if not last vertex, delete from vertexList + if (delVert != _numVerts - 1) + { + for (int j = delVert; j < _numVerts - 1; j++) + _vertices[j] = _vertices[j + 1]; + + for (int row = delVert; row < _numVerts - 1; row++) + moveRowUp(row, _numVerts); + + for (int col = delVert; col < _numVerts - 1; col++) + moveColLeft(col, _numVerts - 1); + } + _numVerts--; // one less vertex + } + + private void moveRowUp(int row, int length) + { + for (int col = 0; col < length; col++) + _matrix[row, col] = _matrix[row + 1, col]; + } + + private void moveColLeft(int col, int length) + { + for (int row = 0; row < length; row++) + _matrix[row, col] = _matrix[row, col + 1]; + } + + #endregion + } +} Modified: trunk/nhibernate/src/NHibernate/NHibernate.csproj =================================================================== --- trunk/nhibernate/src/NHibernate/NHibernate.csproj 2011-05-30 17:31:39 UTC (rev 5888) +++ trunk/nhibernate/src/NHibernate/NHibernate.csproj 2011-05-30 18:24:30 UTC (rev 5889) @@ -281,6 +281,7 @@ <Compile Include="Linq\Visitors\ResultOperatorProcessors\ProcessAggregateFromSeed.cs" /> <Compile Include="Linq\Visitors\SelectAndOrderByJoinDetector.cs" /> <Compile Include="Linq\Visitors\WhereJoinDetector.cs" /> + <Compile Include="Loader\TopologicalSorter.cs" /> <Compile Include="Loader\Loader.cs" /> <Compile Include="Loader\OuterJoinLoader.cs" /> <Compile Include="LockMode.cs" /> Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Fixture.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Fixture.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Fixture.cs 2011-05-30 18:24:30 UTC (rev 5889) @@ -0,0 +1,76 @@ +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH2703 +{ + [TestFixture] + public class Fixture : BugTestCase + { + Parent RootElement = null; + + protected override void OnSetUp() + { + using (ISession session = Sfi.OpenSession()) + { + var parent = new Parent(); + parent.A.Add(new A() { PropA = "Child" }); + parent.B.Add(new B() { PropB = "Child" }); + parent.C.Add(new C() { PropC = "Child" }); + session.Persist(parent); + session.Flush(); + } + } + + protected override void OnTearDown() + { + using (ISession session = Sfi.OpenSession()) + { + session.CreateQuery("delete from A").ExecuteUpdate(); + session.CreateQuery("delete from B").ExecuteUpdate(); + session.CreateQuery("delete from C").ExecuteUpdate(); + session.CreateQuery("delete from Parent").ExecuteUpdate(); + session.Flush(); + } + base.OnTearDown(); + } + + [Test] + public void CanOuterJoinMultipleTablesWithSimpleWithClause() + { + using (ISession session = Sfi.OpenSession()) + { + IQueryOver<Parent, Parent> query = session.QueryOver(() => RootElement); + + A A_Alias = null; + B B_Alias = null; + C C_Alias = null; + query.Left.JoinQueryOver(parent => parent.C, () => C_Alias, c => c.PropC == A_Alias.PropA); + query.Left.JoinQueryOver(parent => parent.A, () => A_Alias); + query.Left.JoinQueryOver(parent => parent.B, () => B_Alias, b => b.PropB == C_Alias.PropC); + // Expected join order: a --> c --> b + + // This query should not throw + query.List(); + } + } + + [Test] + public void CanOuterJoinMultipleTablesWithComplexWithClause() + { + using (ISession session = Sfi.OpenSession()) + { + IQueryOver<Parent, Parent> query = session.QueryOver(() => RootElement); + + A A_Alias = null; + B B_Alias = null; + C C_Alias = null; + query.Left.JoinQueryOver(parent => parent.C, () => C_Alias, c => c.PropC == A_Alias.PropA && c.PropC == B_Alias.PropB); + query.Left.JoinQueryOver(parent => parent.A, () => A_Alias); + query.Left.JoinQueryOver(parent => parent.B, () => B_Alias, b => b.PropB == A_Alias.PropA); + // Expected join order: a --> b --> c + + // This query should not throw + query.List(); + } + } + } +} Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Mappings.hbm.xml =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Mappings.hbm.xml (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Mappings.hbm.xml 2011-05-30 18:24:30 UTC (rev 5889) @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8" ?> +<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" + namespace="NHibernate.Test.NHSpecificTest.NH2703" + assembly="NHibernate.Test"> + + <class name="Parent" table="`Parent_Table`"> + <id name="Id"> + <generator class="native"/> + </id> + <list name="A" cascade="all"> + <key column="parent_id" /> + <index column="a_index" /> + <one-to-many class="A" /> + </list> + <list name="B" cascade="all"> + <key column="parent_id" /> + <index column="b_index" /> + <one-to-many class="B" /> + </list> + <list name="C" cascade="all"> + <key column="parent_id" /> + <index column="c_index" /> + <one-to-many class="C" /> + </list> + </class> + + <class name="A" table="`A_Table`"> + <id name="Id"> + <generator class="native"/> + </id> + <property name="PropA" type="String(15)"/> + </class> + + <class name="B" table="`B_Table`"> + <id name="Id"> + <generator class="native"/> + </id> + <property name="PropB" type="String(15)"/> + </class> + + <class name="C" table="`C_Table`"> + <id name="Id"> + <generator class="native"/> + </id> + <property name="PropC" type="String(15)"/> + </class> +</hibernate-mapping> Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Model.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Model.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2703/Model.cs 2011-05-30 18:24:30 UTC (rev 5889) @@ -0,0 +1,75 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH2703 +{ + public class Parent + { + private IList<A> a = new List<A>(); + private IList<B> b = new List<B>(); + private IList<C> c = new List<C>(); + private int id; + + public virtual IList<A> A + { + get { return a; } + set { a = value; } + } + + public virtual IList<B> B + { + get { return b; } + set { b = value; } + } + + public virtual IList<C> C + { + get { return c; } + set { c = value; } + } + + public virtual int Id + { + get { return id; } + set { id = value; } + } + } + + public class A + { + private int id; + + public virtual int Id + { + get { return id; } + set { id = value; } + } + + public virtual string PropA { get; set; } + } + + public class B + { + private int id; + + public virtual int Id + { + get { return id; } + set { id = value; } + } + + public virtual string PropB { get; set; } + } + + public class C + { + private int id; + + public virtual int Id + { + get { return id; } + set { id = value; } + } + + public virtual string PropC { get; set; } + } +} Modified: trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2011-05-30 17:31:39 UTC (rev 5888) +++ trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2011-05-30 18:24:30 UTC (rev 5889) @@ -854,6 +854,8 @@ <Compile Include="NHSpecificTest\NH2697\ArticleGroupItem.cs" /> <Compile Include="NHSpecificTest\NH2697\ArticleItem.cs" /> <Compile Include="NHSpecificTest\NH2697\SampleTest.cs" /> + <Compile Include="NHSpecificTest\NH2703\Fixture.cs" /> + <Compile Include="NHSpecificTest\NH2703\Model.cs" /> <Compile Include="NHSpecificTest\NH2705\ItemBase.cs" /> <Compile Include="NHSpecificTest\NH2705\SubItemBase.cs" /> <Compile Include="NHSpecificTest\NH2705\SubItemDetails.cs" /> @@ -2707,6 +2709,7 @@ <EmbeddedResource Include="NHSpecificTest\NH1291AnonExample\Mappings.hbm.xml" /> </ItemGroup> <ItemGroup> + <EmbeddedResource Include="NHSpecificTest\NH2703\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH2736\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH2721\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH2733\Mappings.hbm.xml" /> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |