Skip to content

Optimize CriteriaQueryTranslator GetColumns and GetTypes #1974

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<Person>("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<Person>("vf_inner")
.SetProjection(Projections.Id())
.Add(Subqueries.Exists(detachedCriteria2))
.Add(Restrictions.EqProperty("vf_inner.Id", "vf.Id"));

using (var s = OpenSession())
{
await (s.CreateCriteria<Person>("vf")
.CreateAlias($"vf.{nameof(p.Father)}", "mk")
.AddOrder(Order.Asc(Projections.SubQuery(detachedCriteria1)))
.ListAsync<Person>());
}
}
}
}
137 changes: 137 additions & 0 deletions src/NHibernate.Test/Criteria/Lambda/CriteriaGenerationBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -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>(() => person)
.WithSubquery.WhereExists(
QueryOver.Of<Child>(() => _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>(() => person)
.WithSubquery.WhereExists(
QueryOver.Of<Child>(() => _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<Person>()
.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>(() => 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<SqlCommand.ISqlCommand>(iterations);

for (int j = 0; j < 5; j++)
{
using (Timer.Start)
for (int i = 0; i < iterations; i++)
{
var batchItem = new Multi.CriteriaBatchItem<Person>(criteria);
batchItem.Init(s.GetSessionImplementation());
commands.AddRange(batchItem.GetCommands());
}
Console.WriteLine("Elapsed time (ms): " + Timer.ElapsedMilliseconds);
}
}

/// <summary>
/// Stopwatch wrapper
/// </summary>
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; } }
}
}
}
27 changes: 27 additions & 0 deletions src/NHibernate.Test/Criteria/Lambda/SubQueryIntegrationFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Person>("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<Person>("vf_inner")
.SetProjection(Projections.Id())
.Add(Subqueries.Exists(detachedCriteria2))
.Add(Restrictions.EqProperty("vf_inner.Id", "vf.Id"));

using (var s = OpenSession())
{
s.CreateCriteria<Person>("vf")
.CreateAlias($"vf.{nameof(p.Father)}", "mk")
.AddOrder(Order.Asc(Projections.SubQuery(detachedCriteria1)))
.List<Person>();
}
}
}
}
121 changes: 71 additions & 50 deletions src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
{
Expand All @@ -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);
}

/// <summary>
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
}
}
}