Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b39dd61

Browse files
committedDec 28, 2024·
Fix Linq .Second bug for PostgreSQL et al.
Refactor tests
1 parent 0c9e442 commit b39dd61

File tree

8 files changed

+439
-94
lines changed

8 files changed

+439
-94
lines changed
 

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

Lines changed: 206 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,98 +9,261 @@
99

1010

1111
using System;
12+
using System.Data;
1213
using System.Linq;
14+
using NHibernate.Cfg;
15+
using NHibernate.SqlTypes;
16+
using NHibernate.Mapping.ByCode;
1317
using NUnit.Framework;
18+
using NHibernate.Type;
1419
using NHibernate.Linq;
20+
using System.Linq.Expressions;
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 bool DialectSupportsDateTimeWithScale => TestDialect.SupportsSqlType(new SqlType(DbType.DateTime,(byte) 2));
31+
private readonly DateTimeTestsClass[] _referenceEntities =
32+
[
33+
new() {Id =1, DateTimeValue = new DateTime(1998, 02, 26)},
34+
new() {Id =2, DateTimeValue = new DateTime(1998, 02, 26)},
35+
new() {Id =3, DateTimeValue = new DateTime(1998, 02, 26, 01, 01, 01)},
36+
new() {Id =4, DateTimeValue = new DateTime(1998, 02, 26, 02, 02, 02)},
37+
new() {Id =5, DateTimeValue = new DateTime(1998, 02, 26, 03, 03, 03)},
38+
new() {Id =6, DateTimeValue = new DateTime(1998, 02, 26, 04, 04, 04)},
39+
new() {Id =7, DateTimeValue = new DateTime(1998, 03, 01)},
40+
new() {Id =8, DateTimeValue = new DateTime(2000, 01, 01)}
41+
];
42+
43+
protected override string[] Mappings => default;
44+
protected override void AddMappings(Configuration configuration)
45+
{
46+
var modelMapper = new ModelMapper();
47+
48+
modelMapper.Class<DateTimeTestsClass>(m =>
49+
{
50+
m.Table("datetimetests");
51+
m.Lazy(false);
52+
m.Id(p => p.Id, p => p.Generator(Generators.Assigned));
53+
m.Property(p => p.DateValue, c => c.Type<DateType>());
54+
m.Property(p => p.DateTimeValue);
55+
m.Property(p => p.DateTimeValueWithScale, c => c.Scale(2));
56+
if (DialectSupportsDateTimeOffset)
57+
{
58+
m.Property(p => p.DateTimeOffsetValue);
59+
m.Property(p => p.DateTimeOffsetValueWithScale, c => c.Scale(2));
60+
}
61+
});
62+
var mapping = modelMapper.CompileMappingForAllExplicitlyAddedEntities();
63+
configuration.AddMapping(mapping);
64+
}
65+
66+
protected override void OnSetUp()
67+
{
68+
foreach (var entity in _referenceEntities)
69+
{
70+
entity.DateValue = entity.DateTimeValue.Date;
71+
entity.DateTimeValueWithScale = entity.DateTimeValue.AddSeconds(0.9);
72+
entity.DateTimeOffsetValue = new DateTimeOffset(entity.DateTimeValue, TimeSpan.FromHours(3));
73+
entity.DateTimeOffsetValueWithScale = new DateTimeOffset(entity.DateTimeValue, TimeSpan.FromHours(3));
74+
}
75+
76+
using (var session = OpenSession())
77+
using (var trans = session.BeginTransaction())
78+
{
79+
foreach (var entity in _referenceEntities)
80+
{
81+
session.Save(entity);
82+
}
83+
trans.Commit();
84+
}
85+
}
86+
87+
protected override void OnTearDown()
88+
{
89+
using (var session = OpenSession())
90+
using (var trans = session.BeginTransaction())
91+
{
92+
session.Query<DateTimeTestsClass>().Delete();
93+
trans.Commit();
94+
}
95+
}
96+
97+
private void AssertDateTimeOffsetSupported()
98+
{
99+
if (!DialectSupportsDateTimeOffset)
100+
{
101+
Assert.Ignore("Dialect doesn't support DateTimeOffset");
102+
}
103+
}
104+
105+
private async Task AssertDateTimeWithScaleSupportedAsync(CancellationToken cancellationToken = default(CancellationToken))
106+
{
107+
if (!DialectSupportsDateTimeWithScale)
108+
{
109+
Assert.Ignore("Dialect doesn't support DateTime with scale (2)");
110+
}
111+
using (var session = OpenSession())
112+
using (var trans = session.BeginTransaction())
113+
{
114+
var entity1 = await (session.GetAsync<DateTimeTestsClass>(_referenceEntities[0].Id, cancellationToken));
115+
if (entity1.DateTimeValueWithScale != entity1.DateTimeValue.AddSeconds(0.9))
116+
{
117+
Assert.Ignore("Current setup doesn't support DateTime with scale (2)");
118+
}
119+
}
120+
121+
}
122+
123+
private Task AssertQueryAsync(Expression<Func<DateTimeTestsClass, bool>> where, CancellationToken cancellationToken = default(CancellationToken)) => AssertQueryAsync(where, x => x.Id, cancellationToken);
124+
125+
private async Task AssertQueryAsync<TSelect>(Expression<Func<DateTimeTestsClass, bool>> where, Expression<Func<DateTimeTestsClass, TSelect>> select, CancellationToken cancellationToken = default(CancellationToken))
126+
{
127+
using var session = OpenSession();
128+
var fromDb = await (session.Query<DateTimeTestsClass>().Where(where).Select(select).ToListAsync(cancellationToken));
129+
var fromMemory = _referenceEntities.AsQueryable().Where(where).Select(select).AsEnumerable().ToList(); //AsEnumerable added to avoid async generator
130+
Assert.That(fromMemory, Has.Count.GreaterThan(0), "Inconclusive, since the query doesn't match anything in the defined set");
131+
Assert.That(fromDb, Has.Count.EqualTo(fromMemory.Count));
132+
Assert.That(fromDb, Is.EquivalentTo(fromMemory));
133+
}
134+
22135
[Test]
23136
public async Task CanQueryByYearAsync()
24137
{
25-
var x = await ((from o in db.Orders
26-
where o.OrderDate.Value.Year == 1998
27-
select o).ToListAsync());
138+
await (AssertQueryAsync(o => o.DateTimeValue.Year == 1998));
139+
}
28140

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

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

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

42159
[Test]
43-
public async Task CanQueryByDateTimeAsync()
160+
public async Task CanQueryDateTimeBySecondWhenValueContainsFractionalSecondsAsync()
44161
{
45-
var x = await ((from o in db.Orders
46-
where o.OrderDate.Value == new DateTime(1998, 02, 26)
47-
select o).ToListAsync());
162+
await (AssertDateTimeWithScaleSupportedAsync());
163+
await (AssertQueryAsync(o => o.DateTimeValueWithScale.Second == 4));
164+
}
48165

49-
Assert.AreEqual(5, x.Count());
166+
[Test]
167+
public async Task CanQueryDateTimeOffsetBySecondAsync()
168+
{
169+
AssertDateTimeOffsetSupported();
170+
await (AssertQueryAsync(o => o.DateTimeOffsetValue.Second == 4));
50171
}
51172

52173
[Test]
53-
public async Task CanQueryByDateTime2Async()
174+
public async Task CanQueryDateTimeOffsetByMinuteAsync()
54175
{
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());
176+
AssertDateTimeOffsetSupported();
177+
await (AssertQueryAsync(o => o.DateTimeOffsetValue.Minute == 4));
178+
}
58179

59-
Assert.AreEqual(1, x.Count());
180+
[Test]
181+
public async Task CanQueryDateTimeOffsetByHourAsync()
182+
{
183+
AssertDateTimeOffsetSupported();
184+
await (AssertQueryAsync(o => o.DateTimeOffsetValue.Hour == 4));
60185
}
61186

62187
[Test]
63-
public async Task CanSelectYearAsync()
188+
public async Task CanQueryDateTimeOffsetBySecondWhenValueContainsFractionalSecondsAsync()
64189
{
65-
var x = await ((from o in db.Orders
66-
where o.OrderDate.Value.Year == 1998
67-
select o.OrderDate.Value.Year).ToListAsync());
190+
AssertDateTimeOffsetSupported();
191+
await (AssertQueryAsync(o => o.DateTimeOffsetValueWithScale.Second == 4));
192+
}
68193

69-
Assert.That(x, Has.All.EqualTo(1998));
70-
Assert.AreEqual(270, x.Count());
194+
[Test]
195+
public async Task CanQueryByDateAsync()
196+
{
197+
await (AssertQueryAsync(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26)));
71198
}
72199

73200
[Test]
74-
public async Task CanSelectDateAsync()
201+
public async Task CanQueryByDateTimeAsync()
75202
{
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());
203+
await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26)));
204+
}
79205

80-
Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26)));
81-
Assert.AreEqual(6, x.Count());
206+
[Test]
207+
public async Task CanQueryByDateTime2Async()
208+
{
209+
await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1)));
82210
}
83211

84212
[Test]
85-
public async Task CanSelectDateTimeAsync()
213+
public async Task CanSelectYearAsync()
214+
{
215+
await (AssertQueryAsync(o => o.DateTimeValue.Year == 1998, o => o.DateTimeValue.Year));
216+
}
217+
218+
[Test]
219+
public async Task CanSelectDateAsync()
86220
{
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());
221+
await (AssertQueryAsync(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26), o => o.DateTimeValue.Date));
222+
}
90223

91-
Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26)));
92-
Assert.AreEqual(5, x.Count());
224+
[Test]
225+
public async Task CanSelectDateTimeAsync()
226+
{
227+
await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26), o => o.DateTimeValue));
93228
}
94229

95230
[Test]
96231
public async Task CanSelectDateTime2Async()
97232
{
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());
233+
await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1), o => o.DateTimeValue));
234+
}
235+
236+
[Test]
237+
public async Task CanSelectDateTimeWithScaleAsync()
238+
{
239+
await (AssertDateTimeWithScaleSupportedAsync());
240+
await (AssertQueryAsync(o => o.DateTimeValueWithScale == _referenceEntities[0].DateTimeValueWithScale, o => o.DateTimeValueWithScale));
241+
}
242+
243+
public class DateTimeTestsClass : IEquatable<DateTimeTestsClass>
244+
{
245+
public int Id { get; set; }
246+
public DateTime DateTimeValue { get; set; }
247+
public DateTime DateTimeValueWithScale { get; set; }
248+
public DateTimeOffset DateTimeOffsetValue { get; set; }
249+
public DateTimeOffset DateTimeOffsetValueWithScale { get; set; }
250+
public DateTime DateValue { get; set; }
251+
252+
public override bool Equals(object obj)
253+
{
254+
return Equals(obj as DateTimeTestsClass);
255+
}
256+
257+
public bool Equals(DateTimeTestsClass other)
258+
{
259+
return other is not null &&
260+
Id.Equals(other.Id);
261+
}
101262

102-
Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26, 0, 1, 0)));
103-
Assert.AreEqual(1, x.Count());
263+
public override int GetHashCode()
264+
{
265+
return HashCode.Combine(Id);
266+
}
104267
}
105268
}
106269
}
Lines changed: 204 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,254 @@
1-
using System;
1+
using System;
2+
using System.Data;
23
using System.Linq;
4+
using System.Linq.Expressions;
5+
using NHibernate.Cfg;
6+
using NHibernate.Linq;
7+
using NHibernate.Mapping.ByCode;
8+
using NHibernate.SqlTypes;
9+
using NHibernate.Type;
310
using NUnit.Framework;
411

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

17-
Assert.AreEqual(270, x.Count());
126+
[Test]
127+
public void CanQueryDateTimeBySecond()
128+
{
129+
AssertQuery(o => o.DateTimeValue.Second == 4);
18130
}
19131

