From: <fab...@us...> - 2011-06-18 12:59:32
|
Revision: 5947 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=5947&view=rev Author: fabiomaulo Date: 2011-06-18 12:59:25 +0000 (Sat, 18 Jun 2011) Log Message: ----------- Fix NH-2296 Modified Paths: -------------- trunk/nhibernate/src/NHibernate/Engine/QueryParameters.cs trunk/nhibernate/src/NHibernate/Loader/Collection/BasicCollectionLoader.cs trunk/nhibernate/src/NHibernate/Loader/Collection/CollectionLoader.cs trunk/nhibernate/src/NHibernate/Loader/Collection/OneToManyLoader.cs trunk/nhibernate/src/NHibernate/Loader/Collection/SubselectCollectionLoader.cs trunk/nhibernate/src/NHibernate/Loader/Collection/SubselectOneToManyLoader.cs trunk/nhibernate/src/NHibernate/Loader/Loader.cs trunk/nhibernate/src/NHibernate/SqlCommand/SubselectClauseExtractor.cs trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj trunk/nhibernate/src/NHibernate.Test/SubselectFetchTest/ParentChild.hbm.xml Added Paths: ----------- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Fixture.cs trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Mappings.hbm.xml trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Model.cs Modified: trunk/nhibernate/src/NHibernate/Engine/QueryParameters.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Engine/QueryParameters.cs 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate/Engine/QueryParameters.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -157,8 +157,8 @@ } public SqlString ProcessedSql { get; internal set; } - public IEnumerable<IParameterSpecification> ProcessedSqlParameters { get; internal set; } + public RowSelection ProcessedRowSelection { get; internal set; } public bool NaturalKeyLookup { get; set; } Modified: trunk/nhibernate/src/NHibernate/Loader/Collection/BasicCollectionLoader.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Loader/Collection/BasicCollectionLoader.cs 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate/Loader/Collection/BasicCollectionLoader.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -30,6 +30,11 @@ ISessionFactoryImplementor factory, IDictionary<string, IFilter> enabledFilters) : base(collectionPersister, factory, enabledFilters) { + InitializeFromWalker(collectionPersister, subquery, batchSize, enabledFilters, factory); + } + + protected virtual void InitializeFromWalker(IQueryableCollection collectionPersister, SqlString subquery, int batchSize, IDictionary<string, IFilter> enabledFilters, ISessionFactoryImplementor factory) + { JoinWalker walker = new BasicCollectionJoinWalker(collectionPersister, batchSize, subquery, factory, enabledFilters); InitFromWalker(walker); Modified: trunk/nhibernate/src/NHibernate/Loader/Collection/CollectionLoader.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Loader/Collection/CollectionLoader.cs 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate/Loader/Collection/CollectionLoader.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -67,5 +67,48 @@ { return parametersSpecifications ?? (parametersSpecifications = CreateParameterSpecificationsAndAssignBackTrack(SqlString.GetParameters()).ToArray()); } + + protected SqlString GetSubSelectWithLimits(SqlString subquery, ICollection<IParameterSpecification> parameterSpecs, RowSelection processedRowSelection, IDictionary<string, TypedValue> parameters, IDictionary<string, int[]> namedParameterLocMap) + { + ISessionFactoryImplementor sessionFactory = Factory; + Dialect.Dialect dialect = sessionFactory.Dialect; + + RowSelection selection = processedRowSelection; + bool useLimit = UseLimit(selection, dialect); + if (useLimit) + { + bool hasFirstRow = GetFirstRow(selection) > 0; + bool useOffset = hasFirstRow && dialect.SupportsLimitOffset; + int max = GetMaxOrLimit(dialect, selection); + int? skip = useOffset ? (int?) dialect.GetOffsetValue(GetFirstRow(selection)) : null; + int? take = max != int.MaxValue ? (int?) max : null; + + Parameter skipSqlParameter = null; + Parameter takeSqlParameter = null; + if (skip.HasValue) + { + string skipParameterName = "nhsubselectskip"; + var skipParameter = new NamedParameterSpecification(1, 0, skipParameterName) {ExpectedType = NHibernateUtil.Int32}; + skipSqlParameter = Parameter.Placeholder; + skipSqlParameter.BackTrack = skipParameter.GetIdsForBackTrack(sessionFactory).First(); + parameters.Add(skipParameterName, new TypedValue(skipParameter.ExpectedType, skip.Value, EntityMode.Poco)); + namedParameterLocMap.Add(skipParameterName, new int[0]); + parameterSpecs.Add(skipParameter); + } + if (take.HasValue) + { + string takeParameterName = "nhsubselecttake"; + var takeParameter = new NamedParameterSpecification(1, 0, takeParameterName) {ExpectedType = NHibernateUtil.Int32}; + takeSqlParameter = Parameter.Placeholder; + takeSqlParameter.BackTrack = takeParameter.GetIdsForBackTrack(sessionFactory).First(); + parameters.Add(takeParameterName, new TypedValue(takeParameter.ExpectedType, take.Value, EntityMode.Poco)); + namedParameterLocMap.Add(takeParameterName, new int[0]); + parameterSpecs.Add(takeParameter); + } + // The dialect can move the given parameters where he need, what it can't do is generates new parameters loosing the BackTrack. + return dialect.GetLimitString(subquery, skip, take, skipSqlParameter, takeSqlParameter); + } + return subquery; + } } } \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/Loader/Collection/OneToManyLoader.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Loader/Collection/OneToManyLoader.cs 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate/Loader/Collection/OneToManyLoader.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -30,6 +30,11 @@ ISessionFactoryImplementor factory, IDictionary<string, IFilter> enabledFilters) : base(oneToManyPersister, factory, enabledFilters) { + InitializeFromWalker(oneToManyPersister, subquery, batchSize, enabledFilters, factory); + } + + protected virtual void InitializeFromWalker(IQueryableCollection oneToManyPersister, SqlString subquery, int batchSize, IDictionary<string, IFilter> enabledFilters, ISessionFactoryImplementor factory) + { JoinWalker walker = new OneToManyJoinWalker(oneToManyPersister, batchSize, subquery, factory, enabledFilters); InitFromWalker(walker); Modified: trunk/nhibernate/src/NHibernate/Loader/Collection/SubselectCollectionLoader.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Loader/Collection/SubselectCollectionLoader.cs 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate/Loader/Collection/SubselectCollectionLoader.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -11,6 +11,7 @@ /// <summary> Implements subselect fetching for a collection</summary> public class SubselectCollectionLoader : BasicCollectionLoader { + private const int BatchSizeForSubselectFetching = 1; private readonly object[] keys; private readonly IDictionary<string, int[]> namedParameterLocMap; private readonly IDictionary<string, TypedValue> namedParameters; @@ -21,7 +22,7 @@ public SubselectCollectionLoader(IQueryableCollection persister, SqlString subquery, ICollection<EntityKey> entityKeys, QueryParameters queryParameters, IDictionary<string, int[]> namedParameterLocMap, ISessionFactoryImplementor factory, IDictionary<string, IFilter> enabledFilters) - : base(persister, 1, subquery, factory, enabledFilters) + : base(persister, BatchSizeForSubselectFetching, factory, enabledFilters) { keys = new object[entityKeys.Count]; int i = 0; @@ -29,13 +30,23 @@ { keys[i++] = entityKey.Identifier; } - + // NH Different behavior: to deal with positionslParameter+NamedParameter+ParameterOfFilters + namedParameters = new Dictionary<string, TypedValue>(queryParameters.NamedParameters); + this.namedParameterLocMap = new Dictionary<string, int[]>(namedParameterLocMap); parametersSpecifications = queryParameters.ProcessedSqlParameters.ToList(); - namedParameters = queryParameters.NamedParameters; + var processedRowSelection = queryParameters.ProcessedRowSelection; + SqlString finalSubquery = subquery; + if (queryParameters.ProcessedRowSelection != null && !SubselectClauseExtractor.HasOrderBy(queryParameters.ProcessedSql)) + { + // when the original query has an "ORDER BY" we can't re-apply the pagination + // this is a simplification, we should actually check which is the "ORDER BY" clause because when the "ORDER BY" is just for the PK we can re-apply "ORDER BY" and pagination. + finalSubquery = GetSubSelectWithLimits(subquery, parametersSpecifications, processedRowSelection, namedParameters, this.namedParameterLocMap); + } + InitializeFromWalker(persister, finalSubquery, BatchSizeForSubselectFetching, enabledFilters, factory); + types = queryParameters.PositionalParameterTypes; values = queryParameters.PositionalParameterValues; - this.namedParameterLocMap = namedParameterLocMap; } public override void Initialize(object id, ISessionImplementor session) Modified: trunk/nhibernate/src/NHibernate/Loader/Collection/SubselectOneToManyLoader.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Loader/Collection/SubselectOneToManyLoader.cs 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate/Loader/Collection/SubselectOneToManyLoader.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -5,7 +5,6 @@ using NHibernate.Persister.Collection; using NHibernate.SqlCommand; using NHibernate.Type; -using NHibernate.Util; namespace NHibernate.Loader.Collection { @@ -14,6 +13,7 @@ /// </summary> public class SubselectOneToManyLoader : OneToManyLoader { + private const int BatchSizeForSubselectFetching = 1; private readonly object[] keys; private readonly IDictionary<string, int[]> namedParameterLocMap; private readonly IDictionary<string, TypedValue> namedParameters; @@ -24,7 +24,7 @@ public SubselectOneToManyLoader(IQueryableCollection persister, SqlString subquery, ICollection<EntityKey> entityKeys, QueryParameters queryParameters, IDictionary<string, int[]> namedParameterLocMap, ISessionFactoryImplementor factory, IDictionary<string, IFilter> enabledFilters) - : base(persister, 1, subquery, factory, enabledFilters) + : base(persister, BatchSizeForSubselectFetching, factory, enabledFilters) { keys = new object[entityKeys.Count]; int i = 0; @@ -34,11 +34,21 @@ } // NH Different behavior: to deal with positionslParameter+NamedParameter+ParameterOfFilters + namedParameters = new Dictionary<string, TypedValue>(queryParameters.NamedParameters); + this.namedParameterLocMap = new Dictionary<string, int[]>(namedParameterLocMap); parametersSpecifications = queryParameters.ProcessedSqlParameters.ToList(); - namedParameters = queryParameters.NamedParameters; + var processedRowSelection = queryParameters.ProcessedRowSelection; + SqlString finalSubquery = subquery; + if (queryParameters.ProcessedRowSelection != null && !SubselectClauseExtractor.HasOrderBy(queryParameters.ProcessedSql)) + { + // when the original query has an "ORDER BY" we can't re-apply the pagination. + // This is a simplification, we should actually check which is the "ORDER BY" clause because when the "ORDER BY" is just for the PK we can re-apply "ORDER BY" and pagination. + finalSubquery = GetSubSelectWithLimits(subquery, parametersSpecifications, processedRowSelection, namedParameters, this.namedParameterLocMap); + } + InitializeFromWalker(persister, finalSubquery, BatchSizeForSubselectFetching, enabledFilters, factory); + types = queryParameters.PositionalParameterTypes; values = queryParameters.PositionalParameterValues; - this.namedParameterLocMap = namedParameterLocMap; } public override void Initialize(object id, ISessionImplementor session) Modified: trunk/nhibernate/src/NHibernate/Loader/Loader.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Loader/Loader.cs 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate/Loader/Loader.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -1647,10 +1647,10 @@ // dynamic-filter parameters: during the createion of the SqlString of allLoader implementation, filters can be added as SQL_TOKEN/string for this reason we have to re-parse the SQL. sqlString = ExpandDynamicFilterParameters(sqlString, parameterSpecs, session); - AdjustQueryParametersForSubSelectFetching(sqlString, parameterSpecs, queryParameters); // NOTE: see TODO below + AdjustQueryParametersForSubSelectFetching(sqlString, parameterSpecs, queryParameters); + // Add limits sqlString = AddLimitsParametersIfNeeded(sqlString, parameterSpecs, queryParameters, session); - // TODO: for sub-select fetching we have to try to assign the QueryParameter.ProcessedSQL here (with limits) but only after use IParameterSpecification for any kind of queries and taking care about the work done by SubselectClauseExtractor // The PreprocessSQL method can modify the SqlString but should never add parameters (or we have to override it) sqlString = PreprocessSQL(sqlString, queryParameters, session.Factory.Dialect); @@ -1668,10 +1668,14 @@ protected abstract IEnumerable<IParameterSpecification> GetParameterSpecifications(); - protected void AdjustQueryParametersForSubSelectFetching(SqlString sqlString, IEnumerable<IParameterSpecification> parameterSpecs, QueryParameters queryParameters) + protected void AdjustQueryParametersForSubSelectFetching(SqlString filteredSqlString, IEnumerable<IParameterSpecification> parameterSpecsWithFilters, QueryParameters queryParameters) { - queryParameters.ProcessedSql = sqlString; - queryParameters.ProcessedSqlParameters = parameterSpecs.ToList(); + queryParameters.ProcessedSql = filteredSqlString; + queryParameters.ProcessedSqlParameters = parameterSpecsWithFilters.ToList(); + if (queryParameters.RowSelection != null) + { + queryParameters.ProcessedRowSelection = new RowSelection { FirstRow = queryParameters.RowSelection.FirstRow, MaxRows = queryParameters.RowSelection.MaxRows }; + } } protected SqlString ExpandDynamicFilterParameters(SqlString sqlString, ICollection<IParameterSpecification> parameterSpecs, ISessionImplementor session) Modified: trunk/nhibernate/src/NHibernate/SqlCommand/SubselectClauseExtractor.cs =================================================================== --- trunk/nhibernate/src/NHibernate/SqlCommand/SubselectClauseExtractor.cs 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate/SqlCommand/SubselectClauseExtractor.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -11,6 +11,11 @@ /// </summary> public class SubselectClauseExtractor { + /* + * NH TODO: this implementation will break, for MsSQL2005Dialect, a when the query is an HQL with skip/take because the last "ORDER BY" is there for pagination. + * Because HQL skip/take are new features, we hope nobody will use it in conjuction with subselect fetching at least until MS-SQL will release a more modern + * syntax for pagination. + */ private const string FromClauseToken = " from "; private const string OrderByToken = "order by"; @@ -94,6 +99,13 @@ return builder.ToSqlString(); } + public static bool HasOrderBy(SqlString subselect) + { + var extractor = new SubselectClauseExtractor((object[])subselect.Parts); + extractor.GetSqlString(); + return extractor.lastOrderByPartIndex >= 0; + } + private int FindFromClauseInPart(string part) { int afterLastClosingParenIndex = 0; Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Fixture.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Fixture.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Fixture.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -0,0 +1,67 @@ +using System.Linq; +using NUnit.Framework; +using SharpTestsEx; + +namespace NHibernate.Test.NHSpecificTest.NH2296 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void OnSetUp() + { + base.OnSetUp(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var o = new Order() { AccountName = "Acct1" }; + o.Products.Add(new Product() { StatusReason = "Success", Order = o }); + o.Products.Add(new Product() { StatusReason = "Failure", Order = o }); + s.Save(o); + + o = new Order() { AccountName = "Acct2" }; + s.Save(o); + + o = new Order() { AccountName = "Acct3" }; + o.Products.Add(new Product() { StatusReason = "Success", Order = o }); + o.Products.Add(new Product() { StatusReason = "Failure", Order = o }); + s.Save(o); + + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.Delete("from Product"); + s.Delete("from Order"); + tx.Commit(); + } + + base.OnTearDown(); + } + + [Test] + public void Test() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var orders = s.CreateQuery("select o from Order o") + .SetMaxResults(2) + .List<Order>(); + + // trigger lazy-loading of products, using subselect fetch. + string sr = orders[0].Products[0].StatusReason; + + // count of entities we want: + int ourEntities = orders.Count + orders.Sum(o => o.Products.Count); + + s.Statistics.EntityCount.Should().Be(ourEntities); + } + } + } +} \ No newline at end of file Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Mappings.hbm.xml =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Mappings.hbm.xml (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Mappings.hbm.xml 2011-06-18 12:59:25 UTC (rev 5947) @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<hibernate-mapping + xmlns="urn:nhibernate-mapping-2.2" + default-access="property" + auto-import="true" + default-cascade="none" + default-lazy="true" + assembly="NHibernate.Test" + namespace="NHibernate.Test.NHSpecificTest.NH2296"> + + <class mutable="true" name="Order" table="`Order`"> + + <id name="Id" type="System.Int32"> + <column name="Id" /> + <generator class="identity" /> + </id> + + <bag cascade="all-delete-orphan" fetch="subselect" inverse="true" name="Products" mutable="true"> + <key> + <column name="Order_id" /> + </key> + <one-to-many class="Product" /> + </bag> + + <property name="AccountName" type="System.String"> + <column name="AccountName" /> + </property> + + </class> + + <class mutable="true" name="Product" table="`Product`"> + + <id name="Id" type="System.Int32"> + <column name="Id" /> + <generator class="identity" /> + </id> + + <property name="StatusReason" type="System.String"> + <column name="StatusReason" /> + </property> + + <many-to-one class="Order" name="Order"> + <column name="Order_id" /> + </many-to-one> + + </class> + +</hibernate-mapping> \ No newline at end of file Added: trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Model.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Model.cs (rev 0) +++ trunk/nhibernate/src/NHibernate.Test/NHSpecificTest/NH2296/Model.cs 2011-06-18 12:59:25 UTC (rev 5947) @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH2296 +{ + public class Order + { + public virtual int Id { get; set; } + public virtual string AccountName { get; set; } + public virtual IList<Product> Products { get; set; } + + public Order() + { + this.Products = new List<Product>(); + } + } + + public class Product + { + public virtual int Id { get; set; } + public virtual string StatusReason { get; set; } + public virtual Order Order { get; set; } + } +} \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj =================================================================== --- trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj 2011-06-18 12:59:25 UTC (rev 5947) @@ -723,6 +723,8 @@ <Compile Include="NHSpecificTest\NH2288\Fixture.cs" /> <Compile Include="NHSpecificTest\NH2293\Fixture.cs" /> <Compile Include="NHSpecificTest\NH2294\Fixture.cs" /> + <Compile Include="NHSpecificTest\NH2296\Fixture.cs" /> + <Compile Include="NHSpecificTest\NH2296\Model.cs" /> <Compile Include="NHSpecificTest\NH2302\Fixture.cs" /> <Compile Include="NHSpecificTest\NH2302\StringLengthEntity.cs" /> <Compile Include="NHSpecificTest\NH2303\Fixture.cs" /> @@ -2716,6 +2718,7 @@ <EmbeddedResource Include="NHSpecificTest\NH1291AnonExample\Mappings.hbm.xml" /> </ItemGroup> <ItemGroup> + <EmbeddedResource Include="NHSpecificTest\NH2296\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH2760\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH2662\Mappings.hbm.xml" /> <EmbeddedResource Include="NHSpecificTest\NH2703\Mappings.hbm.xml" /> Modified: trunk/nhibernate/src/NHibernate.Test/SubselectFetchTest/ParentChild.hbm.xml =================================================================== --- trunk/nhibernate/src/NHibernate.Test/SubselectFetchTest/ParentChild.hbm.xml 2011-06-17 18:09:19 UTC (rev 5946) +++ trunk/nhibernate/src/NHibernate.Test/SubselectFetchTest/ParentChild.hbm.xml 2011-06-18 12:59:25 UTC (rev 5947) @@ -8,7 +8,7 @@ <generator class="assigned" /> </id> <bag name="Friends" fetch="subselect" table="ChildChild"> - <key column="childName1" /><!-- H3 has not-null="true" --> + <key column="childName1" not-null="true" /> <many-to-many class="Child" column="childName2"/> </bag> </class> @@ -18,12 +18,12 @@ <generator class="assigned" /> </id> <list name="Children" fetch="subselect" cascade="all"> - <key column="parentName" /><!-- H3 has not-null="true" --> + <key column="parentName" not-null="true"/> <index column="loc"/> <one-to-many class="Child"/> </list> <list name="MoreChildren" table="ParentChild" fetch="subselect"> - <key column="parentName" /><!-- H3 has not-null="true" --> + <key column="parentName" not-null="true"/> <index column="loc"/> <many-to-many class="Child" column="childName"/> </list> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |