Skip to content

Commit d84b43e

Browse files
Support evaluation of DateTime.Now on db side
And of all similar properties: UtcNow, Today, and DateTimeOffset's ones. Part of #959 Co-authored-by: maca88 <bostjan.markezic@siol.net>
1 parent 6c252ee commit d84b43e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+798
-48
lines changed

doc/reference/modules/configuration.xml

+51
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,57 @@ var session = sessions.OpenSession(conn);
717717
</para>
718718
</entry>
719719
</row>
720+
<row>
721+
<entry>
722+
<literal>linqtohql.legacy_preevaluation</literal>
723+
</entry>
724+
<entry>
725+
Whether to use the legacy pre-evaluation or not in Linq queries. Defaults to <literal>true</literal>.
726+
<para>
727+
<emphasis role="strong">eg.</emphasis>
728+
<literal>true</literal> | <literal>false</literal>
729+
</para>
730+
<para>
731+
Legacy pre-evaluation is causing special properties or functions like <literal>DateTime.Now</literal>
732+
or <literal>Guid.NewGuid()</literal> to be always evaluated with the .Net runtime and replaced in the
733+
query by parameter values.
734+
</para>
735+
<para>
736+
The new pre-evaluation allows them to be converted to HQL function calls which will be run on the db
737+
side. This allows for example to retrieve the server time instead of the client time, or to generate
738+
UUIDs for each row instead of an unique one for all rows.
739+
</para>
740+
<para>
741+
The new pre-evaluation will likely be enabled by default in the next major version (6.0).
742+
</para>
743+
</entry>
744+
</row>
745+
<row>
746+
<entry>
747+
<literal>linqtohql.fallback_on_preevaluation</literal>
748+
</entry>
749+
<entry>
750+
When the new pre-evaluation is enabled, should methods which translation is not supported by the current
751+
dialect fallback to pre-evaluation? Defaults to <literal>false</literal>.
752+
<para>
753+
<emphasis role="strong">eg.</emphasis>
754+
<literal>true</literal> | <literal>false</literal>
755+
</para>
756+
<para>
757+
When this fallback option is enabled while legacy pre-evaluation is disabled, properties or functions
758+
like <literal>DateTime.Now</literal> or <literal>Guid.NewGuid()</literal> used in Linq expressions
759+
will not fail when the dialect does not support them, but will instead be pre-evaluated.
760+
</para>
761+
<para>
762+
When this fallback option is disabled while legacy pre-evaluation is disabled, properties or functions
763+
like <literal>DateTime.Now</literal> or <literal>Guid.NewGuid()</literal> used in Linq expressions
764+
will fail when the dialect does not support them.
765+
</para>
766+
<para>
767+
This option has no effect if the legacy pre-evaluation is enabled.
768+
</para>
769+
</entry>
770+
</row>
720771
<row>
721772
<entry>
722773
<literal>sql_exception_converter</literal>

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public class MiscellaneousTextFixtureAsync : LinqTestCase
2727
[Test(Description = "This sample uses Count to find the number of Orders placed before yesterday in the database.")]
2828
public async Task CountWithWhereClauseAsync()
2929
{
30-
var q = from o in db.Orders where o.OrderDate <= DateTime.Today.AddDays(-1) select o;
30+
var yesterday = DateTime.Today.AddDays(-1);
31+
var q = from o in db.Orders where o.OrderDate <= yesterday select o;
3132

3233
var count = await (q.CountAsync());
3334

src/NHibernate.Test/Linq/MiscellaneousTextFixture.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ from s in db.Shippers
2727
[Test(Description = "This sample uses Count to find the number of Orders placed before yesterday in the database.")]
2828
public void CountWithWhereClause()
2929
{
30-
var q = from o in db.Orders where o.OrderDate <= DateTime.Today.AddDays(-1) select o;
30+
var yesterday = DateTime.Today.AddDays(-1);
31+
var q = from o in db.Orders where o.OrderDate <= yesterday select o;
3132

3233
var count = q.Count();
3334

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NHibernate.Cfg;
5+
using NHibernate.SqlTypes;
6+
using NUnit.Framework;
7+
using Environment = NHibernate.Cfg.Environment;
8+
9+
namespace NHibernate.Test.Linq
10+
{
11+
[TestFixture(false, false)]
12+
[TestFixture(true, false)]
13+
[TestFixture(false, true)]
14+
public class PreEvaluationTests : LinqTestCase
15+
{
16+
private readonly bool LegacyPreEvaluation;
17+
private readonly bool FallbackOnPreEvaluation;
18+
19+
public PreEvaluationTests(bool legacy, bool fallback)
20+
{
21+
LegacyPreEvaluation = legacy;
22+
FallbackOnPreEvaluation = fallback;
23+
}
24+
25+
protected override void Configure(Configuration configuration)
26+
{
27+
base.Configure(configuration);
28+
29+
configuration.SetProperty(Environment.FormatSql, "false");
30+
configuration.SetProperty(Environment.LinqToHqlLegacyPreEvaluation, LegacyPreEvaluation.ToString());
31+
configuration.SetProperty(Environment.LinqToHqlFallbackOnPreEvaluation, FallbackOnPreEvaluation.ToString());
32+
}
33+
34+
[Test]
35+
public void CanQueryByDateTimeNowUsingNotEqual()
36+
{
37+
var isSupported = IsFunctionSupported("current_timestamp");
38+
RunTest(
39+
isSupported,
40+
spy =>
41+
{
42+
var x = db.Orders.Count(o => o.OrderDate.Value != DateTime.Now);
43+
44+
Assert.That(x, Is.GreaterThan(0));
45+
AssertFunctionInSql("current_timestamp", spy);
46+
});
47+
}
48+
49+
[Test]
50+
public void CanQueryByDateTimeNow()
51+
{
52+
var isSupported = IsFunctionSupported("current_timestamp");
53+
RunTest(
54+
isSupported,
55+
spy =>
56+
{
57+
var x = db.Orders.Count(o => o.OrderDate.Value < DateTime.Now);
58+
59+
Assert.That(x, Is.GreaterThan(0));
60+
AssertFunctionInSql("current_timestamp", spy);
61+
});
62+
}
63+
64+
[Test]
65+
public void CanSelectDateTimeNow()
66+
{
67+
var isSupported = IsFunctionSupported("current_timestamp");
68+
RunTest(
69+
isSupported,
70+
spy =>
71+
{
72+
var x =
73+
db
74+
.Orders.Select(o => new { id = o.OrderId, d = DateTime.Now })
75+
.OrderBy(o => o.id).Take(1).ToList();
76+
77+
Assert.That(x, Has.Count.GreaterThan(0));
78+
Assert.That(x[0].d.Kind, Is.EqualTo(DateTimeKind.Local));
79+
AssertFunctionInSql("current_timestamp", spy);
80+
});
81+
}
82+
83+
[Test]
84+
public void CanQueryByDateTimeUtcNow()
85+
{
86+
var isSupported = IsFunctionSupported("current_utctimestamp");
87+
RunTest(
88+
isSupported,
89+
spy =>
90+
{
91+
var x = db.Orders.Count(o => o.OrderDate.Value < DateTime.UtcNow);
92+
93+
Assert.That(x, Is.GreaterThan(0));
94+
AssertFunctionInSql("current_utctimestamp", spy);
95+
});
96+
}
97+
98+
[Test]
99+
public void CanSelectDateTimeUtcNow()
100+
{
101+
var isSupported = IsFunctionSupported("current_utctimestamp");
102+
RunTest(
103+
isSupported,
104+
spy =>
105+
{
106+
var x =
107+
db
108+
.Orders.Select(o => new { id = o.OrderId, d = DateTime.UtcNow })
109+
.OrderBy(o => o.id).Take(1).ToList();
110+
111+
Assert.That(x, Has.Count.GreaterThan(0));
112+
Assert.That(x[0].d.Kind, Is.EqualTo(DateTimeKind.Utc));
113+
AssertFunctionInSql("current_utctimestamp", spy);
114+
});
115+
}
116+
117+
[Test]
118+
public void CanQueryByDateTimeToday()
119+
{
120+
var isSupported = IsFunctionSupported("current_date");
121+
RunTest(
122+
isSupported,
123+
spy =>
124+
{
125+
var x = db.Orders.Count(o => o.OrderDate.Value < DateTime.Today);
126+
127+
Assert.That(x, Is.GreaterThan(0));
128+
AssertFunctionInSql("current_date", spy);
129+
});
130+
}
131+
132+
[Test]
133+
public void CanSelectDateTimeToday()
134+
{
135+
var isSupported = IsFunctionSupported("current_date");
136+
RunTest(
137+
isSupported,
138+
spy =>
139+
{
140+
var x =
141+
db
142+
.Orders.Select(o => new { id = o.OrderId, d = DateTime.Today })
143+
.OrderBy(o => o.id).Take(1).ToList();
144+
145+
Assert.That(x, Has.Count.GreaterThan(0));
146+
Assert.That(x[0].d.Kind, Is.EqualTo(DateTimeKind.Local));
147+
AssertFunctionInSql("current_date", spy);
148+
});
149+
}
150+
151+
[Test]
152+
public void CanQueryByDateTimeOffsetTimeNow()
153+
{
154+
if (!TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
155+
Assert.Ignore("Dialect does not support DateTimeOffSet");
156+
157+
var isSupported = IsFunctionSupported("current_timestamp_offset");
158+
RunTest(
159+
isSupported,
160+
spy =>
161+
{
162+
var testDate = DateTimeOffset.Now.AddDays(-1);
163+
var x = db.Orders.Count(o => testDate < DateTimeOffset.Now);
164+
165+
Assert.That(x, Is.GreaterThan(0));
166+
AssertFunctionInSql("current_timestamp_offset", spy);
167+
});
168+
}
169+
170+
[Test]
171+
public void CanSelectDateTimeOffsetNow()
172+
{
173+
if (!TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
174+
Assert.Ignore("Dialect does not support DateTimeOffSet");
175+
176+
var isSupported = IsFunctionSupported("current_timestamp_offset");
177+
RunTest(
178+
isSupported,
179+
spy =>
180+
{
181+
var x =
182+
db
183+
.Orders.Select(o => new { id = o.OrderId, d = DateTimeOffset.Now })
184+
.OrderBy(o => o.id).Take(1).ToList();
185+
186+
Assert.That(x, Has.Count.GreaterThan(0));
187+
Assert.That(x[0].d.Offset, Is.EqualTo(DateTimeOffset.Now.Offset));
188+
AssertFunctionInSql("current_timestamp_offset", spy);
189+
});
190+
}
191+
192+
[Test]
193+
public void CanQueryByDateTimeOffsetUtcNow()
194+
{
195+
if (!TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
196+
Assert.Ignore("Dialect does not support DateTimeOffSet");
197+
198+
var isSupported = IsFunctionSupported("current_utctimestamp_offset");
199+
RunTest(
200+
isSupported,
201+
spy =>
202+
{
203+
var testDate = DateTimeOffset.UtcNow.AddDays(-1);
204+
var x = db.Orders.Count(o => testDate < DateTimeOffset.UtcNow);
205+
206+
Assert.That(x, Is.GreaterThan(0));
207+
AssertFunctionInSql("current_utctimestamp_offset", spy);
208+
});
209+
}
210+
211+
[Test]
212+
public void CanSelectDateTimeOffsetUtcNow()
213+
{
214+
if (!TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
215+
Assert.Ignore("Dialect does not support DateTimeOffSet");
216+
217+
var isSupported = IsFunctionSupported("current_utctimestamp_offset");
218+
RunTest(
219+
isSupported,
220+
spy =>
221+
{
222+
var x =
223+
db
224+
.Orders.Select(o => new { id = o.OrderId, d = DateTimeOffset.UtcNow })
225+
.OrderBy(o => o.id).Take(1).ToList();
226+
227+
Assert.That(x, Has.Count.GreaterThan(0));
228+
Assert.That(x[0].d.Offset, Is.EqualTo(TimeSpan.Zero));
229+
AssertFunctionInSql("current_utctimestamp_offset", spy);
230+
});
231+
}
232+
233+
private void RunTest(bool isSupported, Action<SqlLogSpy> test)
234+
{
235+
using (var spy = new SqlLogSpy())
236+
{
237+
try
238+
{
239+
test(spy);
240+
}
241+
catch (QueryException)
242+
{
243+
if (!isSupported && !FallbackOnPreEvaluation)
244+
// Expected failure
245+
return;
246+
throw;
247+
}
248+
}
249+
250+
if (!isSupported && !FallbackOnPreEvaluation)
251+
Assert.Fail("The test should have thrown a QueryException, but has not thrown anything");
252+
}
253+
254+
private void AssertFunctionInSql(string functionName, SqlLogSpy spy)
255+
{
256+
if (!IsFunctionSupported(functionName))
257+
Assert.Inconclusive($"{functionName} is not supported by the dialect");
258+
259+
var function = Dialect.Functions[functionName].Render(new List<object>(), Sfi).ToString();
260+
261+
if (LegacyPreEvaluation)
262+
Assert.That(spy.GetWholeLog(), Does.Not.Contain(function));
263+
else
264+
Assert.That(spy.GetWholeLog(), Does.Contain(function));
265+
}
266+
}
267+
}

src/NHibernate.Test/Linq/TryGetMappedTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ private void AssertResult(
773773
expectedComponentType = expectedComponentType ?? (o => o == null);
774774

775775
var expression = query.Expression;
776-
NhRelinqQueryParser.PreTransform(expression);
776+
NhRelinqQueryParser.PreTransform(expression, Sfi);
777777
var constantToParameterMap = ExpressionParameterVisitor.Visit(expression, Sfi);
778778
var queryModel = NhRelinqQueryParser.Parse(expression);
779779
var requiredHqlParameters = new List<NamedParameterDescriptor>();

0 commit comments

Comments
 (0)