20132
[Test]
21-
public void CanQueryByDate()
133+
public void CanQueryDateTimeByMinute()
22134
{
23-
var x = (from o in db.Orders
24-
where o.OrderDate.Value.Date == new DateTime(1998, 02, 26)
25-
select o).ToList();
135+
AssertQuery(o => o.DateTimeValue.Minute == 4);
136+
}
26137

27-
Assert.AreEqual(6, x.Count());
138+
[Test]
139+
public void CanQueryDateTimeByHour()
140+
{
141+
AssertQuery(o => o.DateTimeValue.Hour == 4);
28142
}
29143

30144
[Test]
31-
public void CanQueryByDateTime()
145+
public void CanQueryDateTimeBySecondWhenValueContainsFractionalSeconds()
32146
{
33-
var x = (from o in db.Orders
34-
where o.OrderDate.Value == new DateTime(1998, 02, 26)
35-
select o).ToList();
147+
AssertDateTimeWithFractionalSecondsSupported();
148+
AssertQuery(o => o.DateTimeValueWithScale.Second == 4);
149+
}
36150

37-
Assert.AreEqual(5, x.Count());
151+
[Test]
152+
public void CanQueryDateTimeOffsetBySecond()
153+
{
154+
AssertDateTimeOffsetSupported();
155+
AssertQuery(o => o.DateTimeOffsetValue.Second == 4);
38156
}
39157

40158
[Test]
41-
public void CanQueryByDateTime2()
159+
public void CanQueryDateTimeOffsetByMinute()
42160
{
43-
var x = (from o in db.Orders
44-
where o.OrderDate.Value == new DateTime(1998, 02, 26, 0, 1, 0)
45-
select o).ToList();
161+
AssertDateTimeOffsetSupported();
162+
AssertQuery(o => o.DateTimeOffsetValue.Minute == 4);
163+
}
46164

47-
Assert.AreEqual(1, x.Count());
165+
[Test]
166+
public void CanQueryDateTimeOffsetByHour()
167+
{
168+
AssertDateTimeOffsetSupported();
169+
AssertQuery(o => o.DateTimeOffsetValue.Hour == 4);
48170
}
49171

50172
[Test]
51-
public void CanSelectYear()
173+
public void CanQueryDateTimeOffsetBySecondWhenValueContainsFractionalSeconds()
52174
{
53-
var x = (from o in db.Orders
54-
where o.OrderDate.Value.Year == 1998
55-
select o.OrderDate.Value.Year).ToList();
175+
AssertDateTimeOffsetSupported();
176+
AssertQuery(o => o.DateTimeOffsetValueWithScale.Second == 4);
177+
}
56178

57-
Assert.That(x, Has.All.EqualTo(1998));
58-
Assert.AreEqual(270, x.Count());
179+
[Test]
180+
public void CanQueryByDate()
181+
{
182+
AssertQuery(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26));
59183
}
60184

61185
[Test]
62-
public void CanSelectDate()
186+
public void CanQueryByDateTime()
63187
{
64-
var x = (from o in db.Orders
65-
where o.OrderDate.Value.Date == new DateTime(1998, 02, 26)
66-
select o.OrderDate.Value.Date).ToList();
188+
AssertQuery(o => o.DateTimeValue == new DateTime(1998, 02, 26));
189+
}
67190

68-
Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26)));
69-
Assert.AreEqual(6, x.Count());
191+
[Test]
192+
public void CanQueryByDateTime2()
193+
{
194+
AssertQuery(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1));
70195
}
71196

72197
[Test]
73-
public void CanSelectDateTime()
198+
public void CanSelectYear()
74199
{
75-
var x = (from o in db.Orders
76-
where o.OrderDate.Value == new DateTime(1998, 02, 26)
77-
select o.OrderDate.Value).ToList();
200+
AssertQuery(o => o.DateTimeValue.Year == 1998, o => o.DateTimeValue.Year);
201+
}
78202

79-
Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26)));
80-
Assert.AreEqual(5, x.Count());
203+
[Test]
204+
public void CanSelectDate()
205+
{
206+
AssertQuery(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26), o => o.DateTimeValue.Date);
207+
}
208+
209+
[Test]
210+
public void CanSelectDateTime()
211+
{
212+
AssertQuery(o => o.DateTimeValue == new DateTime(1998, 02, 26), o => o.DateTimeValue);
81213
}
82214

