Skip to content

WIP - Support non parameterized constants query plan caching for Linq provider #2375

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -70,7 +70,7 @@ public void ToFutureValueWithSumOnEmptySetThrowsAsync()
.Select(x => x.Id)
.ToFutureValue(x => x.Sum());

Assert.That(() => personsSum.GetValueAsync(), Throws.InnerException.TypeOf<InvalidOperationException>().Or.InnerException.TypeOf<ArgumentNullException>());
Assert.That(() => personsSum.GetValueAsync(), Throws.TypeOf<InvalidOperationException>().Or.InnerException.TypeOf<ArgumentNullException>());
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/NHibernate.Test/Async/Linq/ConstantTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public async Task DmlPlansAreCachedAsync()
}

[Test]
public async Task PlansWithNonParameterizedConstantsAreNotCachedAsync()
public async Task PlansWithNonParameterizedConstantsAreCachedAsync()
{
var queryPlanCacheType = typeof(QueryPlanCache);

Expand All @@ -314,12 +314,12 @@ public async Task PlansWithNonParameterizedConstantsAreNotCachedAsync()
select new { c.CustomerId, c.ContactName, Constant = 1 }).FirstAsync());
Assert.That(
cache,
Has.Count.EqualTo(0),
"Query plan should not be cached.");
Has.Count.EqualTo(1),
"Query plan should be cached.");
}

[Test]
public async Task PlansWithNonParameterizedConstantsAreNotCachedForExpandedQueryAsync()
public async Task PlansWithNonParameterizedConstantsAreCachedForExpandedQueryAsync()
{
var queryPlanCacheType = typeof(QueryPlanCache);

Expand All @@ -335,8 +335,8 @@ public async Task PlansWithNonParameterizedConstantsAreNotCachedForExpandedQuery

Assert.That(
cache,
Has.Count.EqualTo(0),
"Query plan should not be cached.");
Has.Count.EqualTo(2), // The second one is for the expanded expression that has two parameters
"Query plan should be cached.");
}

//GH-2298 - Different Update queries - same query cache plan
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate.Test/Async/Linq/ParameterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ public async Task UsingTwoParametersInDMLDeleteAsync()
{
// In case of arrays linqParameterNumber and parameterNumber will be different
Assert.That(
GetLinqExpression(query).ParameterValuesByName.Count,
GetLinqExpression(query).NamedParameters.Count,
Is.EqualTo(linqParameterNumber ?? parameterNumber),
"Linq expression has different number of parameters");

Expand Down
175 changes: 175 additions & 0 deletions src/NHibernate.Test/Async/Linq/QueryPlanTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


using System.Linq;
using NHibernate.Dialect;
using NHibernate.Linq;
using NSubstitute;
using NSubstitute.Extensions;
using NUnit.Framework;

namespace NHibernate.Test.Linq
{
using System.Threading.Tasks;
[TestFixture]
public class QueryPlanTestsAsync : LinqTestCase
{
[Test]
public async Task SelectConstantShouldBeCachedAsync()
{
ClearQueryPlanCache();

var c1 = await (db.Customers.Select(o => new {o.CustomerId, Constant = "constant"}).FirstAsync());
var c2 = await (db.Customers.Select(o => new {o.CustomerId, Constant = "constant2"}).FirstAsync());
var constant = "constant3";
var c3 = await (db.Customers.Select(o => new {o.CustomerId, Constant = constant}).FirstAsync());
constant = "constant4";
var c4 = await (db.Customers.Select(o => new {o.CustomerId, Constant = constant}).FirstAsync());

var queryCache = GetQueryPlanCache();
Assert.That(queryCache.Count, Is.EqualTo(1));

Assert.That(c1.Constant, Is.EqualTo("constant"));
Assert.That(c2.Constant, Is.EqualTo("constant2"));
Assert.That(c3.Constant, Is.EqualTo("constant3"));
Assert.That(c4.Constant, Is.EqualTo("constant4"));
}

[Test]
public async Task GroupByConstantShouldBeCachedAsync()
{
ClearQueryPlanCache();

var c1 = await (db.Customers.GroupBy(o => new {o.CustomerId, Constant = "constant"}).Select(o => o.Key).FirstAsync());
var c2 = await (db.Customers.GroupBy(o => new {o.CustomerId, Constant = "constant2"}).Select(o => o.Key).FirstAsync());
var constant = "constant3";
var c3 = await (db.Customers.GroupBy(o => new {o.CustomerId, Constant = constant}).Select(o => o.Key).FirstAsync());
constant = "constant4";
var c4 = await (db.Customers.GroupBy(o => new {o.CustomerId, Constant = constant}).Select(o => o.Key).FirstAsync());

var queryCache = GetQueryPlanCache();
Assert.That(queryCache.Count, Is.EqualTo(1));

Assert.That(c1.Constant, Is.EqualTo("constant"));
Assert.That(c2.Constant, Is.EqualTo("constant2"));
Assert.That(c3.Constant, Is.EqualTo("constant3"));
Assert.That(c4.Constant, Is.EqualTo("constant4"));
}

[Test]
public async Task WithLockShouldBeCachedAsync()
{
ClearQueryPlanCache();
// Limit to a few dialects where we know the "nowait" keyword is used to make life easier.
Assume.That(Dialect is MsSql2000Dialect || Dialect is Oracle8iDialect || Dialect is PostgreSQL81Dialect);

await (db.Customers.WithLock(LockMode.Upgrade).ToListAsync());
await (db.Customers.WithLock(LockMode.UpgradeNoWait).ToListAsync());
var lockMode = LockMode.None;
await (db.Customers.WithLock(lockMode).ToListAsync());
lockMode = LockMode.Read;
await (db.Customers.WithLock(lockMode).ToListAsync());

var queryCache = GetQueryPlanCache();
Assert.That(queryCache.Count, Is.EqualTo(4));
}

[TestCase(true)]
[TestCase(false)]
public async Task SkipShouldBeCachedAsync(bool supportsVariableLimit)
{
if (!Dialect.SupportsLimit || (supportsVariableLimit && !Dialect.SupportsVariableLimit))
{
Assert.Ignore();
}

ClearQueryPlanCache();
using (var substitute = SubstituteDialect())
{
substitute.Value.Configure().SupportsVariableLimit.Returns(supportsVariableLimit);

var c1 = await (db.Customers.Skip(1).ToListAsync());
var c2 = await (db.Customers.Skip(2).ToListAsync());
var skip = 3;
var c3 = await (db.Customers.Skip(skip).ToListAsync());
skip = 4;
var c4 = await (db.Customers.Skip(skip).ToListAsync());

var queryCache = GetQueryPlanCache();
Assert.That(c1.Count, Is.Not.EqualTo(c2.Count));
Assert.That(c2.Count, Is.Not.EqualTo(c3.Count));
Assert.That(c3.Count, Is.Not.EqualTo(c4.Count));
Assert.That(queryCache.Count, Is.EqualTo(supportsVariableLimit ? 1 : 4));
}
}

[TestCase(true)]
[TestCase(false)]
public async Task TakeShouldBeCachedAsync(bool supportsVariableLimit)
{
if (!Dialect.SupportsLimit || (supportsVariableLimit && !Dialect.SupportsVariableLimit))
{
Assert.Ignore();
}

ClearQueryPlanCache();
using (var substitute = SubstituteDialect())
{
substitute.Value.Configure().SupportsVariableLimit.Returns(supportsVariableLimit);

var c1 = await (db.Customers.Take(1).ToListAsync());
var c2 = await (db.Customers.Take(2).ToListAsync());
var skip = 3;
var c3 = await (db.Customers.Take(skip).ToListAsync());
skip = 4;
var c4 = await (db.Customers.Take(skip).ToListAsync());

var queryCache = GetQueryPlanCache();
Assert.That(c1.Count, Is.EqualTo(1));
Assert.That(c2.Count, Is.EqualTo(2));
Assert.That(c3.Count, Is.EqualTo(3));
Assert.That(c4.Count, Is.EqualTo(4));
Assert.That(queryCache.Count, Is.EqualTo(supportsVariableLimit ? 1 : 4));
}
}

[Test]
public async Task TrimFunctionShouldNotBeCachedAsync()
{
ClearQueryPlanCache();

await (db.Customers.Select(o => new {CustomerId = o.CustomerId.Trim('-')}).FirstAsync());
await (db.Customers.Select(o => new {CustomerId = o.CustomerId.Trim('+')}).FirstAsync());

var queryCache = GetQueryPlanCache();
Assert.That(queryCache.Count, Is.EqualTo(0));
}

[Test]
public async Task SubstringFunctionShouldBeCachedAsync()
{
ClearQueryPlanCache();

var queryCache = GetQueryPlanCache();
var c1 = await (db.Customers.Select(o => new {Name = o.ContactName.Substring(1)}).FirstAsync());
var c2 = await (db.Customers.Select(o => new {Name = o.ContactName.Substring(2)}).FirstAsync());

Assert.That(c1.Name, Is.Not.EqualTo(c2.Name));
Assert.That(queryCache.Count, Is.EqualTo(1));

ClearQueryPlanCache();
c1 = await (db.Customers.Select(o => new { Name = o.ContactName.Substring(1, 2) }).FirstAsync());
c2 = await (db.Customers.Select(o => new { Name = o.ContactName.Substring(2, 1) }).FirstAsync());

Assert.That(c1.Name, Is.Not.EqualTo(c2.Name));
Assert.That(queryCache.Count, Is.EqualTo(1));
}
}
}
18 changes: 9 additions & 9 deletions src/NHibernate.Test/Async/NHSpecificTest/NH3850/MainFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,7 @@ public async Task LongCountObjectAsync()
"Non nullable decimal max has failed");
var futureNonNullableDec = dcQuery.ToFutureValue(qdc => qdc.Max(dc => dc.NonNullableDecimal));
Assert.That(() => futureNonNullableDec.GetValueAsync(cancellationToken),
Throws.TargetInvocationException.And.InnerException.InstanceOf<InvalidOperationException>(),
Throws.InstanceOf<InvalidOperationException>(),
"Future non nullable decimal max has failed");
}
}
Expand Down Expand Up @@ -1002,7 +1002,7 @@ public async Task LongCountObjectAsync()
"Non nullable decimal min has failed");
var futureNonNullableDec = dcQuery.ToFutureValue(qdc => qdc.Min(dc => dc.NonNullableDecimal));
Assert.That(() => futureNonNullableDec.GetValueAsync(cancellationToken),
Throws.TargetInvocationException.And.InnerException.InstanceOf<InvalidOperationException>(),
Throws.InstanceOf<InvalidOperationException>(),
"Future non nullable decimal min has failed");
}
}
Expand All @@ -1017,7 +1017,7 @@ public void SingleOrDefaultBBaseAsync()
var query = session.Query<DomainClassBExtendedByA>();
Assert.That(() => query.SingleOrDefaultAsync(), Throws.InvalidOperationException);
var futureQuery = query.ToFutureValue(qdc => qdc.SingleOrDefault());
Assert.That(() => futureQuery.GetValueAsync(), Throws.TargetInvocationException.And.InnerException.TypeOf<InvalidOperationException>(), "Future");
Assert.That(() => futureQuery.GetValueAsync(), Throws.InstanceOf<InvalidOperationException>(), "Future");
}
}

