|
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.
|