83215
[Test]
84216
public void CanSelectDateTime2()
85217
{
86-
var x = (from o in db.Orders
87-
where o.OrderDate.Value == new DateTime(1998, 02, 26, 0, 1, 0)
88-
select o.OrderDate.Value).ToList();
218+
AssertQuery(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1), o => o.DateTimeValue);
219+
}
89220

90-
Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26, 0, 1, 0)));
91-
Assert.AreEqual(1, x.Count());
221+
[Test]
222+
public void CanSelectDateTimeWithScale()
223+
{
224+
AssertDateTimeWithFractionalSecondsSupported();
225+
AssertQuery(o => o.DateTimeValueWithScale == _referenceEntities[0].DateTimeValueWithScale, o => o.DateTimeValueWithScale);
226+
}
227+
228+
public class DateTimeTestsClass : IEquatable<DateTimeTestsClass>
229+
{
230+
public int Id { get; set; }
231+
public DateTime DateTimeValue { get; set; }
232+
public DateTime DateTimeValueWithScale { get; set; }
233+
public DateTimeOffset DateTimeOffsetValue { get; set; }
234+
public DateTimeOffset DateTimeOffsetValueWithScale { get; set; }
235+
public DateTime DateValue { get; set; }
236+
237+
public override bool Equals(object obj)
238+
{
239+
return Equals(obj as DateTimeTestsClass);
240+
}
241+
242+
public bool Equals(DateTimeTestsClass other)
243+
{
244+
return other is not null &&
245+
Id.Equals(other.Id);
246+
}
247+
248+
public override int GetHashCode()
249+
{
250+
return HashCode.Combine(Id);
251+
}
92252
}
93253
}
94254
}

‎src/NHibernate.Test/TestDialect.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Data;
33
using NHibernate.Hql.Ast.ANTLR;
44
using NHibernate.Id;
@@ -61,7 +61,7 @@ public bool NativeGeneratorSupportsBulkInsertion
6161
/// Some databases do not support SELECT FOR UPDATE
6262
/// </summary>
6363
public virtual bool SupportsSelectForUpdate => true;
64-
64+
6565
/// <summary>
6666
/// Some databases do not support SELECT FOR UPDATE with paging
6767
/// </summary>
@@ -218,5 +218,7 @@ public virtual bool SupportsSqlType(SqlType sqlType)
218218
/// Some databases (MySql) don't support using main table aliases in subquery inside join ON clause
219219
/// </summary>
220220
public virtual bool SupportsCorrelatedColumnsInSubselectJoin => true;
221+
222+
public virtual bool SupportsDateTimeWithFractionalSeconds => _dialect.TimestampResolutionInTicks < TimeSpan.TicksPerSecond && SupportsSqlType(new SqlType(DbType.DateTime, (byte) 2));
221223
}
222224
}

‎src/NHibernate/Dialect/FirebirdDialect.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ private void RegisterDateTimeFunctions()
537537
RegisterFunction("addweek", new StandardSQLFunction("addweek", NHibernateUtil.DateTime));
538538
RegisterFunction("addyear", new StandardSQLFunction("addyear", NHibernateUtil.DateTime));
539539
RegisterFunction("getexacttimestamp", new NoArgSQLFunction("getexacttimestamp", NHibernateUtil.DateTime));
540+
RegisterFunction("secondtruncated", new SQLFunctionTemplate(NHibernateUtil.Int32, "cast(floor(extract(second from ?1)) as int)"));
540541
}
541542

542543
private void RegisterStringAndCharFunctions()

‎src/NHibernate/Dialect/Function/SQLFunctionRegistry.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ public class SQLFunctionRegistry
77
{
88
private readonly Dialect dialect;
99
private readonly IDictionary<string, ISQLFunction> userFunctions;
10+
//Temporary alias support
11+
private static Dictionary<string, string> _functionAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "secondtruncated", "second" } };
1012

