Skip to content

Commit f214221

Browse files
authored
Fix Linq .Second bug for PostgreSQL et al. (#3528)
1 parent 3a2e1cb commit f214221

File tree

8 files changed

+438
-97
lines changed

8 files changed

+438
-97
lines changed

src/NHibernate.Test/Async/Linq/DateTimeTests.cs

Lines changed: 205 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,98 +9,259 @@
99

1010

1111
using System;
12+
using System.Data;
1213
using System.Linq;
13-
using NUnit.Framework;
14+
using System.Linq.Expressions;
15+
using NHibernate.Cfg;
1416
using NHibernate.Linq;
17+
using NHibernate.Mapping.ByCode;
18+
using NHibernate.SqlTypes;
19+
using NHibernate.Type;
20+
using NUnit.Framework;
1521

1622
namespace NHibernate.Test.Linq
1723
{
1824
using System.Threading.Tasks;
25+
using System.Threading;
1926
[TestFixture]
20-
public class DateTimeTestsAsync : LinqTestCase
27+
public class DateTimeTestsAsync : TestCase
2128
{
29+
private bool DialectSupportsDateTimeOffset => TestDialect.SupportsSqlType(new SqlType(DbType.DateTimeOffset));
30+
private readonly DateTimeTestsClass[] _referenceEntities =
31+
[
32+
new() {Id =1, DateTimeValue = new DateTime(1998, 02, 26)},
33+
new() {Id =2, DateTimeValue = new DateTime(1998, 02, 26)},
34+
new() {Id =3, DateTimeValue = new DateTime(1998, 02, 26, 01, 01, 01)},
35+
new() {Id =4, DateTimeValue = new DateTime(1998, 02, 26, 02, 02, 02)},
36+
new() {Id =5, DateTimeValue = new DateTime(1998, 02, 26, 03, 03, 03)},
37+
new() {Id =6, DateTimeValue = new DateTime(1998, 02, 26, 04, 04, 04)},
38+
new() {Id =7, DateTimeValue = new DateTime(1998, 03, 01)},
39+
new() {Id =8, DateTimeValue = new DateTime(2000, 01, 01)}
40+
];
41+
42+
private TimeSpan FractionalSecondsAdded => TimeSpan.FromMilliseconds(900);
43+
44+
protected override string[] Mappings => default;
45+
protected override void AddMappings(Configuration configuration)
46+
{
47+
var modelMapper = new ModelMapper();
48+
49+
modelMapper.Class<DateTimeTestsClass>(m =>
50+
{
51+
m.Table("datetimetests");
52+
m.Lazy(false);
53+
m.Id(p => p.Id, p => p.Generator(Generators.Assigned));
54+
m.Property(p => p.DateValue, c => c.Type<DateType>());
55+
m.Property(p => p.DateTimeValue);
56+
m.Property(p => p.DateTimeValueWithScale, c => c.Scale(2));
57+
if (DialectSupportsDateTimeOffset)
58+
{
59+
m.Property(p => p.DateTimeOffsetValue);
60+
m.Property(p => p.DateTimeOffsetValueWithScale, c => c.Scale(2));
61+
}
62+
});
63+
var mapping = modelMapper.CompileMappingForAllExplicitlyAddedEntities();
64+
configuration.AddMapping(mapping);
65+
}
66+
67+
protected override void OnSetUp()
68+
{
69+
foreach (var entity in _referenceEntities)
70+
{
71+
entity.DateValue = entity.DateTimeValue.Date;
72+
entity.DateTimeValueWithScale = entity.DateTimeValue + FractionalSecondsAdded;
73+
entity.DateTimeOffsetValue = new DateTimeOffset(entity.DateTimeValue, TimeSpan.FromHours(3));
74+
entity.DateTimeOffsetValueWithScale = new DateTimeOffset(entity.DateTimeValueWithScale, TimeSpan.FromHours(3));
75+
}
76+
77+
using var session = OpenSession();
78+
using var trans = session.BeginTransaction();
79+
foreach (var entity in _referenceEntities)
80+
{
81+
session.Save(entity);
82+
}
83+
trans.Commit();
84+
}
85+
86+
protected override void OnTearDown()
87+
{
88+
using var session = OpenSession();
89+
using var trans = session.BeginTransaction();
90+
session.Query<DateTimeTestsClass>().Delete();
91+
trans.Commit();
92+
}
93+
94+
private void AssertDateTimeOffsetSupported()
95+
{
96+
if (!DialectSupportsDateTimeOffset)
97+
{
98+
Assert.Ignore("Dialect doesn't support DateTimeOffset");
99+
}
100+
}
101+
102+
private async Task AssertDateTimeWithFractionalSecondsSupportedAsync(CancellationToken cancellationToken = default(CancellationToken))
103+
{
104+
//Ideally, the dialect should know whether this is supported or not
105+
if (!TestDialect.SupportsDateTimeWithFractionalSeconds)
106+
{
107+
Assert.Ignore("Dialect doesn't support DateTime with factional seconds");
108+
}
109+
110+
//But it sometimes doesn't
111+
using var session = OpenSession();
112+
using var trans = session.BeginTransaction();
113+
var entity1 = await (session.GetAsync<DateTimeTestsClass>(_referenceEntities[0].Id, cancellationToken));
114+
await (trans.CommitAsync(cancellationToken));
115+
if (entity1.DateTimeValueWithScale != entity1.DateTimeValue + FractionalSecondsAdded)
116+
{
117+
Assert.Ignore("Current setup doesn't support DateTime with scale (2)");
118+
}
119+
}
120+
121+
private Task AssertQueryAsync(Expression<Func<DateTimeTestsClass, bool>> where, CancellationToken cancellationToken = default(CancellationToken)) => AssertQueryAsync(where, x => x.Id, cancellationToken);
122+
123+
private async Task AssertQueryAsync<TSelect>(Expression<Func<DateTimeTestsClass, bool>> where, Expression<Func<DateTimeTestsClass, TSelect>> select, CancellationToken cancellationToken = default(CancellationToken))
124+
{
125+
using var session = OpenSession();
126+
var fromDb = await (session.Query<DateTimeTestsClass>().Where(where).Select(select).ToListAsync(cancellationToken));
127+
var fromMemory = _referenceEntities.AsQueryable().Where(where).Select(select).AsEnumerable().ToList(); //AsEnumerable added to avoid async generator
128+
Assert.That(fromMemory, Has.Count.GreaterThan(0), "Inconclusive, since the query doesn't match anything in the defined set");
129+
Assert.That(fromDb, Has.Count.EqualTo(fromMemory.Count));
130+
Assert.That(fromDb, Is.EquivalentTo(fromMemory));
131+
}
132+
22133
[Test]
23134
public async Task CanQueryByYearAsync()
24135
{
25-
var x = await ((from o in db.Orders
26-
where o.OrderDate.Value.Year == 1998
27-
select o).ToListAsync());
136+
await (AssertQueryAsync(o => o.DateTimeValue.Year == 1998));
137+
}
28138

29-
Assert.AreEqual(270, x.Count());
139+
[Test]
140+
public async Task CanQueryDateTimeBySecondAsync()
141+
{
142+
await (AssertQueryAsync(o => o.DateTimeValue.Second == 4));
30143
}
31144

32145
[Test]
33-
public async Task CanQueryByDateAsync()
146+
public async Task CanQueryDateTimeByMinuteAsync()
34147
{
35-
var x = await ((from o in db.Orders
36-
where o.OrderDate.Value.Date == new DateTime(1998, 02, 26)
37-
select o).ToListAsync());
148+
await (AssertQueryAsync(o => o.DateTimeValue.Minute == 4));
149+
}
38150

39-
Assert.AreEqual(6, x.Count());
151+
[Test]
152+
public async Task CanQueryDateTimeByHourAsync()
153+
{
154+
await (AssertQueryAsync(o => o.DateTimeValue.Hour == 4));
40155
}
41156

42157
[Test]
43-
public async Task CanQueryByDateTimeAsync()
158+
public async Task CanQueryDateTimeBySecondWhenValueContainsFractionalSecondsAsync()
159+
{
160+
await (AssertDateTimeWithFractionalSecondsSupportedAsync());
161+
await (AssertQueryAsync(o => o.DateTimeValueWithScale.Second == 4));
162+
}
163+
164+
[Test]
165+
public async Task CanQueryDateTimeOffsetBySecondAsync()
44166
{
45-
var x = await ((from o in db.Orders
46-
where o.OrderDate.Value == new DateTime(1998, 02, 26)
47-
select o).ToListAsync());
167+
AssertDateTimeOffsetSupported();
168+
await (AssertQueryAsync(o => o.DateTimeOffsetValue.Second == 4));
169+
}
48170

49-
Assert.AreEqual(5, x.Count());
171+
[Test]
172+
public async Task CanQueryDateTimeOffsetByMinuteAsync()
173+
{
174+
AssertDateTimeOffsetSupported();
175+
await (AssertQueryAsync(o => o.DateTimeOffsetValue.Minute == 4));
50176
}
51177

52178
[Test]
53-
public async Task CanQueryByDateTime2Async()
179+
public async Task CanQueryDateTimeOffsetByHourAsync()
54180
{
55-
var x = await ((from o in db.Orders
56-
where o.OrderDate.Value == new DateTime(1998, 02, 26, 0, 1, 0)
57-
select o).ToListAsync());
181+
AssertDateTimeOffsetSupported();
182+
await (AssertQueryAsync(o => o.DateTimeOffsetValue.Hour == 4));
183+
}
58184

59-
Assert.AreEqual(1, x.Count());
185+
[Test]
186+
public async Task CanQueryDateTimeOffsetBySecondWhenValueContainsFractionalSecondsAsync()
187+
{
188+
AssertDateTimeOffsetSupported();
189+
await (AssertQueryAsync(o => o.DateTimeOffsetValueWithScale.Second == 4));
60190
}
61191

62192
[Test]
63-
public async Task CanSelectYearAsync()
193+
public async Task CanQueryByDateAsync()
64194
{
65-
var x = await ((from o in db.Orders
66-
where o.OrderDate.Value.Year == 1998
67-
select o.OrderDate.Value.Year).ToListAsync());
195+
await (AssertQueryAsync(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26)));
196+
}
68197

69-
Assert.That(x, Has.All.EqualTo(1998));
70-
Assert.AreEqual(270, x.Count());
198+
[Test]
199+
public async Task CanQueryByDateTimeAsync()
200+
{
201+
await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26)));
71202
}
72203

