diff --git a/src/NHibernate.Test/Async/Criteria/Lambda/SubQueryIntegrationFixture.cs b/src/NHibernate.Test/Async/Criteria/Lambda/SubQueryIntegrationFixture.cs index 0dcc02003c0..18fef49bb5e 100644 --- a/src/NHibernate.Test/Async/Criteria/Lambda/SubQueryIntegrationFixture.cs +++ b/src/NHibernate.Test/Async/Criteria/Lambda/SubQueryIntegrationFixture.cs @@ -166,5 +166,32 @@ public async Task SubQueryAsync() Assert.That(nameAndChildCount[1].ChildCount, Is.EqualTo(1)); } } + + //NH-3493 - Cannot use alias between more than 1 level of nested queries + [Test] + public async Task ThreeLevelSubqueryAsync() + { + if (!Dialect.SupportsScalarSubSelects) + Assert.Ignore("Dialect does not support scalar sub-select"); + + Person p = null; + var detachedCriteria2 = DetachedCriteria.For("vf_inner_2") + .SetProjection(Projections.Id()) + .Add(Restrictions.Eq($@"mk.{nameof(p.Age)}", 20)) + .Add(Restrictions.EqProperty("vf_inner_2.Id", "vf_inner.Id")); + + var detachedCriteria1 = DetachedCriteria.For("vf_inner") + .SetProjection(Projections.Id()) + .Add(Subqueries.Exists(detachedCriteria2)) + .Add(Restrictions.EqProperty("vf_inner.Id", "vf.Id")); + + using (var s = OpenSession()) + { + await (s.CreateCriteria("vf") + .CreateAlias($"vf.{nameof(p.Father)}", "mk") + .AddOrder(Order.Asc(Projections.SubQuery(detachedCriteria1))) + .ListAsync()); + } + } } } diff --git a/src/NHibernate.Test/Criteria/Lambda/CriteriaGenerationBenchmarks.cs b/src/NHibernate.Test/Criteria/Lambda/CriteriaGenerationBenchmarks.cs new file mode 100644 index 00000000000..ed6ce2d9ae4 --- /dev/null +++ b/src/NHibernate.Test/Criteria/Lambda/CriteriaGenerationBenchmarks.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using NHibernate.Criterion; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria.Lambda +{ + [TestFixture, Explicit] + public class CriteriaGenerationBenchmarks : TestCase + { + protected override string MappingsAssembly + { + get { return "NHibernate.Test"; } + } + + protected override string[] Mappings + { + get { return new[] { "Criteria.Lambda.Mappings.hbm.xml" }; } + } + + protected override void OnSetUp() + { + } + + protected override void OnTearDown() + { + } + + //NH-1200 - Exception occurs when using criteria exist queries + [Test, Explicit] + public void Subquery() + { + using (var s = OpenSession()) + { + Child _subqueryChildAlias = null; + + Person person = null; + ICriteria criteria = s.QueryOver(() => person) + .WithSubquery.WhereExists( + QueryOver.Of(() => _subqueryChildAlias) + .Where(() => _subqueryChildAlias.Parent.Id == person.Id).Select(c => c.Id)).UnderlyingCriteria; + + BenchQuery(s, criteria); + } + } + + //NH-1200 - Exception occurs when using criteria exist queries + [Test, Explicit] + public void SubqueryManyParamsFromOuterQuery() + { + using (var s = OpenSession()) + { + Child _subqueryChildAlias = null; + + Person person = null; + ICriteria criteria = s.QueryOver(() => person) + .WithSubquery.WhereExists( + QueryOver.Of(() => _subqueryChildAlias) + .Where(() => _subqueryChildAlias.Age > person.Age && person.Age > 40 && person.Name == "name").Select(c => c.Id)).UnderlyingCriteria; + + BenchQuery(s, criteria); + } + } + + [Test, Explicit] + public void SimplePropertyCompare() + { + using (var s = OpenSession()) + { + ICriteria criteria = s.QueryOver() + .Where(p => p.Name == "aaa").UnderlyingCriteria; + BenchQuery(s, criteria); + } + } + + [Test, Explicit] + public void ManyAliases() + { + using (var s = OpenSession()) + { + Child child = null; + Person father = null; + Person person = null; + ICriteria criteria = s.QueryOver(() => person) + .JoinAlias(p => p.Children, () => child) + .JoinAlias(p => p.Father, () => father) + .Where(p => p.Name == father.Name && p.Father.Id == 10 && child.Nickname == "nickname" && child.Age > person.Age).UnderlyingCriteria; + + BenchQuery(s, criteria); + + } + } + + private static void BenchQuery(ISession s, ICriteria criteria) + { + const int iterations = 15000; + + var commands = new List(iterations); + + for (int j = 0; j < 5; j++) + { + using (Timer.Start) + for (int i = 0; i < iterations; i++) + { + var batchItem = new Multi.CriteriaBatchItem(criteria); + batchItem.Init(s.GetSessionImplementation()); + commands.AddRange(batchItem.GetCommands()); + } + Console.WriteLine("Elapsed time (ms): " + Timer.ElapsedMilliseconds); + } + } + + /// + /// Stopwatch wrapper + /// + class Timer : IDisposable + { + static Stopwatch stop = new Stopwatch(); + + public Timer() + { + stop.Reset(); + stop.Start(); + } + + public static Timer Start { get { return new Timer(); } } + + public void Dispose() + { + stop.Stop(); + } + + static public long ElapsedMilliseconds { get { return stop.ElapsedMilliseconds; } } + } + } +} diff --git a/src/NHibernate.Test/Criteria/Lambda/SubQueryIntegrationFixture.cs b/src/NHibernate.Test/Criteria/Lambda/SubQueryIntegrationFixture.cs index 75a8c97bde5..542db3248f7 100644 --- a/src/NHibernate.Test/Criteria/Lambda/SubQueryIntegrationFixture.cs +++ b/src/NHibernate.Test/Criteria/Lambda/SubQueryIntegrationFixture.cs @@ -155,5 +155,32 @@ public void SubQuery() Assert.That(nameAndChildCount[1].ChildCount, Is.EqualTo(1)); } } + + //NH-3493 - Cannot use alias between more than 1 level of nested queries + [Test] + public void ThreeLevelSubquery() + { + if (!Dialect.SupportsScalarSubSelects) + Assert.Ignore("Dialect does not support scalar sub-select"); + + Person p = null; + var detachedCriteria2 = DetachedCriteria.For("vf_inner_2") + .SetProjection(Projections.Id()) + .Add(Restrictions.Eq($@"mk.{nameof(p.Age)}", 20)) + .Add(Restrictions.EqProperty("vf_inner_2.Id", "vf_inner.Id")); + + var detachedCriteria1 = DetachedCriteria.For("vf_inner") + .SetProjection(Projections.Id()) + .Add(Subqueries.Exists(detachedCriteria2)) + .Add(Restrictions.EqProperty("vf_inner.Id", "vf.Id")); + + using (var s = OpenSession()) + { + s.CreateCriteria("vf") + .CreateAlias($"vf.{nameof(p.Father)}", "mk") + .AddOrder(Order.Asc(Projections.SubQuery(detachedCriteria1))) + .List(); + } + } } } diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index a76b64ab2d4..620cd936c0f 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -718,22 +718,14 @@ public string GetColumn(ICriteria criteria, string propertyName) public string[] GetColumnsUsingProjection(ICriteria subcriteria, string propertyName) { // NH Different behavior: we don't use the projection alias for NH-1023 - try - { - return GetColumns(subcriteria, propertyName); - } - catch (HibernateException) - { - //not found in inner query , try the outer query - if (outerQueryTranslator != null) - { - return outerQueryTranslator.GetColumnsUsingProjection(subcriteria, propertyName); - } - else - { - throw; - } - } + if (TryGetColumns(subcriteria, propertyName, outerQueryTranslator != null, out var columns)) + return columns; + + //not found in inner query , try the outer query + if (outerQueryTranslator != null) + return outerQueryTranslator.GetColumnsUsingProjection(subcriteria, propertyName); + + throw new QueryException("Could not find property " + propertyName); } public string[] GetIdentifierColumns(ICriteria subcriteria) @@ -755,12 +747,29 @@ public TypedValue GetTypedIdentifierValue(ICriteria subcriteria, object value) public string[] GetColumns(ICriteria subcriteria, string propertyName) { - string entName = GetEntityName(subcriteria, propertyName); - if (entName == null) + if (TryGetColumns(subcriteria, propertyName, false, out var columns)) + return columns; + + throw new QueryException("Could not find property " + propertyName); + } + + private bool TryGetColumns(ICriteria subcriteria, string path, bool verifyPropertyName, out string[] columns) + { + if (!TryParseCriteriaPath(subcriteria, path, out var entName, out var propertyName, out var pathCriteria)) { - throw new QueryException("Could not find property " + propertyName); + columns = null; + return false; + } + var propertyMapping = GetPropertyMapping(entName); + + if (verifyPropertyName && !propertyMapping.TryToType(propertyName, out var type)) + { + columns = null; + return false; } - return GetPropertyMapping(entName).ToColumns(GetSQLAlias(subcriteria, propertyName), GetPropertyName(propertyName)); + + columns = propertyMapping.ToColumns(GetSQLAlias(pathCriteria), propertyName); + return true; } public IType GetTypeUsingProjection(ICriteria subcriteria, string propertyName) @@ -771,24 +780,18 @@ public IType GetTypeUsingProjection(ICriteria subcriteria, string propertyName) if (projectionTypes == null) { - try - { //it does not refer to an alias of a projection, //look for a property - return GetType(subcriteria, propertyName); + + if (TryGetType(subcriteria, propertyName, out var type)) + { + return type; } - catch (HibernateException) + if (outerQueryTranslator != null) { - //not found in inner query , try the outer query - if (outerQueryTranslator != null) - { - return outerQueryTranslator.GetType(subcriteria, propertyName); - } - else - { - throw; - } + return outerQueryTranslator.GetTypeUsingProjection(subcriteria, propertyName); } + throw new QueryException("Could not find property " + propertyName); } else { @@ -803,7 +806,21 @@ public IType GetTypeUsingProjection(ICriteria subcriteria, string propertyName) public IType GetType(ICriteria subcriteria, string propertyName) { - return GetPropertyMapping(GetEntityName(subcriteria, propertyName)).ToType(GetPropertyName(propertyName)); + if(!TryParseCriteriaPath(subcriteria, propertyName, out var entityName, out var entityPropName, out _)) + throw new QueryException("Could not find property " + propertyName); + + return GetPropertyMapping(entityName).ToType(entityPropName); + } + + public bool TryGetType(ICriteria subcriteria, string propertyName, out IType type) + { + if (!TryParseCriteriaPath(subcriteria, propertyName, out var entityName, out var entityPropName, out _)) + { + type = null; + return false; + } + + return GetPropertyMapping(entityName).TryToType(entityPropName, out type); } /// @@ -1003,22 +1020,7 @@ public string[] GetColumnAliasesUsingProjection(ICriteria subcriteria, string pr { //it does not refer to an alias of a projection, //look for a property - try - { - return GetColumns(subcriteria, propertyName); - } - catch (HibernateException) - { - //not found in inner query , try the outer query - if (outerQueryTranslator != null) - { - return outerQueryTranslator.GetColumnAliasesUsingProjection(subcriteria, propertyName); - } - else - { - throw; - } - } + return GetColumnsUsingProjection(subcriteria, propertyName); } else { @@ -1052,6 +1054,25 @@ private IQueryable GetQueryablePersister(string entityName) { return (IQueryable) sessionFactory.GetEntityPersister(entityName); } + + private bool TryParseCriteriaPath(ICriteria subcriteria, string path, out string entityName, out string propertyName, out ICriteria pathCriteria) + { + if(StringHelper.IsNotRoot(path, out var root, out var unrootPath)) + { + ICriteria crit = GetAliasedCriteria(root); + if (crit != null) + { + propertyName = unrootPath; + entityName = GetEntityName(crit); + pathCriteria = crit; + return entityName != null; + } + } + pathCriteria = subcriteria; + propertyName = path; + entityName = GetEntityName(subcriteria); + return entityName != null; + } } }