1113
public SQLFunctionRegistry(Dialect dialect, IDictionary<string, ISQLFunction> userFunctions)
1214
{
@@ -20,8 +22,11 @@ public SQLFunctionRegistry(Dialect dialect, IDictionary<string, ISQLFunction> us
2022
/// </summary>
2123
public ISQLFunction FindSQLFunction(string functionName)
2224
{
23-
ISQLFunction result;
24-
if (!userFunctions.TryGetValue(functionName, out result))
25+
if (!userFunctions.ContainsKey(functionName) && !dialect.Functions.ContainsKey(functionName) && _functionAliases.TryGetValue(functionName, out var sqlFunction))
26+
{
27+
functionName = sqlFunction;
28+
}
29+
if (!userFunctions.TryGetValue(functionName, out ISQLFunction result))
2530
{
2631
dialect.Functions.TryGetValue(functionName, out result);
2732
}
@@ -30,6 +35,10 @@ public ISQLFunction FindSQLFunction(string functionName)
3035

3136
public bool HasFunction(string functionName)
3237
{
38+
if (!userFunctions.ContainsKey(functionName) && !dialect.Functions.ContainsKey(functionName) && _functionAliases.TryGetValue(functionName, out var sqlFunction))
39+
{
40+
functionName = sqlFunction;
41+
}
3342
return userFunctions.ContainsKey(functionName) || dialect.Functions.ContainsKey(functionName);
3443
}
3544
}

‎src/NHibernate/Dialect/Oracle8iDialect.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,11 @@ protected virtual void RegisterFunctions()
265265

266266
// Cast is needed because EXTRACT treats DATE not as legacy Oracle DATE but as ANSI DATE, without time elements.
267267
// Therefore, you can extract only YEAR, MONTH, and DAY from a DATE value.
268+
// Oracle returns the seconds with fractional precision. It has to be truncated to return the actual second part
268269
RegisterFunction("second", new SQLFunctionTemplate(NHibernateUtil.Int32, "extract(second from cast(?1 as timestamp))"));
269270
RegisterFunction("minute", new SQLFunctionTemplate(NHibernateUtil.Int32, "extract(minute from cast(?1 as timestamp))"));
270271
RegisterFunction("hour", new SQLFunctionTemplate(NHibernateUtil.Int32, "extract(hour from cast(?1 as timestamp))"));
272+
RegisterFunction("secondtruncated", new SQLFunctionTemplate(NHibernateUtil.Int32, "cast(floor(extract(second from cast(?1 as timestamp))) as int)"));
271273

272274
RegisterFunction("date", new StandardSQLFunction("trunc", NHibernateUtil.Date));
273275

‎src/NHibernate/Dialect/PostgreSQLDialect.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ public PostgreSQLDialect()
105105
// and NHibernate.TestDatabaseSetup does install it.
106106
RegisterFunction("new_uuid", new NoArgSQLFunction("uuid_generate_v4", NHibernateUtil.Guid));
107107

108+
RegisterFunction("secondtruncated", new SQLFunctionTemplate(NHibernateUtil.Int32, "cast(floor(extract(second from ?1)) as int)"));
109+
108110
RegisterKeywords();
109111
}
110112

‎src/NHibernate/Linq/Functions/DateTimePropertiesHqlGenerator.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
using System;
1+
using System;
22
using System.Linq.Expressions;
33
using System.Reflection;
4+
using NHibernate.Engine;
45
using NHibernate.Hql.Ast;
56
using NHibernate.Linq.Visitors;
67
using NHibernate.Util;
@@ -33,8 +34,13 @@ public DateTimePropertiesHqlGenerator()
3334

3435
public override HqlTreeNode BuildHql(MemberInfo member, Expression expression, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
3536
{
36-
return treeBuilder.MethodCall(member.Name.ToLowerInvariant(),
37+
var functionName = member.Name.ToLowerInvariant();
38+
if (functionName == "second")
39+
{
40+
functionName = "secondtruncated";
41+
}
42+
return treeBuilder.MethodCall(functionName,
3743
visitor.Visit(expression).AsExpression());
3844
}
3945
}
40-
}
46+
}

0 commit comments

Comments
 (0)
Please sign in to comment.