73204
[Test]
74-
public async Task CanSelectDateAsync()
205+
public async Task CanQueryByDateTime2Async()
206+
{
207+
await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1)));
208+
}
209+
210+
[Test]
211+
public async Task CanSelectYearAsync()
75212
{
76-
var x = await ((from o in db.Orders
77-
where o.OrderDate.Value.Date == new DateTime(1998, 02, 26)
78-
select o.OrderDate.Value.Date).ToListAsync());
213+
await (AssertQueryAsync(o => o.DateTimeValue.Year == 1998, o => o.DateTimeValue.Year));
214+
}
79215

80-
Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26)));
81-
Assert.AreEqual(6, x.Count());
216+
[Test]
217+
public async Task CanSelectDateAsync()
218+
{
219+
await (AssertQueryAsync(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26), o => o.DateTimeValue.Date));
82220
}
83221

84222
[Test]
85223
public async Task CanSelectDateTimeAsync()
86224
{
87-
var x = await ((from o in db.Orders
88-
where o.OrderDate.Value == new DateTime(1998, 02, 26)
89-
select o.OrderDate.Value).ToListAsync());
90-
91-
Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26)));
92-
Assert.AreEqual(5, x.Count());
225+
await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26), o => o.DateTimeValue));
93226
}
94227

95228
[Test]
96229
public async Task CanSelectDateTime2Async()
97230
{
98-
var x = await ((from o in db.Orders
99-
where o.OrderDate.Value == new DateTime(1998, 02, 26, 0, 1, 0)
100-
select o.OrderDate.Value).ToListAsync());
231+
await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1), o => o.DateTimeValue));
232+
}
233+
234+
[Test]
235+
public async Task CanSelectDateTimeWithScaleAsync()
236+
{
237+
await (AssertDateTimeWithFractionalSecondsSupportedAsync());
238+
await (AssertQueryAsync(o => o.DateTimeValueWithScale == _referenceEntities[0].DateTimeValueWithScale, o => o.DateTimeValueWithScale));
239+
}
240+
241+
public class DateTimeTestsClass : IEquatable<DateTimeTestsClass>
242+
{
243+
public int Id { get; set; }
244+
public DateTime DateTimeValue { get; set; }
245+
public DateTime DateTimeValueWithScale { get; set; }
246+
public DateTimeOffset DateTimeOffsetValue { get; set; }
247+
public DateTimeOffset DateTimeOffsetValueWithScale { get; set; }
248+
public DateTime DateValue { get; set; }
249+
250+
public override bool Equals(object obj)
251+
{
252+
return Equals(obj as DateTimeTestsClass);
253+
}
254+
255+
public bool Equals(DateTimeTestsClass other)
256+
{
257+
return other is not null &&
258+
Id.Equals(other.Id);
259+
}
101260

102-
Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26, 0, 1, 0)));
103-
Assert.AreEqual(1, x.Count());
261+
public override int GetHashCode()
262+
{
263+
return HashCode.Combine(Id);
264+
}
104265
}
105266
}
106267
}

0 commit comments

Comments
 (0)