Expand Down Expand Up @@ -1050,7 +1050,7 @@ public void SingleOrDefaultCBaseAsync()
var query = session.Query<DomainClassCExtendedByD>();
Assert.That(() => query.SingleOrDefaultAsync(), Throws.InvalidOperationException);
var futureQuery = query.ToFutureValue(qdc => qdc.SingleOrDefault());
Assert.That(() => futureQuery.GetValueAsync(), Throws.TargetInvocationException.And.InnerException.TypeOf<InvalidOperationException>(), "Future");
Assert.That(() => futureQuery.GetValueAsync(), Throws.InstanceOf<InvalidOperationException>(), "Future");
}
}

Expand Down Expand Up @@ -1083,7 +1083,7 @@ public void SingleOrDefaultEAsync()
var query = session.Query<DomainClassE>();
Assert.That(() => query.SingleOrDefaultAsync(), Throws.InvalidOperationException);
var futureQuery = query.ToFutureValue(qdc => qdc.SingleOrDefault());
Assert.That(() => futureQuery.GetValueAsync(), Throws.TargetInvocationException.And.InnerException.TypeOf<InvalidOperationException>(), "Future");
Assert.That(() => futureQuery.GetValueAsync(), Throws.InstanceOf<InvalidOperationException>(), "Future");
}
}

Expand Down Expand Up @@ -1146,7 +1146,7 @@ public void SingleOrDefaultGBaseAsync()
var query = session.Query<DomainClassGExtendedByH>();
Assert.That(() => query.SingleOrDefaultAsync(), Throws.InvalidOperationException);
var futureQuery = query.ToFutureValue(qdc => qdc.SingleOrDefault());
Assert.That(() => futureQuery.GetValueAsync(), Throws.TargetInvocationException.And.InnerException.TypeOf<InvalidOperationException>(), "Future");
Assert.That(() => futureQuery.GetValueAsync(), Throws.InstanceOf<InvalidOperationException>(), "Future");
}
}

