Skip to content

Commit 8dabd92

Browse files
Handle multi-queries support in FutureBatch (#1666)
The previous Future implementation was letting all implementors handling the lack of multi-queries support themselves, leading to many discrepancies in behavior with a data provider which did not support multi-queries: * Accessing the future value was causing the query to be executed at each access. * Only the accessed future was executed, not all the other defined futures. This is the cause of #1663. * Futures having an ExecuteOnEval were not executing it, leading to NH-3850 bugs. Now FutureBatch handles itself the multi-queries support or lack of support. NH-3850 was not fixed for data providers which did not support multi-queries. But this was undetected because its tests were executed on too few databases, due to them requiring the support of DateTimeOffset.
1 parent 77d2ac6 commit 8dabd92

22 files changed

+1094
-664
lines changed

src/NHibernate.Test/Async/Futures/FallbackFixture.cs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ protected override void OnTearDown()
5858
{
5959
using (var session = Sfi.OpenSession())
6060
{
61-
session.Delete("from Person");
62-
session.Flush();
61+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
6362
}
6463

6564
base.OnTearDown();
@@ -179,6 +178,82 @@ public async Task FutureValueWithSelectorOfLinqCanGetSingleEntityWhenQueryBatchi
179178
}
180179
}
181180

181+
[Test]
182+
public async Task FutureValueWithLinqPolymorphicAggregateAsync()
183+
{
184+
using (var session = OpenSession())
185+
{
186+
var futureExists =
187+
session
188+
.Query<PolymorphicA>()
189+
.ToFutureValue(q => q.Any());
190+
Assert.That(await (futureExists.GetValueAsync()), Is.False);
191+
192+
var b = new PolymorphicB();
193+
await (session.SaveAsync(b));
194+
await (session.FlushAsync());
195+
196+
futureExists =
197+
session
198+
.Query<PolymorphicA>()
199+
.ToFutureValue(q => q.Any());
200+
Assert.That(await (futureExists.GetValueAsync()), Is.True, "Has not found B");
201+
202+
await (session.DeleteAsync(b));
203+
await (session.SaveAsync(new PolymorphicA()));
204+
await (session.FlushAsync());
205+
206+
futureExists =
207+
session
208+
.Query<PolymorphicA>()
209+
.ToFutureValue(q => q.Any());
210+
Assert.That(await (futureExists.GetValueAsync()), Is.True, "Has not found A");
211+
}
212+
}
213+
214+
[Test]
215+
public async Task NonExplicitlyExecutedFutureAreExecutedAsync()
216+
{
217+
Sfi.Statistics.IsStatisticsEnabled = true;
218+
try
219+
{
220+
using (var s = Sfi.OpenSession())
221+
{
222+
var persons = s.CreateQuery("from Person").Future<Person>();
223+
s.Query<Person>().ToFutureValue(q => q.Any());
224+
Sfi.Statistics.Clear();
225+
Assert.That((await (persons.GetEnumerableAsync())).FirstOrDefault(), Is.Null);
226+
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2));
227+
}
228+
}
229+
finally
230+
{
231+
Sfi.Statistics.IsStatisticsEnabled = false;
232+
}
233+
}
234+
235+
[Test]
236+
public async Task FutureIsNotReexecutedAsync()
237+
{
238+
Sfi.Statistics.IsStatisticsEnabled = true;
239+
try
240+
{
241+
using (var s = Sfi.OpenSession())
242+
{
243+
var exists = s.Query<Person>().ToFutureValue(q => q.Any());
244+
Assert.That(await (exists.GetValueAsync()), Is.False);
245+
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(1));
246+
Sfi.Statistics.Clear();
247+
Assert.That(await (exists.GetValueAsync()), Is.False);
248+
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(0));
249+
}
250+
}
251+
finally
252+
{
253+
Sfi.Statistics.IsStatisticsEnabled = false;
254+
}
255+
}
256+
182257
private async Task<int> CreatePersonAsync(CancellationToken cancellationToken = default(CancellationToken))
183258
{
184259
using (var session = Sfi.OpenSession())
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Collections;
12+
using System.Data;
13+
using System.Linq;
14+
using NHibernate.Driver;
15+
using NHibernate.Linq;
16+
using NHibernate.SqlTypes;
17+
using NUnit.Framework;
18+
19+
namespace NHibernate.Test.NHSpecificTest.NH3850
20+
{
21+
using System.Threading.Tasks;
22+
using System.Threading;
23+
[TestFixture]
24+
public class DateTimeOffsetFixtureAsync : FixtureBaseAsync
25+
{
26+
protected override IList Mappings => new[] { $"NHSpecificTest.{BugNumber}.DateTimeOffsetMappings.hbm.xml" };
27+
28+
protected override bool AppliesTo(Dialect.Dialect dialect)
29+
{
30+
return TestDialect.SupportsSqlType(new SqlType(DbType.DateTimeOffset));
31+
}
32+
33+
protected override bool AppliesTo(Engine.ISessionFactoryImplementor factory)
34+
{
35+
// Cannot handle DbType.DateTimeOffset via ODBC.
36+
return !(factory.ConnectionProvider.Driver is OdbcDriver);
37+
}
38+
39+
protected override async Task MaxAsync<DC>(int? expectedResult, CancellationToken cancellationToken = default(CancellationToken))
40+
{
41+
using (var session = OpenSession())
42+
{
43+
var dcQuery = session.Query<DC>();
44+
var dateWithOffset = await (dcQuery.MaxAsync(dc => dc.DateTimeOffset, cancellationToken));
45+
var futureDateWithOffset = dcQuery.ToFutureValue(qdc => qdc.Max(dc => dc.DateTimeOffset));
46+
if (expectedResult.HasValue)
47+
{
48+
Assert.That(dateWithOffset, Is.GreaterThan(TestDateWithOffset), "DateTimeOffset max has failed");
49+
Assert.That(
50+
await (futureDateWithOffset.GetValueAsync(cancellationToken)),
51+
Is.GreaterThan(TestDateWithOffset),
52+
"Future DateTimeOffset max has failed");
53+
}
54+
else
55+
{
56+
Assert.That(dateWithOffset, Is.Null, "DateTimeOffset max has failed");
57+
Assert.That(await (futureDateWithOffset.GetValueAsync(cancellationToken)), Is.Null, "Future DateTimeOffset max has failed");
58+
}
59+
}
60+
}
61+
62+
protected override async Task MinAsync<DC>(int? expectedResult, CancellationToken cancellationToken = default(CancellationToken))
63+
{
64+
using (var session = OpenSession())
65+
{
66+
var dcQuery = session.Query<DC>();
67+
var dateWithOffset = await (dcQuery.MinAsync(dc => dc.DateTimeOffset, cancellationToken));
68+
var futureDateWithOffset = dcQuery.ToFutureValue(qdc => qdc.Min(dc => dc.DateTimeOffset));
69+
if (expectedResult.HasValue)
70+
{
71+
Assert.That(dateWithOffset, Is.LessThan(TestDateWithOffset), "DateTimeOffset min has failed");
72+
Assert.That(await (futureDateWithOffset.GetValueAsync(cancellationToken)), Is.LessThan(TestDateWithOffset), "Future DateTimeOffset min has failed");
73+
}
74+
else
75+
{
76+
Assert.That(dateWithOffset, Is.Null, "DateTimeOffset min has failed");
77+
Assert.That(await (futureDateWithOffset.GetValueAsync(cancellationToken)), Is.Null, "Future DateTimeOffset min has failed");
78+
}
79+
}
80+
}
81+
}
82+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System;
12+
using NUnit.Framework;
13+
14+
namespace NHibernate.Test.NHSpecificTest.NH3850
15+
{
16+
using System.Threading.Tasks;
17+
using System.Threading;
18+
[TestFixture]
19+
public abstract class FixtureBaseAsync : BugTestCase
20+
{
21+
protected const string SearchName1 = "name";
22+
protected const string SearchName2 = "name2";
23+
protected const int TotalEntityCount = 10;
24+
protected readonly DateTime TestDate = DateTime.Now;
25+
protected readonly DateTimeOffset TestDateWithOffset = DateTimeOffset.Now;
26+
27+
protected override void OnSetUp()
28+
{
29+
base.OnSetUp();
30+
using (var session = OpenSession())
31+
{
32+
var dateTime1 = TestDate.AddDays(-1);
33+
var dateTime2 = TestDate.AddDays(1);
34+
var dateTimeOffset1 = TestDateWithOffset.AddDays(-1);
35+
var dateTimeOffset2 = TestDateWithOffset.AddDays(1);
36+
Action<DomainClassBase> init1 = dc =>
37+
{
38+
dc.Id = 1;
39+
dc.Name = SearchName1;
40+
dc.Integer = 1;
41+
dc.Long = 1;
42+
dc.Decimal = 1;
43+
dc.Double = 1;
44+
dc.DateTime = dateTime1;
45+
dc.DateTimeOffset = dateTimeOffset1;
46+
dc.NonNullableDecimal = 1;
47+
};
48+
Action<DomainClassBase> init2 = dc =>
49+
{
50+
dc.Id = 2;
51+
dc.Name = SearchName2;
52+
dc.Integer = 2;
53+
dc.Long = 2;
54+
dc.Decimal = 2;
55+
dc.Double = 2;
56+
dc.DateTime = dateTime2;
57+
dc.DateTimeOffset = dateTimeOffset2;
58+
dc.NonNullableDecimal = 2;
59+
};
60+
61+
DomainClassBase entity = new DomainClassBExtendedByA();
62+
init1(entity);
63+
session.Save(entity);
64+
entity = new DomainClassBExtendedByA();
65+
init2(entity);
66+
session.Save(entity);
67+
68+
entity = new DomainClassCExtendedByD();
69+
init1(entity);
70+
session.Save(entity);
71+
entity = new DomainClassCExtendedByD();
72+
init2(entity);
73+
session.Save(entity);
74+
75+
entity = new DomainClassE();
76+
init1(entity);
77+
session.Save(entity);
78+
entity = new DomainClassE();
79+
init2(entity);
80+
session.Save(entity);
81+
82+
entity = new DomainClassGExtendedByH();
83+
init1(entity);
84+
session.Save(entity);
85+
entity = new DomainClassGExtendedByH();
86+
init2(entity);
87+
session.Save(entity);
88+
entity = new DomainClassHExtendingG
89+
{
90+
Id = 3,
91+
Name = SearchName1,
92+
Integer = 3,
93+
Long = 3,
94+
Decimal = 3,
95+
Double = 3,
96+
DateTime = dateTime1,
97+
DateTimeOffset = dateTimeOffset1,
98+
NonNullableDecimal = 3
99+
};
100+
session.Save(entity);
101+
entity = new DomainClassHExtendingG
102+
{
103+
Id = 4,
104+
Name = SearchName2,
105+
Integer = 4,
106+
Long = 4,
107+
Decimal = 4,
108+
Double = 4,
109+
DateTime = dateTime2,
110+
DateTimeOffset = dateTimeOffset2,
111+
NonNullableDecimal = 4
112+
};
113+
session.Save(entity);
114+
115+
session.Flush();
116+
}
117+
}
118+
119+
protected override void OnTearDown()
120+
{
121+
base.OnTearDown();
122+
using (var session = OpenSession())
123+
{
124+
var hql = "from System.Object";
125+
session.Delete(hql);
126+
session.Flush();
127+
}
128+
}
129+
130+
// Failing case till NH-3850 is fixed
131+
[Test]
132+
public Task MaxBBaseAsync()
133+
{
134+
return MaxAsync<DomainClassBExtendedByA>(2);
135+
}
136+
137+
// Failing case till NH-3850 is fixed
138+
[Test]
139+
public Task MaxCBaseAsync()
140+
{
141+
return MaxAsync<DomainClassCExtendedByD>(2);
142+
}
143+
144+
// Non-reg case
145+
[Test]
146+
public Task MaxEAsync()
147+
{
148+
return MaxAsync<DomainClassE>(2);
149+
}
150+
151+
// Non-reg case
152+
[Test]
153+
public Task MaxFAsync()
154+
{
155+
return MaxAsync<DomainClassF>(null);
156+
}
157+
158+
// Failing case till NH-3850 is fixed
159+
[Test]
160+
public Task MaxGBaseAsync()
161+
{
162+
return MaxAsync<DomainClassGExtendedByH>(4);
163+
}
164+
165+
protected abstract Task MaxAsync<TDc>(int? expectedResult, CancellationToken cancellationToken = default(CancellationToken)) where TDc : DomainClassBase;
166+
167+
// Failing case till NH-3850 is fixed
168+
[Test]
169+
public Task MinBBaseAsync()
170+
{
171+
return MinAsync<DomainClassBExtendedByA>(1);
172+
}
173+
174+
// Failing case till NH-3850 is fixed
175+
[Test]
176+
public Task MinCBaseAsync()
177+
{
178+
return MinAsync<DomainClassCExtendedByD>(1);
179+
}
180+
181+
// Non-reg case
182+
[Test]
183+
public Task MinEAsync()
184+
{
185+
return MinAsync<DomainClassE>(1);
186+
}
187+
188+
// Non-reg case
189+
[Test]
190+
public Task MinFAsync()
191+
{
192+
return MinAsync<DomainClassF>(null);
193+
}
194+
195+
// Non-reg case
196+
[Test]
197+
public Task MinGBaseAsync()
198+
{
199+
return MinAsync<DomainClassGExtendedByH>(1);
200+
}
201+
202+
protected abstract Task MinAsync<TDc>(int? expectedResult, CancellationToken cancellationToken = default(CancellationToken)) where TDc : DomainClassBase;
203+
}
204+
}

0 commit comments

Comments
 (0)