From 3e5261da4c503078d3baa5560add7a59add704df Mon Sep 17 00:00:00 2001 From: maca88 Date: Thu, 17 Sep 2020 16:35:33 +0200 Subject: [PATCH 01/17] Add support for joining a subquery in hql --- .../Northwind/Entities/CompositeOrder.cs | 46 + .../Northwind/Entities/Northwind.cs | 7 +- .../Northwind/Entities/Order.cs | 4 +- .../Northwind/Mappings/CompositeOrder.hbm.xml | 21 + .../Northwind/Mappings/Order.hbm.xml | 7 +- .../Async/Linq/ByMethod/JoinSubqueryTests.cs | 869 ++++++++++++++++++ .../Linq/ByMethod/JoinSubqueryTests.cs | 858 +++++++++++++++++ src/NHibernate.Test/Linq/LinqTestCase.cs | 3 +- src/NHibernate/Criterion/EntityProjection.cs | 11 +- src/NHibernate/Engine/JoinSequence.cs | 2 +- src/NHibernate/Hql/Ast/ANTLR/Hql.g | 1 + src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs | 92 +- src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g | 15 +- .../ANTLR/SessionFactoryHelperExtensions.cs | 2 + src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.cs | 45 +- src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g | 1 + .../ANTLR/Tree/AbstractSelectExpression.cs | 31 +- .../Hql/Ast/ANTLR/Tree/AggregateNode.cs | 12 +- .../Tree/BinaryArithmeticOperatorNode.cs | 8 + .../Hql/Ast/ANTLR/Tree/Case2Node.cs | 10 +- src/NHibernate/Hql/Ast/ANTLR/Tree/CaseNode.cs | 10 +- .../Hql/Ast/ANTLR/Tree/ComponentJoin.cs | 17 + .../Hql/Ast/ANTLR/Tree/ConstructorNode.cs | 33 +- .../Hql/Ast/ANTLR/Tree/CountNode.cs | 19 +- src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs | 24 +- .../Hql/Ast/ANTLR/Tree/FromClause.cs | 53 +- .../Hql/Ast/ANTLR/Tree/FromElement.cs | 115 ++- .../Hql/Ast/ANTLR/Tree/FromElementFactory.cs | 2 +- .../Hql/Ast/ANTLR/Tree/FromElementType.cs | 227 +++-- .../Hql/Ast/ANTLR/Tree/ISelectExpression.cs | 40 +- .../Hql/Ast/ANTLR/Tree/IdentNode.cs | 25 +- .../Hql/Ast/ANTLR/Tree/IndexNode.cs | 8 + .../Ast/ANTLR/Tree/JoinSubqueryFromElement.cs | 75 ++ .../Tree/JoinSubqueryJoinSequenceImpl.cs | 37 + .../Hql/Ast/ANTLR/Tree/LiteralNode.cs | 8 + .../Hql/Ast/ANTLR/Tree/MethodNode.cs | 10 + .../Hql/Ast/ANTLR/Tree/ParameterNode.cs | 13 +- .../Hql/Ast/ANTLR/Tree/QueryNode.cs | 13 +- .../Hql/Ast/ANTLR/Tree/SelectClause.cs | 596 +++++++----- .../Ast/ANTLR/Tree/SelectExpressionImpl.cs | 13 +- .../Ast/ANTLR/Tree/SelectExpressionList.cs | 82 +- .../Hql/Ast/ANTLR/Tree/UnaryArithmeticNode.cs | 8 + .../Hql/Ast/ANTLR/Util/ColumnHelper.cs | 50 +- .../Hql/Ast/ANTLR/Util/JoinProcessor.cs | 2 +- src/NHibernate/Hql/NameGenerator.cs | 4 +- .../Collection/BasicCollectionJoinWalker.cs | 2 +- .../Loader/Custom/Sql/SQLQueryParser.cs | 2 +- src/NHibernate/Loader/Hql/QueryLoader.cs | 5 +- .../Collection/AbstractCollectionPersister.cs | 17 +- .../Collection/BasicCollectionPersister.cs | 4 +- .../Collection/IQueryableCollection.cs | 43 + .../Collection/OneToManyPersister.cs | 2 +- .../Entity/AbstractEntityPersister.cs | 95 +- src/NHibernate/Persister/Entity/IQueryable.cs | 61 +- .../Entity/SubqueryPropertyMapping.cs | 334 +++++++ src/NHibernate/SqlCommand/SelectFragment.cs | 71 +- src/NHibernate/Type/ComponentType.cs | 24 + src/NHibernate/Type/SubqueryComponentType.cs | 24 + 58 files changed, 3746 insertions(+), 467 deletions(-) create mode 100644 src/NHibernate.DomainModel/Northwind/Entities/CompositeOrder.cs create mode 100644 src/NHibernate.DomainModel/Northwind/Mappings/CompositeOrder.hbm.xml create mode 100644 src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs create mode 100644 src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs create mode 100644 src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs create mode 100644 src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryJoinSequenceImpl.cs create mode 100644 src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs create mode 100644 src/NHibernate/Type/SubqueryComponentType.cs diff --git a/src/NHibernate.DomainModel/Northwind/Entities/CompositeOrder.cs b/src/NHibernate.DomainModel/Northwind/Entities/CompositeOrder.cs new file mode 100644 index 00000000000..c32ef6a209e --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Entities/CompositeOrder.cs @@ -0,0 +1,46 @@ +using System; + +namespace NHibernate.DomainModel.Northwind.Entities +{ + public class CompositeOrder : IEquatable + { + public virtual int OrderId { get; set; } + + public virtual Customer Customer { get; set; } + + public virtual DateTime? OrderDate { get; set; } + + public virtual DateTime? RequiredDate { get; set; } + + public virtual DateTime? ShippingDate { get; set; } + + public virtual Shipper Shipper { get; set; } + + public virtual decimal? Freight { get; set; } + + public virtual string ShippedTo { get; set; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((CompositeOrder) obj); + } + + public virtual bool Equals(CompositeOrder other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return OrderId == other.OrderId && Equals(Customer, other.Customer); + } + + public override int GetHashCode() + { + unchecked + { + return (OrderId * 397) ^ (Customer != null ? Customer.GetHashCode() : 0); + } + } + } +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs b/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs index 670b94b5bc5..bda526da00e 100755 --- a/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs @@ -33,7 +33,12 @@ public IQueryable Orders { get { return _session.Query(); } } - + + public IQueryable CompositeOrders + { + get { return _session.Query(); } + } + public IQueryable OrderLines { get { return _session.Query(); } diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Order.cs b/src/NHibernate.DomainModel/Northwind/Entities/Order.cs index ea01182d4c5..8db8935d480 100755 --- a/src/NHibernate.DomainModel/Northwind/Entities/Order.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Order.cs @@ -88,6 +88,8 @@ public virtual ISet OrderLines get { return _orderLines; } } + public virtual ISet ProductIds { get; set; } + public virtual void AddOrderLine(OrderLine orderLine) { if (!_orderLines.Contains(orderLine)) @@ -106,4 +108,4 @@ public virtual void RemoveOrderLine(OrderLine orderLine) } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.DomainModel/Northwind/Mappings/CompositeOrder.hbm.xml b/src/NHibernate.DomainModel/Northwind/Mappings/CompositeOrder.hbm.xml new file mode 100644 index 00000000000..59a8db13c1c --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Mappings/CompositeOrder.hbm.xml @@ -0,0 +1,21 @@ + + + + + select * from Orders + + + + + + + + + + + + + + + + diff --git a/src/NHibernate.DomainModel/Northwind/Mappings/Order.hbm.xml b/src/NHibernate.DomainModel/Northwind/Mappings/Order.hbm.xml index efae7f61cb9..5c95fa00d7b 100755 --- a/src/NHibernate.DomainModel/Northwind/Mappings/Order.hbm.xml +++ b/src/NHibernate.DomainModel/Northwind/Mappings/Order.hbm.xml @@ -57,7 +57,12 @@ + + + + + - \ No newline at end of file + diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs new file mode 100644 index 00000000000..b617f05bdb3 --- /dev/null +++ b/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs @@ -0,0 +1,869 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections; +using NHibernate.DomainModel.Northwind.Entities; +using NUnit.Framework; +using NUnit.Framework.Constraints; + +namespace NHibernate.Test.Linq.ByMethod +{ + using System.Threading.Tasks; + [TestFixture] + public class JoinSubqueryTestsAsync : LinqTestCase + { + #region HqlEntitySubQuery + + [Test] + public async Task HqlEntitySubQueryAsync() + { + var result = await (session.CreateQuery("from Order o inner join (from Order where OrderId = 10248) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); + AssertEntitySubQuery(result); + + result = await (session.CreateQuery("from Order o inner join (from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").ListAsync()); + AssertEntitySubQuery(result); + + result = await (session.CreateQuery("from Order o inner join (select o2 from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").ListAsync()); + AssertEntitySubQuery(result); + + result = await (session.CreateQuery("select o, o3 from Order o inner join (select o2 from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").ListAsync()); + AssertEntitySubQuery(result); + + result = await (session.CreateQuery("select o, o3 from Order o inner join (from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").ListAsync()); + AssertEntitySubQuery(result); + } + + private void AssertEntitySubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + } + + #endregion + + #region ScalarSubQuery + + [Test] + public async Task HqlScalarSubQueryAsync() + { + var result = await (session.CreateQuery(@" + select o.Customer.CustomerId, o.ShippedTo, o2 + from Order o inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); + AssertScalarSubQuery(result); + + result = await (session.CreateQuery(@" + select o.Customer.CustomerId, o.ShippedTo, o2.OrderId, o2.CustomerId, o2.ShippedTo + from Order o inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); + AssertScalarSubQuery(result); + } + + private void AssertScalarSubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(5)); + Assert.That(array[0], Is.EqualTo("TOMSP")); + Assert.That(array[1], Is.EqualTo("Toms Spezialitäten")); + Assert.That(array[2], Is.EqualTo(10248)); + Assert.That(array[3], Is.EqualTo("VINET")); + Assert.That(array[4], Is.EqualTo("Vins et alcools Chevalier")); + } + + #endregion + + #region IdSubQuery + + [Test] + public async Task HqlIdSubQueryAsync() + { + var result = await (session.CreateQuery(@" + select o + from Order o inner join ( + select id from Order where OrderId = 10248 + ) o2 on o.id = o2.id").ListAsync()); + AssertIdSubQuery(result); + + result = await (session.CreateQuery(@" + select o + from CompositeOrder o inner join ( + select id from CompositeOrder where OrderId = 10248 + ) o2 on o.id = o2.id").ListAsync()); + AssertIdSubQuery(result); + } + + private void AssertIdSubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + Assert.That(item, Has.Property("OrderId").EqualTo(10248)); + } + + #endregion + + #region SubclassSubQuery + + [Test] + public async Task HqlSubclassSubQueryAsync() + { + var result = await (session.CreateQuery(@" + from Animal a inner join ( + from Animal where Father is null + ) a2 on a.Father = a2").ListAsync()); + AssertSubclassSubQuery(result); + } + + private void AssertSubclassSubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf()); + Assert.That(array[1], Is.TypeOf()); + } + + #endregion + + #region MixedSubQuery + + [Test] + public async Task HqlMixedSubQueryAsync() + { + var result = await (session.CreateQuery(@" + select o, o2, o.Customer.CustomerId, o.ShippedTo + from Order o inner join ( + select OrderId, Customer, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); + AssertMixedSubQuery(result); + + result = await (session.CreateQuery(@" + select o, o2.OrderId, o2.cu, o2.ShippedTo, o.Customer.CustomerId, o.ShippedTo + from Order o inner join ( + select OrderId, Customer as cu, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); + AssertMixedSubQuery(result); + } + + private void AssertMixedSubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(6)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.EqualTo(10248)); + Assert.That(array[2], Is.TypeOf().And.Property("CustomerId").EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Vins et alcools Chevalier")); + Assert.That(array[4], Is.EqualTo("TOMSP")); + Assert.That(array[5], Is.EqualTo("Toms Spezialitäten")); + } + + #endregion + + #region MixedSubQuery + + [Test] + public async Task HqlSubQueryComponentPropertySelectionAsync() + { + var result = await (session.CreateQuery(@" + select o2.cu.Address.Street, o2.emp.Address.Street + from Order o inner join ( + select OrderId, Customer as cu, Employee as emp from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); + AssertSubQueryComponentPropertySelection(result); + } + + private void AssertSubQueryComponentPropertySelection(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.EqualTo("59 rue de l'Abbaye")); + Assert.That(array[1], Is.EqualTo("14 Garrett Hill")); + } + + #endregion + + #region IdSubQueryWithPagingAndOrderBy + + [Test] + public async Task HqlIdSubQueryWithPagingAndOrderByAsync() + { + var result = await (session.CreateQuery(@" + select o + from Order o inner join ( + select id from Order order by OrderId skip 2 take 2 + ) o2 on o.id = o2.id").ListAsync()); + AssertIdSubQueryWithPagingAndOrderBy(result); + + result = await (session.CreateQuery(@" + select o + from CompositeOrder o inner join ( + select id from CompositeOrder order by OrderId skip 2 take 2 + ) o2 on o.id = o2.id").ListAsync()); + AssertIdSubQueryWithPagingAndOrderBy(result); + } + + private void AssertIdSubQueryWithPagingAndOrderBy(IList result) + { + Assert.That(result, Has.Count.EqualTo(2)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + Assert.That(item, Has.Property("OrderId").EqualTo(10250)); + item = result[1]; + Assert.That(item, Is.TypeOf()); + Assert.That(item, Has.Property("OrderId").EqualTo(10251)); + } + + #endregion + + #region GroupBySubQueryWithAliases + + [Test] + public async Task HqlGroupBySubQueryWithAliasesAsync() + { + var result = await (session.CreateQuery(@" + select o, o2 + from Order o inner join ( + select Customer.CustomerId, count(*) as total, max(OrderId) as orderId from Order group by Customer.CustomerId + ) o2 on o.id = o2.orderId + where o2.total > 30").ListAsync()); + AssertGroupBySubQueryWithAliases(result); + + result = await (session.CreateQuery(@" + select o, o2.CustomerId, o2.total, o2.orderId + from Order o inner join ( + select Customer.CustomerId, count(*) as total, max(OrderId) as orderId from Order group by Customer.CustomerId + ) o2 on o.id = o2.orderId + where o2.total > 30").ListAsync()); + AssertGroupBySubQueryWithAliases(result); + } + + private void AssertGroupBySubQueryWithAliases(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(4)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(11064)); + Assert.That(array[1], Is.EqualTo("SAVEA")); + Assert.That(array[2], Is.EqualTo(31)); + Assert.That(array[3], Is.EqualTo(11064)); + } + + #endregion + + #region SubQueryWithEntityAlias + + [Test] + public async Task HqlSubQueryWithEntityAliasAsync() + { + var result = await (session.CreateQuery(@" + select o, o3.order.OrderId, o3.customer.CustomerId, o3.ShippedTo + from Order o inner join ( + select o2 as order, o2.Customer as customer, o2.ShippedTo from Order o2 where o2.OrderId = 10248 + ) o3 on (o.OrderId - 1) = o3.order.OrderId").ListAsync()); + AssertSubQueryWithEntityAlias(result); + } + + private void AssertSubQueryWithEntityAlias(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(4)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.EqualTo(10248)); + Assert.That(array[2], Is.EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Vins et alcools Chevalier")); + } + + #endregion + + #region MultipleEntitySubQueries + + [Test] + public async Task HqlMultipleEntitySubQueriesAsync() + { + var result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + left join ( + from Order where OrderId = 10250 + ) o3 on (o3.OrderId - 2) = o2.OrderId").ListAsync()); + AssertMultipleEntitySubQueries(result); + + result = await (session.CreateQuery(@" + select o, o2, o3 + from Order o + inner join ( + from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + left join ( + from Order where OrderId = 10250 + ) o3 on (o3.OrderId - 2) = o2.OrderId").ListAsync()); + AssertMultipleEntitySubQueries(result); + } + + private void AssertMultipleEntitySubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(3)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[2], Is.TypeOf().And.Property("OrderId").EqualTo(10250)); + } + + #endregion + + #region MultipleScalarSubQueries + + [Test] + public async Task HqlMultipleScalarSubQueriesAsync() + { + var result = await (session.CreateQuery(@" + from Order o + inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + left join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10250 + ) o3 on (o3.OrderId - 2) = o2.OrderId").ListAsync()); + AssertMultipleScalarSubQueries(result); + + result = await (session.CreateQuery(@" + select o, o2, o3 + from Order o + inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + left join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10250 + ) o3 on (o3.OrderId - 2) = o2.OrderId").ListAsync()); + AssertMultipleScalarSubQueries(result); + } + + private void AssertMultipleScalarSubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(7)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.EqualTo(10248)); + Assert.That(array[2], Is.EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Vins et alcools Chevalier")); + Assert.That(array[4], Is.EqualTo(10250)); + Assert.That(array[5], Is.EqualTo("HANAR")); + Assert.That(array[6], Is.EqualTo("Hanari Carnes")); + } + + #endregion + + #region NestedEntitySubQueries + + [Test] + public async Task HqlNestedEntitySubQueriesAsync() + { + var result = await (session.CreateQuery(@" + from Order o + left join ( + select e as emp, e2 as sup + from Employee e + left join (from Employee) e2 on e.Superior = e2 + ) o3 on o.Employee = o3.emp + order by o.id + take 1").ListAsync()); + AssertNestedEntitySubQueries(result); + + result = await (session.CreateQuery(@" + select o, o3 + from Order o + left join ( + select e as emp, e2 as sup + from Employee e + left join (from Employee) e2 on e.Superior = e2 + ) o3 on o.Employee = o3.emp + order by o.id + take 1").ListAsync()); + AssertNestedEntitySubQueries(result); + + result = await (session.CreateQuery(@" + select o, o3.emp, o3.sup + from Order o + left join ( + select e as emp, e2 as sup + from Employee e + left join (from Employee) e2 on e.Superior = e2 + ) o3 on o.Employee = o3.emp + order by o.id + take 1").ListAsync()); + AssertNestedEntitySubQueries(result); + } + + private void AssertNestedEntitySubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(3)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[1], Is.TypeOf().And.Property("EmployeeId").EqualTo(5)); + Assert.That(array[2], Is.Null); + } + + #endregion + + #region NestedScalarSubQueries + + [Test] + public async Task HqlNestedScalarSubQueriesAsync() + { + var result = await (session.CreateQuery(@" + select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3 as ord3 + from Order o + left join ( + select o1.OrderId, o1.ShippedTo, o1.Customer.CustomerId, o1.ShippingAddress.City, o2 as ord2 + from Order o1 + left join ( + select OrderId, ShippedTo, Customer.CustomerId, ShippingAddress.City + from Order + ) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.OrderId-1 + where o.OrderId = 10248").ListAsync()); + AssertNestedScalarSubQueries(result); + + result = await (session.CreateQuery(@" + select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3 as ord3 + from Order o + left join ( + select + o1.OrderId, o1.ShippedTo, o1.Customer.CustomerId, o1.ShippingAddress.City, + o2.OrderId as oid, o2.ShippedTo as sto, o2.CustomerId as cuid, o2.City as cty + from Order o1 + left join ( + select OrderId, ShippedTo, Customer.CustomerId, ShippingAddress.City + from Order + ) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.OrderId-1 + where o.OrderId = 10248").ListAsync()); + AssertNestedScalarSubQueries(result); + + result = await (session.CreateQuery(@" + select + o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, + o3.OrderId, o3.ShippedTo, o3.CustomerId, o3.City, o3.oid, o3.sto, o3.cuid, o3.cty + from Order o + left join ( + select + o1.OrderId, o1.ShippedTo, o1.Customer.CustomerId, o1.ShippingAddress.City, + o2.OrderId as oid, o2.ShippedTo as sto, o2.CustomerId as cuid, o2.City as cty + from Order o1 + left join ( + select OrderId, ShippedTo, Customer.CustomerId, ShippingAddress.City + from Order + ) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.OrderId-1 + where o.OrderId = 10248").ListAsync()); + AssertNestedScalarSubQueries(result); + } + + private void AssertNestedScalarSubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(12)); + Assert.That(array[0], Is.EqualTo(10248)); + Assert.That(array[1], Is.EqualTo("Vins et alcools Chevalier")); + Assert.That(array[2], Is.EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Reims")); + Assert.That(array[4], Is.EqualTo(10249)); + Assert.That(array[5], Is.EqualTo("Toms Spezialitäten")); + Assert.That(array[6], Is.EqualTo("TOMSP")); + Assert.That(array[7], Is.EqualTo("Münster")); + Assert.That(array[8], Is.EqualTo(10250)); + Assert.That(array[9], Is.EqualTo("Hanari Carnes")); + Assert.That(array[10], Is.EqualTo("HANAR")); + Assert.That(array[11], Is.EqualTo("Rio de Janeiro")); + } + + #endregion + + #region NestedMixedSubQueries + + [Test] + public async Task HqlNestedMixedSubQueriesAsync() + { + var result = await (session.CreateQuery(@" + select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3 + from Order o + left join ( + select o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, o2 + from Order o1 + left join ( + select o0 as ord1, o0.ShippedTo as sto1, o0.Customer as cu1, o0.Customer.Address.City as cty1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").ListAsync()); + AssertNestedMixedSubQueries(result); + + result = await (session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.sto2, o3.cu2, o3.cty2, o3.sub1 + from Order o + left join ( + select o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, o2 as sub1 + from Order o1 + left join ( + select o0 as ord1, o0.ShippedTo as sto1, o0.Customer as cu1, o0.Customer.Address.City as cty1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").ListAsync()); + AssertNestedMixedSubQueries(result); + + result = await (session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.sto2, o3.cu2, o3.cty2, + o3.sub1.ord1, o3.sub1.sto1, o3.sub1.cu1, o3.sub1.cty1 + from Order o + left join ( + select o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, o2 as sub1 + from Order o1 + left join ( + select o0 as ord1, o0.ShippedTo as sto1, o0.Customer as cu1, o0.Customer.Address.City as cty1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").ListAsync()); + AssertNestedMixedSubQueries(result); + + result = await (session.CreateQuery(@" + select + o4.ord3, o4.ord3.ShippedTo, o4.cu3, o4.ord3.ShippingAddress.City, + o4.sub2.ord2, o4.sub2.ord2.ShippedTo, o4.sub2.cu2, o4.sub2.cu2.Address.City, + o4.sub2.sub1.ord1, o4.sub2.sub1.ord1.ShippedTo, o4.sub2.sub1.cu1, o4.sub2.sub1.cu1.Address.City + from Order o5 + inner join ( + select + o as ord3, o.Customer as cu3, o3 as sub2 + from Order o + left join ( + select o1 as ord2, o1.Customer as cu2, o2 as sub1 + from Order o1 + left join ( + select o0 as ord1, o0.Customer as cu1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248 + ) o4 on o5.OrderId = o4.ord3.OrderId + ").ListAsync()); + AssertNestedMixedSubQueries(result); + + result = await (session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.sto2, o3.cu2, o3.cty2, + o3.ord1, o3.sto1, o3.cu1, o3.cty1 + from Order o + left join ( + select + o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, + o2.ord1, o2.ord1.ShippedTo as sto1, o2.cu1, o2.cu1.Address.City as cty1 + from Order o1 + left join ( + select o0 as ord1, o0.Customer as cu1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").ListAsync()); + AssertNestedMixedSubQueries(result); + + result = await (session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.sto2, o3.cu2, o3.cty2, + o3.ord1, o3.sto1, o3.cu1, o3.cty1 + from Order o + left join ( + select + o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, + o2 as ord1, o2.ShippedTo as sto1, o2.Customer as cu1, o2.Customer.Address.City as cty1 + from Order o1 + left join (from Order) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").ListAsync()); + AssertNestedMixedSubQueries(result); + + result = await (session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.ord2.ShippedTo, o3.ord2.Customer, o3.ord2.Customer.Address.City, + o3.ord1, o3.ord1.ShippedTo, o3.ord1.Customer, o3.ord1.Customer.Address.City + from Order o + left join ( + select o1 as ord2, o2 as ord1 + from Order o1 + left join (from Order) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").ListAsync()); + AssertNestedMixedSubQueries(result); + } + + private void AssertNestedMixedSubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(12)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[1], Is.EqualTo("Vins et alcools Chevalier")); + Assert.That(array[2], Is.TypeOf().And.Property("CustomerId").EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Reims")); + Assert.That(array[4], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[5], Is.EqualTo("Toms Spezialitäten")); + Assert.That(array[6], Is.TypeOf().And.Property("CustomerId").EqualTo("TOMSP")); + Assert.That(array[7], Is.EqualTo("Münster")); + Assert.That(array[8], Is.TypeOf().And.Property("OrderId").EqualTo(10250)); + Assert.That(array[9], Is.EqualTo("Hanari Carnes")); + Assert.That(array[10], Is.TypeOf().And.Property("CustomerId").EqualTo("HANAR")); + Assert.That(array[11], Is.EqualTo("Rio de Janeiro")); + } + + #endregion + + #region EntitySubQueryWithEntityFetch + + [Test] + public async Task HqlEntitySubQueryWithEntityFetchAsync() + { + session.Clear(); + var result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.Customer + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); + AssertEntitySubQueryWithEntityFetch(result, new[] {false, true}); + + session.Clear(); + result = await (session.CreateQuery(@" + select o, o2.ord + from Order o + inner join ( + select o1 as ord, o1.Customer.CustomerId + from Order o1 + inner join fetch o1.Customer + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.ord.OrderId").ListAsync()); + AssertEntitySubQueryWithEntityFetch(result, new[] {false, true}); + + session.Clear(); + result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.Customer").ListAsync()); + AssertEntitySubQueryWithEntityFetch(result, new[] {true, false}); + + session.Clear(); + result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.Customer + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.Customer").ListAsync()); + AssertEntitySubQueryWithEntityFetch(result, new[] {true, true}); + } + + private void AssertEntitySubQueryWithEntityFetch(IList result, bool[] fetches) + { + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + var order = (Order) array[0]; + Assert.That(NHibernateUtil.IsInitialized(order.Customer), fetches[0] ? Is.True : (IResolveConstraint) Is.False); + order = (Order) array[1]; + Assert.That(NHibernateUtil.IsInitialized(order.Customer), fetches[1] ? Is.True : (IResolveConstraint) Is.False); + } + + #endregion + + #region EntitySubQueryWithCollectionFetch + + [Test] + public async Task HqlEntitySubQueryWithCollectionFetchAsync() + { + session.Clear(); + var result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.OrderLines + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); + AssertEntitySubQueryWithCollectionFetch(result, new[] {false, true}); + + session.Clear(); + result = await (session.CreateQuery(@" + select o, o2.ord + from Order o + inner join ( + select o1 as ord, o1.OrderLines.size + from Order o1 + inner join fetch o1.OrderLines + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.ord.OrderId").ListAsync()); + AssertEntitySubQueryWithCollectionFetch(result, new[] {false, true}); + + session.Clear(); + result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.OrderLines").ListAsync()); + AssertEntitySubQueryWithCollectionFetch(result, new[] {true, false}); + + session.Clear(); + result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.OrderLines + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.OrderLines").ListAsync()); + AssertEntitySubQueryWithCollectionFetch(result, new[] {true, true}); + } + + private void AssertEntitySubQueryWithCollectionFetch(IList result, bool[] fetches) + { + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + var order = (Order)array[0]; + Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), fetches[0] ? Is.True : (IResolveConstraint) Is.False); + order = (Order) array[1]; + Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), fetches[1] ? Is.True : (IResolveConstraint) Is.False); + } + + #endregion + + #region EntitySubQueryWithCollectionOfValuesFetch + + [Test] + public async Task HqlEntitySubQueryWithCollectionOfValuesFetchAsync() + { + session.Clear(); + var result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.ProductIds + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); + AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {false, true}); + + session.Clear(); + result = await (session.CreateQuery(@" + select o, o2.ord + from Order o + inner join ( + select o1 as ord, o1.OrderLines.size + from Order o1 + inner join fetch o1.ProductIds + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.ord.OrderId").ListAsync()); + AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {false, true}); + + session.Clear(); + result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.ProductIds").ListAsync()); + AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {true, false}); + + session.Clear(); + result = await (session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.ProductIds + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.ProductIds").ListAsync()); + AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {true, true}); + } + + private void AssertEntitySubQueryWithCollectionOfValuesFetch(IList result, bool[] fetches) + { + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + var order = (Order)array[0]; + Assert.That(NHibernateUtil.IsInitialized(order.ProductIds), fetches[0] ? Is.True : (IResolveConstraint) Is.False); + order = (Order) array[1]; + Assert.That(NHibernateUtil.IsInitialized(order.ProductIds), fetches[1] ? Is.True : (IResolveConstraint) Is.False); + } + + #endregion + } +} diff --git a/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs new file mode 100644 index 00000000000..ae591b8d3d6 --- /dev/null +++ b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs @@ -0,0 +1,858 @@ +using System.Collections; +using NHibernate.DomainModel.Northwind.Entities; +using NUnit.Framework; +using NUnit.Framework.Constraints; + +namespace NHibernate.Test.Linq.ByMethod +{ + [TestFixture] + public class JoinSubqueryTests : LinqTestCase + { + #region HqlEntitySubQuery + + [Test] + public void HqlEntitySubQuery() + { + var result = session.CreateQuery("from Order o inner join (from Order where OrderId = 10248) o2 on (o.OrderId - 1) = o2.OrderId").List(); + AssertEntitySubQuery(result); + + result = session.CreateQuery("from Order o inner join (from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").List(); + AssertEntitySubQuery(result); + + result = session.CreateQuery("from Order o inner join (select o2 from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").List(); + AssertEntitySubQuery(result); + + result = session.CreateQuery("select o, o3 from Order o inner join (select o2 from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").List(); + AssertEntitySubQuery(result); + + result = session.CreateQuery("select o, o3 from Order o inner join (from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").List(); + AssertEntitySubQuery(result); + } + + private void AssertEntitySubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + } + + #endregion + + #region ScalarSubQuery + + [Test] + public void HqlScalarSubQuery() + { + var result = session.CreateQuery(@" + select o.Customer.CustomerId, o.ShippedTo, o2 + from Order o inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").List(); + AssertScalarSubQuery(result); + + result = session.CreateQuery(@" + select o.Customer.CustomerId, o.ShippedTo, o2.OrderId, o2.CustomerId, o2.ShippedTo + from Order o inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").List(); + AssertScalarSubQuery(result); + } + + private void AssertScalarSubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(5)); + Assert.That(array[0], Is.EqualTo("TOMSP")); + Assert.That(array[1], Is.EqualTo("Toms Spezialitäten")); + Assert.That(array[2], Is.EqualTo(10248)); + Assert.That(array[3], Is.EqualTo("VINET")); + Assert.That(array[4], Is.EqualTo("Vins et alcools Chevalier")); + } + + #endregion + + #region IdSubQuery + + [Test] + public void HqlIdSubQuery() + { + var result = session.CreateQuery(@" + select o + from Order o inner join ( + select id from Order where OrderId = 10248 + ) o2 on o.id = o2.id").List(); + AssertIdSubQuery(result); + + result = session.CreateQuery(@" + select o + from CompositeOrder o inner join ( + select id from CompositeOrder where OrderId = 10248 + ) o2 on o.id = o2.id").List(); + AssertIdSubQuery(result); + } + + private void AssertIdSubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + Assert.That(item, Has.Property("OrderId").EqualTo(10248)); + } + + #endregion + + #region SubclassSubQuery + + [Test] + public void HqlSubclassSubQuery() + { + var result = session.CreateQuery(@" + from Animal a inner join ( + from Animal where Father is null + ) a2 on a.Father = a2").List(); + AssertSubclassSubQuery(result); + } + + private void AssertSubclassSubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf()); + Assert.That(array[1], Is.TypeOf()); + } + + #endregion + + #region MixedSubQuery + + [Test] + public void HqlMixedSubQuery() + { + var result = session.CreateQuery(@" + select o, o2, o.Customer.CustomerId, o.ShippedTo + from Order o inner join ( + select OrderId, Customer, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").List(); + AssertMixedSubQuery(result); + + result = session.CreateQuery(@" + select o, o2.OrderId, o2.cu, o2.ShippedTo, o.Customer.CustomerId, o.ShippedTo + from Order o inner join ( + select OrderId, Customer as cu, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").List(); + AssertMixedSubQuery(result); + } + + private void AssertMixedSubQuery(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(6)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.EqualTo(10248)); + Assert.That(array[2], Is.TypeOf().And.Property("CustomerId").EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Vins et alcools Chevalier")); + Assert.That(array[4], Is.EqualTo("TOMSP")); + Assert.That(array[5], Is.EqualTo("Toms Spezialitäten")); + } + + #endregion + + #region MixedSubQuery + + [Test] + public void HqlSubQueryComponentPropertySelection() + { + var result = session.CreateQuery(@" + select o2.cu.Address.Street, o2.emp.Address.Street + from Order o inner join ( + select OrderId, Customer as cu, Employee as emp from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").List(); + AssertSubQueryComponentPropertySelection(result); + } + + private void AssertSubQueryComponentPropertySelection(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.EqualTo("59 rue de l'Abbaye")); + Assert.That(array[1], Is.EqualTo("14 Garrett Hill")); + } + + #endregion + + #region IdSubQueryWithPagingAndOrderBy + + [Test] + public void HqlIdSubQueryWithPagingAndOrderBy() + { + var result = session.CreateQuery(@" + select o + from Order o inner join ( + select id from Order order by OrderId skip 2 take 2 + ) o2 on o.id = o2.id").List(); + AssertIdSubQueryWithPagingAndOrderBy(result); + + result = session.CreateQuery(@" + select o + from CompositeOrder o inner join ( + select id from CompositeOrder order by OrderId skip 2 take 2 + ) o2 on o.id = o2.id").List(); + AssertIdSubQueryWithPagingAndOrderBy(result); + } + + private void AssertIdSubQueryWithPagingAndOrderBy(IList result) + { + Assert.That(result, Has.Count.EqualTo(2)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + Assert.That(item, Has.Property("OrderId").EqualTo(10250)); + item = result[1]; + Assert.That(item, Is.TypeOf()); + Assert.That(item, Has.Property("OrderId").EqualTo(10251)); + } + + #endregion + + #region GroupBySubQueryWithAliases + + [Test] + public void HqlGroupBySubQueryWithAliases() + { + var result = session.CreateQuery(@" + select o, o2 + from Order o inner join ( + select Customer.CustomerId, count(*) as total, max(OrderId) as orderId from Order group by Customer.CustomerId + ) o2 on o.id = o2.orderId + where o2.total > 30").List(); + AssertGroupBySubQueryWithAliases(result); + + result = session.CreateQuery(@" + select o, o2.CustomerId, o2.total, o2.orderId + from Order o inner join ( + select Customer.CustomerId, count(*) as total, max(OrderId) as orderId from Order group by Customer.CustomerId + ) o2 on o.id = o2.orderId + where o2.total > 30").List(); + AssertGroupBySubQueryWithAliases(result); + } + + private void AssertGroupBySubQueryWithAliases(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(4)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(11064)); + Assert.That(array[1], Is.EqualTo("SAVEA")); + Assert.That(array[2], Is.EqualTo(31)); + Assert.That(array[3], Is.EqualTo(11064)); + } + + #endregion + + #region SubQueryWithEntityAlias + + [Test] + public void HqlSubQueryWithEntityAlias() + { + var result = session.CreateQuery(@" + select o, o3.order.OrderId, o3.customer.CustomerId, o3.ShippedTo + from Order o inner join ( + select o2 as order, o2.Customer as customer, o2.ShippedTo from Order o2 where o2.OrderId = 10248 + ) o3 on (o.OrderId - 1) = o3.order.OrderId").List(); + AssertSubQueryWithEntityAlias(result); + } + + private void AssertSubQueryWithEntityAlias(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(4)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.EqualTo(10248)); + Assert.That(array[2], Is.EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Vins et alcools Chevalier")); + } + + #endregion + + #region MultipleEntitySubQueries + + [Test] + public void HqlMultipleEntitySubQueries() + { + var result = session.CreateQuery(@" + from Order o + inner join ( + from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + left join ( + from Order where OrderId = 10250 + ) o3 on (o3.OrderId - 2) = o2.OrderId").List(); + AssertMultipleEntitySubQueries(result); + + result = session.CreateQuery(@" + select o, o2, o3 + from Order o + inner join ( + from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + left join ( + from Order where OrderId = 10250 + ) o3 on (o3.OrderId - 2) = o2.OrderId").List(); + AssertMultipleEntitySubQueries(result); + } + + private void AssertMultipleEntitySubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(3)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[2], Is.TypeOf().And.Property("OrderId").EqualTo(10250)); + } + + #endregion + + #region MultipleScalarSubQueries + + [Test] + public void HqlMultipleScalarSubQueries() + { + var result = session.CreateQuery(@" + from Order o + inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + left join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10250 + ) o3 on (o3.OrderId - 2) = o2.OrderId").List(); + AssertMultipleScalarSubQueries(result); + + result = session.CreateQuery(@" + select o, o2, o3 + from Order o + inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + left join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10250 + ) o3 on (o3.OrderId - 2) = o2.OrderId").List(); + AssertMultipleScalarSubQueries(result); + } + + private void AssertMultipleScalarSubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(7)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.EqualTo(10248)); + Assert.That(array[2], Is.EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Vins et alcools Chevalier")); + Assert.That(array[4], Is.EqualTo(10250)); + Assert.That(array[5], Is.EqualTo("HANAR")); + Assert.That(array[6], Is.EqualTo("Hanari Carnes")); + } + + #endregion + + #region NestedEntitySubQueries + + [Test] + public void HqlNestedEntitySubQueries() + { + var result = session.CreateQuery(@" + from Order o + left join ( + select e as emp, e2 as sup + from Employee e + left join (from Employee) e2 on e.Superior = e2 + ) o3 on o.Employee = o3.emp + order by o.id + take 1").List(); + AssertNestedEntitySubQueries(result); + + result = session.CreateQuery(@" + select o, o3 + from Order o + left join ( + select e as emp, e2 as sup + from Employee e + left join (from Employee) e2 on e.Superior = e2 + ) o3 on o.Employee = o3.emp + order by o.id + take 1").List(); + AssertNestedEntitySubQueries(result); + + result = session.CreateQuery(@" + select o, o3.emp, o3.sup + from Order o + left join ( + select e as emp, e2 as sup + from Employee e + left join (from Employee) e2 on e.Superior = e2 + ) o3 on o.Employee = o3.emp + order by o.id + take 1").List(); + AssertNestedEntitySubQueries(result); + } + + private void AssertNestedEntitySubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(3)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[1], Is.TypeOf().And.Property("EmployeeId").EqualTo(5)); + Assert.That(array[2], Is.Null); + } + + #endregion + + #region NestedScalarSubQueries + + [Test] + public void HqlNestedScalarSubQueries() + { + var result = session.CreateQuery(@" + select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3 as ord3 + from Order o + left join ( + select o1.OrderId, o1.ShippedTo, o1.Customer.CustomerId, o1.ShippingAddress.City, o2 as ord2 + from Order o1 + left join ( + select OrderId, ShippedTo, Customer.CustomerId, ShippingAddress.City + from Order + ) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.OrderId-1 + where o.OrderId = 10248").List(); + AssertNestedScalarSubQueries(result); + + result = session.CreateQuery(@" + select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3 as ord3 + from Order o + left join ( + select + o1.OrderId, o1.ShippedTo, o1.Customer.CustomerId, o1.ShippingAddress.City, + o2.OrderId as oid, o2.ShippedTo as sto, o2.CustomerId as cuid, o2.City as cty + from Order o1 + left join ( + select OrderId, ShippedTo, Customer.CustomerId, ShippingAddress.City + from Order + ) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.OrderId-1 + where o.OrderId = 10248").List(); + AssertNestedScalarSubQueries(result); + + result = session.CreateQuery(@" + select + o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, + o3.OrderId, o3.ShippedTo, o3.CustomerId, o3.City, o3.oid, o3.sto, o3.cuid, o3.cty + from Order o + left join ( + select + o1.OrderId, o1.ShippedTo, o1.Customer.CustomerId, o1.ShippingAddress.City, + o2.OrderId as oid, o2.ShippedTo as sto, o2.CustomerId as cuid, o2.City as cty + from Order o1 + left join ( + select OrderId, ShippedTo, Customer.CustomerId, ShippingAddress.City + from Order + ) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.OrderId-1 + where o.OrderId = 10248").List(); + AssertNestedScalarSubQueries(result); + } + + private void AssertNestedScalarSubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(12)); + Assert.That(array[0], Is.EqualTo(10248)); + Assert.That(array[1], Is.EqualTo("Vins et alcools Chevalier")); + Assert.That(array[2], Is.EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Reims")); + Assert.That(array[4], Is.EqualTo(10249)); + Assert.That(array[5], Is.EqualTo("Toms Spezialitäten")); + Assert.That(array[6], Is.EqualTo("TOMSP")); + Assert.That(array[7], Is.EqualTo("Münster")); + Assert.That(array[8], Is.EqualTo(10250)); + Assert.That(array[9], Is.EqualTo("Hanari Carnes")); + Assert.That(array[10], Is.EqualTo("HANAR")); + Assert.That(array[11], Is.EqualTo("Rio de Janeiro")); + } + + #endregion + + #region NestedMixedSubQueries + + [Test] + public void HqlNestedMixedSubQueries() + { + var result = session.CreateQuery(@" + select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3 + from Order o + left join ( + select o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, o2 + from Order o1 + left join ( + select o0 as ord1, o0.ShippedTo as sto1, o0.Customer as cu1, o0.Customer.Address.City as cty1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").List(); + AssertNestedMixedSubQueries(result); + + result = session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.sto2, o3.cu2, o3.cty2, o3.sub1 + from Order o + left join ( + select o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, o2 as sub1 + from Order o1 + left join ( + select o0 as ord1, o0.ShippedTo as sto1, o0.Customer as cu1, o0.Customer.Address.City as cty1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").List(); + AssertNestedMixedSubQueries(result); + + result = session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.sto2, o3.cu2, o3.cty2, + o3.sub1.ord1, o3.sub1.sto1, o3.sub1.cu1, o3.sub1.cty1 + from Order o + left join ( + select o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, o2 as sub1 + from Order o1 + left join ( + select o0 as ord1, o0.ShippedTo as sto1, o0.Customer as cu1, o0.Customer.Address.City as cty1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").List(); + AssertNestedMixedSubQueries(result); + + result = session.CreateQuery(@" + select + o4.ord3, o4.ord3.ShippedTo, o4.cu3, o4.ord3.ShippingAddress.City, + o4.sub2.ord2, o4.sub2.ord2.ShippedTo, o4.sub2.cu2, o4.sub2.cu2.Address.City, + o4.sub2.sub1.ord1, o4.sub2.sub1.ord1.ShippedTo, o4.sub2.sub1.cu1, o4.sub2.sub1.cu1.Address.City + from Order o5 + inner join ( + select + o as ord3, o.Customer as cu3, o3 as sub2 + from Order o + left join ( + select o1 as ord2, o1.Customer as cu2, o2 as sub1 + from Order o1 + left join ( + select o0 as ord1, o0.Customer as cu1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248 + ) o4 on o5.OrderId = o4.ord3.OrderId + ").List(); + AssertNestedMixedSubQueries(result); + + result = session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.sto2, o3.cu2, o3.cty2, + o3.ord1, o3.sto1, o3.cu1, o3.cty1 + from Order o + left join ( + select + o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, + o2.ord1, o2.ord1.ShippedTo as sto1, o2.cu1, o2.cu1.Address.City as cty1 + from Order o1 + left join ( + select o0 as ord1, o0.Customer as cu1 + from Order o0 + ) o2 on o1.OrderId = o2.ord1.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").List(); + AssertNestedMixedSubQueries(result); + + result = session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.sto2, o3.cu2, o3.cty2, + o3.ord1, o3.sto1, o3.cu1, o3.cty1 + from Order o + left join ( + select + o1 as ord2, o1.ShippedTo as sto2, o1.Customer as cu2, o1.Customer.Address.City as cty2, + o2 as ord1, o2.ShippedTo as sto1, o2.Customer as cu1, o2.Customer.Address.City as cty1 + from Order o1 + left join (from Order) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").List(); + AssertNestedMixedSubQueries(result); + + result = session.CreateQuery(@" + select + o, o.ShippedTo, o.Customer, o.ShippingAddress.City, + o3.ord2, o3.ord2.ShippedTo, o3.ord2.Customer, o3.ord2.Customer.Address.City, + o3.ord1, o3.ord1.ShippedTo, o3.ord1.Customer, o3.ord1.Customer.Address.City + from Order o + left join ( + select o1 as ord2, o2 as ord1 + from Order o1 + left join (from Order) o2 on o1.OrderId = o2.OrderId-1 + ) o3 on o.OrderId = o3.ord2.OrderId-1 + where o.OrderId = 10248").List(); + AssertNestedMixedSubQueries(result); + } + + private void AssertNestedMixedSubQueries(IList result) + { + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(12)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[1], Is.EqualTo("Vins et alcools Chevalier")); + Assert.That(array[2], Is.TypeOf().And.Property("CustomerId").EqualTo("VINET")); + Assert.That(array[3], Is.EqualTo("Reims")); + Assert.That(array[4], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[5], Is.EqualTo("Toms Spezialitäten")); + Assert.That(array[6], Is.TypeOf().And.Property("CustomerId").EqualTo("TOMSP")); + Assert.That(array[7], Is.EqualTo("Münster")); + Assert.That(array[8], Is.TypeOf().And.Property("OrderId").EqualTo(10250)); + Assert.That(array[9], Is.EqualTo("Hanari Carnes")); + Assert.That(array[10], Is.TypeOf().And.Property("CustomerId").EqualTo("HANAR")); + Assert.That(array[11], Is.EqualTo("Rio de Janeiro")); + } + + #endregion + + #region EntitySubQueryWithEntityFetch + + [Test] + public void HqlEntitySubQueryWithEntityFetch() + { + session.Clear(); + var result = session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.Customer + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").List(); + AssertEntitySubQueryWithEntityFetch(result, new[] {false, true}); + + session.Clear(); + result = session.CreateQuery(@" + select o, o2.ord + from Order o + inner join ( + select o1 as ord, o1.Customer.CustomerId + from Order o1 + inner join fetch o1.Customer + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.ord.OrderId").List(); + AssertEntitySubQueryWithEntityFetch(result, new[] {false, true}); + + session.Clear(); + result = session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.Customer").List(); + AssertEntitySubQueryWithEntityFetch(result, new[] {true, false}); + + session.Clear(); + result = session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.Customer + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.Customer").List(); + AssertEntitySubQueryWithEntityFetch(result, new[] {true, true}); + } + + private void AssertEntitySubQueryWithEntityFetch(IList result, bool[] fetches) + { + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + var order = (Order) array[0]; + Assert.That(NHibernateUtil.IsInitialized(order.Customer), fetches[0] ? Is.True : (IResolveConstraint) Is.False); + order = (Order) array[1]; + Assert.That(NHibernateUtil.IsInitialized(order.Customer), fetches[1] ? Is.True : (IResolveConstraint) Is.False); + } + + #endregion + + #region EntitySubQueryWithCollectionFetch + + [Test] + public void HqlEntitySubQueryWithCollectionFetch() + { + session.Clear(); + var result = session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.OrderLines + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").List(); + AssertEntitySubQueryWithCollectionFetch(result, new[] {false, true}); + + session.Clear(); + result = session.CreateQuery(@" + select o, o2.ord + from Order o + inner join ( + select o1 as ord, o1.OrderLines.size + from Order o1 + inner join fetch o1.OrderLines + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.ord.OrderId").List(); + AssertEntitySubQueryWithCollectionFetch(result, new[] {false, true}); + + session.Clear(); + result = session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.OrderLines").List(); + AssertEntitySubQueryWithCollectionFetch(result, new[] {true, false}); + + session.Clear(); + result = session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.OrderLines + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.OrderLines").List(); + AssertEntitySubQueryWithCollectionFetch(result, new[] {true, true}); + } + + private void AssertEntitySubQueryWithCollectionFetch(IList result, bool[] fetches) + { + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + var order = (Order)array[0]; + Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), fetches[0] ? Is.True : (IResolveConstraint) Is.False); + order = (Order) array[1]; + Assert.That(NHibernateUtil.IsInitialized(order.OrderLines), fetches[1] ? Is.True : (IResolveConstraint) Is.False); + } + + #endregion + + #region EntitySubQueryWithCollectionOfValuesFetch + + [Test] + public void HqlEntitySubQueryWithCollectionOfValuesFetch() + { + session.Clear(); + var result = session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.ProductIds + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId").List(); + AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {false, true}); + + session.Clear(); + result = session.CreateQuery(@" + select o, o2.ord + from Order o + inner join ( + select o1 as ord, o1.OrderLines.size + from Order o1 + inner join fetch o1.ProductIds + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.ord.OrderId").List(); + AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {false, true}); + + session.Clear(); + result = session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.ProductIds").List(); + AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {true, false}); + + session.Clear(); + result = session.CreateQuery(@" + from Order o + inner join ( + from Order o1 + inner join fetch o1.ProductIds + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId + inner join fetch o.ProductIds").List(); + AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {true, true}); + } + + private void AssertEntitySubQueryWithCollectionOfValuesFetch(IList result, bool[] fetches) + { + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(2)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + var order = (Order)array[0]; + Assert.That(NHibernateUtil.IsInitialized(order.ProductIds), fetches[0] ? Is.True : (IResolveConstraint) Is.False); + order = (Order) array[1]; + Assert.That(NHibernateUtil.IsInitialized(order.ProductIds), fetches[1] ? Is.True : (IResolveConstraint) Is.False); + } + + #endregion + } +} diff --git a/src/NHibernate.Test/Linq/LinqTestCase.cs b/src/NHibernate.Test/Linq/LinqTestCase.cs index ecd9c7690e3..529b263fdcc 100755 --- a/src/NHibernate.Test/Linq/LinqTestCase.cs +++ b/src/NHibernate.Test/Linq/LinqTestCase.cs @@ -36,7 +36,8 @@ protected override string[] Mappings "Northwind.Mappings.Animal.hbm.xml", "Northwind.Mappings.Patient.hbm.xml", "Northwind.Mappings.DynamicUser.hbm.xml", - "Northwind.Mappings.NumericEntity.hbm.xml" + "Northwind.Mappings.NumericEntity.hbm.xml", + "Northwind.Mappings.CompositeOrder.hbm.xml" }; } } diff --git a/src/NHibernate/Criterion/EntityProjection.cs b/src/NHibernate/Criterion/EntityProjection.cs index 37750e90597..d6f7ee13c1e 100644 --- a/src/NHibernate/Criterion/EntityProjection.cs +++ b/src/NHibernate/Criterion/EntityProjection.cs @@ -128,20 +128,21 @@ SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQue { SetFields(criteriaQuery); - string identifierSelectFragment = Persister.IdentifierSelectFragment(TableAlias, ColumnAliasSuffix); + var identifierSelectFragment = Persister.GetIdentifierSelectFragment(TableAlias, ColumnAliasSuffix) + .ToSqlStringFragment(false); return new SqlString( Lazy ? identifierSelectFragment : string.Concat( identifierSelectFragment, - GetPropertySelectFragment())); + GetPropertySelectFragment().ToSqlStringFragment())); } - private string GetPropertySelectFragment() + private SelectFragment GetPropertySelectFragment() { return FetchLazyProperties - ? Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyProperties) - : Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyPropertyGroups); + ? Persister.GetPropertiesSelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyProperties) + : Persister.GetPropertiesSelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyPropertyGroups); } SqlString IProjection.ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) diff --git a/src/NHibernate/Engine/JoinSequence.cs b/src/NHibernate/Engine/JoinSequence.cs index 3b3bae924cc..d8ec7143ab9 100644 --- a/src/NHibernate/Engine/JoinSequence.cs +++ b/src/NHibernate/Engine/JoinSequence.cs @@ -154,7 +154,7 @@ public JoinFragment ToJoinFragment( return ToJoinFragment(enabledFilters, includeExtraJoins, true, withClauseFragment); } - internal JoinFragment ToJoinFragment( + internal virtual JoinFragment ToJoinFragment( IDictionary enabledFilters, bool includeAllSubclassJoins, bool renderSubclassJoins, diff --git a/src/NHibernate/Hql/Ast/ANTLR/Hql.g b/src/NHibernate/Hql/Ast/ANTLR/Hql.g index f074f6e44d9..8dcd5dbc533 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Hql.g +++ b/src/NHibernate/Hql/Ast/ANTLR/Hql.g @@ -255,6 +255,7 @@ fromClause fromJoin : ( ( ( LEFT | RIGHT ) (OUTER)? ) | FULL | INNER )? JOIN^ (FETCH)? path (asAlias)? (propertyFetch)? (withClause)? + | ( ( ( LEFT | RIGHT ) (OUTER)? ) | FULL | INNER )? JOIN^ OPEN! selectStatement CLOSE! (asAlias)? (withClause)? | ( ( ( LEFT | RIGHT ) (OUTER)? ) | FULL | INNER )? JOIN^ (FETCH)? ELEMENTS! OPEN! path CLOSE! (asAlias)? (propertyFetch)? (withClause)? | CROSS JOIN^ { WeakKeywords(); } path (asAlias)? (propertyFetch)? ; diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index 31f8254b230..85e90112886 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -64,6 +64,10 @@ public partial class HqlSqlWalker private readonly List assignmentSpecifications = new List(); private int numberOfParametersInSetClause; private Stack clauseStack=new Stack(); + private readonly Dictionary _suffixes = new Dictionary(); + private readonly Dictionary _collectionSuffixes = new Dictionary(); + + internal int CurrentScalarIndex; public HqlSqlWalker( QueryTranslatorImpl qti, @@ -476,6 +480,63 @@ IASTNode Resolve(IASTNode node) return node; } + internal string GetSuffix(FromElement fromElement) + { + if (_suffixes.TryGetValue(fromElement, out var suffix)) + { + return suffix; + } + + suffix = _suffixes.Count == 0 ? "" : _suffixes.Count.ToString() + '_'; + _suffixes.Add(fromElement, suffix); + + return suffix; + } + + internal string GetEntitySuffix(FromElement fromElement) + { + if (!fromElement.IsEntity) + { + return null; + } + + // In case the subquery returns an entity, extract the suffix from the returned entity + if (fromElement is JoinSubqueryFromElement joinSubquery) + { + return GetSuffix(joinSubquery.QueryNode.GetSelectClause().NonScalarExpressions[0].FromElement); + } + else + { + return GetSuffix(fromElement); + } + } + + internal string GetCollectionSuffix(FromElement fromElement) + { + if (!fromElement.CollectionJoin && fromElement.QueryableCollection == null) + { + return null; + } + + if (_collectionSuffixes.TryGetValue(fromElement, out var suffix)) + { + return suffix; + } + + if (_collectionSuffixes.Count == 0) + { + suffix = SelectClause.VERSION2_SQL ? "__" : "0__"; + } + else + { + suffix = _collectionSuffixes.Count + "__"; + } + + _collectionSuffixes.Add(fromElement, suffix); + + return suffix; + } + void ProcessQuery(IASTNode select, IASTNode query) { if ( log.IsDebugEnabled() ) { @@ -682,6 +743,32 @@ void PrepareFilterParameter() } } + void CreateJoinSubquery( + IASTNode query, + IASTNode alias, + int joinType, + IASTNode with) + { + if (log.IsDebugEnabled()) + { + log.Debug($"Creating subquery-join FromElement [{alias?.Text}]"); + } + + var join = new JoinSubqueryFromElement( + CurrentFromClause, + (QueryNode) query, + JoinProcessor.ToHibernateJoinType(joinType), + alias?.Text + ); + join.AddChild(query); + if (with != null) + { + HandleWithFragment(join, with); + } + + CurrentFromClause.AppendFromElement(join); + } + void CreateFromJoinElement( IASTNode path, IASTNode alias, @@ -691,7 +778,7 @@ void CreateFromJoinElement( IASTNode with) { bool fetch = fetchNode != null; - if ( fetch && IsSubQuery ) + if (fetch && IsScalarSubQuery) { throw new QueryException( "fetch not allowed in subquery from-elements" ); } @@ -931,6 +1018,7 @@ void SetImpliedJoinType(int joinType) void PushFromClause(IASTNode fromNode) { FromClause newFromClause = (FromClause)fromNode; + newFromClause.IsJoinSubQuery = _currentClauseType == JOIN; newFromClause.SetParentFromClause(_currentFromClause); _currentFromClause = newFromClause; } @@ -1106,6 +1194,8 @@ public bool IsSubQuery get { return _level > 1; } } + public bool IsScalarSubQuery => IsSubQuery && !_currentFromClause.IsJoinSubQuery; + public bool IsSelectStatement { get { return _statementType == SELECT; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g index fba1010337c..47b42f286ea 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g @@ -14,6 +14,7 @@ tokens IMPLIED_FROM; // An implied FROM element. JOIN_FRAGMENT; // A JOIN fragment. ENTITY_JOIN; // An "ad-hoc" join to an entity + JOIN_SUBQUERY; SELECT_CLAUSE; LEFT_OUTER; RIGHT_OUTER; @@ -190,8 +191,9 @@ groupClause finally {HandleClauseEnd( GROUP );} havingClause - : ^(HAVING logicalExpr) + : ^(HAVING { HandleClauseStart( HAVING ); } logicalExpr) ; + finally { HandleClauseEnd( HAVING ); } selectClause! : ^(SELECT { HandleClauseStart( SELECT ); BeforeSelectClause(); } (d=DISTINCT)? x=selectExprList ) @@ -277,7 +279,10 @@ fromElement! -> ^({CreateFromFilterElement($fe,$a3)}) ; -joinElement! +joinElement! + @init{ + HandleClauseStart( JOIN ); + } // A from element with a join. This time, the 'path' should be treated as an AST // and resolved (like any path in a WHERE clause). Make sure all implied joins // generated by the property ref use the join type, if it was specified. @@ -286,7 +291,13 @@ joinElement! CreateFromJoinElement($pRef.tree,$a,$j.j,$f, $pf.tree, $with); SetImpliedJoinType(INNER); // Reset the implied join type. } + | ^(JOIN (j=joinType { SetImpliedJoinType($j.j); } )? q=query (a=ALIAS)? (^((with=WITH) .*))? ) + { + CreateJoinSubquery($q.tree,$a,$j.j, $with); + SetImpliedJoinType(INNER); // Reset the implied join type. + } ; + finally { HandleClauseEnd( JOIN ); } // Returns an node type integer that represents the join type // tokens. diff --git a/src/NHibernate/Hql/Ast/ANTLR/SessionFactoryHelperExtensions.cs b/src/NHibernate/Hql/Ast/ANTLR/SessionFactoryHelperExtensions.cs index 33d6103ec86..2159b05ebce 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/SessionFactoryHelperExtensions.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/SessionFactoryHelperExtensions.cs @@ -354,6 +354,8 @@ public JoinSequence CreateJoinSequence(bool implicitJoin, IAssociationType assoc return joinSequence; } + // Since v5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public String[][] GenerateColumnNames(IType[] sqlResultTypes) { return NameGenerator.GenerateColumnNames(sqlResultTypes, _sfi); diff --git a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.cs b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.cs index 0f1c7a64603..5ce57f20308 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.cs @@ -29,7 +29,7 @@ public partial class SqlGenerator : IErrorReporter * This is because sql function templates need arguments as separate string chunks * that will be assembled into the target dialect-specific function call. */ - private readonly List outputStack = new List(); + private readonly Stack outputStack = new Stack(); /// /// Handles parser errors. @@ -200,17 +200,17 @@ protected virtual void FromFragmentSeparator(IASTNode a) right = (FromElement)right.NextSibling; } - if (right == null) + if (right == null || !HasText(right)) { return; } - /////////////////////////////////////////////////////////////////////// - if (!HasText(right)) - { - return; - } - if (right.Type == ENTITY_JOIN) + AppendFromFragmentSeparator(right, left); + } + + private void AppendFromFragmentSeparator(FromElement right, FromElement left) + { + if (right.Type == ENTITY_JOIN || right.Type == JOIN_SUBQUERY) { Out(" "); } @@ -287,7 +287,7 @@ private void BeginFunctionTemplate(IASTNode m, IASTNode i) else { // this function has a template -> redirect output and catch the arguments - outputStack.Insert(0, writer); + outputStack.Push(writer); writer = new FunctionArguments(); } } @@ -304,8 +304,7 @@ private void EndFunctionTemplate(IASTNode m) { // this function has a template -> restore output, apply the template and write the result out var functionArguments = (FunctionArguments)writer; // TODO: Downcast to avoid using an interface? Yuck. - writer = outputStack[0]; - outputStack.RemoveAt(0); + writer = outputStack.Pop(); Out(template.Render(functionArguments.Args, sessionFactory)); } } @@ -321,9 +320,23 @@ private void CommaBetweenParameters(String comma) writer.CommaBetweenParameters(comma); } + private void StartJoinSubquery() + { + outputStack.Push(writer); + writer = new QueryWriter(); + } + + private void EndJoinSubquery(IASTNode node) + { + var joinSubquery = (JoinSubqueryFromElement) node; + var sqlString = ((QueryWriter) writer).ToSqlString(); + writer = outputStack.Pop(); + Out(joinSubquery.RenderText(sqlString, sessionFactory)); + } + private void StartQuery() { - outputStack.Insert(0, writer); + outputStack.Push(writer); writer = new QueryWriter(); } @@ -331,8 +344,7 @@ private void EndQuery() { SqlString sqlString = GetSqlStringWithLimitsIfNeeded((QueryWriter)writer); - writer = outputStack[0]; - outputStack.RemoveAt(0); + writer = outputStack.Pop(); Out(sqlString); } @@ -401,7 +413,7 @@ private void BeginBitwiseOp(string op) if (function == null) return; - outputStack.Insert(0, writer); + outputStack.Push(writer); writer = new BitwiseOpWriter(); } @@ -412,8 +424,7 @@ private void EndBitwiseOp(string op) return; var functionArguments = (BitwiseOpWriter)writer; - writer = outputStack[0]; - outputStack.RemoveAt(0); + writer = outputStack.Pop(); Out(function.Render(functionArguments.Args, sessionFactory)); } diff --git a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g index 21cb3c7521e..8f1a351d357 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g +++ b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g @@ -189,6 +189,7 @@ fromTable : ^( a=FROM_FRAGMENT { Out(a); } (tableJoin [ a ])* ) | ^( a=JOIN_FRAGMENT { Out(a); } (tableJoin [ a ])* ) | ^( a=ENTITY_JOIN { Out(a); } (tableJoin [ a ])* ) + | ^( a=JOIN_SUBQUERY { StartJoinSubquery(); } selectStatement { EndJoinSubquery(a); } (tableJoin [ a ])* ) ; tableJoin [ IASTNode parent ] diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/AbstractSelectExpression.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/AbstractSelectExpression.cs index ecec2835fd6..6d0a6c5e8f2 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/AbstractSelectExpression.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/AbstractSelectExpression.cs @@ -1,10 +1,11 @@ using System; using Antlr.Runtime; +using NHibernate.Type; namespace NHibernate.Hql.Ast.ANTLR.Tree { [CLSCompliant(false)] - public abstract class AbstractSelectExpression : HqlSqlWalkerNode, ISelectExpression + public abstract class AbstractSelectExpression : HqlSqlWalkerNode, ISelectExpressionExtension { private string _alias; private int _scalarColumnIndex = -1; @@ -19,12 +20,21 @@ public string Alias set { _alias = value; } } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public void SetScalarColumn(int i) { _scalarColumnIndex = i; SetScalarColumnText(i); } + /// + public string[] SetScalarColumn(int i, Func aliasCreator) + { + _scalarColumnIndex = i; + return SetScalarColumnText(i, aliasCreator); + } + public int ScalarColumnIndex { get { return _scalarColumnIndex; } @@ -52,10 +62,27 @@ public virtual bool IsScalar { // Default implementation: // If this node has a data type, and that data type is not an association, then this is scalar. - return DataType != null && !DataType.IsAssociationType; // Moved here from SelectClause [jsd] + return DataType != null && !DataType.IsAssociationType && !(DataType is SubqueryComponentType); // Moved here from SelectClause [jsd] } } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public abstract void SetScalarColumnText(int i); + + // 6.0 TODO: Make abstract + /// + /// Sets the index and text for select expression in the projection list. + /// + /// The index of the select expression in the projection list. + /// The alias creator. + /// The generated scalar column names. + public virtual string[] SetScalarColumnText(int i, Func aliasCreator) + { +#pragma warning disable 618 + SetScalarColumnText(i); +#pragma warning restore 618 + return null; + } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/AggregateNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/AggregateNode.cs index eb0f40f28d0..7e146cbe6ef 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/AggregateNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/AggregateNode.cs @@ -14,7 +14,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree /// Ported by: Steve Strong /// [CLSCompliant(false)] - public class AggregateNode : AbstractSelectExpression, ISelectExpression + public class AggregateNode : AbstractSelectExpression { public AggregateNode(IToken token) : base(token) @@ -46,10 +46,18 @@ public override IType DataType base.DataType = value; } } - + + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i); } + + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + return new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)}; + } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryArithmeticOperatorNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryArithmeticOperatorNode.cs index 60ca24ca379..10ccd41d78d 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryArithmeticOperatorNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryArithmeticOperatorNode.cs @@ -199,11 +199,19 @@ private IType ResolveDateTimeArithmeticResultType(IType lhType, IType rhType) return null; } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i); } + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + return new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)}; + } + /** * Retrieves the left-hand operand of the operator. * diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/Case2Node.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/Case2Node.cs index 73f6cf08455..f572c5459e3 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/Case2Node.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/Case2Node.cs @@ -9,7 +9,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree /// Represents a case ... when .. then ... else ... end expression in a select. /// [CLSCompliant(false)] - public class Case2Node : AbstractSelectExpression, ISelectExpression + public class Case2Node : AbstractSelectExpression { public Case2Node(IToken token) : base(token) { @@ -26,9 +26,17 @@ private ISelectExpression GetFirstThenNode() return (ISelectExpression) GetChild(0).NextSibling.GetChild(0).NextSibling; } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i ); } + + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + return new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)}; + } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/CaseNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/CaseNode.cs index a60f08e7ad9..8299ec5b76b 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/CaseNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/CaseNode.cs @@ -12,7 +12,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree /// Ported by: Steve Strong /// [CLSCompliant(false)] - public class CaseNode : AbstractSelectExpression, ISelectExpression + public class CaseNode : AbstractSelectExpression { public CaseNode(IToken token) : base(token) { @@ -59,9 +59,17 @@ public override IType DataType set { base.DataType = value; } } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i ); } + + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + return new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)}; + } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/ComponentJoin.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/ComponentJoin.cs index bfffb9be928..a3d164a4373 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/ComponentJoin.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/ComponentJoin.cs @@ -2,6 +2,7 @@ using System.Text; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; +using NHibernate.SqlCommand; using NHibernate.Type; using NHibernate.Util; @@ -100,6 +101,8 @@ public override IType GetPropertyType(string propertyName, string propertyPath) return fromElement.ComponentType.Subtypes[index]; } + // Since v5.4 + [Obsolete("Use GetScalarIdentifierSelectFragment method instead.")] public override string RenderScalarIdentifierSelect(int i) { String[] cols = GetBasePropertyMapping().ToColumns(fromElement.TableAlias, fromElement.ComponentProperty); @@ -117,6 +120,20 @@ public override string RenderScalarIdentifierSelect(int i) return buf.ToString(); } + /// + public override SelectFragment GetScalarIdentifierSelectFragment(int i, Func aliasCreator) + { + var cols = GetBasePropertyMapping().ToColumns(fromElement.TableAlias, fromElement.ComponentProperty); + var fragment = new SelectFragment(fromElement.Walker.SessionFactoryHelper.Factory.Dialect); + // For property references generate . as + for (var j = 0; j < cols.Length; j++) + { + fragment.AddColumn(null, cols[j], aliasCreator(i, j)); + } + + return fragment; + } + protected IPropertyMapping GetBasePropertyMapping() { return fromElement.Origin.GetPropertyMapping(""); diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/ConstructorNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/ConstructorNode.cs index 6155b54e670..ca59169def6 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/ConstructorNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/ConstructorNode.cs @@ -8,7 +8,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree { [CLSCompliant(false)] - public class ConstructorNode : SelectExpressionList, ISelectExpression + public class ConstructorNode : SelectExpressionList, ISelectExpressionExtension { private IType[] _constructorArgumentTypes; private ConstructorInfo _constructor; @@ -27,9 +27,9 @@ public IList ConstructorArgumentTypeList public string[] GetAliases() { - ISelectExpression[] selectExpressions = CollectSelectExpressions(); - string[] aliases = new string[selectExpressions.Length]; - for (int i = 0; i < selectExpressions.Length; i++) + var selectExpressions = GetSelectExpressions(); + string[] aliases = new string[selectExpressions.Count]; + for (int i = 0; i < selectExpressions.Count; i++) { string alias = selectExpressions[i].Alias; aliases[i] = alias ?? i.ToString(); @@ -48,6 +48,8 @@ public int ScalarColumnIndex get { return _scalarColumnIndex; } } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public void SetScalarColumn(int i) { ISelectExpression[] selectExpressions = CollectSelectExpressions(); @@ -59,6 +61,23 @@ public void SetScalarColumn(int i) } } + /// + public string[] SetScalarColumn(int i, Func aliasCreator) + { + var selectExpressions = GetSelectExpressions(); + var aliases = new List(); + // Invoke setScalarColumnText on each constructor argument. + for (var j = 0; j < selectExpressions.Count; j++) + { + var selectExpression = selectExpressions[j]; + aliases.AddRange(selectExpression.SetScalarColumn(j, aliasCreator)); + } + + return aliases.ToArray(); + } + + // Since v5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public void SetScalarColumnText(int i) { ISelectExpression[] selectExpressions = CollectSelectExpressions(); @@ -132,9 +151,9 @@ public void Prepare() private IType[] ResolveConstructorArgumentTypes() { - ISelectExpression[] argumentExpressions = CollectSelectExpressions(); - IType[] types = new IType[argumentExpressions.Length]; - for ( int x = 0; x < argumentExpressions.Length; x++ ) + var argumentExpressions = GetSelectExpressions(); + var types = new IType[argumentExpressions.Count]; + for (var x = 0; x < argumentExpressions.Count; x++) { types[x] = argumentExpressions[x].DataType; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/CountNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/CountNode.cs index e381c921bbe..7ba3946b28d 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/CountNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/CountNode.cs @@ -1,6 +1,6 @@ -using System.Linq; +using System; +using System.Linq; using Antlr.Runtime; -using NHibernate.Dialect.Function; using NHibernate.Hql.Ast.ANTLR.Util; using NHibernate.Type; @@ -11,7 +11,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree /// Author: josh /// Ported by: Steve Strong /// - class CountNode : AggregateNode, ISelectExpression + class CountNode : AggregateNode { public CountNode(IToken token) : base(token) { @@ -28,5 +28,18 @@ public override IType DataType base.DataType = value; } } + + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] + public override void SetScalarColumnText(int i) + { + ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i); + } + + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + return new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)}; + } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index 128addbdcbd..ea779f8f5ed 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -124,12 +124,20 @@ public string PropertyPath set { _propertyPath = value; } } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { string[] sqlColumns = GetColumns(); ColumnHelper.GenerateScalarColumns(Walker.ASTFactory, this, sqlColumns, i); } + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + return ColumnHelper.GenerateScalarColumns(ASTFactory, this, GetColumns(), i, aliasCreator); + } + public override void ResolveIndex(IASTNode parent) { if (IsResolved) @@ -462,9 +470,13 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b ASTUtil.GetDebugstring( parent )); } - // Create a new FROM node for the referenced class. - string associatedEntityName = propertyType.GetAssociatedEntityName(); - string tableAlias = AliasGenerator.CreateName( associatedEntityName ); + if (FromElement is JoinSubqueryFromElement joinSubquery && + joinSubquery.PropertyMapping.ContainsEntityAlias(PropertyPath, propertyType)) + { + // No need to create a join + SetPropertyNameAndPath(parent); + return; + } string[] joinColumns = GetColumns(); string joinPath = Path; @@ -509,6 +521,10 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b if ( ! useFoundFromElement ) { + // Create a new FROM node for the referenced class. + var associatedEntityName = propertyType.GetAssociatedEntityName(); + var tableAlias = AliasGenerator.CreateName(associatedEntityName); + // If this is an implied join in a from element, then use the impled join type which is part of the // tree parser's state (set by the gramamar actions). JoinSequence joinSequence = SessionFactoryHelper @@ -556,7 +572,7 @@ private bool CanReuse(string classAlias, FromElement fromElement) } // otherwise (subquery case) don't reuse the fromElement if we are processing the from-clause of the subquery - return Walker.CurrentClauseType != HqlSqlWalker.FROM; + return Walker.CurrentClauseType != HqlSqlWalker.FROM && Walker.CurrentClauseType != HqlSqlWalker.JOIN; } private void SetImpliedJoin(FromElement elem) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs index d09f15bb4b8..faafc1e08c4 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs @@ -36,7 +36,7 @@ public class FromClause : HqlSqlWalkerNode, IDisplayableNode private readonly Dictionary _fromElementByTableAlias = new Dictionary(); private readonly NullableDictionary _fromElementsByPath = new NullableDictionary(); private readonly List _fromElements = new List(); - private readonly List _entityJoinFromElements = new List(); + private readonly List _appendFromElements = new List(); // Used for entity and subquery joins /// /// All of the implicit FROM xxx JOIN yyy elements that are the destination of a collection. These are created from @@ -216,12 +216,12 @@ public FromElement GetFromElement(string aliasOrClassName) /// The list of from elements (instances of FromElement). public IList GetFromElements() { - return ASTUtil.CollectChildren(this, FromElementPredicate); + return ASTUtil.CollectChildren(this, node => FromElementPredicate(node, this)); } internal IList GetFromElementsTyped() { - return ASTUtil.CollectChildren(this, FromElementPredicate); + return ASTUtil.CollectChildren(this, node => FromElementPredicate(node, this)); } //6.0 TODO: Replace with Typed version below @@ -231,12 +231,17 @@ internal IList GetFromElementsTyped() /// the list of from elements that will be part of the result set. public IList GetProjectionList() { - return ASTUtil.CollectChildren(this, ProjectionListPredicate); + return ASTUtil.CollectChildren(this, node => ProjectionListPredicate(node, this)); } internal IList GetProjectionListTyped() { - return ASTUtil.CollectChildren(this, ProjectionListPredicate); + return ASTUtil.CollectChildren(this, node => ProjectionListPredicate(node, this)); + } + + internal IList GetAllProjectionListTyped() + { + return ASTUtil.CollectChildren(this, node => AllProjectionListPredicate(node)); } public FromElement GetFromElement() @@ -279,6 +284,10 @@ public bool IsSubQuery } } + internal bool IsScalarSubQuery => IsSubQuery && !IsJoinSubQuery; + + internal bool IsJoinSubQuery { get; set; } + public string GetDisplayText() { return "FromClause{" + @@ -300,25 +309,28 @@ private void CheckForDuplicateClassAlias(string classAlias) } } - private static bool ProjectionListPredicate(IASTNode node) + private static bool ProjectionListPredicate(IASTNode node, FromClause fromClause) { - var fromElement = node as FromElement; - - if (fromElement != null) - { - return fromElement.InProjectionList; - } + return node is FromElement fromElement && + fromElement.InProjectionList && + // Skip in case node is within a join subquery + fromElement.FromClause == fromClause; + } - return false; + private static bool AllProjectionListPredicate(IASTNode node) + { + return node is FromElement fromElement && fromElement.InProjectionList; } - private static bool FromElementPredicate(IASTNode node) + private static bool FromElementPredicate(IASTNode node, FromClause fromClause) { var fromElement = node as FromElement; if (fromElement != null) { - return fromElement.IsFromOrJoinFragment; + return fromElement.IsFromOrJoinFragment && + // Skip in case node is within a join subquery + fromElement.FromClause == fromClause; } return false; @@ -379,10 +391,15 @@ public void RegisterFromElement(FromElement element) if (element.IsEntityJoin()) { - _entityJoinFromElements.Add((EntityJoinFromElement) element); + _appendFromElements.Add((EntityJoinFromElement) element); } } + internal void AppendFromElement(FromElement element) + { + _appendFromElements.Add(element); + } + private FromElement FindJoinByPathLocal(string path) { return _fromElementsByPath[path]; @@ -417,11 +434,11 @@ public FromElement GetFromElementByClassName(string className) internal void FinishInit() { - foreach (var item in _entityJoinFromElements) + foreach (var item in _appendFromElements) { AddChild(item); } - _entityJoinFromElements.Clear(); + _appendFromElements.Clear(); } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs index ffd15584604..5ec88bf378c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs @@ -103,7 +103,11 @@ public bool IsEntity public bool IsFromOrJoinFragment { - get { return Type == HqlSqlWalker.FROM_FRAGMENT || Type == HqlSqlWalker.JOIN_FRAGMENT || Type == HqlSqlWalker.ENTITY_JOIN; } + get + { + return Type == HqlSqlWalker.FROM_FRAGMENT || Type == HqlSqlWalker.JOIN_FRAGMENT || + Type == HqlSqlWalker.ENTITY_JOIN || Type == HqlSqlWalker.JOIN_SUBQUERY; + } } public bool IsAllPropertyFetch @@ -271,7 +275,13 @@ public string CollectionSuffix set { _elementType.CollectionSuffix = value; } } - public IType SelectType + public string EntitySuffix + { + get { return _elementType.EntitySuffix; } + set { _elementType.EntitySuffix = value; } + } + + public virtual IType SelectType { get { return _elementType.SelectType; } } @@ -291,6 +301,8 @@ public void SetRole(string role) _role = role; } + internal FromElement ParentFromElement { get; set; } + public FromElement Origin { get { return _origin; } @@ -331,17 +343,36 @@ public string WithClauseJoinAlias /// The total number of returned types. /// The sequence of the current returned type. /// the identifier select SQL fragment. + // Since v5.4 + [Obsolete("Use GetIdentifierSelectFragment method instead.")] public string RenderIdentifierSelect(int size, int k) { return _elementType.RenderIdentifierSelect(size, k); } + /// + /// Returns the identifier select fragment. + /// + /// The column suffix. + /// The identifier select fragment. + public SelectFragment GetIdentifierSelectFragment(string suffix) + { + return _elementType.GetIdentifierSelectFragment(suffix); + } + + internal SelectFragment GetIdentifierSelectFragment(string suffix, string alias) + { + return _elementType.GetIdentifierSelectFragment(suffix, alias); + } + /// /// Returns the property select SQL fragment. /// /// The total number of returned types. /// The sequence of the current returned type. /// the property select SQL fragment. + // Since v5.4 + [Obsolete("Use GetPropertiesSelectFragment method instead.")] public string RenderPropertySelect(int size, int k) { return IsAllPropertyFetch @@ -349,6 +380,29 @@ public string RenderPropertySelect(int size, int k) : _elementType.RenderPropertySelect(size, k, _fetchLazyProperties); } + /// + /// Returns the properties select fragment. + /// + /// The column suffix. + /// The properties select fragment. + public SelectFragment GetPropertiesSelectFragment(string suffix) + { + return GetPropertiesSelectFragment(suffix, ParentFromElement?.TableAlias ?? TableAlias); + } + + /// + /// Returns the properties select fragment. + /// + /// The column suffix. + /// The alias for the columns. + /// The properties select fragment. + internal SelectFragment GetPropertiesSelectFragment(string suffix, string alias) + { + return IsAllPropertyFetch + ? _elementType.GetPropertiesSelectFragment(suffix, IsAllPropertyFetch, alias) + : _elementType.GetPropertiesSelectFragment(suffix, _fetchLazyProperties, alias); + } + public override SqlString RenderText(Engine.ISessionFactoryImplementor sessionFactory) { var result = SqlString.Parse(Text); @@ -369,16 +423,40 @@ public override SqlString RenderText(Engine.ISessionFactoryImplementor sessionFa return result; } + // Since v5.4 + [Obsolete("Use GetCollectionSelectFragment method instead.")] public string RenderCollectionSelectFragment(int size, int k) { return _elementType.RenderCollectionSelectFragment(size, k); } + /// + /// Returns the collection select fragment. + /// + /// The column suffix. + /// The collection select fragment. + public SelectFragment GetCollectionSelectFragment(string suffix) + { + return _elementType.GetCollectionSelectFragment(suffix); + } + + // Since v5.4 + [Obsolete("Use GetValueCollectionSelectFragment method instead.")] public string RenderValueCollectionSelectFragment(int size, int k) { return _elementType.RenderValueCollectionSelectFragment(size, k); } + /// + /// Returns the value collection select fragment. + /// + /// The column suffix. + /// The value collection select fragment. + public SelectFragment GetValueCollectionSelectFragment(string suffix) + { + return _elementType.GetValueCollectionSelectFragment(suffix); + } + public void SetIndexCollectionSelectorParamSpec(IParameterSpecification indexCollectionSelectorParamSpec) { if (indexCollectionSelectorParamSpec == null) @@ -446,11 +524,24 @@ public bool Fetch /// /// the sequence of the returned type /// the identifier select with the column alias. + // Since v5.4 + [Obsolete("Use GetScalarIdentifierSelectFragment method instead.")] public string RenderScalarIdentifierSelect(int i) { return _elementType.RenderScalarIdentifierSelect(i); } + /// + /// Render the identifier select fragment, but in a 'scalar' context (i.e. generate the column alias). + /// + /// The sequence of the returned type + /// A function to generate aliases. + /// The identifier select fragment. + public SelectFragment GetScalarIdentifierSelectFragment(int i, Func aliasCreator) + { + return _elementType.GetScalarIdentifierSelectFragment(i, aliasCreator); + } + public bool UseFromFragment { get @@ -641,7 +732,7 @@ public virtual string GetDisplayText() public void InitializeCollection(FromClause fromClause, string classAlias, string tableAlias) { - DoInitialize(fromClause, tableAlias, null, classAlias, null, null); + DoInitialize(fromClause, tableAlias, null, classAlias, null, null, null); _initialized = true; } @@ -652,7 +743,19 @@ public void InitializeEntity(FromClause fromClause, string classAlias, string tableAlias) { - DoInitialize(fromClause, tableAlias, className, classAlias, persister, type); + DoInitialize(fromClause, tableAlias, className, classAlias, persister, type, null); + _initialized = true; + } + + internal void Initialize( + FromClause fromClause, + IPropertyMapping propertyMapping, + IType type, + string classAlias, + string tableAlias, + IEntityPersister persister) + { + DoInitialize(fromClause, tableAlias, null, classAlias, persister, type, propertyMapping); _initialized = true; } @@ -700,7 +803,7 @@ private void AddDestination(FromElement fromElement) } private void DoInitialize(FromClause fromClause, string tableAlias, string className, string classAlias, - IEntityPersister persister, EntityType type) + IEntityPersister persister, IType type, IPropertyMapping propertyMapping) { if (_initialized) { @@ -710,7 +813,7 @@ private void DoInitialize(FromClause fromClause, string tableAlias, string class _tableAlias = tableAlias; _className = className; _classAlias = classAlias; - _elementType = new FromElementType(this, persister, type); + _elementType = new FromElementType(this, persister, propertyMapping, type); if (Walker == null) { Walker = _fromClause.Walker; diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs index 3d04fdcfd7b..98c641062e7 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs @@ -167,7 +167,7 @@ public FromElement CreateCollection(IQueryableCollection queryableCollection, // Correlated subqueries create 'special' implied from nodes // because correlated subselects can't use an ANSI-style join - bool explicitSubqueryFromElement = _fromClause.IsSubQuery && !_implied; + bool explicitSubqueryFromElement = _fromClause.IsScalarSubQuery && !_implied; if (explicitSubqueryFromElement) { string pathRoot = StringHelper.Root(_path); diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementType.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementType.cs index 5856bfad1dc..1dcc0c0179c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementType.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementType.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; -using System.Text; - using NHibernate.Engine; using NHibernate.Param; using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; +using NHibernate.SqlCommand; using NHibernate.Type; using NHibernate.Util; +using IQueryable = NHibernate.Persister.Entity.IQueryable; namespace NHibernate.Hql.Ast.ANTLR.Tree { @@ -23,7 +23,8 @@ public class FromElementType private readonly FromElement _fromElement; private readonly IEntityPersister _persister; - private readonly EntityType _entityType; + private readonly IPropertyMapping _propertyMapping; + private readonly IType _type; private IQueryableCollection _queryableCollection; private CollectionPropertyMapping _collectionPropertyMapping; private JoinSequence _joinSequence; @@ -31,10 +32,16 @@ public class FromElementType private string _collectionSuffix; public FromElementType(FromElement fromElement, IEntityPersister persister, EntityType entityType) + : this(fromElement, persister, null, entityType) + { + } + + internal FromElementType(FromElement fromElement, IEntityPersister persister, IPropertyMapping propertyMapping, IType type) { _fromElement = fromElement; _persister = persister; - _entityType = entityType; + _type = type; + _propertyMapping = propertyMapping ?? _persister as IPropertyMapping; var queryable = persister as IQueryable; if (queryable != null) @@ -65,18 +72,12 @@ public virtual IType DataType { get { - if (_persister == null) + if (_persister == null && _queryableCollection != null) { - if (_queryableCollection == null) - { - return null; - } return _queryableCollection.Type; } - else - { - return _entityType; - } + + return _type; } } @@ -84,13 +85,13 @@ public IType SelectType { get { - if (_entityType == null) + if (!(_type is EntityType entityType)) { return null; } bool shallow = _fromElement.FromClause.Walker.IsShallowQuery; - return TypeFactory.ManyToOne(_entityType.GetAssociatedEntityName(), shallow); + return TypeFactory.ManyToOne(entityType.GetAssociatedEntityName(), shallow); } } @@ -100,6 +101,8 @@ public string CollectionSuffix set { _collectionSuffix = value; } } + public string EntitySuffix { get; set; } + public IParameterSpecification IndexCollectionSelectorParamSpec { get { return _indexCollectionSelectorParamSpec; } @@ -138,36 +141,52 @@ public JoinSequence JoinSequence /// The total number of returned types. /// The sequence of the current returned type. /// the identifier select SQL fragment. + // Since v5.4 + [Obsolete("Use GetIdentifierSelectFragment method instead.")] public string RenderIdentifierSelect(int size, int k) + { + return GetIdentifierSelectFragment(GetSuffix(size, k))?.ToSqlStringFragment(false) ?? string.Empty; + } + + /// + /// Gets the identifier select fragment. + /// + /// The column suffix. + /// The identifier select fragment. + public SelectFragment GetIdentifierSelectFragment(string suffix) + { + return GetIdentifierSelectFragment(suffix, _fromElement.ParentFromElement?.TableAlias ?? TableAlias); + } + + internal SelectFragment GetIdentifierSelectFragment(string suffix, string alias) { CheckInitialized(); // Render the identifier select fragment using the table alias. - if (_fromElement.FromClause.IsSubQuery) + if (_fromElement.FromClause.IsScalarSubQuery) { var queryable = Queryable; if (queryable == null) - return string.Empty; - - // TODO: Replace this with a more elegant solution. - string[] idColumnNames = queryable.IdentifierColumnNames; + return null; - var buf = new StringBuilder(); - for (int i = 0; i < idColumnNames.Length; i++) - { - buf.Append(_fromElement.TableAlias).Append('.').Append(idColumnNames[i]); - if (i != idColumnNames.Length - 1) buf.Append(", "); - } - return buf.ToString(); + return new SelectFragment(queryable.Factory.Dialect) + .AddColumns(alias, queryable.IdentifierColumnNames); + } + else if (_fromElement is JoinSubqueryFromElement joinSubquery) + { + var columns = joinSubquery.PropertyMapping.GetIdentifiersColumns(alias); + return columns.Count > 0 + ? new SelectFragment(_fromElement.Walker.SessionFactoryHelper.Factory.Dialect).AddColumns(columns) + : null; } else { var queryable = Queryable; if (queryable == null) throw new QueryException("not an entity"); - - string fragment = queryable.IdentifierSelectFragment(TableAlias, GetSuffix(size, k)); - return TrimLeadingCommaAndSpaces(fragment); + + return queryable.GetIdentifierSelectFragment(alias, suffix) + .UseAliasesAsColumns(_fromElement.ParentFromElement != null); } } @@ -176,22 +195,32 @@ public string RenderIdentifierSelect(int size, int k) /// /// the sequence of the returned type /// the identifier select with the column alias. + // Since v5.4 + [Obsolete("Use GetScalarIdentifierSelectFragment method instead.")] public virtual string RenderScalarIdentifierSelect(int i) + { + return GetScalarIdentifierSelectFragment(i, NameGenerator.ScalarName).ToSqlStringFragment(false); + } + + /// + /// Gets the identifier select fragment, but in a 'scalar' context (i.e. generate the column alias). + /// + /// The sequence of the returned type + /// A function to generate aliases. + /// The identifier select fragment. + public virtual SelectFragment GetScalarIdentifierSelectFragment(int i, Func aliasCreator) { CheckInitialized(); - string[] cols = GetPropertyMapping(Persister.Entity.EntityPersister.EntityID).ToColumns(TableAlias, Persister.Entity.EntityPersister.EntityID); - StringBuilder buf = new StringBuilder(); + var cols = GetPropertyMapping(Persister.Entity.EntityPersister.EntityID).ToColumns(TableAlias, Persister.Entity.EntityPersister.EntityID); + var factory = Queryable?.Factory ?? QueryableCollection?.Factory ?? throw new QueryException("not an entity or collection"); + var fragment = new SelectFragment(factory.Dialect); // For property references generate . as - for (int j = 0; j < cols.Length; j++) + for (var j = 0; j < cols.Length; j++) { - string column = cols[j]; - if (j > 0) - { - buf.Append(", "); - } - buf.Append(column).Append(" as ").Append(NameGenerator.ScalarName(i, j)); + fragment.AddColumn(null, cols[j], aliasCreator(i, j)); } - return buf.ToString(); + + return fragment; } /// @@ -201,55 +230,117 @@ public virtual string RenderScalarIdentifierSelect(int i) /// The sequence of the current returned type. /// /// the property select SQL fragment. + // Since v5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public string RenderPropertySelect(int size, int k, bool allProperties) { - return RenderPropertySelect(size, k, null, allProperties); + return GetPropertiesSelectFragment(GetSuffix(size, k), null, allProperties, TableAlias).ToSqlStringFragment(); } + /// + /// Gets the properties select fragment. + /// + /// The column suffix. + /// Whether to include all lazy properties. + /// The alias for the columns. + /// The properties select fragment. + internal SelectFragment GetPropertiesSelectFragment(string suffix, bool allProperties, string alias) + { + return GetPropertiesSelectFragment(suffix, null, allProperties, alias); + } + + // Since v5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public string RenderPropertySelect(int size, int k, string[] fetchLazyProperties) { - return RenderPropertySelect(size, k, fetchLazyProperties, false); + return GetPropertiesSelectFragment(GetSuffix(size, k), fetchLazyProperties, false, TableAlias).ToSqlStringFragment(); } - private string RenderPropertySelect(int size, int k, string[] fetchLazyProperties, bool allProperties) + /// + /// Gets the properties select fragment. + /// + /// The column suffix. + /// Lazy properties to be included. + /// The alias for the columns. + /// The properties select fragment. + internal SelectFragment GetPropertiesSelectFragment(string suffix, string[] fetchLazyProperties, string alias) + { + return GetPropertiesSelectFragment(suffix, fetchLazyProperties, false, alias); + } + + /// + /// Gets the properties select fragment. + /// + /// The column suffix. + /// Lazy properties to be included. + /// Whether to include all lazy properties. + /// The alias for the columns. + /// The properties select fragment. + private SelectFragment GetPropertiesSelectFragment(string suffix, string[] fetchLazyProperties, bool allProperties, string alias) { CheckInitialized(); - var queryable = Queryable; - if (queryable == null) - return ""; + if (_fromElement is JoinSubqueryFromElement joinSubquery) + { + return new SelectFragment(_fromElement.Walker.SessionFactoryHelper.Factory.Dialect) + .AddColumns(joinSubquery.PropertyMapping.GetPropertiesColumns(alias)); + } // Use the old method when fetchProperties is null to prevent any breaking changes // 6.0 TODO: simplify condition by removing the fetchProperties part - string fragment = fetchLazyProperties == null || allProperties - ? queryable.PropertySelectFragment(TableAlias, GetSuffix(size, k), allProperties) - : queryable.PropertySelectFragment(TableAlias, GetSuffix(size, k), fetchLazyProperties); + var fragment = fetchLazyProperties == null || allProperties + ? Queryable?.GetPropertiesSelectFragment(alias, suffix, allProperties) + : Queryable?.GetPropertiesSelectFragment(alias, suffix, fetchLazyProperties); - return TrimLeadingCommaAndSpaces(fragment); + return fragment?.UseAliasesAsColumns(_fromElement.ParentFromElement != null); } + // Since v5.4 + [Obsolete("Use GetCollectionSelectFragment method instead.")] public string RenderCollectionSelectFragment(int size, int k) + { + return GetCollectionSelectFragment(GenerateSuffix(size, k))?.ToSqlStringFragment(false) ?? string.Empty; + } + + /// + /// Gets the collection select fragment. + /// + /// The column suffix. + /// The collection select fragment + public SelectFragment GetCollectionSelectFragment(string suffix) { if (_queryableCollection == null) - return ""; + return null; if (_collectionSuffix == null) - _collectionSuffix = GenerateSuffix(size, k); + _collectionSuffix = suffix; - string fragment = _queryableCollection.SelectFragment(CollectionTableAlias, _collectionSuffix); - return TrimLeadingCommaAndSpaces(fragment); + return _queryableCollection.GetSelectFragment(_fromElement.ParentFromElement?.TableAlias ?? CollectionTableAlias, _collectionSuffix) + .UseAliasesAsColumns(_fromElement.ParentFromElement != null); } + // Since v5.4 + [Obsolete("Use GetValueCollectionSelectFragment method instead.")] public string RenderValueCollectionSelectFragment(int size, int k) + { + return GetValueCollectionSelectFragment(GenerateSuffix(size, k))?.ToSqlStringFragment(false) ?? string.Empty; + } + + /// + /// Gets the value collection select fragment. + /// + /// The column suffix. + /// The value collection select fragment + public SelectFragment GetValueCollectionSelectFragment(string suffix) { if (_queryableCollection == null) - return ""; + return null; if (_collectionSuffix == null) - _collectionSuffix = GenerateSuffix(size, k); + _collectionSuffix = suffix; - string fragment = _queryableCollection.SelectFragment(TableAlias, _collectionSuffix); - return TrimLeadingCommaAndSpaces(fragment); + return _queryableCollection.GetSelectFragment(_fromElement.ParentFromElement?.TableAlias ?? TableAlias, _collectionSuffix) + .UseAliasesAsColumns(_fromElement.ParentFromElement != null); } public bool IsEntity @@ -276,9 +367,9 @@ public virtual IPropertyMapping GetPropertyMapping(string propertyName) CheckInitialized(); if (_queryableCollection == null) - { + { // Not a collection? - return (IPropertyMapping)_persister; // Return the entity property mapping. + return _propertyMapping; } // If the property is a special collection property name, return a CollectionPropertyMapping. @@ -492,21 +583,5 @@ private string ExtractTableName() // should be safe to only ever expect EntityPersister references here return _fromElement.Queryable.TableName; } - - /// - /// This accounts for a quirk in Queryable, where it sometimes generates ', ' in front of the - /// SQL fragment. :-P - /// - /// A SQL fragment. - /// The fragment, without the leading comma and spaces. - private static string TrimLeadingCommaAndSpaces(String fragment) - { - if (fragment.Length > 0 && fragment[0] == ',') - { - fragment = fragment.Substring(1); - } - fragment = fragment.Trim(); - return fragment.Trim(); - } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/ISelectExpression.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/ISelectExpression.cs index a89fef53ade..8315a7f31aa 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/ISelectExpression.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/ISelectExpression.cs @@ -21,12 +21,16 @@ public interface ISelectExpression /// (e.g. 'as col0_O_') /// /// The index of the select expression in the projection list. + // Since v5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] void SetScalarColumnText(int i); /// /// Sets the index and text for select expression in the projection list. /// /// The index of the select expression in the projection list. + // Since v5.4 + [Obsolete("Use SetScalarColumn extension method with aliasCreator parameter instead.")] void SetScalarColumn(int i); /// @@ -58,5 +62,39 @@ public interface ISelectExpression bool IsScalar { get; } string Alias { get; set; } - } + } + + // 6.0 TODO: Merge into ISelectExpression + internal interface ISelectExpressionExtension : ISelectExpression + { + /// + /// Sets the index and text for select expression in the projection list. + /// + /// The index of the select expression in the projection list. + /// The alias creator. + string[] SetScalarColumn(int i, Func aliasCreator); + } + + // 6.0 TODO: Remove + public static class SelectExpressionExtensions + { + /// + /// Sets the index and text for select expression in the projection list. + /// + /// The select expression. + /// The index of the select expression in the projection list. + /// The alias creator. + public static string[] SetScalarColumn(this ISelectExpression selectExpression, int i, Func aliasCreator) + { + if (selectExpression is ISelectExpressionExtension selectExpressionExtension) + { + return selectExpressionExtension.SetScalarColumn(i, aliasCreator); + } + +#pragma warning disable 618 + selectExpression.SetScalarColumn(i); +#pragma warning restore 618 + return null; + } + } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/IdentNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/IdentNode.cs index 095f8b03545..4df59cb3138 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/IdentNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/IdentNode.cs @@ -13,7 +13,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree { [CLSCompliant(false)] - public class IdentNode : FromReferenceNode, ISelectExpression + public class IdentNode : FromReferenceNode { private const int Unknown = 0; private const int PropertyRef = 1; @@ -48,6 +48,8 @@ public override IType DataType } } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { if (_nakedPropertyRef) @@ -67,6 +69,27 @@ public override void SetScalarColumnText(int i) } } + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + if (_nakedPropertyRef) + { + // do *not* over-write the column text, as that has already been + // "rendered" during resolve + return new[] {ColumnHelper.GenerateSingleScalarColumn(Walker.ASTFactory, this, i, aliasCreator)}; + } + + var fe = FromElement; + if (fe != null) + { + var fragment = fe.GetScalarIdentifierSelectFragment(i, aliasCreator); + Text = fragment.ToSqlStringFragment(false); + return fragment.GetColumnAliases().ToArray(); + } + + return new[] {ColumnHelper.GenerateSingleScalarColumn(Walker.ASTFactory, this, i, aliasCreator)}; + } + public override void ResolveIndex(IASTNode parent) { // An ident node can represent an index expression if the ident diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/IndexNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/IndexNode.cs index f938c173b93..51b240a4b4d 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/IndexNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/IndexNode.cs @@ -25,11 +25,19 @@ public IndexNode(IToken token) : base(token) { } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { throw new InvalidOperationException("An IndexNode cannot generate column text!"); } + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + throw new InvalidOperationException("An IndexNode cannot generate column text!"); + } + public override void ResolveIndex(IASTNode parent) { throw new InvalidOperationException(); diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs new file mode 100644 index 00000000000..d1330926268 --- /dev/null +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs @@ -0,0 +1,75 @@ +using System.Linq; +using Antlr.Runtime; +using NHibernate.Engine; +using NHibernate.Persister.Entity; +using NHibernate.SqlCommand; +using NHibernate.Type; + +namespace NHibernate.Hql.Ast.ANTLR.Tree +{ + internal class JoinSubqueryFromElement : FromElement + { + public JoinSubqueryFromElement(FromClause fromClause, QueryNode queryNode, JoinType joinType, string alias) + : base(new CommonToken(HqlSqlWalker.JOIN_SUBQUERY, "join sub-query")) + { + var tableAlias = fromClause.AliasGenerator.CreateName("subquery"); + var querySelectClause = queryNode.GetSelectClause(); + var dataType = CreateDataType(querySelectClause, out var entityPersister); + PropertyMapping = new SubqueryPropertyMapping(dataType, querySelectClause); + QueryNode = queryNode; + DataType = dataType; + foreach (var fromElement in querySelectClause.SelectExpressions.Select(o => o.FromElement) + .Union(querySelectClause.NonScalarExpressions?.Select(o => o.FromElement) ?? Enumerable.Empty()) + .Union(querySelectClause.CollectionFromElements ?? Enumerable.Empty()) + .Where(o => o != null)) + { + fromElement.ParentFromElement = this; + } + + Initialize(fromClause, PropertyMapping, dataType, alias, tableAlias, entityPersister); + JoinSequence = new JoinSubqueryJoinSequenceImpl( + SessionFactoryHelper.Factory, + tableAlias, + joinType); + } + + private IType CreateDataType(SelectClause selectClause, out IEntityPersister entityPersister) + { + var typeLength = selectClause.QueryReturnTypes.Length; + if (typeLength == 1) + { + entityPersister = selectClause + .SelectExpressions + .Select(o => o.FromElement?.EntityPersister) + .FirstOrDefault(o => o != null); + return selectClause.QueryReturnTypes[0]; + } + + entityPersister = null; + return new SubqueryComponentType(selectClause.QueryReturnTypes); + } + + public override IType SelectType => base.SelectType ?? DataType; + + public QueryNode QueryNode { get; } + + public SubqueryPropertyMapping PropertyMapping { get; } + + public SqlString RenderText(SqlString subQuery, ISessionFactoryImplementor sessionFactory) + { + return RenderText(sessionFactory).Replace("{query}", subQuery.ToString()); + } + + public override string GetIdentityColumn() + { + // Return null for a scalar subquery instead of throwing an exception. + // The node will be removed in the SelectClause + if (DataType is SubqueryComponentType && PropertyMapping.GetIdentifiersColumns(TableAlias).Count == 0) + { + return null; + } + + return base.GetIdentityColumn(); + } + } +} diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryJoinSequenceImpl.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryJoinSequenceImpl.cs new file mode 100644 index 00000000000..6de5022d9d9 --- /dev/null +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryJoinSequenceImpl.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using NHibernate.Engine; +using NHibernate.SqlCommand; + +namespace NHibernate.Hql.Ast.ANTLR.Tree +{ + internal class JoinSubqueryJoinSequenceImpl : JoinSequence + { + private readonly string _tableAlias; + private readonly JoinType _joinType; + + public JoinSubqueryJoinSequenceImpl(ISessionFactoryImplementor factory, string tableAlias, JoinType joinType) : base(factory) + { + _tableAlias = tableAlias; + _joinType = joinType; + } + + internal override JoinFragment ToJoinFragment( + IDictionary enabledFilters, + bool includeAllSubclassJoins, + bool renderSubclassJoins, + SqlString withClauseFragment) + { + var joinFragment = new QueryJoinFragment(Factory.Dialect, false); + // The query will be added in the next phase, when generating sql + joinFragment.AddJoin( + "({query})", + _tableAlias, + Array.Empty(), + Array.Empty(), + _joinType, + withClauseFragment ?? SqlString.Empty); + return joinFragment; + } + } +} diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/LiteralNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/LiteralNode.cs index 9bd69fe0c43..1218cd1945c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/LiteralNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/LiteralNode.cs @@ -18,11 +18,19 @@ public LiteralNode(IToken token) : base(token) { } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i ); } + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + return new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)}; + } + public override IType DataType { get diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/MethodNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/MethodNode.cs index dc6c04042e7..ddcfc82f854 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/MethodNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/MethodNode.cs @@ -48,6 +48,8 @@ public virtual void Resolve(bool inSelect) } } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { if ( _selectColumns == null ) @@ -60,6 +62,14 @@ public override void SetScalarColumnText(int i) } } + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + return _selectColumns == null + ? new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)} // Dialect function + : ColumnHelper.GenerateScalarColumns(ASTFactory, this, _selectColumns, i, aliasCreator); // Collection 'property function' + } + public void InitializeMethodNode(IASTNode name, bool inSelect) { name.Type = HqlSqlWalker.METHOD_NAME; diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/ParameterNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/ParameterNode.cs index 8d0a83a3f04..b9a63ce98fd 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/ParameterNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/ParameterNode.cs @@ -14,7 +14,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree /// Ported by: Steve Strong /// [CLSCompliant(false)] - public class ParameterNode : HqlSqlWalkerNode, IDisplayableNode, IExpectedTypeAwareNode, ISelectExpression + public class ParameterNode : HqlSqlWalkerNode, IDisplayableNode, IExpectedTypeAwareNode, ISelectExpressionExtension { private string _alias; private IParameterSpecification _parameterSpecification; @@ -73,6 +73,8 @@ public override SqlString RenderText(ISessionFactoryImplementor sessionFactory) #region ISelectExpression + // Since 5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public void SetScalarColumnText(int i) { ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i); @@ -104,12 +106,21 @@ public string Alias set { _alias = value; } } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public void SetScalarColumn(int i) { _scalarColumnIndex = i; SetScalarColumnText(i); } + /// + public string[] SetScalarColumn(int i, Func aliasCreator) + { + _scalarColumnIndex = i; + return new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)}; + } + public int ScalarColumnIndex { get { return _scalarColumnIndex; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/QueryNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/QueryNode.cs index f6f002f38fe..05cbd5cc3fb 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/QueryNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/QueryNode.cs @@ -7,7 +7,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree { [CLSCompliant(false)] - public class QueryNode : AbstractRestrictableStatement, ISelectExpression + public class QueryNode : AbstractRestrictableStatement, ISelectExpressionExtension { private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(QueryNode)); @@ -51,6 +51,8 @@ public override IType DataType } } + // Since v5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public void SetScalarColumnText(int i) { ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i); @@ -82,12 +84,21 @@ public string Alias set; } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public void SetScalarColumn(int i) { _scalarColumn = i; SetScalarColumnText(i); } + /// + public string[] SetScalarColumn(int i, Func aliasCreator) + { + _scalarColumn = i; + return new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)}; + } + public int ScalarColumnIndex { get { return _scalarColumn; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs index a3e4c9af4e1..6e2af65648a 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs @@ -4,6 +4,7 @@ using System.Reflection; using Antlr.Runtime; using NHibernate.Hql.Ast.ANTLR.Util; +using NHibernate.SqlCommand; using NHibernate.Type; namespace NHibernate.Hql.Ast.ANTLR.Tree @@ -21,6 +22,9 @@ public class SelectClause : SelectExpressionList private bool _scalarSelect; private List _collectionFromElements; private IType[] _queryReturnTypes; + // An 2d array of column names, the first dimension is parallel with the + // return types array. The second dimension is the list of column names for each + // type. private string[][] _columnNames; private readonly List _fromElementsForLoad = new List(); private readonly Dictionary _entityByResultTypeDic = new Dictionary(); @@ -28,6 +32,12 @@ public class SelectClause : SelectExpressionList private ConstructorNode _constructorNode; private string[] _aliases; private int[] _columnNamesStartPositions; + private HashSet _derivedSelectExpressions; + private Dictionary> _replacedExpressions = new Dictionary>(); + + internal List SelectExpressions; + internal List OriginalSelectExpressions; + internal List NonScalarExpressions; public static bool VERSION2_SQL; @@ -52,58 +62,23 @@ public void InitializeDerivedSelectClause(FromClause fromClause) // // NOTE : the isSubQuery() bit is a temporary hack... // throw new QuerySyntaxException( "JPA-QL compliance requires select clause" ); // } + var appender = new ASTAppender(ASTFactory, this); var fromElements = fromClause.GetProjectionListTyped(); - - ASTAppender appender = new ASTAppender(ASTFactory, this); // Get ready to start adding nodes. - int size = fromElements.Count; - List queryReturnTypeList = new List(size); - - int k = 0; + _derivedSelectExpressions = new HashSet(); foreach (FromElement fromElement in fromElements) { - IType type = fromElement.SelectType; - - AddCollectionFromElement(fromElement); - - if (type != null) + if (fromElement.SelectType == null || fromElement.IsFetch || fromElement.IsCollectionOfValuesOrComponents) { - bool collectionOfElements = fromElement.IsCollectionOfValuesOrComponents; - if (!collectionOfElements) - { - if (!fromElement.IsFetch) - { - // Add the type to the list of returned sqlResultTypes. - queryReturnTypeList.Add(type); - } - - _fromElementsForLoad.Add(fromElement); - - // Generate the select expression. - string text = fromElement.RenderIdentifierSelect(size, k); - - SelectExpressionImpl generatedExpr = (SelectExpressionImpl)appender.Append(HqlSqlWalker.SELECT_EXPR, text, false); - if (generatedExpr != null) - { - generatedExpr.FromElement = fromElement; - } - } + continue; } - k++; - } - - // Get all the select expressions (that we just generated) and render the select. - ISelectExpression[] selectExpressions = CollectSelectExpressions(); - if (Walker.IsShallowQuery) - { - RenderScalarSelects(selectExpressions, fromClause); - } - else - { - RenderNonScalarSelects(selectExpressions, fromClause); + var node = (IdentNode)appender.Append(HqlSqlWalker.IDENT, fromElement.ClassAlias ?? "", true); + node.FromElement = fromElement; + node.DataType = fromElement.SelectType; + _derivedSelectExpressions.Add(node); } - FinishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList); + InitializeExplicitSelectClause(fromClause); } /// @@ -125,126 +100,211 @@ public void InitializeExplicitSelectClause(FromClause fromClause) // First, collect all of the select expressions. // NOTE: This must be done *before* invoking setScalarColumnText() because setScalarColumnText() // changes the AST!!! - ISelectExpression[] selectExpressions = CollectSelectExpressions(); - - for (int i = 0; i < selectExpressions.Length; i++) + var inheritedExpressions = new Dictionary(); + SelectExpressions = GetSelectExpressions(); + OriginalSelectExpressions = SelectExpressions.ToList(); + NonScalarExpressions = !Walker.IsShallowQuery + ? new List() + : null; + var length = SelectExpressions.Count; + for (var i = 0; i < length; i++) { - ISelectExpression expr = selectExpressions[i]; - + var expr = SelectExpressions[i]; if (expr.IsConstructor) { - _constructorNode = (ConstructorNode)expr; - //sqlResultTypeList.addAll( constructorArgumentTypeList ); + _constructorNode = (ConstructorNode) expr; _scalarSelect = true; + NonScalarExpressions?.AddRange(_constructorNode.GetSelectExpressions(true, o => !o.IsScalar)); + foreach (var argumentExpression in _constructorNode.GetSelectExpressions()) + { + SelectExpressions.Insert(i, argumentExpression); + i++; + AddExpression(argumentExpression, queryReturnTypeList); + } - var ctorSelectExpressions = _constructorNode.CollectSelectExpressions(); - for (int j = 0; j < ctorSelectExpressions.Length; j++) + SelectExpressions.Remove(expr); + length = SelectExpressions.Count; + i--; + } + else if (expr.FromElement is JoinSubqueryFromElement joinSubquery) + { + SelectClause selectClause; + List subqueryExpressions; + if (expr is IdentNode) + { + selectClause = joinSubquery.QueryNode.GetSelectClause(); + subqueryExpressions = selectClause.SelectExpressions; + NonScalarExpressions.Add(expr); + } + else if (expr is DotNode dotNode) { - ISelectExpression se = ctorSelectExpressions[j]; + var relatedExpressions = joinSubquery.PropertyMapping.GetRelatedSelectExpressions(dotNode.PropertyPath, out selectClause); + if (relatedExpressions == null) + { + if (!expr.IsScalar) + { + NonScalarExpressions.Add(expr); + } + + AddExpression(expr, queryReturnTypeList); + continue; + } - if (IsReturnableEntity(se)) + if (!selectClause.IsScalarSelect) { - AddEntityToProjection(queryReturnTypeList.Count, se); + RemoveChild((IASTNode) expr); } - queryReturnTypeList.Add(se.DataType); + subqueryExpressions = new List(); + foreach (var relatedExpression in relatedExpressions) + { + if (!relatedExpression.IsScalar) + { + NonScalarExpressions.Add(relatedExpression); + } + + subqueryExpressions.Add(relatedExpression); + } } - } - else - { - IType type = expr.DataType; - if (type == null && !(expr is ParameterNode)) + else { - throw new QueryException("No data type for node: " + expr.GetType().Name + " " + new ASTPrinter().ShowAsString((IASTNode)expr, "")); + if (!expr.IsScalar) + { + NonScalarExpressions?.Add(expr); + } + + AddExpression(expr, queryReturnTypeList); + continue; } - //sqlResultTypeList.add( type ); - // If the data type is not an association type, it could not have been in the FROM clause. - if (expr.IsScalar) + var indexes = new List(subqueryExpressions.Count); + foreach (var expression in subqueryExpressions) { - _scalarSelect = true; + inheritedExpressions.Add(expression, selectClause); + indexes.Add(i); + SelectExpressions.Insert(i, expression); + i++; + AddExpression(expression, queryReturnTypeList); } - else if (IsReturnableEntity(expr)) + + _replacedExpressions.Add(expr, indexes); + SelectExpressions.Remove(expr); + length = SelectExpressions.Count; + i--; + } + else + { + if (!expr.IsScalar) { - AddEntityToProjection(queryReturnTypeList.Count, expr); + NonScalarExpressions?.Add(expr); } - // Always add the type to the return type list. - queryReturnTypeList.Add(type); + AddExpression(expr, queryReturnTypeList); } } - //init the aliases, after initing the constructornode - InitAliases(selectExpressions); + _queryReturnTypes = queryReturnTypeList.ToArray(); - if (!Walker.IsShallowQuery) + // Init the aliases for explicit select, after initing the constructornode + if (_derivedSelectExpressions == null) { - // add the fetched entities - var fromElements = fromClause.GetProjectionListTyped(); + InitAliases(SelectExpressions); + } + + Render(fromClause, inheritedExpressions); - ASTAppender appender = new ASTAppender(ASTFactory, this); // Get ready to start adding nodes. - int size = fromElements.Count; - int k = 0; + FinishInitialization(); + } - foreach (FromElement fromElement in fromElements) + private void Render( + FromClause fromClause, + Dictionary inheritedExpressions) + { + if (_scalarSelect || Walker.IsShallowQuery) + { + // If there are any scalars (non-entities) selected, render the select column aliases. + RenderScalarSelects(fromClause, inheritedExpressions); + InitializeScalarColumnNames(); + } + + if (Walker.IsShallowQuery) + { + RenderDerivedNonScalarIdentifiers(fromClause); + return; + } + + var fetchedFromElements = new List(); + // add the fetched entities + var fromElements = fromClause.GetAllProjectionListTyped(); + foreach (FromElement fromElement in fromElements) + { + if (!fromElement.IsFetch) { - if (fromElement.IsFetch) - { - var origin = GetOrigin(fromElement); + continue; + } - // Only perform the fetch if its owner is included in the select - if (!_fromElementsForLoad.Contains(origin)) - { - // NH-2846: Before 2012-01-18, we threw this exception. However, some - // components using LINQ (e.g. paging) like to automatically append e.g. Count(). It - // can then be difficult to avoid having a bogus fetch statement, so just ignore those. - // An alternative solution may be to have the linq provider filter out the fetch instead. - // throw new QueryException(string.Format(JoinFetchWithoutOwnerExceptionMsg, fromElement.GetDisplayText())); - - //throw away the fromElement. It's clearly redundant. - fromElement.Parent.RemoveChild(fromElement); - } - else - { - IType type = fromElement.SelectType; - AddCollectionFromElement(fromElement); + var origin = GetOrigin(fromElement); - if (type != null) - { - bool collectionOfElements = fromElement.IsCollectionOfValuesOrComponents; - if (!collectionOfElements) - { - // Add the type to the list of returned sqlResultTypes. - fromElement.IncludeSubclasses = true; - _fromElementsForLoad.Add(fromElement); - //sqlResultTypeList.add( type ); - // Generate the select expression. - String text = fromElement.RenderIdentifierSelect(size, k); - SelectExpressionImpl generatedExpr = (SelectExpressionImpl)appender.Append(HqlSqlWalker.SELECT_EXPR, text, false); - if (generatedExpr != null) - { - generatedExpr.FromElement = fromElement; - } - } - } - } + // Only perform the fetch if its owner is included in the select + if (!_fromElementsForLoad.Contains(origin)) + { + // NH-2846: Before 2012-01-18, we threw this exception. However, some + // components using LINQ (e.g. paging) like to automatically append e.g. Count(). It + // can then be difficult to avoid having a bogus fetch statement, so just ignore those. + // An alternative solution may be to have the linq provider filter out the fetch instead. + // throw new QueryException(string.Format(JoinFetchWithoutOwnerExceptionMsg, fromElement.GetDisplayText())); + + //throw away the fromElement. It's clearly redundant. + if (fromElement.FromClause == fromClause) + { + fromElement.Parent.RemoveChild(fromElement); } + } + else + { + IType type = fromElement.SelectType; + AddCollectionFromElement(fromElement); - k++; + if (type != null && !fromElement.IsCollectionOfValuesOrComponents) + { + // Add the type to the list of returned sqlResultTypes. + fromElement.IncludeSubclasses = true; + _fromElementsForLoad.Add(fromElement); + //sqlResultTypeList.add( type ); + // We will generate the select expression later in order to avoid having different + // columns order when _scalarSelect is true + fetchedFromElements.Add(fromElement); + } } + } - // generate id select fragment and then property select fragment for - // each expression, just like generateSelectFragments(). - RenderNonScalarSelects(CollectSelectExpressions(true), fromClause); + // generate id select fragment and then property select fragment for + // each expression, just like generateSelectFragments(). + RenderNonScalarSelects(fromClause, inheritedExpressions, fetchedFromElements); + } + + private void AddExpression(ISelectExpression expr, List queryReturnTypeList) + { + IType type = expr.DataType; + if (type == null && !(expr is ParameterNode)) + { + throw new QueryException( + "No data type for node: " + expr.GetType().Name + " " + new ASTPrinter().ShowAsString((IASTNode) expr, "")); } + //sqlResultTypeList.add( type ); - if (_scalarSelect || Walker.IsShallowQuery) + // If the data type is not an association type, it could not have been in the FROM clause. + if (expr.IsScalar) { - // If there are any scalars (non-entities) selected, render the select column aliases. - RenderScalarSelects(selectExpressions, fromClause); + _scalarSelect = true; + } + else if (IsReturnableEntity(expr)) + { + AddEntityToProjection(queryReturnTypeList.Count, expr); } - FinishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList); + // Always add the type to the return type list. + queryReturnTypeList.Add(type); } private void AddEntityToProjection(int resultIndex, ISelectExpression se) @@ -368,12 +428,12 @@ private static bool IsReturnableEntity(ISelectExpression selectExpression) } } - private void InitAliases(ISelectExpression[] selectExpressions) + private void InitAliases(List selectExpressions) { if (_constructorNode == null) { - _aliases = new String[selectExpressions.Length]; - for (int i = 0; i < selectExpressions.Length; i++) + _aliases = new String[selectExpressions.Count]; + for (int i = 0; i < selectExpressions.Count; i++) { string alias = selectExpressions[i].Alias; _aliases[i] = alias ?? i.ToString(); @@ -385,139 +445,257 @@ private void InitAliases(ISelectExpression[] selectExpressions) } } - private void RenderNonScalarSelects(ISelectExpression[] selectExpressions, FromClause currentFromClause) + private void RenderNonScalarSelects( + FromClause currentFromClause, + Dictionary inheritedExpressions, + IList fetchedFromElements) { var appender = new ASTAppender(ASTFactory, this); - var nonscalarSize = selectExpressions.Count(e => !e.IsScalar); + var combinedFromElements = new List(); + foreach (var e in NonScalarExpressions) + { + var fromElement = e.FromElement; + if (fromElement != null) + { + combinedFromElements.Add(fromElement); + RenderNonScalarIdentifiers(fromElement, inheritedExpressions.ContainsKey(e) ? null : e, appender); + } + } - int j = 0; - foreach (var e in selectExpressions) + // Append fetched elements + foreach (var fetchedFromElement in fetchedFromElements) { - if (!e.IsScalar) + fetchedFromElement.EntitySuffix = Walker.GetEntitySuffix(fetchedFromElement); + combinedFromElements.Add(fetchedFromElement); + var fragment = fetchedFromElement.GetIdentifierSelectFragment(fetchedFromElement.EntitySuffix); + if (fragment == null) { - FromElement fromElement = e.FromElement; - if (fromElement != null) - { - RenderNonScalarIdentifiers(fromElement, nonscalarSize, j, e, appender); - j++; - } + // When a subquery join has a scalar select only + continue; } + + var generatedExpr = (SelectExpressionImpl) Append(appender, HqlSqlWalker.SELECT_EXPR, fragment); + generatedExpr.FromElement = fetchedFromElement; + generatedExpr.DataType = fetchedFromElement.DataType; + NonScalarExpressions.Add(generatedExpr); + } + + if (currentFromClause.IsScalarSubQuery) + { + return; + } + + // Generate the property select tokens. + foreach (var fromElement in combinedFromElements) + { + RenderNonScalarProperties(appender, fromElement); } - if (!currentFromClause.IsSubQuery) + // Generate properties for fetched collections of components or values + var fromElements = currentFromClause.GetAllProjectionListTyped(); + foreach (var fromElement in fromElements) { - // Generate the property select tokens. - int k = 0; - foreach (var e in selectExpressions) + if (fromElement.IsCollectionOfValuesOrComponents && fromElement.IsFetch) { - if (!e.IsScalar) - { - FromElement fromElement = e.FromElement; - if (fromElement != null) - { - RenderNonScalarProperties(appender, fromElement, nonscalarSize, k); - k++; - } - } + var suffix = Walker.GetSuffix(fromElement); + var fragment = fromElement.GetValueCollectionSelectFragment(suffix); + Append(appender, HqlSqlWalker.SQL_TOKEN, fragment); } } } - private void RenderNonScalarIdentifiers(FromElement fromElement, int nonscalarSize, int j, ISelectExpression expr, ASTAppender appender) + private IASTNode Append(ASTAppender appender, int type, SelectFragment fragment) { - string text = fromElement.RenderIdentifierSelect(nonscalarSize, j); + if (fragment == null) + { + return null; + } - if (!fromElement.FromClause.IsSubQuery) + return appender.Append(type, fragment.ToSqlStringFragment(false), false); + } + + private void RenderDerivedNonScalarIdentifiers(FromClause fromClause) + { + // Render only when scalar columns are not rendered + if (_derivedSelectExpressions == null || !fromClause.IsScalarSubQuery) { - if (!_scalarSelect && !Walker.IsShallowQuery) - { - //TODO: is this a bit ugly? - expr.Text = text; - } - else - { - appender.Append(HqlSqlWalker.SQL_TOKEN, text, false); - } + return; + } + + var appender = new ASTAppender(ASTFactory, this); + foreach (var derivedSelectExpression in _derivedSelectExpressions) + { + RenderNonScalarIdentifiers(derivedSelectExpression.FromElement, derivedSelectExpression, appender); } } - private static void RenderNonScalarProperties(ASTAppender appender, FromElement fromElement, int nonscalarSize, int k) + private void RenderNonScalarIdentifiers(FromElement fromElement, ISelectExpression expr, ASTAppender appender) { - string text = fromElement.RenderPropertySelect(nonscalarSize, k); - appender.Append(HqlSqlWalker.SQL_TOKEN, text, false); + if (fromElement.FromClause.IsScalarSubQuery && _derivedSelectExpressions?.Contains(expr) != true) + { + return; + } + + fromElement.EntitySuffix = Walker.GetEntitySuffix(fromElement); + var fragment = fromElement.GetIdentifierSelectFragment(fromElement.EntitySuffix); + if (fragment == null) + { + // When a subquery join has a scalar select only + var node = (IASTNode) expr; + node?.Parent.RemoveChild(node); + return; + } + + if ((!_scalarSelect || fromElement.Type == HqlSqlWalker.JOIN_SUBQUERY) && expr != null) + { + //TODO: is this a bit ugly? + expr.Text = fragment.ToSqlStringFragment(false); + } + else + { + Append(appender, HqlSqlWalker.SQL_TOKEN, fragment); + } + } + + private void RenderNonScalarProperties(ASTAppender appender, FromElement fromElement) + { + var suffix = fromElement.EntitySuffix; + var fragment = fromElement.GetPropertiesSelectFragment(suffix); + Append(appender, HqlSqlWalker.SQL_TOKEN, fragment); if (fromElement.QueryableCollection != null && fromElement.IsFetch) { - text = fromElement.RenderCollectionSelectFragment(nonscalarSize, k); - appender.Append(HqlSqlWalker.SQL_TOKEN, text, false); + fragment = fromElement.GetCollectionSelectFragment(suffix); + Append(appender, HqlSqlWalker.SQL_TOKEN, fragment); } + } - // Look through the FromElement's children to find any collections of values that should be fetched... - ASTIterator iter = new ASTIterator(fromElement); - foreach (FromElement child in iter) + internal List GetReplacedExpressions(ISelectExpression expression) + { + if (!_replacedExpressions.TryGetValue(expression, out var indexes)) { - if (child.IsCollectionOfValuesOrComponents && child.IsFetch) - { - // Need a better way to define the suffixes here... - text = child.RenderValueCollectionSelectFragment(nonscalarSize, nonscalarSize + k); - appender.Append(HqlSqlWalker.SQL_TOKEN, text, false); - } + return null; } + + return indexes.Select(o => SelectExpressions[o]).ToList(); } - private static void RenderScalarSelects(ISelectExpression[] se, FromClause currentFromClause) + internal string[] GetScalarColumns(ISelectExpression expression) { - if (!currentFromClause.IsSubQuery) + if (_replacedExpressions.TryGetValue(expression, out var indexes)) { - for (int i = 0; i < se.Length; i++) + var columns = new List(); + foreach (var index in indexes) { - ISelectExpression expr = se[i]; - expr.SetScalarColumn(i); // Create SQL_TOKEN nodes for the columns. + columns.AddRange(ColumnNames[index]); } + + return columns.ToArray(); + } + else + { + if (!IsScalarSelect) + { + return expression.FromElement + .GetIdentifierSelectFragment(expression.FromElement.EntitySuffix) + .GetColumnAliases() + .ToArray(); + } + + var index = SelectExpressions.IndexOf(expression); + if (index < 0) + { + throw new InvalidOperationException($"Unable to get scalar columns for expression: {expression}"); + } + + return ColumnNames[index]; } } - private void AddCollectionFromElement(FromElement fromElement) + private void RenderScalarSelects( + FromClause currentFromClause, + Dictionary inheritedExpressions) { - if (fromElement.IsFetch) + if (currentFromClause.IsScalarSubQuery) + { + return; + } + + List deprecateExpressions = null; // 6.0 TODO: Remove + _columnNames = new string[SelectExpressions.Count][]; + for (var i = 0; i < SelectExpressions.Count; i++) { - if (fromElement.CollectionJoin || fromElement.QueryableCollection != null) + var expr = SelectExpressions[i]; + if (inheritedExpressions.TryGetValue(expr, out var selectClause)) { - String suffix; - if (_collectionFromElements == null) - { - _collectionFromElements = new List(); - suffix = VERSION2_SQL ? "__" : "0__"; - } - else + _columnNames[i] = selectClause.GetScalarColumns(expr); + + continue; + } + + _columnNames[i] = expr.SetScalarColumn(Walker.CurrentScalarIndex++, NameGenerator.ScalarName); // Create SQL_TOKEN nodes for the columns. + // 6.0 TODO: Remove + if (_columnNames[i] == null) + { + if (deprecateExpressions == null) { - suffix = _collectionFromElements.Count + "__"; + deprecateExpressions = new List(); } - _collectionFromElements.Add(fromElement); - fromElement.CollectionSuffix = suffix; + + deprecateExpressions.Add(i); + } + } + + // 6.0 TODO: Remove + if (deprecateExpressions != null) + { +#pragma warning disable 618 + var columnNames = SessionFactoryHelper.GenerateColumnNames(_queryReturnTypes); +#pragma warning restore 618 + foreach (var index in deprecateExpressions) + { + _columnNames[index] = columnNames[index]; } } } - private void FinishInitialization(/*ArrayList sqlResultTypeList,*/ List queryReturnTypeList) + private void AddCollectionFromElement(FromElement fromElement) + { + if (!fromElement.IsFetch) + { + return; + } + + var suffix = Walker.GetCollectionSuffix(fromElement); + if (suffix == null) + { + return; + } + + if (_collectionFromElements == null) + { + _collectionFromElements = new List(); + } + + _collectionFromElements.Add(fromElement); + fromElement.CollectionSuffix = suffix; + } + + private void FinishInitialization() { - //sqlResultTypes = ( Type[] ) sqlResultTypeList.toArray( new Type[sqlResultTypeList.size()] ); - _queryReturnTypes = queryReturnTypeList.ToArray(); - InitializeColumnNames(); _prepared = true; } - private void InitializeColumnNames() + private void InitializeScalarColumnNames() { - // Generate an 2d array of column names, the first dimension is parallel with the - // return types array. The second dimension is the list of column names for each - // type. + if (_columnNames == null) + { + return; + } - // todo: we should really just collect these from the various SelectExpressions, rather than regenerating here - _columnNames = SessionFactoryHelper.GenerateColumnNames(_queryReturnTypes); _columnNamesStartPositions = new int[_columnNames.Length]; - int startPosition = 1; - for (int i = 0; i < _columnNames.Length; i++) + var startPosition = 1; + for (var i = 0; i < _columnNames.Length; i++) { _columnNamesStartPositions[i] = startPosition; startPosition += _columnNames[i].Length; diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectExpressionImpl.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectExpressionImpl.cs index 3482ef5111c..0adc8819889 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectExpressionImpl.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectExpressionImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Antlr.Runtime; namespace NHibernate.Hql.Ast.ANTLR.Tree @@ -9,7 +10,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree /// Ported by: Steve Strong /// [CLSCompliant(false)] - public class SelectExpressionImpl : FromReferenceNode, ISelectExpression + public class SelectExpressionImpl : FromReferenceNode { public SelectExpressionImpl(IToken token) : base(token) { @@ -20,11 +21,21 @@ public override void ResolveIndex(IASTNode parent) throw new InvalidOperationException(); } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { Text = FromElement.RenderScalarIdentifierSelect(i); } + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + var fragment = FromElement.GetScalarIdentifierSelectFragment(i, aliasCreator); + Text = fragment.ToSqlStringFragment(false); + return fragment.GetColumnAliases().ToArray(); + } + public override void Resolve(bool generateJoin, bool implicitJoin, string classAlias, IASTNode parent) { // Generated select expressions are already resolved, nothing to do. diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectExpressionList.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectExpressionList.cs index 5c9713a5aa7..4a495ac4ef5 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectExpressionList.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectExpressionList.cs @@ -20,6 +20,8 @@ protected SelectExpressionList(IToken token) : base(token) /// /// Returns an array of SelectExpressions gathered from the children of the given parent AST node. /// + // Since v5.4 + [Obsolete("Use GetSelectExpressions method instead.")] public ISelectExpression[] CollectSelectExpressions() { return CollectSelectExpressions(false); @@ -28,61 +30,61 @@ public ISelectExpression[] CollectSelectExpressions() /// /// Returns an array of SelectExpressions gathered from the children of the given parent AST node. /// - public ISelectExpression[] CollectSelectExpressions(bool recurse) + // Since v5.4 + [Obsolete("Use GetSelectExpressions method instead.")] + public ISelectExpression[] CollectSelectExpressions(bool recurse) + { + return GetSelectExpressions(recurse, null).ToArray(); + } + + /// + /// Gets a list of gathered from the children of the given parent AST node. + /// + public List GetSelectExpressions() + { + return GetSelectExpressions(false, null); + } + + /// + /// Gets a list of gathered from the children of the given parent AST node. + /// + public List GetSelectExpressions( + bool recurse, + Predicate predicate) { // Get the first child to be considered. Sub-classes may do this differently in order to skip nodes that // are not select expressions (e.g. DISTINCT). IASTNode firstChild = GetFirstSelectExpression(); IASTNode parent = this; var list = new List(parent.ChildCount); - for (IASTNode n = firstChild; n != null; n = n.NextSibling) { - if (recurse) + if (recurse && n is ConstructorNode ctor) { - var ctor = n as ConstructorNode; - - if (ctor != null) - { - for (IASTNode cn = ctor.GetChild(1); cn != null; cn = cn.NextSibling) - { - var se = cn as ISelectExpression; - if (se != null) - { - list.Add(se); - } - } - } - else + for (IASTNode cn = ctor.GetChild(1); cn != null; cn = cn.NextSibling) { - var se = n as ISelectExpression; - if (se != null) - { - list.Add(se); - } - else - { - throw new InvalidOperationException("Unexpected AST: " + n.GetType().FullName + " " + - new ASTPrinter().ShowAsString(n, "")); - } + AddExpression(cn); } } - else + + AddExpression(n); + } + + return list; + + void AddExpression(IASTNode n) + { + if (!(n is ISelectExpression se)) { - var se = n as ISelectExpression; - if (se != null) - { - list.Add(se); - } - else - { - throw new InvalidOperationException("Unexpected AST: " + n.GetType().FullName + " " + - new ASTPrinter().ShowAsString(n, "")); - } + throw new InvalidOperationException( + "Unexpected AST: " + n.GetType().FullName + " " + new ASTPrinter().ShowAsString(n, "")); } - } - return list.ToArray(); + if (predicate?.Invoke(se) != false) + { + list.Add(se); + } + } } /// diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/UnaryArithmeticNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/UnaryArithmeticNode.cs index 0af72f3c26f..a2326e6cb13 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/UnaryArithmeticNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/UnaryArithmeticNode.cs @@ -24,11 +24,19 @@ public override IType DataType } } + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public override void SetScalarColumnText(int i) { ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i ); } + /// + public override string[] SetScalarColumnText(int i, Func aliasCreator) + { + return new[] {ColumnHelper.GenerateSingleScalarColumn(ASTFactory, this, i, aliasCreator)}; + } + public void Initialize() { // nothing to do; even if the operand is a parameter, no way we could diff --git a/src/NHibernate/Hql/Ast/ANTLR/Util/ColumnHelper.cs b/src/NHibernate/Hql/Ast/ANTLR/Util/ColumnHelper.cs index 28f432be49d..5646b68754b 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Util/ColumnHelper.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Util/ColumnHelper.cs @@ -6,35 +6,57 @@ namespace NHibernate.Hql.Ast.ANTLR.Util [CLSCompliant(false)] public class ColumnHelper { + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public static void GenerateSingleScalarColumn(IASTFactory factory, IASTNode node, int i) { - node.AddSibling(factory.CreateNode(HqlSqlWalker.SELECT_COLUMNS, " as " + NameGenerator.ScalarName(i, 0))); + GenerateSingleScalarColumn(factory, node, i, NameGenerator.ScalarName); + } + + public static string GenerateSingleScalarColumn(IASTFactory factory, IASTNode node, int i, Func aliasCreator) + { + var alias = aliasCreator(i, 0); + node.AddSibling(factory.CreateNode(HqlSqlWalker.SELECT_COLUMNS, " as " + alias)); + return alias; } /// /// Generates the scalar column AST nodes for a given array of SQL columns /// + // Since v5.4 + [Obsolete("Use overload with aliasCreator parameter instead.")] public static void GenerateScalarColumns(IASTFactory factory, IASTNode node, string[] sqlColumns, int i) + { + GenerateScalarColumns(factory, node, sqlColumns, i, NameGenerator.ScalarName); + } + + /// + /// Generates the scalar column AST nodes for a given array of SQL columns + /// + public static string[] GenerateScalarColumns(IASTFactory factory, IASTNode node, string[] sqlColumns, int i, + Func aliasCreator) { if (sqlColumns.Length == 1) { - GenerateSingleScalarColumn(factory, node, i); + return new[] {GenerateSingleScalarColumn(factory, node, i, aliasCreator)}; } - else - { - node.Text = sqlColumns[0]; // Use the DOT node to emit the first column name. - // Create the column names, folled by the column aliases. - for (int j = 0; j < sqlColumns.Length; j++) - { - if (j > 0) - { - node = node.AddSibling(factory.CreateNode(HqlSqlWalker.SQL_TOKEN, sqlColumns[j])); - } + var aliases = new string[sqlColumns.Length]; + node.Text = sqlColumns[0]; // Use the DOT node to emit the first column name. - node = node.AddSibling(factory.CreateNode(HqlSqlWalker.SELECT_COLUMNS, " as " + NameGenerator.ScalarName(i, j))); + // Create the column names, folled by the column aliases. + for (var j = 0; j < sqlColumns.Length; j++) + { + if (j > 0) + { + node = node.AddSibling(factory.CreateNode(HqlSqlWalker.SQL_TOKEN, sqlColumns[j])); } + + aliases[j] = aliasCreator(i, j); + node = node.AddSibling(factory.CreateNode(HqlSqlWalker.SELECT_COLUMNS, " as " + aliases[j])); } + + return aliases; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Hql/Ast/ANTLR/Util/JoinProcessor.cs b/src/NHibernate/Hql/Ast/ANTLR/Util/JoinProcessor.cs index 9140e3780f0..20d54fff7fd 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Util/JoinProcessor.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Util/JoinProcessor.cs @@ -245,7 +245,7 @@ public bool IncludeSubclasses(string alias) } bool shallowQuery = _walker.IsShallowQuery; bool includeSubclasses = _fromElement.IncludeSubclasses; - bool subQuery = _fromClause.IsSubQuery; + bool subQuery = _fromClause.IsScalarSubQuery; return includeSubclasses && containsTableAlias && !subQuery && !shallowQuery; } } diff --git a/src/NHibernate/Hql/NameGenerator.cs b/src/NHibernate/Hql/NameGenerator.cs index d4ba5af7f5b..2f838d1a46b 100644 --- a/src/NHibernate/Hql/NameGenerator.cs +++ b/src/NHibernate/Hql/NameGenerator.cs @@ -12,6 +12,8 @@ namespace NHibernate.Hql /// public class NameGenerator { + // Since v5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public static string[][] GenerateColumnNames(IType[] types, ISessionFactoryImplementor f) { string[][] columnNames = new string[types.Length][]; @@ -38,4 +40,4 @@ public static string ScalarName(int x, int y) .ToString(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Loader/Collection/BasicCollectionJoinWalker.cs b/src/NHibernate/Loader/Collection/BasicCollectionJoinWalker.cs index 04605581112..9f7943b0034 100644 --- a/src/NHibernate/Loader/Collection/BasicCollectionJoinWalker.cs +++ b/src/NHibernate/Loader/Collection/BasicCollectionJoinWalker.cs @@ -71,7 +71,7 @@ private void InitStatementString(string alias, int batchSize, SqlString subquery SqlSelectBuilder select = new SqlSelectBuilder(Factory) - .SetSelectClause(collectionPersister.SelectFragment(alias, CollectionSuffixes[0]) + .SetSelectClause(collectionPersister.GetSelectFragment(alias, CollectionSuffixes[0]).ToSqlStringFragment(false) + SelectString(associations)) .SetFromClause(collectionPersister.TableName, alias) .SetWhereClause(whereString.ToSqlString()) diff --git a/src/NHibernate/Loader/Custom/Sql/SQLQueryParser.cs b/src/NHibernate/Loader/Custom/Sql/SQLQueryParser.cs index 95943273a32..ecf7020a047 100644 --- a/src/NHibernate/Loader/Custom/Sql/SQLQueryParser.cs +++ b/src/NHibernate/Loader/Custom/Sql/SQLQueryParser.cs @@ -144,7 +144,7 @@ private string ResolveCollectionProperties(string aliasName, string propertyName throw new QueryException("Using return-propertys together with * syntax is not supported."); } - string selectFragment = collectionPersister.SelectFragment(aliasName, collectionSuffix); + var selectFragment = collectionPersister.GetSelectFragment(aliasName, collectionSuffix).ToSqlStringFragment(false); aliasesFound++; // Collection may just contain elements and no entities, in which case resolution of diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index 5fdda0cde17..4b6f1490e99 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -260,11 +260,10 @@ private void Initialize(SelectClause selectClause) _entityFetchLazyProperties[i] = element.FetchLazyProperties != null ? new HashSet(element.FetchLazyProperties) : null; - _sqlAliases[i] = element.TableAlias; + _sqlAliases[i] = element.ParentFromElement?.TableAlias ?? element.TableAlias; _entityAliases[i] = element.ClassAlias; _sqlAliasByEntityAlias.Add(_entityAliases[i], _sqlAliases[i]); - // TODO should we just collect these like with the collections above? - _sqlAliasSuffixes[i] = (size == 1) ? "" : i + "_"; + _sqlAliasSuffixes[i] = element.EntitySuffix; // sqlAliasSuffixes[i] = element.getColumnAliasSuffix(); _includeInSelect[i] = !element.IsFetch; if (_includeInSelect[i]) diff --git a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs index 033673f9656..33c78eb6a83 100644 --- a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs @@ -857,14 +857,27 @@ public string GetIdentifierColumnAlias(string suffix) } } + // Since v5.4 + [Obsolete("Use GetSelectFragment method instead.")] public string SelectFragment(string alias, string columnSuffix) { - SelectFragment frag = GenerateSelectFragment(alias, columnSuffix); + return GetSelectFragment(alias, columnSuffix).ToSqlStringFragment(false); + } + + /// + /// Gets the select fragment containing collection element, index and indentifier columns. + /// + /// The table alias. + /// The column suffix. + /// The select fragment containing collection element, index and indentifier columns. + public SelectFragment GetSelectFragment(string alias, string columnSuffix) + { + var frag = GenerateSelectFragment(alias, columnSuffix); AppendElementColumns(frag, alias); AppendIndexColumns(frag, alias); AppendIdentifierColumns(frag, alias); - return frag.ToSqlStringFragment(false); + return frag; } private void AddWhereFragment(SqlSimpleSelectBuilder sql) diff --git a/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs b/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs index 18ca77da8ac..198f1d0c406 100644 --- a/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs @@ -274,7 +274,9 @@ public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhs return ManyToManySelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, elementType); } } - return includeCollectionColumns ? SelectFragment(lhsAlias, collectionSuffix) : string.Empty; + return includeCollectionColumns + ? GetSelectFragment(lhsAlias, collectionSuffix).ToSqlStringFragment(false) + : string.Empty; } private string ManyToManySelectFragment( diff --git a/src/NHibernate/Persister/Collection/IQueryableCollection.cs b/src/NHibernate/Persister/Collection/IQueryableCollection.cs index ba24382c62b..7b23d1f3bb4 100644 --- a/src/NHibernate/Persister/Collection/IQueryableCollection.cs +++ b/src/NHibernate/Persister/Collection/IQueryableCollection.cs @@ -1,5 +1,7 @@ using System; +using System.Linq; using NHibernate.Persister.Entity; +using NHibernate.SqlCommand; namespace NHibernate.Persister.Collection { @@ -45,6 +47,8 @@ public interface IQueryableCollection : IPropertyMapping, IJoinable, ICollection /// /// Generate a list of collection index and element columns /// + // Since v5.4 + [Obsolete("Use GetSelectFragment extension method instead.")] string SelectFragment(string alias, string columnSuffix); /// @@ -91,4 +95,43 @@ public interface IQueryableCollection : IPropertyMapping, IJoinable, ICollection [Obsolete("Use directly the alias parameter value instead")] string GenerateTableAliasForKeyColumns(string alias); } + + public static class QueryableCollectionExtensions + { + /// + /// Gets the select fragment containing collection element, index and indentifier columns. + /// + /// The instance. + /// The table alias. + /// The column suffix. + /// The element, index and indentifier select fragment. + // 6.0 TODO: Move into IQueryableCollection + public static SelectFragment GetSelectFragment(this IQueryableCollection queryable, string alias, string columnSuffix) + { + if (queryable is AbstractCollectionPersister collectionPersister) + { + return collectionPersister.GetSelectFragment(alias, columnSuffix); + } + +#pragma warning disable 618 + var renderedText = queryable.SelectFragment(alias, columnSuffix); +#pragma warning restore 618 + var identifierAlias = queryable.GetIdentifierColumnAlias(null); + var indexAliases = queryable.GetIndexColumnAliases(null); + var columnAliases = queryable.GetKeyColumnAliases(null) + .Union(queryable.GetElementColumnAliases(null)); + if (indexAliases != null) + { + columnAliases = columnAliases.Union(indexAliases); + } + + if (identifierAlias != null) + { + columnAliases = columnAliases.Union(new[] {identifierAlias}); + } + + return new SelectFragment(queryable.Factory.Dialect, renderedText, columnAliases.ToList()) + .SetSuffix(columnSuffix); + } + } } diff --git a/src/NHibernate/Persister/Collection/OneToManyPersister.cs b/src/NHibernate/Persister/Collection/OneToManyPersister.cs index a501de1b8c0..987253fc04b 100644 --- a/src/NHibernate/Persister/Collection/OneToManyPersister.cs +++ b/src/NHibernate/Persister/Collection/OneToManyPersister.cs @@ -300,7 +300,7 @@ public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhs if (includeCollectionColumns) { - buf.Append(SelectFragment(lhsAlias, collectionSuffix)).Append(StringHelper.CommaSpace); + buf.Append(GetSelectFragment(lhsAlias, collectionSuffix).ToSqlStringFragment(false)).Append(StringHelper.CommaSpace); } //6.0 TODO: Remove diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index 2c71d037c0b..314668f9362 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -1608,7 +1608,8 @@ public string SelectFragment(string alias, string suffix) public string SelectFragment(string alias, string suffix, bool fetchLazyProperties) { - return IdentifierSelectFragment(alias, suffix) + PropertySelectFragment(alias, suffix, fetchLazyProperties); + return GetIdentifierSelectFragment(alias, suffix).ToSqlStringFragment(false) + + GetPropertiesSelectFragment(alias, suffix, null, fetchLazyProperties).ToSqlStringFragment(); } public string[] GetIdentifierAliases(string suffix) @@ -1633,25 +1634,65 @@ public string GetDiscriminatorAlias(string suffix) return entityMetamodel.HasSubclasses ? new Alias(suffix).ToAliasString(DiscriminatorAlias, factory.Dialect) : null; } + // Since v5.4 + [Obsolete("Use GetIdentifierSelectFragment method instead.")] public virtual string IdentifierSelectFragment(string name, string suffix) + { + return GetIdentifierSelectFragment(name, suffix).ToSqlStringFragment(false); + } + + /// + /// Gets the identifier select fragment. + /// + /// The table alias + /// The column suffix. + /// The identifier select fragment. + public virtual SelectFragment GetIdentifierSelectFragment(string alias, string suffix) { return new SelectFragment(factory.Dialect) .SetSuffix(suffix) - .AddColumns(name, IdentifierColumnNames, IdentifierAliases) - .ToSqlStringFragment(false); + .AddColumns(alias, IdentifierColumnNames, IdentifierAliases); } + // Since v5.4 + [Obsolete("Use GetPropertiesSelectFragment method instead.")] public string PropertySelectFragment(string name, string suffix, bool allProperties) { - return PropertySelectFragment(name, suffix, null, allProperties); + return GetPropertiesSelectFragment(name, suffix, null, allProperties).ToSqlStringFragment(); } + /// + /// Gets the properties select fragment. + /// + /// The table alias + /// The column suffix. + /// Whether to fetch all lazy properties. + /// The properties select fragment. + public SelectFragment GetPropertiesSelectFragment(string alias, string suffix, bool allProperties) + { + return GetPropertiesSelectFragment(alias, suffix, null, allProperties); + } + + // Since v5.4 + [Obsolete("Use GetPropertiesSelectFragment method instead.")] public string PropertySelectFragment(string name, string suffix, ICollection fetchProperties) { - return PropertySelectFragment(name, suffix, fetchProperties, false); + return GetPropertiesSelectFragment(name, suffix, fetchProperties, false).ToSqlStringFragment(); + } + + /// + /// Gets the properties select fragment. + /// + /// The table alias + /// The column suffix. + /// Lazy properties to fetch. + /// The properties select fragment. + public SelectFragment GetPropertiesSelectFragment(string alias, string suffix, ICollection fetchProperties) + { + return GetPropertiesSelectFragment(alias, suffix, fetchProperties, false); } - private string PropertySelectFragment(string name, string suffix, ICollection fetchProperties, bool allProperties) + private SelectFragment GetPropertiesSelectFragment(string alias, string suffix, ICollection fetchProperties, bool allProperties) { SelectFragment select = new SelectFragment(Factory.Dialect) .SetSuffix(suffix) @@ -1693,7 +1734,7 @@ private string PropertySelectFragment(string name, string suffix, ICollection - /// Given a query alias and an identifying suffix, render the property select fragment. + /// Gets the properties select fragment. /// - //6.0 TODO: Merge into IQueryable - public static string PropertySelectFragment(this IQueryable query, string alias, string suffix, ICollection fetchProperties) + /// The instance. + /// The table alias + /// The column suffix. + /// Lazy properties to fetch. + /// The properties select fragment. + internal static SelectFragment GetPropertiesSelectFragment(this IQueryable query, string alias, string suffix, ICollection fetchProperties) { return ReflectHelper.CastOrThrow(query, "individual lazy property fetches") - .PropertySelectFragment(alias, suffix, fetchProperties); + .GetPropertiesSelectFragment(alias, suffix, fetchProperties); + } + + /// + /// Gets the identifier select fragment. + /// + /// The instance. + /// The table alias + /// The column suffix. + /// The identifier select fragment. + public static SelectFragment GetIdentifierSelectFragment(this IQueryable queryable, string alias, string suffix) + { + if (queryable is AbstractEntityPersister entityPersister) + { + return entityPersister.GetIdentifierSelectFragment(alias, suffix); + } + + return new SelectFragment(queryable.Factory.Dialect) + .SetSuffix(suffix) + .AddColumns(alias, queryable.IdentifierColumnNames, queryable.GetIdentifierAliases(null)); + } + + /// + /// Gets the properties select fragment. + /// + /// The instance. + /// The table alias + /// The column suffix. + /// Whether to fetch all lazy properties. + /// The properties select fragment. + public static SelectFragment GetPropertiesSelectFragment(this IQueryable queryable, string alias, string suffix, bool allProperties) + { + if (queryable is AbstractEntityPersister entityPersister) + { + return entityPersister.GetPropertiesSelectFragment(alias, suffix, allProperties); + } + +#pragma warning disable 618 + var text = queryable.PropertySelectFragment(alias, suffix, allProperties); +#pragma warning restore 618 + return new SelectFragment(queryable.Factory.Dialect, text, null) + .SetSuffix(suffix); } } @@ -101,11 +148,15 @@ public interface IQueryable : ILoadable, IPropertyMapping, IJoinable /// /// /// + // Since v5.4 + [Obsolete("Use GetIdentifierSelectFragment extension method instead.")] string IdentifierSelectFragment(string name, string suffix); /// /// Given a query alias and an identifying suffix, render the property select fragment. /// + // Since v5.4 + [Obsolete("Use GetPropertiesSelectFragment extension method instead.")] string PropertySelectFragment(string alias, string suffix, bool allProperties); /// diff --git a/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs new file mode 100644 index 00000000000..225f968af33 --- /dev/null +++ b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Hql.Ast.ANTLR.Tree; +using NHibernate.Type; +using NHibernate.Util; + +namespace NHibernate.Persister.Entity +{ + internal class SubqueryPropertyMapping : IPropertyMapping + { + private readonly Dictionary _propertyColumns = new Dictionary(); + private readonly Dictionary _propertyTypes = new Dictionary(); + private readonly Dictionary _propertyAliasMappings = new Dictionary(); + private readonly Dictionary _propertyMappingSuffixes = new Dictionary(); + private readonly Dictionary _propertyMappings = new Dictionary(); + private readonly Dictionary _aliasSelectExpressions = new Dictionary(); + private readonly SelectClause _selectClause; + + public SubqueryPropertyMapping(IType type, SelectClause selectClause) + { + _selectClause = selectClause; + // We need unflattened expressions to correctly process nested aliases + var selectExpressions = selectClause.OriginalSelectExpressions; + var nonScalarExpressions = selectClause.NonScalarExpressions; + Type = type; + if (selectClause.IsScalarSelect) + { + for (var i = 0; i < selectExpressions.Count; i++) + { + var scalarExpression = selectExpressions[i]; + var key = scalarExpression.Alias; + if (string.IsNullOrEmpty(key) && scalarExpression is DotNode dotNode) + { + key = dotNode.PropertyPath.Split(StringHelper.Dot).Last(); + } + + if (key == null) + { + continue; + } + + if (_aliasSelectExpressions.ContainsKey(key)) + { + throw new QueryException($"Subquery contains duplicated property '{key}', use an alias with a different name."); + } + + _aliasSelectExpressions.Add(key, scalarExpression); + _propertyColumns.Add(key, selectClause.GetScalarColumns(scalarExpression)); + _propertyTypes.Add(key, scalarExpression.DataType); + } + } + + for (var i = 0; i < nonScalarExpressions.Count; i++) + { + var expression = nonScalarExpressions[i]; + var fromElement = expression.FromElement; + var mapping = fromElement?.GetPropertyMapping(""); + if (mapping == null) + { + continue; + } + + var alias = expression.Alias; + if (!string.IsNullOrEmpty(alias)) + { + _propertyAliasMappings.Add(alias, mapping); + _propertyMappingSuffixes.Add(alias, fromElement.EntitySuffix ?? fromElement.CollectionSuffix); + if (!_aliasSelectExpressions.ContainsKey(alias)) + { + _aliasSelectExpressions.Add(alias, expression); + } + } + else + { + _propertyMappings.Add(mapping, fromElement.EntitySuffix ?? fromElement.CollectionSuffix); + } + } + } + + public IType Type { get; } + + public List GetRelatedSelectExpressions(string path, out SelectClause selectClause) + { + var paths = path.Split(StringHelper.Dot); + var alias = paths[0]; + if (!_aliasSelectExpressions.TryGetValue(alias, out var expression)) + { + selectClause = null; + return null; + } + + if (paths.Length == 1) + { + selectClause = _selectClause; + var replacements = _selectClause.GetReplacedExpressions(expression); + if (replacements != null) + { + return replacements; + } + + return new List {expression}; + } + + if (_propertyAliasMappings.TryGetValue(alias, out var mapping) && + mapping is SubqueryPropertyMapping joinSubQueryMapping) + { + return joinSubQueryMapping.GetRelatedSelectExpressions(JoinPaths(paths.Skip(1)), out selectClause); + } + + selectClause = null; + return null; + } + + public bool ContainsEntityAlias(string alias, IType type) + { + var aliases = alias.Split(StringHelper.Dot); + var rootAlias = aliases[0]; + if (!_propertyAliasMappings.TryGetValue(rootAlias, out var mapping)) + { + return false; + } + + if (aliases.Length == 1) + { + return mapping.Type.Equals(type); + } + + if (mapping is SubqueryPropertyMapping joinSubQueryMapping) + { + return joinSubQueryMapping.ContainsEntityAlias(JoinPaths(aliases.Skip(1)), type); + } + + return false; + } + + public IType ToType(string propertyName) + { + if (!TryToType(propertyName, out var type)) + { + throw new QueryException(string.Format("could not resolve property: {0} of: {1}", propertyName, "SubQuery")); + } + + return type; + } + + public bool TryToType(string propertyName, out IType type) + { + var paths = propertyName.Split(StringHelper.Dot); + var propertyAlias = paths[0]; + if (paths.Length == 1 && _propertyTypes.TryGetValue(propertyAlias, out type)) + { + return true; + } + + if (_propertyAliasMappings.TryGetValue(propertyAlias, out var mapping)) + { + if (paths.Length > 1) + { + return mapping.TryToType(JoinPaths(paths.Skip(1)), out type); + } + + type = mapping.Type; + return type != null; + } + + foreach (var propertyMapping in _propertyMappings.Keys) + { + if (propertyMapping.TryToType(propertyName, out type)) + { + return true; + } + } + + type = null; + return false; + } + + public string[] ToColumns(string alias, string propertyName) + { + if (TryGetColumns(alias, propertyName, out var columns)) + { + return columns; + } + + throw new QueryException(string.Format("could not resolve property: {0} of: {1}", propertyName, "SubQuery")); + } + + public List GetPropertiesColumns(string alias) + { + var columns = new List(); + foreach (var expression in _selectClause.NonScalarExpressions) + { + if (expression.FromElement == null || expression.FromElement.IsFetch) + { + continue; + } + + var fragment = expression.FromElement.GetPropertiesSelectFragment(expression.FromElement.EntitySuffix, alias); + if (expression.FromElement is JoinSubqueryFromElement) + { + columns.AddRange(fragment.GetColumnAliases()); + } + else + { + columns.AddRange(fragment.GetColumnAliases().Select(o => StringHelper.Qualify(alias, o))); + } + } + + if (_selectClause.ColumnNames != null) + { + foreach (var scalarColumns in _selectClause.ColumnNames) + { + columns.AddRange(QualifyColumns(scalarColumns, alias)); + } + } + + return columns; + } + + public List GetIdentifiersColumns(string alias) + { + var columns = new List(); + foreach (var expression in _selectClause.NonScalarExpressions) + { + if (expression.FromElement == null || expression.FromElement.FromClause.IsScalarSubQuery || expression.FromElement.IsFetch) + { + continue; + } + + var fragment = expression.FromElement.GetIdentifierSelectFragment(expression.FromElement.EntitySuffix, alias); + if (fragment == null) + { + continue; // Subquery with scalar select + } + + if (expression.FromElement is JoinSubqueryFromElement) + { + columns.AddRange(fragment.GetColumnAliases()); + } + else + { + columns.AddRange(fragment.GetColumnAliases().Select(o => StringHelper.Qualify(alias, o))); + } + } + + return columns; + } + + private bool TryGetColumns(string alias, string propertyName, out string[] columns) + { + var paths = propertyName.Split(StringHelper.Dot); + var propertyAlias = paths[0]; + if (paths.Length == 1 && _propertyColumns.TryGetValue(propertyAlias, out columns)) + { + columns = QualifyColumns(columns, alias); + return true; + } + + if (_propertyAliasMappings.TryGetValue(propertyAlias, out var mapping)) + { + var suffix = _propertyMappingSuffixes[propertyAlias]; + if ( + (paths.Length > 1 && TryGetQualifiedColumns(mapping, JoinPaths(paths.Skip(1)), alias, suffix, out columns)) || + (paths.Length == 1 && TryGetQualifiedColumns(mapping, EntityPersister.EntityID, alias, suffix, out columns)) + ) + { + return true; + } + } + + if (propertyName == EntityPersister.EntityID) + { + columns = GetIdentifiersColumns(alias).ToArray(); + return columns.Length > 0; + } + + foreach (var pair in _propertyMappings) + { + if (pair.Key.TryToType(propertyName, out _) && TryGetQualifiedColumns(pair.Key, propertyName, alias, pair.Value, out columns)) + { + return true; + } + } + + columns = null; + return false; + } + + private bool TryGetQualifiedColumns(IPropertyMapping propertyMapping, string propertyName, string alias, string suffix, out string[] columns) + { + columns = null; + if (propertyMapping is ISqlLoadable loadable) + { + var aliasedColumns = loadable.GetSubclassPropertyColumnAliases(propertyName, suffix); + if (aliasedColumns != null) + { + columns = QualifyColumns(aliasedColumns, alias); + } + } + else if (propertyMapping is SubqueryPropertyMapping subQueryMapping && subQueryMapping.TryGetColumns(alias, propertyName, out columns)) + { + return true; + } + + return columns != null; + } + + private static string[] QualifyColumns(string[] columns, string alias) + { + var result = new string[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + result[i] = StringHelper.Qualify(alias, columns[i]); + } + + return result; + } + + private static string JoinPaths(IEnumerable paths) + { +#if NETCOREAPP + return string.Join(StringHelper.Dot, paths); +#else + return string.Join(".", paths); +#endif + } + + public string[] ToColumns(string propertyName) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/NHibernate/SqlCommand/SelectFragment.cs b/src/NHibernate/SqlCommand/SelectFragment.cs index dfe8de0f059..1d5c4a6f982 100644 --- a/src/NHibernate/SqlCommand/SelectFragment.cs +++ b/src/NHibernate/SqlCommand/SelectFragment.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Text; using NHibernate.Util; @@ -17,12 +18,25 @@ public class SelectFragment private Dialect.Dialect dialect; private string[] usedAliases; private string extraSelectList; + private string[] _extraAliases; + private bool _useAliasesAsColumns; + private string _renderedText; // 6.0 TODO: Remove public SelectFragment(Dialect.Dialect d) { this.dialect = d; } + internal List Columns => columns; + + // 6.0 TODO: Remove + internal SelectFragment(Dialect.Dialect d, string renderedText, List columnAliases) + { + dialect = d; + _renderedText = renderedText.TrimStart(' ', ','); + this.columnAliases = columnAliases; + } + public SelectFragment SetUsedAliases(string[] usedAliases) { this.usedAliases = usedAliases; @@ -52,7 +66,7 @@ public SelectFragment AddColumns(string[] columnNames) public SelectFragment AddColumn(string tableAlias, string columnName) { - return AddColumn(tableAlias, columnName, columnName); + return AddColumn(tableAlias, columnName, null); } public SelectFragment AddColumn(string tableAlias, string columnName, string columnAlias) @@ -80,6 +94,19 @@ public SelectFragment AddColumns(string tableAlias, string[] columnNames) return this; } + internal SelectFragment AddColumns(IEnumerable columnNames) + { + foreach (var columnName in columnNames) + { + if (columnName != null) + { + AddColumn(columnName); + } + } + + return this; + } + public SelectFragment AddColumns(string tableAlias, string[] columnNames, string[] columnAliases) { for (int i = 0; i < columnNames.Length; i++) @@ -132,6 +159,11 @@ public string ToSqlStringFragment() public string ToSqlStringFragment(bool includeLeadingComma) { + if (!string.IsNullOrEmpty(_renderedText)) + { + return includeLeadingComma ? $"{StringHelper.CommaSpace}{_renderedText}" : _renderedText; + } + StringBuilder buf = new StringBuilder(columns.Count * 10); HashSet columnsUnique = usedAliases != null ? new HashSet(usedAliases) : new HashSet(); @@ -142,16 +174,28 @@ public string ToSqlStringFragment(bool includeLeadingComma) string col = columns[i]; string columnAlias = columnAliases[i]; - if (columnsUnique.Add(columnAlias)) + if (columnsUnique.Add(columnAlias ?? col)) { if (found || includeLeadingComma) { buf.Append(StringHelper.CommaSpace); } - buf.Append(col) - .Append(" as ") - .Append(new Alias(suffix).ToAliasString(columnAlias, dialect)); + if (_useAliasesAsColumns) + { + col = col.Split(StringHelper.Dot)[0] + StringHelper.Dot + columnAlias; + buf.Append(new Alias(suffix).ToAliasString(col, dialect)); + } + else if (columnAlias == null) + { + buf.Append(col); + } + else + { + buf.Append(col) + .Append(" as ") + .Append(new Alias(suffix).ToAliasString(columnAlias, dialect)); + } // Set the flag for the next time found = true; @@ -171,6 +215,8 @@ public string ToSqlStringFragment(bool includeLeadingComma) return buf.ToString(); } + // Since v5.4 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public SelectFragment SetExtraSelectList(string extraSelectList) { this.extraSelectList = extraSelectList; @@ -179,7 +225,20 @@ public SelectFragment SetExtraSelectList(string extraSelectList) public SelectFragment SetExtraSelectList(CaseFragment caseFragment, string fragmentAlias) { - SetExtraSelectList(caseFragment.SetReturnColumnName(fragmentAlias, suffix).ToSqlStringFragment()); + extraSelectList = caseFragment.SetReturnColumnName(fragmentAlias, suffix).ToSqlStringFragment(); + _extraAliases = new[] {caseFragment.returnColumnName}; + return this; + } + + internal IEnumerable GetColumnAliases() + { + return columnAliases.Select((o, i) => new Alias(suffix).ToAliasString(o ?? columns[i], dialect)) + .Union(_extraAliases ?? Enumerable.Empty()); + } + + internal SelectFragment UseAliasesAsColumns(bool value) + { + _useAliasesAsColumns = value; return this; } } diff --git a/src/NHibernate/Type/ComponentType.cs b/src/NHibernate/Type/ComponentType.cs index b00a3459ee9..cf9cdbf552b 100644 --- a/src/NHibernate/Type/ComponentType.cs +++ b/src/NHibernate/Type/ComponentType.cs @@ -78,6 +78,30 @@ public ComponentType(ComponentMetamodel metamodel) } } + internal ComponentType( + IType[] propertyTypes, + string[] propertyNames, + bool[] propertyNullability, + int propertySpan, + CascadeStyle[] cascade, + FetchMode?[] joinedFetch, + bool isKey, + bool overridesGetHashCode, + IComponentTuplizer tuplizer, + EntityMode entityMode) + { + this.propertyTypes = propertyTypes; + this.propertyNames = propertyNames; + this.propertyNullability = propertyNullability; + this.propertySpan = propertySpan; + this.cascade = cascade; + this.joinedFetch = joinedFetch; + this.isKey = isKey; + this.overridesGetHashCode = overridesGetHashCode; + ComponentTuplizer = tuplizer; + EntityMode = entityMode; + } + /// public override bool IsCollectionType { diff --git a/src/NHibernate/Type/SubqueryComponentType.cs b/src/NHibernate/Type/SubqueryComponentType.cs new file mode 100644 index 00000000000..728372bda9a --- /dev/null +++ b/src/NHibernate/Type/SubqueryComponentType.cs @@ -0,0 +1,24 @@ +using System; +using NHibernate.Engine; + +namespace NHibernate.Type +{ + [Serializable] + internal class SubqueryComponentType : ComponentType + { + internal SubqueryComponentType(IType[] propertyTypes) + : base( + propertyTypes, + new string[propertyTypes.Length], + new bool[propertyTypes.Length], + propertyTypes.Length, + new CascadeStyle[propertyTypes.Length], + new FetchMode?[propertyTypes.Length], + false, + false, + null, + EntityMode.Poco) + { + } + } +} From 6f882fb689ef510a6497436aa7220dbc669e587d Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 19 Sep 2020 16:54:45 +0200 Subject: [PATCH 02/17] Fix broken tests --- .../Linq/ByMethod/JoinSubqueryTests.cs | 86 ++++++++++++++++++- src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs | 2 +- .../Hql/Ast/ANTLR/Tree/SelectClause.cs | 83 +++++++++++------- .../Entity/SubqueryPropertyMapping.cs | 24 ++++-- 4 files changed, 156 insertions(+), 39 deletions(-) diff --git a/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs index ae591b8d3d6..990dd3332ea 100644 --- a/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Linq; using NHibernate.DomainModel.Northwind.Entities; using NUnit.Framework; using NUnit.Framework.Constraints; @@ -745,7 +746,7 @@ inner join fetch o1.OrderLines select o, o2.ord from Order o inner join ( - select o1 as ord, o1.OrderLines.size + select o1 as ord, o1.ShippedTo from Order o1 inner join fetch o1.OrderLines where o1.OrderId = 10248 @@ -810,7 +811,7 @@ inner join fetch o1.ProductIds select o, o2.ord from Order o inner join ( - select o1 as ord, o1.OrderLines.size + select o1 as ord, o1.ShippedTo from Order o1 inner join fetch o1.ProductIds where o1.OrderId = 10248 @@ -854,5 +855,86 @@ private void AssertEntitySubQueryWithCollectionOfValuesFetch(IList result, bool[ } #endregion + + #region HqlDuplicateEntitySelectionSubQuery + + [Test] + public void HqlDuplicateEntitySelectionSubQuery() + { + AssertDuplicateEntitySelectionSubQuery(@" + from Order o + inner join ( + select o1 as ord1, o1 as ord2 + from Order o1 + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.ord1.OrderId"); + + AssertDuplicateEntitySelectionSubQuery(@" + select o, o2.ord1, o2.ord2 + from Order o + inner join ( + select o1 as ord1, o1 as ord2 + from Order o1 + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.ord1.OrderId"); + + AssertDuplicateEntitySelectionSubQuery(@" + select o, o2, o2 + from Order o + inner join ( + select o1 + from Order o1 + where o1.OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId"); + + AssertDuplicateEntitySelectionSubQuery(@" + select o, o2, o2 + from Order o + inner join ( + from Order + where OrderId = 10248 + ) o2 on (o.OrderId - 1) = o2.OrderId"); + } + + private void AssertDuplicateEntitySelectionSubQuery(string query) + { + IList result; + using (var logSpy = new SqlLogSpy()) + { + result = session.CreateQuery(query).List(); + AssertDuplicateEntitySelectionSubQuery(logSpy.GetWholeLog(), result, false); + } + + using (var logSpy = new SqlLogSpy()) + { + result = session.CreateQuery(query).Enumerable().OfType().ToList(); + AssertDuplicateEntitySelectionSubQuery(logSpy.GetWholeLog(), result, true); + } + } + + private void AssertDuplicateEntitySelectionSubQuery(string sql, IList result, bool shallow) + { + var selectSql = sql.Substring(0, sql.IndexOf("from")); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(3)); + if (shallow) + { + Assert.That(GetTotalOccurrences(selectSql, ","), Is.EqualTo(1)); + Assert.That(array[0], Is.AssignableFrom().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.AssignableFrom().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[1], Is.AssignableFrom().And.Property("OrderId").EqualTo(10248)); + } + else + { + Assert.That(GetTotalOccurrences(selectSql, ","), Is.EqualTo(27)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + } + } + + #endregion } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index 85e90112886..d96c1dfc990 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -1237,7 +1237,7 @@ public bool IsShallowQuery get { // select clauses for insert statements should alwasy be treated as shallow - return StatementType == INSERT || _qti.IsShallowQuery; + return StatementType == INSERT || (_qti.IsShallowQuery && !_currentFromClause.IsJoinSubQuery); } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs index 6e2af65648a..d222a66146c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs @@ -103,9 +103,7 @@ public void InitializeExplicitSelectClause(FromClause fromClause) var inheritedExpressions = new Dictionary(); SelectExpressions = GetSelectExpressions(); OriginalSelectExpressions = SelectExpressions.ToList(); - NonScalarExpressions = !Walker.IsShallowQuery - ? new List() - : null; + NonScalarExpressions = new List(); var length = SelectExpressions.Count; for (var i = 0; i < length; i++) { @@ -152,7 +150,7 @@ public void InitializeExplicitSelectClause(FromClause fromClause) if (!selectClause.IsScalarSelect) { - RemoveChild((IASTNode) expr); + RemoveChildAndUnsetParent((IASTNode) expr); } subqueryExpressions = new List(); @@ -180,7 +178,7 @@ public void InitializeExplicitSelectClause(FromClause fromClause) var indexes = new List(subqueryExpressions.Count); foreach (var expression in subqueryExpressions) { - inheritedExpressions.Add(expression, selectClause); + inheritedExpressions[expression] = selectClause; indexes.Add(i); SelectExpressions.Insert(i, expression); i++; @@ -227,16 +225,21 @@ private void Render( InitializeScalarColumnNames(); } + // generate id select fragment and then property select fragment for + // each expression, just like generateSelectFragments(). + RenderNonScalarSelects(fromClause, inheritedExpressions, GetFetchedFromElements(fromClause)); + } + + private List GetFetchedFromElements(FromClause fromClause) + { + var fetchedFromElements = new List(); if (Walker.IsShallowQuery) { - RenderDerivedNonScalarIdentifiers(fromClause); - return; + return fetchedFromElements; } - var fetchedFromElements = new List(); // add the fetched entities - var fromElements = fromClause.GetAllProjectionListTyped(); - foreach (FromElement fromElement in fromElements) + foreach (FromElement fromElement in fromClause.GetAllProjectionListTyped()) { if (!fromElement.IsFetch) { @@ -278,9 +281,7 @@ private void Render( } } - // generate id select fragment and then property select fragment for - // each expression, just like generateSelectFragments(). - RenderNonScalarSelects(fromClause, inheritedExpressions, fetchedFromElements); + return fetchedFromElements; } private void AddExpression(ISelectExpression expr, List queryReturnTypeList) @@ -452,19 +453,40 @@ private void RenderNonScalarSelects( { var appender = new ASTAppender(ASTFactory, this); var combinedFromElements = new List(); + var processedElements = new HashSet(); foreach (var e in NonScalarExpressions) { var fromElement = e.FromElement; - if (fromElement != null) + if (fromElement == null) + { + continue; + } + + var node = (IASTNode) e; + if (processedElements.Add(fromElement)) { combinedFromElements.Add(fromElement); RenderNonScalarIdentifiers(fromElement, inheritedExpressions.ContainsKey(e) ? null : e, appender); } + else if (!inheritedExpressions.ContainsKey(e) && node.Parent != null) + { + RemoveChildAndUnsetParent(node); + } + } + + if (Walker.IsShallowQuery) + { + return; } // Append fetched elements foreach (var fetchedFromElement in fetchedFromElements) { + if (!processedElements.Add(fetchedFromElement)) + { + continue; + } + fetchedFromElement.EntitySuffix = Walker.GetEntitySuffix(fetchedFromElement); combinedFromElements.Add(fetchedFromElement); var fragment = fetchedFromElement.GetIdentifierSelectFragment(fetchedFromElement.EntitySuffix); @@ -495,7 +517,9 @@ private void RenderNonScalarSelects( var fromElements = currentFromClause.GetAllProjectionListTyped(); foreach (var fromElement in fromElements) { - if (fromElement.IsCollectionOfValuesOrComponents && fromElement.IsFetch) + if (fromElement.IsCollectionOfValuesOrComponents && + fromElement.IsFetch && + processedElements.Add(fromElement)) { var suffix = Walker.GetSuffix(fromElement); var fragment = fromElement.GetValueCollectionSelectFragment(suffix); @@ -514,25 +538,16 @@ private IASTNode Append(ASTAppender appender, int type, SelectFragment fragment) return appender.Append(type, fragment.ToSqlStringFragment(false), false); } - private void RenderDerivedNonScalarIdentifiers(FromClause fromClause) + private void RenderNonScalarIdentifiers(FromElement fromElement, ISelectExpression expr, ASTAppender appender) { - // Render only when scalar columns are not rendered - if (_derivedSelectExpressions == null || !fromClause.IsScalarSubQuery) + if (fromElement.FromClause.IsScalarSubQuery && _derivedSelectExpressions?.Contains(expr) != true) { return; } - var appender = new ASTAppender(ASTFactory, this); - foreach (var derivedSelectExpression in _derivedSelectExpressions) - { - RenderNonScalarIdentifiers(derivedSelectExpression.FromElement, derivedSelectExpression, appender); - } - } - - private void RenderNonScalarIdentifiers(FromElement fromElement, ISelectExpression expr, ASTAppender appender) - { - if (fromElement.FromClause.IsScalarSubQuery && _derivedSelectExpressions?.Contains(expr) != true) + if (Walker.IsShallowQuery && !fromElement.FromClause.IsScalarSubQuery && SelectExpressions.Contains(expr)) { + // A scalar column was generated return; } @@ -541,8 +556,7 @@ private void RenderNonScalarIdentifiers(FromElement fromElement, ISelectExpressi if (fragment == null) { // When a subquery join has a scalar select only - var node = (IASTNode) expr; - node?.Parent.RemoveChild(node); + RemoveChildAndUnsetParent((IASTNode) expr); return; } @@ -706,5 +720,14 @@ public int GetColumnNamesStartPosition(int i) { return _columnNamesStartPositions[i]; } + + private static void RemoveChildAndUnsetParent(IASTNode node) + { + if (node?.Parent != null) + { + node.Parent.RemoveChild(node); + node.Parent = null; + } + } } } diff --git a/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs index 225f968af33..7cb429de6be 100644 --- a/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs +++ b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs @@ -73,6 +73,11 @@ public SubqueryPropertyMapping(IType type, SelectClause selectClause) } else { + if (_propertyMappings.ContainsKey(mapping)) + { + throw new QueryException($"Subquery selects the same type '{mapping.Type}' multiple times. Use unique alias for each type selection."); + } + _propertyMappings.Add(mapping, fromElement.EntitySuffix ?? fromElement.CollectionSuffix); } } @@ -189,15 +194,17 @@ public string[] ToColumns(string alias, string propertyName) public List GetPropertiesColumns(string alias) { var columns = new List(); + var processedElements = new HashSet(); foreach (var expression in _selectClause.NonScalarExpressions) { - if (expression.FromElement == null || expression.FromElement.IsFetch) + var fromElement = expression.FromElement; + if (fromElement == null || fromElement.IsFetch || !processedElements.Add(fromElement)) { continue; } - var fragment = expression.FromElement.GetPropertiesSelectFragment(expression.FromElement.EntitySuffix, alias); - if (expression.FromElement is JoinSubqueryFromElement) + var fragment = fromElement.GetPropertiesSelectFragment(fromElement.EntitySuffix, alias); + if (fromElement is JoinSubqueryFromElement) { columns.AddRange(fragment.GetColumnAliases()); } @@ -221,20 +228,25 @@ public List GetPropertiesColumns(string alias) public List GetIdentifiersColumns(string alias) { var columns = new List(); + var processedElements = new HashSet(); foreach (var expression in _selectClause.NonScalarExpressions) { - if (expression.FromElement == null || expression.FromElement.FromClause.IsScalarSubQuery || expression.FromElement.IsFetch) + var fromElement = expression.FromElement; + if (fromElement == null || + fromElement.FromClause.IsScalarSubQuery || + fromElement.IsFetch || + !processedElements.Add(fromElement)) { continue; } - var fragment = expression.FromElement.GetIdentifierSelectFragment(expression.FromElement.EntitySuffix, alias); + var fragment = fromElement.GetIdentifierSelectFragment(fromElement.EntitySuffix, alias); if (fragment == null) { continue; // Subquery with scalar select } - if (expression.FromElement is JoinSubqueryFromElement) + if (fromElement is JoinSubqueryFromElement) { columns.AddRange(fragment.GetColumnAliases()); } From 269992a28241c847419ebc5ca7c074734610f2f0 Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 19 Sep 2020 18:29:45 +0200 Subject: [PATCH 03/17] Fix and add parameter tests --- .../Async/Linq/ByMethod/JoinSubqueryTests.cs | 465 ++++++++++-------- .../Linq/ByMethod/JoinSubqueryTests.cs | 384 +++++++-------- .../Ast/ANTLR/Tree/JoinSubqueryFromElement.cs | 4 +- 3 files changed, 467 insertions(+), 386 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs index b617f05bdb3..f6addaa6529 100644 --- a/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs +++ b/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs @@ -9,6 +9,7 @@ using System.Collections; +using System.Linq; using NHibernate.DomainModel.Northwind.Entities; using NUnit.Framework; using NUnit.Framework.Constraints; @@ -16,6 +17,7 @@ namespace NHibernate.Test.Linq.ByMethod { using System.Threading.Tasks; + using System.Threading; [TestFixture] public class JoinSubqueryTestsAsync : LinqTestCase { @@ -24,24 +26,16 @@ public class JoinSubqueryTestsAsync : LinqTestCase [Test] public async Task HqlEntitySubQueryAsync() { - var result = await (session.CreateQuery("from Order o inner join (from Order where OrderId = 10248) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); - AssertEntitySubQuery(result); - - result = await (session.CreateQuery("from Order o inner join (from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").ListAsync()); - AssertEntitySubQuery(result); - - result = await (session.CreateQuery("from Order o inner join (select o2 from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").ListAsync()); - AssertEntitySubQuery(result); - - result = await (session.CreateQuery("select o, o3 from Order o inner join (select o2 from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").ListAsync()); - AssertEntitySubQuery(result); - - result = await (session.CreateQuery("select o, o3 from Order o inner join (from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").ListAsync()); - AssertEntitySubQuery(result); + await (AssertEntitySubQueryAsync("from Order o inner join (from Order where OrderId = :id) o2 on (o.OrderId - 1) = o2.OrderId")); + await (AssertEntitySubQueryAsync("from Order o inner join (from Order o2 where o2.OrderId = :id) o3 on (o.OrderId - 1) = o3.OrderId")); + await (AssertEntitySubQueryAsync("from Order o inner join (select o2 from Order o2 where o2.OrderId = :id) o3 on (o.OrderId - 1) = o3.OrderId")); + await (AssertEntitySubQueryAsync("select o, o3 from Order o inner join (select o2 from Order o2 where o2.OrderId = :id) o3 on (o.OrderId - 1) = o3.OrderId")); + await (AssertEntitySubQueryAsync("select o, o3 from Order o inner join (from Order o2 where o2.OrderId = :id) o3 on (o.OrderId - 1) = o3.OrderId")); } - private void AssertEntitySubQuery(IList result) + private async Task AssertEntitySubQueryAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -58,23 +52,24 @@ private void AssertEntitySubQuery(IList result) [Test] public async Task HqlScalarSubQueryAsync() { - var result = await (session.CreateQuery(@" + await (AssertScalarSubQueryAsync(@" select o.Customer.CustomerId, o.ShippedTo, o2 - from Order o inner join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); - AssertScalarSubQuery(result); + from Order o + inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId")); - result = await (session.CreateQuery(@" + await (AssertScalarSubQueryAsync(@" select o.Customer.CustomerId, o.ShippedTo, o2.OrderId, o2.CustomerId, o2.ShippedTo - from Order o inner join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); - AssertScalarSubQuery(result); + from Order o + inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId")); } - private void AssertScalarSubQuery(IList result) + private async Task AssertScalarSubQueryAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -94,23 +89,22 @@ private void AssertScalarSubQuery(IList result) [Test] public async Task HqlIdSubQueryAsync() { - var result = await (session.CreateQuery(@" + await (AssertIdSubQueryAsync(@" select o from Order o inner join ( - select id from Order where OrderId = 10248 - ) o2 on o.id = o2.id").ListAsync()); - AssertIdSubQuery(result); + select id from Order where OrderId = :id + ) o2 on o.id = o2.id")); - result = await (session.CreateQuery(@" + await (AssertIdSubQueryAsync(@" select o from CompositeOrder o inner join ( - select id from CompositeOrder where OrderId = 10248 - ) o2 on o.id = o2.id").ListAsync()); - AssertIdSubQuery(result); + select id from CompositeOrder where OrderId = :id + ) o2 on o.id = o2.id")); } - private void AssertIdSubQuery(IList result) + private async Task AssertIdSubQueryAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -149,23 +143,22 @@ private void AssertSubclassSubQuery(IList result) [Test] public async Task HqlMixedSubQueryAsync() { - var result = await (session.CreateQuery(@" + await (AssertMixedSubQueryAsync(@" select o, o2, o.Customer.CustomerId, o.ShippedTo from Order o inner join ( - select OrderId, Customer, ShippedTo from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); - AssertMixedSubQuery(result); + select OrderId, Customer, ShippedTo from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId")); - result = await (session.CreateQuery(@" + await (AssertMixedSubQueryAsync(@" select o, o2.OrderId, o2.cu, o2.ShippedTo, o.Customer.CustomerId, o.ShippedTo from Order o inner join ( - select OrderId, Customer as cu, ShippedTo from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); - AssertMixedSubQuery(result); + select OrderId, Customer as cu, ShippedTo from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId")); } - private void AssertMixedSubQuery(IList result) + private async Task AssertMixedSubQueryAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -186,16 +179,16 @@ private void AssertMixedSubQuery(IList result) [Test] public async Task HqlSubQueryComponentPropertySelectionAsync() { - var result = await (session.CreateQuery(@" + await (AssertSubQueryComponentPropertySelectionAsync(@" select o2.cu.Address.Street, o2.emp.Address.Street from Order o inner join ( - select OrderId, Customer as cu, Employee as emp from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); - AssertSubQueryComponentPropertySelection(result); + select OrderId, Customer as cu, Employee as emp from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId")); } - private void AssertSubQueryComponentPropertySelection(IList result) + private async Task AssertSubQueryComponentPropertySelectionAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -212,23 +205,35 @@ private void AssertSubQueryComponentPropertySelection(IList result) [Test] public async Task HqlIdSubQueryWithPagingAndOrderByAsync() { - var result = await (session.CreateQuery(@" + await (AssertIdSubQueryWithPagingAndOrderByAsync(@" select o from Order o inner join ( - select id from Order order by OrderId skip 2 take 2 - ) o2 on o.id = o2.id").ListAsync()); - AssertIdSubQueryWithPagingAndOrderBy(result); + select id from Order order by OrderId skip :s take :t + ) o2 on o.id = o2.id")); - result = await (session.CreateQuery(@" + await (AssertIdSubQueryWithPagingAndOrderByAsync(@" select o from CompositeOrder o inner join ( - select id from CompositeOrder order by OrderId skip 2 take 2 - ) o2 on o.id = o2.id").ListAsync()); - AssertIdSubQueryWithPagingAndOrderBy(result); - } - - private void AssertIdSubQueryWithPagingAndOrderBy(IList result) - { + select id from CompositeOrder order by OrderId skip :s take :t + ) o2 on o.id = o2.id")); + } + + private async Task AssertIdSubQueryWithPagingAndOrderByAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) + { + IList result; + if (Sfi.Dialect.SupportsVariableLimit) + { + result = await (session.CreateQuery(query) + .SetParameter("s", 2) + .SetParameter("t", 2) + .ListAsync(cancellationToken)); + } + else + { + query = query.Replace(":s", "2").Replace(":t", "2"); + result = await (session.CreateQuery(query).ListAsync(cancellationToken)); + } + Assert.That(result, Has.Count.EqualTo(2)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -245,25 +250,24 @@ private void AssertIdSubQueryWithPagingAndOrderBy(IList result) [Test] public async Task HqlGroupBySubQueryWithAliasesAsync() { - var result = await (session.CreateQuery(@" + await (AssertGroupBySubQueryWithAliasesAsync(@" select o, o2 from Order o inner join ( select Customer.CustomerId, count(*) as total, max(OrderId) as orderId from Order group by Customer.CustomerId ) o2 on o.id = o2.orderId - where o2.total > 30").ListAsync()); - AssertGroupBySubQueryWithAliases(result); + where o2.total > :total")); - result = await (session.CreateQuery(@" + await (AssertGroupBySubQueryWithAliasesAsync(@" select o, o2.CustomerId, o2.total, o2.orderId from Order o inner join ( select Customer.CustomerId, count(*) as total, max(OrderId) as orderId from Order group by Customer.CustomerId ) o2 on o.id = o2.orderId - where o2.total > 30").ListAsync()); - AssertGroupBySubQueryWithAliases(result); + where o2.total > :total")); } - private void AssertGroupBySubQueryWithAliases(IList result) + private async Task AssertGroupBySubQueryWithAliasesAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("total", 30).ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -282,16 +286,16 @@ private void AssertGroupBySubQueryWithAliases(IList result) [Test] public async Task HqlSubQueryWithEntityAliasAsync() { - var result = await (session.CreateQuery(@" + await (AssertSubQueryWithEntityAliasAsync(@" select o, o3.order.OrderId, o3.customer.CustomerId, o3.ShippedTo from Order o inner join ( - select o2 as order, o2.Customer as customer, o2.ShippedTo from Order o2 where o2.OrderId = 10248 - ) o3 on (o.OrderId - 1) = o3.order.OrderId").ListAsync()); - AssertSubQueryWithEntityAlias(result); + select o2 as order, o2.Customer as customer, o2.ShippedTo from Order o2 where o2.OrderId = :id + ) o3 on (o.OrderId - 1) = o3.order.OrderId")); } - private void AssertSubQueryWithEntityAlias(IList result) + private async Task AssertSubQueryWithEntityAliasAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -310,30 +314,32 @@ private void AssertSubQueryWithEntityAlias(IList result) [Test] public async Task HqlMultipleEntitySubQueriesAsync() { - var result = await (session.CreateQuery(@" + await (AssertMultipleEntitySubQueriesAsync(@" from Order o inner join ( - from Order where OrderId = 10248 + from Order where OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId left join ( - from Order where OrderId = 10250 - ) o3 on (o3.OrderId - 2) = o2.OrderId").ListAsync()); - AssertMultipleEntitySubQueries(result); + from Order where OrderId = :id2 + ) o3 on (o3.OrderId - 2) = o2.OrderId")); - result = await (session.CreateQuery(@" + await (AssertMultipleEntitySubQueriesAsync(@" select o, o2, o3 from Order o inner join ( - from Order where OrderId = 10248 + from Order where OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId left join ( - from Order where OrderId = 10250 - ) o3 on (o3.OrderId - 2) = o2.OrderId").ListAsync()); - AssertMultipleEntitySubQueries(result); + from Order where OrderId = :id2 + ) o3 on (o3.OrderId - 2) = o2.OrderId")); } - private void AssertMultipleEntitySubQueries(IList result) + private async Task AssertMultipleEntitySubQueriesAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query) + .SetParameter("id", 10248) + .SetParameter("id2", 10250) + .ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -351,30 +357,32 @@ private void AssertMultipleEntitySubQueries(IList result) [Test] public async Task HqlMultipleScalarSubQueriesAsync() { - var result = await (session.CreateQuery(@" + await (AssertMultipleScalarSubQueriesAsync(@" from Order o inner join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId left join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10250 - ) o3 on (o3.OrderId - 2) = o2.OrderId").ListAsync()); - AssertMultipleScalarSubQueries(result); + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id2 + ) o3 on (o3.OrderId - 2) = o2.OrderId")); - result = await (session.CreateQuery(@" + await (AssertMultipleScalarSubQueriesAsync(@" select o, o2, o3 from Order o inner join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId left join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10250 - ) o3 on (o3.OrderId - 2) = o2.OrderId").ListAsync()); - AssertMultipleScalarSubQueries(result); + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id2 + ) o3 on (o3.OrderId - 2) = o2.OrderId")); } - private void AssertMultipleScalarSubQueries(IList result) + private async Task AssertMultipleScalarSubQueriesAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query) + .SetParameter("id", 10248) + .SetParameter("id2", 10250) + .ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -396,7 +404,7 @@ private void AssertMultipleScalarSubQueries(IList result) [Test] public async Task HqlNestedEntitySubQueriesAsync() { - var result = await (session.CreateQuery(@" + await (AssertNestedEntitySubQueriesAsync(@" from Order o left join ( select e as emp, e2 as sup @@ -404,10 +412,9 @@ from Employee e left join (from Employee) e2 on e.Superior = e2 ) o3 on o.Employee = o3.emp order by o.id - take 1").ListAsync()); - AssertNestedEntitySubQueries(result); + take :t")); - result = await (session.CreateQuery(@" + await (AssertNestedEntitySubQueriesAsync(@" select o, o3 from Order o left join ( @@ -416,10 +423,9 @@ from Employee e left join (from Employee) e2 on e.Superior = e2 ) o3 on o.Employee = o3.emp order by o.id - take 1").ListAsync()); - AssertNestedEntitySubQueries(result); + take :t")); - result = await (session.CreateQuery(@" + await (AssertNestedEntitySubQueriesAsync(@" select o, o3.emp, o3.sup from Order o left join ( @@ -428,12 +434,24 @@ from Employee e left join (from Employee) e2 on e.Superior = e2 ) o3 on o.Employee = o3.emp order by o.id - take 1").ListAsync()); - AssertNestedEntitySubQueries(result); + take :t")); } - private void AssertNestedEntitySubQueries(IList result) + private async Task AssertNestedEntitySubQueriesAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + IList result; + if (Sfi.Dialect.SupportsVariableLimit) + { + result = await (session.CreateQuery(query) + .SetParameter("t", 1) + .ListAsync(cancellationToken)); + } + else + { + query = query.Replace(":t", "1"); + result = await (session.CreateQuery(query).ListAsync(cancellationToken)); + } + Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -451,7 +469,7 @@ private void AssertNestedEntitySubQueries(IList result) [Test] public async Task HqlNestedScalarSubQueriesAsync() { - var result = await (session.CreateQuery(@" + await (AssertNestedScalarSubQueriesAsync(@" select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3 as ord3 from Order o left join ( @@ -462,10 +480,9 @@ left join ( from Order ) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.OrderId-1 - where o.OrderId = 10248").ListAsync()); - AssertNestedScalarSubQueries(result); + where o.OrderId = :id")); - result = await (session.CreateQuery(@" + await (AssertNestedScalarSubQueriesAsync(@" select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3 as ord3 from Order o left join ( @@ -478,10 +495,9 @@ left join ( from Order ) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.OrderId-1 - where o.OrderId = 10248").ListAsync()); - AssertNestedScalarSubQueries(result); + where o.OrderId = :id")); - result = await (session.CreateQuery(@" + await (AssertNestedScalarSubQueriesAsync(@" select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3.OrderId, o3.ShippedTo, o3.CustomerId, o3.City, o3.oid, o3.sto, o3.cuid, o3.cty @@ -496,12 +512,12 @@ left join ( from Order ) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.OrderId-1 - where o.OrderId = 10248").ListAsync()); - AssertNestedScalarSubQueries(result); + where o.OrderId = :id")); } - private void AssertNestedScalarSubQueries(IList result) + private async Task AssertNestedScalarSubQueriesAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -528,7 +544,7 @@ private void AssertNestedScalarSubQueries(IList result) [Test] public async Task HqlNestedMixedSubQueriesAsync() { - var result = await (session.CreateQuery(@" + await (AssertNestedMixedSubQueriesAsync(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3 from Order o left join ( @@ -539,10 +555,9 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").ListAsync()); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id")); - result = await (session.CreateQuery(@" + await (AssertNestedMixedSubQueriesAsync(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.sto2, o3.cu2, o3.cty2, o3.sub1 @@ -555,10 +570,9 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").ListAsync()); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id")); - result = await (session.CreateQuery(@" + await (AssertNestedMixedSubQueriesAsync(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.sto2, o3.cu2, o3.cty2, @@ -572,10 +586,9 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").ListAsync()); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id")); - result = await (session.CreateQuery(@" + await (AssertNestedMixedSubQueriesAsync(@" select o4.ord3, o4.ord3.ShippedTo, o4.cu3, o4.ord3.ShippingAddress.City, o4.sub2.ord2, o4.sub2.ord2.ShippedTo, o4.sub2.cu2, o4.sub2.cu2.Address.City, @@ -593,12 +606,10 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248 - ) o4 on o5.OrderId = o4.ord3.OrderId - ").ListAsync()); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id + ) o4 on o5.OrderId = o4.ord3.OrderId")); - result = await (session.CreateQuery(@" + await (AssertNestedMixedSubQueriesAsync(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.sto2, o3.cu2, o3.cty2, @@ -614,10 +625,9 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").ListAsync()); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id")); - result = await (session.CreateQuery(@" + await (AssertNestedMixedSubQueriesAsync(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.sto2, o3.cu2, o3.cty2, @@ -630,10 +640,9 @@ left join ( from Order o1 left join (from Order) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").ListAsync()); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id")); - result = await (session.CreateQuery(@" + await (AssertNestedMixedSubQueriesAsync(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.ord2.ShippedTo, o3.ord2.Customer, o3.ord2.Customer.Address.City, @@ -644,12 +653,12 @@ left join ( from Order o1 left join (from Order) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").ListAsync()); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id")); } - private void AssertNestedMixedSubQueries(IList result) + private async Task AssertNestedMixedSubQueriesAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -677,51 +686,48 @@ private void AssertNestedMixedSubQueries(IList result) public async Task HqlEntitySubQueryWithEntityFetchAsync() { session.Clear(); - var result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithEntityFetchAsync(@" from Order o inner join ( from Order o1 inner join fetch o1.Customer - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); - AssertEntitySubQueryWithEntityFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId", new[] {false, true})); session.Clear(); - result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithEntityFetchAsync(@" select o, o2.ord from Order o inner join ( select o1 as ord, o1.Customer.CustomerId from Order o1 inner join fetch o1.Customer - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.ord.OrderId").ListAsync()); - AssertEntitySubQueryWithEntityFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.ord.OrderId", new[] {false, true})); session.Clear(); - result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithEntityFetchAsync(@" from Order o inner join ( from Order o1 - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.Customer").ListAsync()); - AssertEntitySubQueryWithEntityFetch(result, new[] {true, false}); + inner join fetch o.Customer", new[] {true, false})); session.Clear(); - result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithEntityFetchAsync(@" from Order o inner join ( from Order o1 inner join fetch o1.Customer - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.Customer").ListAsync()); - AssertEntitySubQueryWithEntityFetch(result, new[] {true, true}); + inner join fetch o.Customer", new[] {true, true})); } - private void AssertEntitySubQueryWithEntityFetch(IList result, bool[] fetches) + private async Task AssertEntitySubQueryWithEntityFetchAsync(string query, bool[] fetches, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); var item = result[0]; Assert.That(item, Is.TypeOf()); var array = (object[]) item; @@ -742,51 +748,48 @@ private void AssertEntitySubQueryWithEntityFetch(IList result, bool[] fetches) public async Task HqlEntitySubQueryWithCollectionFetchAsync() { session.Clear(); - var result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithCollectionFetchAsync(@" from Order o inner join ( from Order o1 inner join fetch o1.OrderLines - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); - AssertEntitySubQueryWithCollectionFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId", new[] {false, true})); session.Clear(); - result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithCollectionFetchAsync(@" select o, o2.ord from Order o inner join ( - select o1 as ord, o1.OrderLines.size + select o1 as ord, o1.ShippedTo from Order o1 inner join fetch o1.OrderLines - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.ord.OrderId").ListAsync()); - AssertEntitySubQueryWithCollectionFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.ord.OrderId", new[] {false, true})); session.Clear(); - result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithCollectionFetchAsync(@" from Order o inner join ( from Order o1 - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.OrderLines").ListAsync()); - AssertEntitySubQueryWithCollectionFetch(result, new[] {true, false}); + inner join fetch o.OrderLines", new[] {true, false})); session.Clear(); - result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithCollectionFetchAsync(@" from Order o inner join ( from Order o1 inner join fetch o1.OrderLines - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.OrderLines").ListAsync()); - AssertEntitySubQueryWithCollectionFetch(result, new[] {true, true}); + inner join fetch o.OrderLines", new[] {true, true})); } - private void AssertEntitySubQueryWithCollectionFetch(IList result, bool[] fetches) + private async Task AssertEntitySubQueryWithCollectionFetchAsync(string query, bool[] fetches, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); var item = result[0]; Assert.That(item, Is.TypeOf()); var array = (object[]) item; @@ -807,51 +810,48 @@ private void AssertEntitySubQueryWithCollectionFetch(IList result, bool[] fetche public async Task HqlEntitySubQueryWithCollectionOfValuesFetchAsync() { session.Clear(); - var result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithCollectionOfValuesFetchAsync(@" from Order o inner join ( from Order o1 inner join fetch o1.ProductIds - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").ListAsync()); - AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId", new[] {false, true})); session.Clear(); - result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithCollectionOfValuesFetchAsync(@" select o, o2.ord from Order o inner join ( - select o1 as ord, o1.OrderLines.size + select o1 as ord, o1.ShippedTo from Order o1 inner join fetch o1.ProductIds - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.ord.OrderId").ListAsync()); - AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.ord.OrderId", new[] {false, true})); session.Clear(); - result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithCollectionOfValuesFetchAsync(@" from Order o inner join ( from Order o1 - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.ProductIds").ListAsync()); - AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {true, false}); + inner join fetch o.ProductIds", new[] {true, false})); session.Clear(); - result = await (session.CreateQuery(@" + await (AssertEntitySubQueryWithCollectionOfValuesFetchAsync(@" from Order o inner join ( from Order o1 inner join fetch o1.ProductIds - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.ProductIds").ListAsync()); - AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {true, true}); + inner join fetch o.ProductIds", new[] {true, true})); } - private void AssertEntitySubQueryWithCollectionOfValuesFetch(IList result, bool[] fetches) + private async Task AssertEntitySubQueryWithCollectionOfValuesFetchAsync(string query, bool[] fetches, CancellationToken cancellationToken = default(CancellationToken)) { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); var item = result[0]; Assert.That(item, Is.TypeOf()); var array = (object[]) item; @@ -865,5 +865,86 @@ private void AssertEntitySubQueryWithCollectionOfValuesFetch(IList result, bool[ } #endregion + + #region HqlDuplicateEntitySelectionSubQuery + + [Test] + public async Task HqlDuplicateEntitySelectionSubQueryAsync() + { + await (AssertDuplicateEntitySelectionSubQueryAsync(@" + from Order o + inner join ( + select o1 as ord1, o1 as ord2 + from Order o1 + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.ord1.OrderId")); + + await (AssertDuplicateEntitySelectionSubQueryAsync(@" + select o, o2.ord1, o2.ord2 + from Order o + inner join ( + select o1 as ord1, o1 as ord2 + from Order o1 + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.ord1.OrderId")); + + await (AssertDuplicateEntitySelectionSubQueryAsync(@" + select o, o2, o2 + from Order o + inner join ( + select o1 + from Order o1 + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId")); + + await (AssertDuplicateEntitySelectionSubQueryAsync(@" + select o, o2, o2 + from Order o + inner join ( + from Order + where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId")); + } + + private async Task AssertDuplicateEntitySelectionSubQueryAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) + { + IList result; + using (var logSpy = new SqlLogSpy()) + { + result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); + AssertDuplicateEntitySelectionSubQuery(logSpy.GetWholeLog(), result, false); + } + + using (var logSpy = new SqlLogSpy()) + { + result = (await (session.CreateQuery(query).SetParameter("id", 10248).EnumerableAsync(cancellationToken))).OfType().ToList(); + AssertDuplicateEntitySelectionSubQuery(logSpy.GetWholeLog(), result, true); + } + } + + private void AssertDuplicateEntitySelectionSubQuery(string sql, IList result, bool shallow) + { + var selectSql = sql.Substring(0, sql.IndexOf("from")); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(3)); + if (shallow) + { + Assert.That(GetTotalOccurrences(selectSql, ","), Is.EqualTo(1)); + Assert.That(array[0], Is.AssignableFrom().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.AssignableFrom().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[1], Is.AssignableFrom().And.Property("OrderId").EqualTo(10248)); + } + else + { + Assert.That(GetTotalOccurrences(selectSql, ","), Is.EqualTo(27)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + } + } + + #endregion } } diff --git a/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs index 990dd3332ea..2f1d361ed3b 100644 --- a/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs @@ -14,24 +14,16 @@ public class JoinSubqueryTests : LinqTestCase [Test] public void HqlEntitySubQuery() { - var result = session.CreateQuery("from Order o inner join (from Order where OrderId = 10248) o2 on (o.OrderId - 1) = o2.OrderId").List(); - AssertEntitySubQuery(result); - - result = session.CreateQuery("from Order o inner join (from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").List(); - AssertEntitySubQuery(result); - - result = session.CreateQuery("from Order o inner join (select o2 from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").List(); - AssertEntitySubQuery(result); - - result = session.CreateQuery("select o, o3 from Order o inner join (select o2 from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").List(); - AssertEntitySubQuery(result); - - result = session.CreateQuery("select o, o3 from Order o inner join (from Order o2 where o2.OrderId = 10248) o3 on (o.OrderId - 1) = o3.OrderId").List(); - AssertEntitySubQuery(result); + AssertEntitySubQuery("from Order o inner join (from Order where OrderId = :id) o2 on (o.OrderId - 1) = o2.OrderId"); + AssertEntitySubQuery("from Order o inner join (from Order o2 where o2.OrderId = :id) o3 on (o.OrderId - 1) = o3.OrderId"); + AssertEntitySubQuery("from Order o inner join (select o2 from Order o2 where o2.OrderId = :id) o3 on (o.OrderId - 1) = o3.OrderId"); + AssertEntitySubQuery("select o, o3 from Order o inner join (select o2 from Order o2 where o2.OrderId = :id) o3 on (o.OrderId - 1) = o3.OrderId"); + AssertEntitySubQuery("select o, o3 from Order o inner join (from Order o2 where o2.OrderId = :id) o3 on (o.OrderId - 1) = o3.OrderId"); } - private void AssertEntitySubQuery(IList result) + private void AssertEntitySubQuery(string query) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -48,23 +40,24 @@ private void AssertEntitySubQuery(IList result) [Test] public void HqlScalarSubQuery() { - var result = session.CreateQuery(@" + AssertScalarSubQuery(@" select o.Customer.CustomerId, o.ShippedTo, o2 - from Order o inner join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").List(); - AssertScalarSubQuery(result); + from Order o + inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId"); - result = session.CreateQuery(@" + AssertScalarSubQuery(@" select o.Customer.CustomerId, o.ShippedTo, o2.OrderId, o2.CustomerId, o2.ShippedTo - from Order o inner join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").List(); - AssertScalarSubQuery(result); + from Order o + inner join ( + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId"); } - private void AssertScalarSubQuery(IList result) + private void AssertScalarSubQuery(string query) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -84,23 +77,22 @@ private void AssertScalarSubQuery(IList result) [Test] public void HqlIdSubQuery() { - var result = session.CreateQuery(@" + AssertIdSubQuery(@" select o from Order o inner join ( - select id from Order where OrderId = 10248 - ) o2 on o.id = o2.id").List(); - AssertIdSubQuery(result); + select id from Order where OrderId = :id + ) o2 on o.id = o2.id"); - result = session.CreateQuery(@" + AssertIdSubQuery(@" select o from CompositeOrder o inner join ( - select id from CompositeOrder where OrderId = 10248 - ) o2 on o.id = o2.id").List(); - AssertIdSubQuery(result); + select id from CompositeOrder where OrderId = :id + ) o2 on o.id = o2.id"); } - private void AssertIdSubQuery(IList result) + private void AssertIdSubQuery(string query) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -139,23 +131,22 @@ private void AssertSubclassSubQuery(IList result) [Test] public void HqlMixedSubQuery() { - var result = session.CreateQuery(@" + AssertMixedSubQuery(@" select o, o2, o.Customer.CustomerId, o.ShippedTo from Order o inner join ( - select OrderId, Customer, ShippedTo from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").List(); - AssertMixedSubQuery(result); + select OrderId, Customer, ShippedTo from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId"); - result = session.CreateQuery(@" + AssertMixedSubQuery(@" select o, o2.OrderId, o2.cu, o2.ShippedTo, o.Customer.CustomerId, o.ShippedTo from Order o inner join ( - select OrderId, Customer as cu, ShippedTo from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").List(); - AssertMixedSubQuery(result); + select OrderId, Customer as cu, ShippedTo from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId"); } - private void AssertMixedSubQuery(IList result) + private void AssertMixedSubQuery(string query) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -176,16 +167,16 @@ private void AssertMixedSubQuery(IList result) [Test] public void HqlSubQueryComponentPropertySelection() { - var result = session.CreateQuery(@" + AssertSubQueryComponentPropertySelection(@" select o2.cu.Address.Street, o2.emp.Address.Street from Order o inner join ( - select OrderId, Customer as cu, Employee as emp from Order where OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").List(); - AssertSubQueryComponentPropertySelection(result); + select OrderId, Customer as cu, Employee as emp from Order where OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId"); } - private void AssertSubQueryComponentPropertySelection(IList result) + private void AssertSubQueryComponentPropertySelection(string query) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -202,23 +193,35 @@ private void AssertSubQueryComponentPropertySelection(IList result) [Test] public void HqlIdSubQueryWithPagingAndOrderBy() { - var result = session.CreateQuery(@" + AssertIdSubQueryWithPagingAndOrderBy(@" select o from Order o inner join ( - select id from Order order by OrderId skip 2 take 2 - ) o2 on o.id = o2.id").List(); - AssertIdSubQueryWithPagingAndOrderBy(result); + select id from Order order by OrderId skip :s take :t + ) o2 on o.id = o2.id"); - result = session.CreateQuery(@" + AssertIdSubQueryWithPagingAndOrderBy(@" select o from CompositeOrder o inner join ( - select id from CompositeOrder order by OrderId skip 2 take 2 - ) o2 on o.id = o2.id").List(); - AssertIdSubQueryWithPagingAndOrderBy(result); + select id from CompositeOrder order by OrderId skip :s take :t + ) o2 on o.id = o2.id"); } - private void AssertIdSubQueryWithPagingAndOrderBy(IList result) + private void AssertIdSubQueryWithPagingAndOrderBy(string query) { + IList result; + if (Sfi.Dialect.SupportsVariableLimit) + { + result = session.CreateQuery(query) + .SetParameter("s", 2) + .SetParameter("t", 2) + .List(); + } + else + { + query = query.Replace(":s", "2").Replace(":t", "2"); + result = session.CreateQuery(query).List(); + } + Assert.That(result, Has.Count.EqualTo(2)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -235,25 +238,24 @@ private void AssertIdSubQueryWithPagingAndOrderBy(IList result) [Test] public void HqlGroupBySubQueryWithAliases() { - var result = session.CreateQuery(@" + AssertGroupBySubQueryWithAliases(@" select o, o2 from Order o inner join ( select Customer.CustomerId, count(*) as total, max(OrderId) as orderId from Order group by Customer.CustomerId ) o2 on o.id = o2.orderId - where o2.total > 30").List(); - AssertGroupBySubQueryWithAliases(result); + where o2.total > :total"); - result = session.CreateQuery(@" + AssertGroupBySubQueryWithAliases(@" select o, o2.CustomerId, o2.total, o2.orderId from Order o inner join ( select Customer.CustomerId, count(*) as total, max(OrderId) as orderId from Order group by Customer.CustomerId ) o2 on o.id = o2.orderId - where o2.total > 30").List(); - AssertGroupBySubQueryWithAliases(result); + where o2.total > :total"); } - private void AssertGroupBySubQueryWithAliases(IList result) + private void AssertGroupBySubQueryWithAliases(string query) { + var result = session.CreateQuery(query).SetParameter("total", 30).List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -272,16 +274,16 @@ private void AssertGroupBySubQueryWithAliases(IList result) [Test] public void HqlSubQueryWithEntityAlias() { - var result = session.CreateQuery(@" + AssertSubQueryWithEntityAlias(@" select o, o3.order.OrderId, o3.customer.CustomerId, o3.ShippedTo from Order o inner join ( - select o2 as order, o2.Customer as customer, o2.ShippedTo from Order o2 where o2.OrderId = 10248 - ) o3 on (o.OrderId - 1) = o3.order.OrderId").List(); - AssertSubQueryWithEntityAlias(result); + select o2 as order, o2.Customer as customer, o2.ShippedTo from Order o2 where o2.OrderId = :id + ) o3 on (o.OrderId - 1) = o3.order.OrderId"); } - private void AssertSubQueryWithEntityAlias(IList result) + private void AssertSubQueryWithEntityAlias(string query) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -300,30 +302,32 @@ private void AssertSubQueryWithEntityAlias(IList result) [Test] public void HqlMultipleEntitySubQueries() { - var result = session.CreateQuery(@" + AssertMultipleEntitySubQueries(@" from Order o inner join ( - from Order where OrderId = 10248 + from Order where OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId left join ( - from Order where OrderId = 10250 - ) o3 on (o3.OrderId - 2) = o2.OrderId").List(); - AssertMultipleEntitySubQueries(result); + from Order where OrderId = :id2 + ) o3 on (o3.OrderId - 2) = o2.OrderId"); - result = session.CreateQuery(@" + AssertMultipleEntitySubQueries(@" select o, o2, o3 from Order o inner join ( - from Order where OrderId = 10248 + from Order where OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId left join ( - from Order where OrderId = 10250 - ) o3 on (o3.OrderId - 2) = o2.OrderId").List(); - AssertMultipleEntitySubQueries(result); + from Order where OrderId = :id2 + ) o3 on (o3.OrderId - 2) = o2.OrderId"); } - private void AssertMultipleEntitySubQueries(IList result) + private void AssertMultipleEntitySubQueries(string query) { + var result = session.CreateQuery(query) + .SetParameter("id", 10248) + .SetParameter("id2", 10250) + .List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -341,30 +345,32 @@ private void AssertMultipleEntitySubQueries(IList result) [Test] public void HqlMultipleScalarSubQueries() { - var result = session.CreateQuery(@" + AssertMultipleScalarSubQueries(@" from Order o inner join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId left join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10250 - ) o3 on (o3.OrderId - 2) = o2.OrderId").List(); - AssertMultipleScalarSubQueries(result); + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id2 + ) o3 on (o3.OrderId - 2) = o2.OrderId"); - result = session.CreateQuery(@" + AssertMultipleScalarSubQueries(@" select o, o2, o3 from Order o inner join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10248 + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId left join ( - select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = 10250 - ) o3 on (o3.OrderId - 2) = o2.OrderId").List(); - AssertMultipleScalarSubQueries(result); + select OrderId, Customer.CustomerId, ShippedTo from Order where OrderId = :id2 + ) o3 on (o3.OrderId - 2) = o2.OrderId"); } - private void AssertMultipleScalarSubQueries(IList result) + private void AssertMultipleScalarSubQueries(string query) { + var result = session.CreateQuery(query) + .SetParameter("id", 10248) + .SetParameter("id2", 10250) + .List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -386,7 +392,7 @@ private void AssertMultipleScalarSubQueries(IList result) [Test] public void HqlNestedEntitySubQueries() { - var result = session.CreateQuery(@" + AssertNestedEntitySubQueries(@" from Order o left join ( select e as emp, e2 as sup @@ -394,10 +400,9 @@ from Employee e left join (from Employee) e2 on e.Superior = e2 ) o3 on o.Employee = o3.emp order by o.id - take 1").List(); - AssertNestedEntitySubQueries(result); + take :t"); - result = session.CreateQuery(@" + AssertNestedEntitySubQueries(@" select o, o3 from Order o left join ( @@ -406,10 +411,9 @@ from Employee e left join (from Employee) e2 on e.Superior = e2 ) o3 on o.Employee = o3.emp order by o.id - take 1").List(); - AssertNestedEntitySubQueries(result); + take :t"); - result = session.CreateQuery(@" + AssertNestedEntitySubQueries(@" select o, o3.emp, o3.sup from Order o left join ( @@ -418,12 +422,24 @@ from Employee e left join (from Employee) e2 on e.Superior = e2 ) o3 on o.Employee = o3.emp order by o.id - take 1").List(); - AssertNestedEntitySubQueries(result); + take :t"); } - private void AssertNestedEntitySubQueries(IList result) + private void AssertNestedEntitySubQueries(string query) { + IList result; + if (Sfi.Dialect.SupportsVariableLimit) + { + result = session.CreateQuery(query) + .SetParameter("t", 1) + .List(); + } + else + { + query = query.Replace(":t", "1"); + result = session.CreateQuery(query).List(); + } + Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -441,7 +457,7 @@ private void AssertNestedEntitySubQueries(IList result) [Test] public void HqlNestedScalarSubQueries() { - var result = session.CreateQuery(@" + AssertNestedScalarSubQueries(@" select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3 as ord3 from Order o left join ( @@ -452,10 +468,9 @@ left join ( from Order ) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.OrderId-1 - where o.OrderId = 10248").List(); - AssertNestedScalarSubQueries(result); + where o.OrderId = :id"); - result = session.CreateQuery(@" + AssertNestedScalarSubQueries(@" select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3 as ord3 from Order o left join ( @@ -468,10 +483,9 @@ left join ( from Order ) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.OrderId-1 - where o.OrderId = 10248").List(); - AssertNestedScalarSubQueries(result); + where o.OrderId = :id"); - result = session.CreateQuery(@" + AssertNestedScalarSubQueries(@" select o.OrderId, o.ShippedTo, o.Customer.CustomerId, o.ShippingAddress.City, o3.OrderId, o3.ShippedTo, o3.CustomerId, o3.City, o3.oid, o3.sto, o3.cuid, o3.cty @@ -486,12 +500,12 @@ left join ( from Order ) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.OrderId-1 - where o.OrderId = 10248").List(); - AssertNestedScalarSubQueries(result); + where o.OrderId = :id"); } - private void AssertNestedScalarSubQueries(IList result) + private void AssertNestedScalarSubQueries(string query) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -518,7 +532,7 @@ private void AssertNestedScalarSubQueries(IList result) [Test] public void HqlNestedMixedSubQueries() { - var result = session.CreateQuery(@" + AssertNestedMixedSubQueries(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3 from Order o left join ( @@ -529,10 +543,9 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").List(); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id"); - result = session.CreateQuery(@" + AssertNestedMixedSubQueries(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.sto2, o3.cu2, o3.cty2, o3.sub1 @@ -545,10 +558,9 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").List(); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id"); - result = session.CreateQuery(@" + AssertNestedMixedSubQueries(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.sto2, o3.cu2, o3.cty2, @@ -562,10 +574,9 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").List(); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id"); - result = session.CreateQuery(@" + AssertNestedMixedSubQueries(@" select o4.ord3, o4.ord3.ShippedTo, o4.cu3, o4.ord3.ShippingAddress.City, o4.sub2.ord2, o4.sub2.ord2.ShippedTo, o4.sub2.cu2, o4.sub2.cu2.Address.City, @@ -583,12 +594,10 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248 - ) o4 on o5.OrderId = o4.ord3.OrderId - ").List(); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id + ) o4 on o5.OrderId = o4.ord3.OrderId"); - result = session.CreateQuery(@" + AssertNestedMixedSubQueries(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.sto2, o3.cu2, o3.cty2, @@ -604,10 +613,9 @@ left join ( from Order o0 ) o2 on o1.OrderId = o2.ord1.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").List(); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id"); - result = session.CreateQuery(@" + AssertNestedMixedSubQueries(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.sto2, o3.cu2, o3.cty2, @@ -620,10 +628,9 @@ left join ( from Order o1 left join (from Order) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").List(); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id"); - result = session.CreateQuery(@" + AssertNestedMixedSubQueries(@" select o, o.ShippedTo, o.Customer, o.ShippingAddress.City, o3.ord2, o3.ord2.ShippedTo, o3.ord2.Customer, o3.ord2.Customer.Address.City, @@ -634,12 +641,12 @@ left join ( from Order o1 left join (from Order) o2 on o1.OrderId = o2.OrderId-1 ) o3 on o.OrderId = o3.ord2.OrderId-1 - where o.OrderId = 10248").List(); - AssertNestedMixedSubQueries(result); + where o.OrderId = :id"); } - private void AssertNestedMixedSubQueries(IList result) + private void AssertNestedMixedSubQueries(string query) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); Assert.That(result, Has.Count.EqualTo(1)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -667,51 +674,48 @@ private void AssertNestedMixedSubQueries(IList result) public void HqlEntitySubQueryWithEntityFetch() { session.Clear(); - var result = session.CreateQuery(@" + AssertEntitySubQueryWithEntityFetch(@" from Order o inner join ( from Order o1 inner join fetch o1.Customer - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").List(); - AssertEntitySubQueryWithEntityFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId", new[] {false, true}); session.Clear(); - result = session.CreateQuery(@" + AssertEntitySubQueryWithEntityFetch(@" select o, o2.ord from Order o inner join ( select o1 as ord, o1.Customer.CustomerId from Order o1 inner join fetch o1.Customer - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.ord.OrderId").List(); - AssertEntitySubQueryWithEntityFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.ord.OrderId", new[] {false, true}); session.Clear(); - result = session.CreateQuery(@" + AssertEntitySubQueryWithEntityFetch(@" from Order o inner join ( from Order o1 - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.Customer").List(); - AssertEntitySubQueryWithEntityFetch(result, new[] {true, false}); + inner join fetch o.Customer", new[] {true, false}); session.Clear(); - result = session.CreateQuery(@" + AssertEntitySubQueryWithEntityFetch(@" from Order o inner join ( from Order o1 inner join fetch o1.Customer - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.Customer").List(); - AssertEntitySubQueryWithEntityFetch(result, new[] {true, true}); + inner join fetch o.Customer", new[] {true, true}); } - private void AssertEntitySubQueryWithEntityFetch(IList result, bool[] fetches) + private void AssertEntitySubQueryWithEntityFetch(string query, bool[] fetches) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); var item = result[0]; Assert.That(item, Is.TypeOf()); var array = (object[]) item; @@ -732,51 +736,48 @@ private void AssertEntitySubQueryWithEntityFetch(IList result, bool[] fetches) public void HqlEntitySubQueryWithCollectionFetch() { session.Clear(); - var result = session.CreateQuery(@" + AssertEntitySubQueryWithCollectionFetch(@" from Order o inner join ( from Order o1 inner join fetch o1.OrderLines - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").List(); - AssertEntitySubQueryWithCollectionFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId", new[] {false, true}); session.Clear(); - result = session.CreateQuery(@" + AssertEntitySubQueryWithCollectionFetch(@" select o, o2.ord from Order o inner join ( select o1 as ord, o1.ShippedTo from Order o1 inner join fetch o1.OrderLines - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.ord.OrderId").List(); - AssertEntitySubQueryWithCollectionFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.ord.OrderId", new[] {false, true}); session.Clear(); - result = session.CreateQuery(@" + AssertEntitySubQueryWithCollectionFetch(@" from Order o inner join ( from Order o1 - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.OrderLines").List(); - AssertEntitySubQueryWithCollectionFetch(result, new[] {true, false}); + inner join fetch o.OrderLines", new[] {true, false}); session.Clear(); - result = session.CreateQuery(@" + AssertEntitySubQueryWithCollectionFetch(@" from Order o inner join ( from Order o1 inner join fetch o1.OrderLines - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.OrderLines").List(); - AssertEntitySubQueryWithCollectionFetch(result, new[] {true, true}); + inner join fetch o.OrderLines", new[] {true, true}); } - private void AssertEntitySubQueryWithCollectionFetch(IList result, bool[] fetches) + private void AssertEntitySubQueryWithCollectionFetch(string query, bool[] fetches) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); var item = result[0]; Assert.That(item, Is.TypeOf()); var array = (object[]) item; @@ -797,51 +798,48 @@ private void AssertEntitySubQueryWithCollectionFetch(IList result, bool[] fetche public void HqlEntitySubQueryWithCollectionOfValuesFetch() { session.Clear(); - var result = session.CreateQuery(@" + AssertEntitySubQueryWithCollectionOfValuesFetch(@" from Order o inner join ( from Order o1 inner join fetch o1.ProductIds - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.OrderId").List(); - AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.OrderId", new[] {false, true}); session.Clear(); - result = session.CreateQuery(@" + AssertEntitySubQueryWithCollectionOfValuesFetch(@" select o, o2.ord from Order o inner join ( select o1 as ord, o1.ShippedTo from Order o1 inner join fetch o1.ProductIds - where o1.OrderId = 10248 - ) o2 on (o.OrderId - 1) = o2.ord.OrderId").List(); - AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {false, true}); + where o1.OrderId = :id + ) o2 on (o.OrderId - 1) = o2.ord.OrderId", new[] {false, true}); session.Clear(); - result = session.CreateQuery(@" + AssertEntitySubQueryWithCollectionOfValuesFetch(@" from Order o inner join ( from Order o1 - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.ProductIds").List(); - AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {true, false}); + inner join fetch o.ProductIds", new[] {true, false}); session.Clear(); - result = session.CreateQuery(@" + AssertEntitySubQueryWithCollectionOfValuesFetch(@" from Order o inner join ( from Order o1 inner join fetch o1.ProductIds - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId - inner join fetch o.ProductIds").List(); - AssertEntitySubQueryWithCollectionOfValuesFetch(result, new[] {true, true}); + inner join fetch o.ProductIds", new[] {true, true}); } - private void AssertEntitySubQueryWithCollectionOfValuesFetch(IList result, bool[] fetches) + private void AssertEntitySubQueryWithCollectionOfValuesFetch(string query, bool[] fetches) { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); var item = result[0]; Assert.That(item, Is.TypeOf()); var array = (object[]) item; @@ -866,7 +864,7 @@ from Order o inner join ( select o1 as ord1, o1 as ord2 from Order o1 - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.ord1.OrderId"); AssertDuplicateEntitySelectionSubQuery(@" @@ -875,7 +873,7 @@ from Order o inner join ( select o1 as ord1, o1 as ord2 from Order o1 - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.ord1.OrderId"); AssertDuplicateEntitySelectionSubQuery(@" @@ -884,7 +882,7 @@ from Order o inner join ( select o1 from Order o1 - where o1.OrderId = 10248 + where o1.OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId"); AssertDuplicateEntitySelectionSubQuery(@" @@ -892,7 +890,7 @@ from Order o1 from Order o inner join ( from Order - where OrderId = 10248 + where OrderId = :id ) o2 on (o.OrderId - 1) = o2.OrderId"); } @@ -901,13 +899,13 @@ private void AssertDuplicateEntitySelectionSubQuery(string query) IList result; using (var logSpy = new SqlLogSpy()) { - result = session.CreateQuery(query).List(); + result = session.CreateQuery(query).SetParameter("id", 10248).List(); AssertDuplicateEntitySelectionSubQuery(logSpy.GetWholeLog(), result, false); } using (var logSpy = new SqlLogSpy()) { - result = session.CreateQuery(query).Enumerable().OfType().ToList(); + result = session.CreateQuery(query).SetParameter("id", 10248).Enumerable().OfType().ToList(); AssertDuplicateEntitySelectionSubQuery(logSpy.GetWholeLog(), result, true); } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs index d1330926268..f9b33dc056b 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs @@ -57,7 +57,9 @@ private IType CreateDataType(SelectClause selectClause, out IEntityPersister ent public SqlString RenderText(SqlString subQuery, ISessionFactoryImplementor sessionFactory) { - return RenderText(sessionFactory).Replace("{query}", subQuery.ToString()); + var array = RenderText(sessionFactory).Split("{query}"); + + return new SqlString(new object[] {array[0], subQuery, array[1]}); } public override string GetIdentityColumn() From bf80075e9c3e7b22ac49cd9edf4ab9e51dc0977c Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 19 Sep 2020 23:09:12 +0200 Subject: [PATCH 04/17] Code review changes --- .../Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs index f9b33dc056b..ae5918f7654 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs @@ -57,9 +57,12 @@ private IType CreateDataType(SelectClause selectClause, out IEntityPersister ent public SqlString RenderText(SqlString subQuery, ISessionFactoryImplementor sessionFactory) { - var array = RenderText(sessionFactory).Split("{query}"); - - return new SqlString(new object[] {array[0], subQuery, array[1]}); + var renderText = RenderText(sessionFactory); + var index = renderText.IndexOfOrdinal("{query}"); + return new SqlString( + renderText.Substring(0, index), + subQuery, + renderText.Substring(index + 7)); } public override string GetIdentityColumn() From e80ee9717526793f541ddb8cf6c413dbf65b9849 Mon Sep 17 00:00:00 2001 From: maca88 Date: Sun, 27 Sep 2020 00:53:27 +0200 Subject: [PATCH 05/17] Fix dependent association joins --- .../Async/Linq/ByMethod/JoinSubqueryTests.cs | 25 ++++++++++++++++ .../Linq/ByMethod/JoinSubqueryTests.cs | 25 ++++++++++++++++ src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs | 7 +++++ src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g | 2 ++ .../Hql/Ast/ANTLR/Tree/FromClause.cs | 29 ++++++++++++++++++- .../Hql/Ast/ANTLR/Tree/FromElement.cs | 2 ++ 6 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs index f6addaa6529..0a67d8912e7 100644 --- a/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs +++ b/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs @@ -946,5 +946,30 @@ private void AssertDuplicateEntitySelectionSubQuery(string sql, IList result, bo } #endregion + + #region DependentEntityJoin + + [Test] + public async Task HqlDependentEntityJoinAsync() + { + await (AssertDependentEntityJoinAsync(@"from Order o +inner join (from Order where OrderId = :id) o2 on (o.OrderId - 1) = o2.OrderId +left join o.Employee e on e = o2.Employee")); + } + + private async Task AssertDependentEntityJoinAsync(string query, CancellationToken cancellationToken = default(CancellationToken)) + { + var result = await (session.CreateQuery(query).SetParameter("id", 10248).ListAsync(cancellationToken)); + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(3)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[2], Is.Null); + } + + #endregion } } diff --git a/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs index 2f1d361ed3b..1639c8fe6df 100644 --- a/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs @@ -934,5 +934,30 @@ private void AssertDuplicateEntitySelectionSubQuery(string sql, IList result, bo } #endregion + + #region DependentEntityJoin + + [Test] + public void HqlDependentEntityJoin() + { + AssertDependentEntityJoin(@"from Order o +inner join (from Order where OrderId = :id) o2 on (o.OrderId - 1) = o2.OrderId +left join o.Employee e on e = o2.Employee"); + } + + private void AssertDependentEntityJoin(string query) + { + var result = session.CreateQuery(query).SetParameter("id", 10248).List(); + Assert.That(result, Has.Count.EqualTo(1)); + var item = result[0]; + Assert.That(item, Is.TypeOf()); + var array = (object[]) item; + Assert.That(array, Has.Length.EqualTo(3)); + Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); + Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); + Assert.That(array[2], Is.Null); + } + + #endregion } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index d96c1dfc990..cdee294116c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -1320,6 +1320,7 @@ private void HandleWithFragment(FromElement fromElement, IASTNode hqlWithNode) sql.whereExpr(); fromElement.WithClauseFragment = new SqlString("(", sql.GetSQL(), ")"); + fromElement.WithClauseFromElements = visitor.FromElements; } catch (SemanticException) { @@ -1345,6 +1346,8 @@ public WithClauseVisitor(FromElement fromElement) _joinFragment = fromElement; } + internal HashSet FromElements { get; } = new HashSet(); + public void Visit(IASTNode node) { if (node is ParameterNode paramNode) @@ -1355,6 +1358,10 @@ public void Visit(IASTNode node) { ApplyParameterSpecifications(paramContainer); } + else if (node is ISelectExpression selectExpression && selectExpression.FromElement != null) + { + FromElements.Add(selectExpression.FromElement); + } } private void ApplyParameterSpecifications(IParameterContainer parameterContainer) diff --git a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g index 8f1a351d357..dc83f96e55b 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g +++ b/src/NHibernate/Hql/Ast/ANTLR/SqlGenerator.g @@ -195,6 +195,8 @@ fromTable tableJoin [ IASTNode parent ] : ^( c=JOIN_FRAGMENT { Out(" "); Out($c); } (tableJoin [ c ] )* ) | ^( d=FROM_FRAGMENT { NestedFromFragment($d,parent); } (tableJoin [ d ] )* ) + | ^( e=ENTITY_JOIN { Out(" "); Out(e); } (tableJoin [ e ])* ) + | ^( f=JOIN_SUBQUERY { Out(" "); StartJoinSubquery(); } selectStatement { EndJoinSubquery(f); } (tableJoin [ f ])* ) ; booleanOp[ bool parens ] diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs index faafc1e08c4..39179f1419a 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs @@ -436,9 +436,36 @@ internal void FinishInit() { foreach (var item in _appendFromElements) { - AddChild(item); + var dependentElement = GetFirstDependentFromElement(item); + if (dependentElement == null) + { + AddChild(item); + } + else + { + var index = dependentElement.ChildIndex; + dependentElement.Parent.InsertChild(index, item); + } } _appendFromElements.Clear(); } + + private FromElement GetFirstDependentFromElement(FromElement element) + { + foreach (var fromElement in _fromElements) + { + if (fromElement == element || + fromElement.WithClauseFromElements?.Contains(element) != true || + // Parent will be null for entity and subquery joins + fromElement.Parent == null) + { + continue; + } + + return fromElement; + } + + return null; + } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs index 5ec88bf378c..7fe367489e3 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs @@ -330,6 +330,8 @@ public SqlString WithClauseFragment set { _withClauseFragment = value; } } + internal HashSet WithClauseFromElements { get; set; } + // Since 5.4 [Obsolete("This method has no more usages and will be removed in a future version.")] public string WithClauseJoinAlias From 67f05f3170a72870dfb748eeaf3073b8e09e663a Mon Sep 17 00:00:00 2001 From: maca88 Date: Mon, 28 Sep 2020 19:50:21 +0200 Subject: [PATCH 06/17] Code review changes. --- .../Hql/Ast/ANTLR/Tree/FromElement.cs | 22 +- .../Ast/ANTLR/Tree/JoinSubqueryFromElement.cs | 31 ++- .../Hql/Ast/ANTLR/Tree/SelectClause.cs | 194 ++++++++++-------- .../Collection/IQueryableCollection.cs | 8 +- .../Entity/SubqueryPropertyMapping.cs | 31 ++- 5 files changed, 157 insertions(+), 129 deletions(-) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs index 7fe367489e3..4c65989d0c7 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs @@ -584,8 +584,12 @@ public IType GetPropertyType(string propertyName, string propertyPath) public virtual string GetIdentityColumn() { var cols = GetIdentityColumns(); - string result = string.Join(", ", cols); + if (cols == null) + { + return null; + } + string result = string.Join(", ", cols); if (cols.Length > 1 && Walker.IsComparativeExpressionClause) { return "(" + result + ")"; @@ -603,10 +607,14 @@ internal string[] GetIdentityColumns() { throw new InvalidOperationException("No table alias for node " + this); } - string[] cols; + + return GetIdentityColumns(table); + } + + internal virtual string[] GetIdentityColumns(string alias) + { string propertyName; - if (EntityPersister != null && EntityPersister.EntityMetamodel != null - && EntityPersister.EntityMetamodel.HasNonIdentifierPropertyNamedId) + if (EntityPersister?.EntityMetamodel?.HasNonIdentifierPropertyNamedId == true) { propertyName = EntityPersister.IdentifierPropertyName; } @@ -619,11 +627,9 @@ internal string[] GetIdentityColumns() propertyName = NHibernate.Persister.Entity.EntityPersister.EntityID; } - cols = UseTableAliases - ? GetPropertyMapping(propertyName).ToColumns(table, propertyName) + return UseTableAliases + ? GetPropertyMapping(propertyName).ToColumns(alias, propertyName) : GetPropertyMapping(propertyName).ToColumns(propertyName); - - return cols; } internal bool UseTableAliases => Walker.StatementType == HqlSqlWalker.SELECT || Walker.IsSubQuery; diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs index ae5918f7654..d2981a3a7bc 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using Antlr.Runtime; using NHibernate.Engine; using NHibernate.Persister.Entity; @@ -19,9 +21,10 @@ public JoinSubqueryFromElement(FromClause fromClause, QueryNode queryNode, JoinT QueryNode = queryNode; DataType = dataType; foreach (var fromElement in querySelectClause.SelectExpressions.Select(o => o.FromElement) - .Union(querySelectClause.NonScalarExpressions?.Select(o => o.FromElement) ?? Enumerable.Empty()) - .Union(querySelectClause.CollectionFromElements ?? Enumerable.Empty()) - .Where(o => o != null)) + .Concat(querySelectClause.NonScalarExpressions?.Select(o => o.FromElement) ?? Enumerable.Empty()) + .Concat(querySelectClause.CollectionFromElements ?? Enumerable.Empty()) + .Where(o => o != null) + .Distinct()) { fromElement.ParentFromElement = this; } @@ -65,16 +68,24 @@ public SqlString RenderText(SqlString subQuery, ISessionFactoryImplementor sessi renderText.Substring(index + 7)); } - public override string GetIdentityColumn() + internal List GetRelatedSelectExpressions(DotNode dotNode, out SelectClause selectClause) { - // Return null for a scalar subquery instead of throwing an exception. - // The node will be removed in the SelectClause - if (DataType is SubqueryComponentType && PropertyMapping.GetIdentifiersColumns(TableAlias).Count == 0) + return PropertyMapping.GetRelatedSelectExpressions(dotNode.PropertyPath, out selectClause); + } + + internal override string[] GetIdentityColumns(string alias) + { + if (DataType is SubqueryComponentType) { - return null; + var idColumns = PropertyMapping.GetIdentifiersColumns(alias); + // Return null for a scalar subquery instead of throwing an exception. + // The node will be removed in the SelectClause + return idColumns.Count == 0 + ? null + : idColumns.ToArray(); } - return base.GetIdentityColumn(); + return base.GetIdentityColumns(alias); } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs index d222a66146c..ea7c85694a8 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs @@ -67,14 +67,15 @@ public void InitializeDerivedSelectClause(FromClause fromClause) _derivedSelectExpressions = new HashSet(); foreach (FromElement fromElement in fromElements) { - if (fromElement.SelectType == null || fromElement.IsFetch || fromElement.IsCollectionOfValuesOrComponents) + IType type; + if (fromElement.IsFetch || fromElement.IsCollectionOfValuesOrComponents || ((type = fromElement.SelectType) == null)) { continue; } var node = (IdentNode)appender.Append(HqlSqlWalker.IDENT, fromElement.ClassAlias ?? "", true); node.FromElement = fromElement; - node.DataType = fromElement.SelectType; + node.DataType = type; _derivedSelectExpressions.Add(node); } @@ -112,7 +113,8 @@ public void InitializeExplicitSelectClause(FromClause fromClause) { _constructorNode = (ConstructorNode) expr; _scalarSelect = true; - NonScalarExpressions?.AddRange(_constructorNode.GetSelectExpressions(true, o => !o.IsScalar)); + SelectExpressions.RemoveAt(i); + NonScalarExpressions.AddRange(_constructorNode.GetSelectExpressions(true, o => !o.IsScalar)); foreach (var argumentExpression in _constructorNode.GetSelectExpressions()) { SelectExpressions.Insert(i, argumentExpression); @@ -120,61 +122,13 @@ public void InitializeExplicitSelectClause(FromClause fromClause) AddExpression(argumentExpression, queryReturnTypeList); } - SelectExpressions.Remove(expr); - length = SelectExpressions.Count; i--; + length = SelectExpressions.Count; } - else if (expr.FromElement is JoinSubqueryFromElement joinSubquery) + else if (expr.FromElement is JoinSubqueryFromElement joinSubquery && + TryProcessSubqueryExpressions(expr, joinSubquery, out var selectClause, out var subqueryExpressions)) { - SelectClause selectClause; - List subqueryExpressions; - if (expr is IdentNode) - { - selectClause = joinSubquery.QueryNode.GetSelectClause(); - subqueryExpressions = selectClause.SelectExpressions; - NonScalarExpressions.Add(expr); - } - else if (expr is DotNode dotNode) - { - var relatedExpressions = joinSubquery.PropertyMapping.GetRelatedSelectExpressions(dotNode.PropertyPath, out selectClause); - if (relatedExpressions == null) - { - if (!expr.IsScalar) - { - NonScalarExpressions.Add(expr); - } - - AddExpression(expr, queryReturnTypeList); - continue; - } - - if (!selectClause.IsScalarSelect) - { - RemoveChildAndUnsetParent((IASTNode) expr); - } - - subqueryExpressions = new List(); - foreach (var relatedExpression in relatedExpressions) - { - if (!relatedExpression.IsScalar) - { - NonScalarExpressions.Add(relatedExpression); - } - - subqueryExpressions.Add(relatedExpression); - } - } - else - { - if (!expr.IsScalar) - { - NonScalarExpressions?.Add(expr); - } - - AddExpression(expr, queryReturnTypeList); - continue; - } - + SelectExpressions.RemoveAt(i); var indexes = new List(subqueryExpressions.Count); foreach (var expression in subqueryExpressions) { @@ -185,16 +139,15 @@ public void InitializeExplicitSelectClause(FromClause fromClause) AddExpression(expression, queryReturnTypeList); } - _replacedExpressions.Add(expr, indexes); - SelectExpressions.Remove(expr); - length = SelectExpressions.Count; i--; + length = SelectExpressions.Count; + _replacedExpressions.Add(expr, indexes); } else { if (!expr.IsScalar) { - NonScalarExpressions?.Add(expr); + NonScalarExpressions.Add(expr); } AddExpression(expr, queryReturnTypeList); @@ -214,6 +167,49 @@ public void InitializeExplicitSelectClause(FromClause fromClause) FinishInitialization(); } + private bool TryProcessSubqueryExpressions( + ISelectExpression selectExpression, + JoinSubqueryFromElement joinSubquery, + out SelectClause selectClause, + out List subqueryExpressions) + { + if (selectExpression is IdentNode) + { + selectClause = joinSubquery.QueryNode.GetSelectClause(); + subqueryExpressions = selectClause.SelectExpressions; + NonScalarExpressions.Add(selectExpression); + } + else if (selectExpression is DotNode dotNode) + { + subqueryExpressions = joinSubquery.GetRelatedSelectExpressions(dotNode, out selectClause); + if (subqueryExpressions == null) + { + return false; + } + + if (!selectClause.IsScalarSelect) + { + RemoveChildAndUnsetParent((IASTNode) selectExpression); + } + + foreach (var expression in subqueryExpressions) + { + if (!expression.IsScalar) + { + NonScalarExpressions.Add(expression); + } + } + } + else + { + selectClause = null; + subqueryExpressions = null; + return false; + } + + return true; + } + private void Render( FromClause fromClause, Dictionary inheritedExpressions) @@ -454,6 +450,46 @@ private void RenderNonScalarSelects( var appender = new ASTAppender(ASTFactory, this); var combinedFromElements = new List(); var processedElements = new HashSet(); + RenderNonScalarIdentifiers(appender, processedElements, combinedFromElements, inheritedExpressions); + if (Walker.IsShallowQuery) + { + return; + } + + // Append fetched elements + RenderFetchedNonScalarIdentifiers(appender, fetchedFromElements, processedElements, combinedFromElements); + if (currentFromClause.IsScalarSubQuery) + { + return; + } + + // Generate the property select tokens. + foreach (var fromElement in combinedFromElements) + { + RenderNonScalarProperties(appender, fromElement); + } + + // Generate properties for fetched collections of components or values + var fromElements = currentFromClause.GetAllProjectionListTyped(); + foreach (var fromElement in fromElements) + { + if (fromElement.IsCollectionOfValuesOrComponents && + fromElement.IsFetch && + processedElements.Add(fromElement)) + { + var suffix = Walker.GetSuffix(fromElement); + var fragment = fromElement.GetValueCollectionSelectFragment(suffix); + Append(appender, HqlSqlWalker.SQL_TOKEN, fragment); + } + } + } + + private void RenderNonScalarIdentifiers( + ASTAppender appender, + HashSet processedElements, + List combinedFromElements, + Dictionary inheritedExpressions) + { foreach (var e in NonScalarExpressions) { var fromElement = e.FromElement; @@ -473,13 +509,14 @@ private void RenderNonScalarSelects( RemoveChildAndUnsetParent(node); } } + } - if (Walker.IsShallowQuery) - { - return; - } - - // Append fetched elements + private void RenderFetchedNonScalarIdentifiers( + ASTAppender appender, + IList fetchedFromElements, + HashSet processedElements, + List combinedFromElements) + { foreach (var fetchedFromElement in fetchedFromElements) { if (!processedElements.Add(fetchedFromElement)) @@ -493,7 +530,7 @@ private void RenderNonScalarSelects( if (fragment == null) { // When a subquery join has a scalar select only - continue; + continue; } var generatedExpr = (SelectExpressionImpl) Append(appender, HqlSqlWalker.SELECT_EXPR, fragment); @@ -501,31 +538,6 @@ private void RenderNonScalarSelects( generatedExpr.DataType = fetchedFromElement.DataType; NonScalarExpressions.Add(generatedExpr); } - - if (currentFromClause.IsScalarSubQuery) - { - return; - } - - // Generate the property select tokens. - foreach (var fromElement in combinedFromElements) - { - RenderNonScalarProperties(appender, fromElement); - } - - // Generate properties for fetched collections of components or values - var fromElements = currentFromClause.GetAllProjectionListTyped(); - foreach (var fromElement in fromElements) - { - if (fromElement.IsCollectionOfValuesOrComponents && - fromElement.IsFetch && - processedElements.Add(fromElement)) - { - var suffix = Walker.GetSuffix(fromElement); - var fragment = fromElement.GetValueCollectionSelectFragment(suffix); - Append(appender, HqlSqlWalker.SQL_TOKEN, fragment); - } - } } private IASTNode Append(ASTAppender appender, int type, SelectFragment fragment) diff --git a/src/NHibernate/Persister/Collection/IQueryableCollection.cs b/src/NHibernate/Persister/Collection/IQueryableCollection.cs index 7b23d1f3bb4..0d3f0cc53ee 100644 --- a/src/NHibernate/Persister/Collection/IQueryableCollection.cs +++ b/src/NHibernate/Persister/Collection/IQueryableCollection.cs @@ -119,18 +119,18 @@ public static SelectFragment GetSelectFragment(this IQueryableCollection queryab var identifierAlias = queryable.GetIdentifierColumnAlias(null); var indexAliases = queryable.GetIndexColumnAliases(null); var columnAliases = queryable.GetKeyColumnAliases(null) - .Union(queryable.GetElementColumnAliases(null)); + .Concat(queryable.GetElementColumnAliases(null)); if (indexAliases != null) { - columnAliases = columnAliases.Union(indexAliases); + columnAliases = columnAliases.Concat(indexAliases); } if (identifierAlias != null) { - columnAliases = columnAliases.Union(new[] {identifierAlias}); + columnAliases = columnAliases.Concat(new[] {identifierAlias}); } - return new SelectFragment(queryable.Factory.Dialect, renderedText, columnAliases.ToList()) + return new SelectFragment(queryable.Factory.Dialect, renderedText, columnAliases.Distinct().ToList()) .SetSuffix(columnSuffix); } } diff --git a/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs index 7cb429de6be..85f7e843d9e 100644 --- a/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs +++ b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs @@ -15,6 +15,7 @@ internal class SubqueryPropertyMapping : IPropertyMapping private readonly Dictionary _propertyMappingSuffixes = new Dictionary(); private readonly Dictionary _propertyMappings = new Dictionary(); private readonly Dictionary _aliasSelectExpressions = new Dictionary(); + private readonly HashSet _nonScalarFromElements = new HashSet(); private readonly SelectClause _selectClause; public SubqueryPropertyMapping(IType type, SelectClause selectClause) @@ -55,7 +56,17 @@ public SubqueryPropertyMapping(IType type, SelectClause selectClause) { var expression = nonScalarExpressions[i]; var fromElement = expression.FromElement; - var mapping = fromElement?.GetPropertyMapping(""); + if (fromElement == null) + { + continue; + } + + if (!fromElement.IsFetch) + { + _nonScalarFromElements.Add(fromElement); + } + + var mapping = fromElement.GetPropertyMapping(""); if (mapping == null) { continue; @@ -194,15 +205,8 @@ public string[] ToColumns(string alias, string propertyName) public List GetPropertiesColumns(string alias) { var columns = new List(); - var processedElements = new HashSet(); - foreach (var expression in _selectClause.NonScalarExpressions) + foreach (var fromElement in _nonScalarFromElements) { - var fromElement = expression.FromElement; - if (fromElement == null || fromElement.IsFetch || !processedElements.Add(fromElement)) - { - continue; - } - var fragment = fromElement.GetPropertiesSelectFragment(fromElement.EntitySuffix, alias); if (fromElement is JoinSubqueryFromElement) { @@ -228,14 +232,9 @@ public List GetPropertiesColumns(string alias) public List GetIdentifiersColumns(string alias) { var columns = new List(); - var processedElements = new HashSet(); - foreach (var expression in _selectClause.NonScalarExpressions) + foreach (var fromElement in _nonScalarFromElements) { - var fromElement = expression.FromElement; - if (fromElement == null || - fromElement.FromClause.IsScalarSubQuery || - fromElement.IsFetch || - !processedElements.Add(fromElement)) + if (fromElement.FromClause.IsScalarSubQuery) { continue; } From 20b737968439bd7ec82a318fc14b9215d1ea1f16 Mon Sep 17 00:00:00 2001 From: maca88 Date: Mon, 9 Nov 2020 21:44:26 +0100 Subject: [PATCH 07/17] Update src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Frédéric Delaporte <12201973+fredericDelaporte@users.noreply.github.com> --- src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index cdee294116c..3a86947fd83 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -749,10 +749,7 @@ void CreateJoinSubquery( int joinType, IASTNode with) { - if (log.IsDebugEnabled()) - { - log.Debug($"Creating subquery-join FromElement [{alias?.Text}]"); - } + log.Debug("Creating subquery-join FromElement [{0}]", alias?.Text); var join = new JoinSubqueryFromElement( CurrentFromClause, From 0a709a122ef8d4c981609c7ac91389e98e9f4d8b Mon Sep 17 00:00:00 2001 From: maca88 Date: Thu, 15 Sep 2022 05:35:29 +0200 Subject: [PATCH 08/17] Fix tests --- src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs | 4 ++-- src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs b/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs index 0a67d8912e7..a174839de78 100644 --- a/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs +++ b/src/NHibernate.Test/Async/Linq/ByMethod/JoinSubqueryTests.cs @@ -233,7 +233,7 @@ from CompositeOrder o inner join ( query = query.Replace(":s", "2").Replace(":t", "2"); result = await (session.CreateQuery(query).ListAsync(cancellationToken)); } - + Assert.That(result, Has.Count.EqualTo(2)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -938,7 +938,7 @@ private void AssertDuplicateEntitySelectionSubQuery(string sql, IList result, bo } else { - Assert.That(GetTotalOccurrences(selectSql, ","), Is.EqualTo(27)); + Assert.That(GetTotalOccurrences(selectSql, ","), Is.EqualTo(29)); Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); diff --git a/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs index 1639c8fe6df..355ff81fbc1 100644 --- a/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs +++ b/src/NHibernate.Test/Linq/ByMethod/JoinSubqueryTests.cs @@ -221,7 +221,7 @@ private void AssertIdSubQueryWithPagingAndOrderBy(string query) query = query.Replace(":s", "2").Replace(":t", "2"); result = session.CreateQuery(query).List(); } - + Assert.That(result, Has.Count.EqualTo(2)); var item = result[0]; Assert.That(item, Is.TypeOf()); @@ -926,7 +926,7 @@ private void AssertDuplicateEntitySelectionSubQuery(string sql, IList result, bo } else { - Assert.That(GetTotalOccurrences(selectSql, ","), Is.EqualTo(27)); + Assert.That(GetTotalOccurrences(selectSql, ","), Is.EqualTo(29)); Assert.That(array[0], Is.TypeOf().And.Property("OrderId").EqualTo(10249)); Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); Assert.That(array[1], Is.TypeOf().And.Property("OrderId").EqualTo(10248)); From 8f9157dbb9b037bbeed2b67e72741b884dbc682e Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 26 Oct 2022 14:15:39 +0300 Subject: [PATCH 09/17] Avoid split and join aliases in ContainsEntityAlias --- src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs index 85f7e843d9e..39b82af651a 100644 --- a/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs +++ b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs @@ -130,21 +130,20 @@ public List GetRelatedSelectExpressions(string path, out Sele public bool ContainsEntityAlias(string alias, IType type) { - var aliases = alias.Split(StringHelper.Dot); - var rootAlias = aliases[0]; + bool isRoot = !StringHelper.IsNotRoot(alias, out var rootAlias); if (!_propertyAliasMappings.TryGetValue(rootAlias, out var mapping)) { return false; } - if (aliases.Length == 1) + if (isRoot) { return mapping.Type.Equals(type); } if (mapping is SubqueryPropertyMapping joinSubQueryMapping) { - return joinSubQueryMapping.ContainsEntityAlias(JoinPaths(aliases.Skip(1)), type); + return joinSubQueryMapping.ContainsEntityAlias(alias.Substring(rootAlias.Length + 1), type); } return false; From f7fedbc24a986fc306712b23b92bafcbc481a549 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 26 Oct 2022 15:48:09 +0300 Subject: [PATCH 10/17] No need to distinct for simple assigment --- .../Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs index d2981a3a7bc..5b9f0652e38 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/JoinSubqueryFromElement.cs @@ -21,10 +21,10 @@ public JoinSubqueryFromElement(FromClause fromClause, QueryNode queryNode, JoinT QueryNode = queryNode; DataType = dataType; foreach (var fromElement in querySelectClause.SelectExpressions.Select(o => o.FromElement) - .Concat(querySelectClause.NonScalarExpressions?.Select(o => o.FromElement) ?? Enumerable.Empty()) - .Concat(querySelectClause.CollectionFromElements ?? Enumerable.Empty()) + .Concat(querySelectClause.NonScalarExpressions?.Select(o => o.FromElement) ?? Array.Empty()) + .Concat(querySelectClause.CollectionFromElements ?? Array.Empty()) .Where(o => o != null) - .Distinct()) + ) { fromElement.ParentFromElement = this; } From 7f7c905aad72fb9cd5dbccc7812bb56dfa026234 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 26 Oct 2022 15:50:37 +0300 Subject: [PATCH 11/17] Avoid SelectExpressions list manipulations --- .../Hql/Ast/ANTLR/Tree/SelectClause.cs | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs index 449a9260ac2..616fdb37ba3 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs @@ -102,45 +102,35 @@ public void InitializeExplicitSelectClause(FromClause fromClause) // NOTE: This must be done *before* invoking setScalarColumnText() because setScalarColumnText() // changes the AST!!! var inheritedExpressions = new Dictionary(); - SelectExpressions = GetSelectExpressions(); - OriginalSelectExpressions = SelectExpressions.ToList(); + var selExprs = GetSelectExpressions(); + OriginalSelectExpressions = selExprs; + SelectExpressions = new List(selExprs.Count); NonScalarExpressions = new List(); - var length = SelectExpressions.Count; - for (var i = 0; i < length; i++) + foreach (var expr in selExprs) { - var expr = SelectExpressions[i]; if (expr.IsConstructor) { _constructorNode = (ConstructorNode) expr; _scalarSelect = true; - SelectExpressions.RemoveAt(i); NonScalarExpressions.AddRange(_constructorNode.GetSelectExpressions(true, o => !o.IsScalar)); foreach (var argumentExpression in _constructorNode.GetSelectExpressions()) { - SelectExpressions.Insert(i, argumentExpression); - i++; + SelectExpressions.Add(argumentExpression); AddExpression(argumentExpression, queryReturnTypeList); } - - i--; - length = SelectExpressions.Count; } else if (expr.FromElement is JoinSubqueryFromElement joinSubquery && TryProcessSubqueryExpressions(expr, joinSubquery, out var selectClause, out var subqueryExpressions)) { - SelectExpressions.RemoveAt(i); var indexes = new List(subqueryExpressions.Count); foreach (var expression in subqueryExpressions) { inheritedExpressions[expression] = selectClause; - indexes.Add(i); - SelectExpressions.Insert(i, expression); - i++; + indexes.Add(SelectExpressions.Count); + SelectExpressions.Add(expression); AddExpression(expression, queryReturnTypeList); } - i--; - length = SelectExpressions.Count; _replacedExpressions.Add(expr, indexes); } else @@ -150,6 +140,7 @@ public void InitializeExplicitSelectClause(FromClause fromClause) NonScalarExpressions.Add(expr); } + SelectExpressions.Add(expr); AddExpression(expr, queryReturnTypeList); } } From b3f3543eb9bb887a411c062a767fa9fe944f3e6d Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 26 Oct 2022 15:52:49 +0300 Subject: [PATCH 12/17] use string.Empty --- src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index d812903a371..d014cc5f46f 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -502,7 +502,7 @@ internal string GetSuffix(FromElement fromElement) return suffix; } - suffix = _suffixes.Count == 0 ? "" : _suffixes.Count.ToString() + '_'; + suffix = _suffixes.Count == 0 ? string.Empty : _suffixes.Count.ToString() + '_'; _suffixes.Add(fromElement, suffix); return suffix; From 0771f9cd6acda622ac97d80420b1fb2d5e2d5818 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 26 Oct 2022 16:29:10 +0300 Subject: [PATCH 13/17] Split only once --- src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs index 39b82af651a..24c825e776c 100644 --- a/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs +++ b/src/NHibernate/Persister/Entity/SubqueryPropertyMapping.cs @@ -33,7 +33,7 @@ public SubqueryPropertyMapping(IType type, SelectClause selectClause) var key = scalarExpression.Alias; if (string.IsNullOrEmpty(key) && scalarExpression is DotNode dotNode) { - key = dotNode.PropertyPath.Split(StringHelper.Dot).Last(); + StringHelper.ParsePathAndPropertyName(dotNode.PropertyPath, out _, out key); } if (key == null) From f59670736828859cb8abbe78d2bbccc3bd916f55 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 26 Oct 2022 16:48:05 +0300 Subject: [PATCH 14/17] Enable GH-2551 fixed tests --- src/NHibernate.Test/Linq/QueryCacheableTests.cs | 3 --- src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs | 8 +------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/NHibernate.Test/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Linq/QueryCacheableTests.cs index 4475971f09e..de4529091bf 100644 --- a/src/NHibernate.Test/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Linq/QueryCacheableTests.cs @@ -443,9 +443,6 @@ public void ProjectedEntitiesAreCachable() [Test] public void CacheHqlQueryWithFetchAndTransformerThatChangeTuple() { - if (!TestDialect.SupportsDuplicatedColumnAliases) - Assert.Ignore("Ignored due to GH-2092"); - Sfi.Statistics.Clear(); Sfi.EvictQueries(); diff --git a/src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs index e814df6bb50..253ed0dbfed 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs @@ -1,9 +1,9 @@ using NUnit.Framework; using System.Collections.Generic; -using NHibernate.Dialect; namespace NHibernate.Test.NHSpecificTest.NH1773 { + //Also tests GH-2551 (Fails with MS SQL Ce & SQL Anywhere due to the query generating a duplicated column alias name, which these databases do not support.) [TestFixture] public class Fixture : BugTestCase { @@ -21,12 +21,6 @@ public void CustomHQLFunctionsShouldBeRecognizedByTheParser() } } - protected override bool AppliesTo(Dialect.Dialect dialect) - { - // Fails with MS SQL Ce & SQL Anywhere due to the query generating a duplicated column alias name, which these databases do not support. - return TestDialect.SupportsDuplicatedColumnAliases; - } - protected override void OnSetUp() { using (var s = OpenSession()) From 9e0d2db5dd591ce5ce5eb49b3ba028d6d59ad605 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Oct 2022 13:51:25 +0000 Subject: [PATCH 15/17] Generate async files --- src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs | 3 --- .../Async/NHSpecificTest/NH1773/Fixture.cs | 8 +------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs index 54a8a6cfc44..3bc5fb1bedc 100644 --- a/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs +++ b/src/NHibernate.Test/Async/Linq/QueryCacheableTests.cs @@ -454,9 +454,6 @@ public async Task ProjectedEntitiesAreCachableAsync() [Test] public async Task CacheHqlQueryWithFetchAndTransformerThatChangeTupleAsync() { - if (!TestDialect.SupportsDuplicatedColumnAliases) - Assert.Ignore("Ignored due to GH-2092"); - Sfi.Statistics.Clear(); await (Sfi.EvictQueriesAsync()); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1773/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1773/Fixture.cs index 8edfd512261..b893e4a6c97 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1773/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1773/Fixture.cs @@ -10,11 +10,11 @@ using NUnit.Framework; using System.Collections.Generic; -using NHibernate.Dialect; namespace NHibernate.Test.NHSpecificTest.NH1773 { using System.Threading.Tasks; + //Also tests GH-2551 (Fails with MS SQL Ce & SQL Anywhere due to the query generating a duplicated column alias name, which these databases do not support.) [TestFixture] public class FixtureAsync : BugTestCase { @@ -32,12 +32,6 @@ public async Task CustomHQLFunctionsShouldBeRecognizedByTheParserAsync() } } - protected override bool AppliesTo(Dialect.Dialect dialect) - { - // Fails with MS SQL Ce & SQL Anywhere due to the query generating a duplicated column alias name, which these databases do not support. - return TestDialect.SupportsDuplicatedColumnAliases; - } - protected override void OnSetUp() { using (var s = OpenSession()) From 99a533fcaa4a1707dd17e18afbd86a644cfe7806 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 27 Oct 2022 10:10:59 +0300 Subject: [PATCH 16/17] oups. It's GH-2092 --- src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs index 253ed0dbfed..8defb90aa2d 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1773/Fixture.cs @@ -3,7 +3,7 @@ namespace NHibernate.Test.NHSpecificTest.NH1773 { - //Also tests GH-2551 (Fails with MS SQL Ce & SQL Anywhere due to the query generating a duplicated column alias name, which these databases do not support.) + //Also tests GH-2092 (Fails with MS SQL Ce & SQL Anywhere due to the query generating a duplicated column alias name, which these databases do not support.) [TestFixture] public class Fixture : BugTestCase { From fd5a43a2e806d79188550488aeadb6962fedfb25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 27 Oct 2022 07:13:35 +0000 Subject: [PATCH 17/17] Generate async files --- src/NHibernate.Test/Async/NHSpecificTest/NH1773/Fixture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1773/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1773/Fixture.cs index b893e4a6c97..6fdfebf4bd8 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1773/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1773/Fixture.cs @@ -14,7 +14,7 @@ namespace NHibernate.Test.NHSpecificTest.NH1773 { using System.Threading.Tasks; - //Also tests GH-2551 (Fails with MS SQL Ce & SQL Anywhere due to the query generating a duplicated column alias name, which these databases do not support.) + //Also tests GH-2092 (Fails with MS SQL Ce & SQL Anywhere due to the query generating a duplicated column alias name, which these databases do not support.) [TestFixture] public class FixtureAsync : BugTestCase {