Expand All @@ -1159,7 +1159,7 @@ public void SingleOrDefaultGBaseWithNameAsync()
var query = session.Query<DomainClassGExtendedByH>();
Assert.That(() => query.SingleOrDefaultAsync(dc => dc.Name == SearchName1), Throws.InvalidOperationException);
var futureQuery = query.ToFutureValue(qdc => qdc.SingleOrDefault(dc => dc.Name == SearchName1));
Assert.That(() => futureQuery.GetValueAsync(), Throws.TargetInvocationException.And.InnerException.TypeOf<InvalidOperationException>(), "Future");
Assert.That(() => futureQuery.GetValueAsync(), Throws.InstanceOf<InvalidOperationException>(), "Future");
}
}

Expand All @@ -1172,7 +1172,7 @@ public void SingleOrDefaultObjectAsync()
var query = session.Query<object>();
Assert.That(() => query.SingleOrDefaultAsync(), Throws.InvalidOperationException);
var futureQuery = query.ToFutureValue(qdc => qdc.SingleOrDefault());
Assert.That(() => futureQuery.GetValueAsync(), Throws.TargetInvocationException.And.InnerException.TypeOf<InvalidOperationException>(), "Future");
Assert.That(() => futureQuery.GetValueAsync(), Throws.InstanceOf<InvalidOperationException>(), "Future");
}
}

Expand Down Expand Up @@ -1276,7 +1276,7 @@ public async Task SumObjectAsync()
"Non nullable decimal sum has failed");
var futureNonNullableDec = dcQuery.ToFutureValue(qdc => qdc.Sum(dc => dc.NonNullableDecimal));
Assert.That(() => futureNonNullableDec.GetValueAsync(cancellationToken),
Throws.TargetInvocationException.And.InnerException.InstanceOf<InvalidOperationException>(),
Throws.InstanceOf<InvalidOperationException>(),
"Future non nullable decimal sum has failed");
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate.Test/Futures/LinqToFutureValueFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void ToFutureValueWithSumOnEmptySetThrows()
.Select(x => x.Id)
.ToFutureValue(x => x.Sum());

Assert.That(() => personsSum.Value, Throws.InnerException.TypeOf<InvalidOperationException>().Or.InnerException.TypeOf<ArgumentNullException>());
Assert.That(() => personsSum.Value, Throws.TypeOf<InvalidOperationException>().Or.InnerException.TypeOf<ArgumentNullException>());
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/NHibernate.Test/Linq/ConstantTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ public void DmlPlansAreCached()
}

[Test]
public void PlansWithNonParameterizedConstantsAreNotCached()
public void PlansWithNonParameterizedConstantsAreCached()
{
var queryPlanCacheType = typeof(QueryPlanCache);

Expand All @@ -339,12 +339,12 @@ public void PlansWithNonParameterizedConstantsAreNotCached()
select new { c.CustomerId, c.ContactName, Constant = 1 }).First();
Assert.That(
cache,
Has.Count.EqualTo(0),
"Query plan should not be cached.");
Has.Count.EqualTo(1),
"Query plan should be cached.");
}

[Test]
public void PlansWithNonParameterizedConstantsAreNotCachedForExpandedQuery()
public void PlansWithNonParameterizedConstantsAreCachedForExpandedQuery()
{
var queryPlanCacheType = typeof(QueryPlanCache);

Expand All @@ -360,8 +360,8 @@ public void PlansWithNonParameterizedConstantsAreNotCachedForExpandedQuery()

Assert.That(
cache,
Has.Count.EqualTo(0),
"Query plan should not be cached.");
Has.Count.EqualTo(2), // The second one is for the expanded expression that has two parameters
"Query plan should be cached.");
}

//GH-2298 - Different Update queries - same query cache plan
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate.Test/Linq/ParameterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ private void AssertTotalParameters<T>(IQueryable<T> query, int parameterNumber,
{
// In case of arrays linqParameterNumber and parameterNumber will be different
Assert.That(
GetLinqExpression(query).ParameterValuesByName.Count,
GetLinqExpression(query).NamedParameters.Count,
Is.EqualTo(linqParameterNumber ?? parameterNumber),
"Linq expression has different number of parameters");

Expand Down
Loading