-
Notifications
You must be signed in to change notification settings - Fork 934
Db side methods #1868
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
Db side methods #1868
Conversation
a4bf411
to
bd97c39
Compare
@@ -77,13 +77,16 @@ public DB2Dialect() | |||
RegisterFunction("log10", new StandardSQLFunction("log10", NHibernateUtil.Double)); | |||
RegisterFunction("radians", new StandardSQLFunction("radians", NHibernateUtil.Double)); | |||
RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); | |||
RegisterFunction("random", new NoArgSQLFunction("rand", NHibernateUtil.Double)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to add a new random
HQL function, although most dialects already have the rand
one. Unfortunately, rand
with SQL-Server and SAP ASE yields the same value for each row, which is useless for our case (better keep it evaluated in runtime in such case).
Changing them for those db could be breaking for users using them while expecting them to yield the same value for each row.
So I have added a random
function, in most cases copy of the rand
one, but adjusted for SQL-Server and ASE for generating new values for each row.
(By the way, yes I have tested DB2, with some non-committed tweaking of other files, notably for supporting Guid in a quick&dirty way. The LINQ test requires Guid support, which is lacking for DB2 currently.)
RegisterFunction( | ||
"current_utctimestamp_offset", | ||
new SQLFunctionTemplate(NHibernateUtil.DateTimeOffset, "todatetimeoffset(sysutcdatetime(), 0)")); | ||
RegisterFunction("date", new SQLFunctionTemplate(NHibernateUtil.Date, "cast(?1 as date)")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 2000 registration uses the dateadd(dd, 0, datediff(dd, 0, getdate()))
hack too. As I have overridden current_date
with the simplier cast to date
here, I have considered the date
function should be overridden too for consistency.
@@ -199,6 +200,8 @@ protected virtual void RegisterFunctions() | |||
|
|||
RegisterFunction("bit_length", new SQLFunctionTemplate(NHibernateUtil.Int32, "datalength(?1) * 8")); | |||
RegisterFunction("extract", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(?1, ?3)")); | |||
|
|||
RegisterFunction("new_uuid", new NoArgSQLFunction("newid", NHibernateUtil.Guid)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No random
support for SQL-Server CE: rand
has the same limitation than SQL-Server one, and it cannot use the SQL-Server hack because it lacks the checksum
function.
|
||
// The uuid_generate_v4 is not native and must be installed, but SelectGUIDString property already uses it, | ||
// and NHibernate.TestDatabaseSetup does install it. | ||
RegisterFunction("new_uuid", new NoArgSQLFunction("uuid_generate_v4", NHibernateUtil.Guid)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PostgreSQL already has a random
function registered, and it works as required.
@@ -56,6 +56,9 @@ public SybaseASE15Dialect() | |||
RegisterColumnType(DbType.Date, "date"); | |||
RegisterColumnType(DbType.Binary, 8000, "varbinary($l)"); | |||
RegisterColumnType(DbType.Binary, "varbinary"); | |||
// newid default is to generate a 32 bytes character uuid (no-dashes), but it has an option for | |||
// including dashes, then raising it to 36 bytes. | |||
RegisterColumnType(DbType.Guid, "varchar(36)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ASE supports uuid, but the adequate registration was lacking. But I have no local installation of ASE (trial is time limited, so I have not bothered with it) to check if it supports DbType.Guid
in DbParameter
. (Main pain point with Guid and DB2, requiring hacking the driver for tweaking the parameter settings and their values.)
So, although it can generates uuid on db-side, maybe this change should be reverted, since it is not unlikely the change is maybe not enough for actually supporting Guid.
@@ -139,6 +139,7 @@ protected virtual void RegisterMathFunctions() | |||
RegisterFunction("power", new StandardSQLFunction("power", NHibernateUtil.Double)); | |||
RegisterFunction("radians", new StandardSQLFunction("radians", NHibernateUtil.Double)); | |||
RegisterFunction("rand", new StandardSQLFunction("rand", NHibernateUtil.Double)); | |||
RegisterFunction("random", new StandardSQLFunction("rand", NHibernateUtil.Double)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked by cherry-picking the dialect and driver from the Anywhere pending PR, it works as required for this PR.
treeBuilder.MethodCall( | ||
_floorFunctionName, | ||
treeBuilder.Multiply( | ||
treeBuilder.MethodCall(_randomFunctionName), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some databases may have suitable functions to call for avoiding this kind of constructs, like Oracle, but this seems to infrequent for being worth coding special cases for them.
dac823b
to
85d6f77
Compare
The HQL generators registry already uses dictionaries to resolve generators for methods found in LINQ expression. But some generators do not declare statically their supported methods and instead have to been queried with a loop on them for support of methods encountered in the LINQ query. The result of this lookup can be cached for avoiding calling this loop again on the next query using the method. If nhibernate#1868 is merged, this change will compensate for nhibernate#1868 causing up to three such lookup by method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe better to leave it for 6.0 then?
Or add it as an experimental feature, disabled by default till 6.0. ( By the way, I have still not really made my mind about the fallback to pre-evaluation when the feature is enabled but the dialect does not support the required translation. Not fall-backing will cause a failure when using the feature, which should be easier and less surprising to diagnose than the consequences of a fallback to pre-evaluation. But that also reduces the portability of an application to other databases, since it is also likely that the fallback could cause less disturbance to the application than a straight failure. |
The HQL generators registry already uses dictionaries to resolve generators for methods found in LINQ expression. But some generators do not declare statically their supported methods and instead have to be queried with a loop on them for support of methods encountered in the LINQ query. The result of this lookup can be cached for avoiding calling this loop again on the next query using the method. If nhibernate#1868 is merged, this change will compensate for nhibernate#1868 causing up to three such lookups by method.
The HQL generators registry already uses dictionaries to resolve generators for methods found in LINQ expression. But some generators do not declare statically their supported methods and instead have to be queried with a loop on them for support of methods encountered in the LINQ query. The result of this lookup can be cached for avoiding calling this loop again on the next query using the method. If #1868 is merged, this change will compensate for #1868 causing up to three such lookups by method.
127a3da
to
e5b2cf8
Compare
This comment has been minimized.
This comment has been minimized.
I have added some commits: The first switch Following commits excepted the last are for an additional option for failing or falling back in case of lack of dialect support: The last one is just a minor adjustments in the documentation of the new pre-evaluation. |
61af90a
to
0cd8a38
Compare
src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs
Outdated
Show resolved
Hide resolved
5f7bf31
to
7d68871
Compare
Rebased, more wording fixed, slightly squashed, commits regrouped for resolving conflicts the fixups were having. Stay as fixup the commit for not enabling this feature by default and the commits for failing or fall-backing. |
7d68871
to
4512499
Compare
This comment has been minimized.
This comment has been minimized.
4512499
to
4e53b0c
Compare
Maybe we just need to add the ability for user register the system methods as not-preevaluateable, but not register any functions (maybe even provide the generators and information how to register them). At this moment following will work and translated to sql call despite being independent of any data: class SqlFunctions
{
[LinqExtensionMethodAttribute("current_timestamp", LinqExtensionPreEvaluation.NoEvaluation)]
public static DateTime Now() => DateTime.Now;
} However, there is no way to register just Also, I feel we need to privde a way to disable some of the functions instead of all of them. |
35951e4
to
e565704
Compare
It seems overly complex to me, when all the user has to do for those functions he does not want to get translated to SQL, is to call them outside of the query. There is only the breaking change aspect of the feature we need to handle. And this PR does it by supplying configuration option, defaulting to legacy behavior for 5.x. |
e565704
to
4c3fe84
Compare
In my opinion the default behavior should be the following: DateTime/DateTimeOffset properties:
from o in db.Orders where o.OrderDate <= DateTime.Today // DateTime.Today should be pre-evaluated
from o in db.Orders where o.OrderDate <= DateTime.Today // DateTime.Today should be converted to SQL
from o in db.Orders where o.OrderDate <= DateTime.Today.AddDays(1)
from o in db.Orders where o.OrderDate <= DateTime.Today.Day
var date = DateTime.Today;
from o in db.Orders where o.OrderDate <= date Guid and Random:
from o in db.Orders where o.Guid != new Guid()
var guid = new Guid();
from o in db.Orders where o.Guid != guid
from o in db.Orders where o.Guid != new Guid()
select new Guid() from o in db.Orders
var random = new Random();
from o in db.Orders where o.OrderId != random.NextDouble().GetHashCode()
from o in db.Orders where o.Guid != new Guid().GetHashCode()
var random = new Random();
select random.NextDouble().GetHashCode() from o in db.Orders
select new Guid().GetHashCode() from o in db.Orders So Instead of adding I will give it a try to see what has to be done in order to achieve such behavior. |
For a minor release (5.x), if we can avoid possible breaking change, we should. I am not convinced we should handle fallbacks for
And if that member ends up supported in some later version, end of pre-evaluation? That could be a serious breaking change, if the database is not in the same timezone, again. |
On second thought, I do agree that we should not provide a fallback for
For the pre-evaluation I now agree with how it is handled by this PR. Also we need to consider that pre-evaluation is a hot path, as it is executed every time the query is executed, so we should keep it simple.
I agree that it should fail when db.Orders.Select(o => DateTime.Now.DayOfYear).ToList(); // Does not work
db.Orders.Select(o => DateTime.Now.AddDays(1)).ToList(); // Works we should support client side evaluation when only And for Guid and Random I think that we should allow client side evaluation when located in a non subquery select statement: var random = new Random();
db.Orders.Select(o => random.NextDouble().GetHashCode()).ToList(); // Works when random is supported, throws otherwise
db.Orders.Select(o => Guid.NewGuid().GetHashCode()).ToList(); // Works when guid is supported, throws otherwise even when |
Assuming you mean the I just write "may" because this does introduce some discrepancy between when those expressions are used in a "final" So overall why not, but I think it would be better to handle it in a dedicated PR. (Maybe not in #2079 either, but in a PR depending on #2079.) |
I had the same issue, but once understood it is very useful to have such feature. I saw many times developers using db.Orders.Select(o => GetValue(o)).ToList();
private string GetValue(Order order)
{
return order.Employee?.Superior?.FirstName;
} the query will work but it will trigger additional queries, which can cause performance issues. This could be fixed by adding a configuration option whether to allow additional queries to be executed while calling Back on the topic, with the last commit two issues were addressed:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed new_uuid
support from Sqlite, which I think can be added in a separate PR (optionally with corrected SelectGUIDString
property) once System.Data.SQLite.Core
will ship with version 3.31
or higher. Feel free to revert the commit whether you don't agree.
Review dismissed due to having to resolve conflicting changes in Oracle 10g dialect. |
And of all similar properties: UtcNow, Today, and DateTimeOffset's ones. Part of nhibernate#959 Co-authored-by: maca88 <[email protected]>
Part of nhibernate#959 Co-authored-by: maca88 <[email protected]>
Fixes nhibernate#959, along with two previous commits
0cf8bf3
to
3a8a1ba
Compare
Furthermore, rebased, since I consider merging rather than squashing, so I have squashed @maca88 fix commits into the three main commits, adding co-author. |
This adds to the LINQ provider the ability to translate
DateTime.Now
and its variants (Utc, Today, Offset) to SQL calls instead of being pre-evaluated before running the query.It does the same for
Guid.NewGuid
,Random.Next
andRandom.NextDouble
.For enabling these capabilities, matching HQL functions are required. This PR also adds them to all dialects whenever possible.
For being as few breaking as possible, when the dialect lacks support of required functions, the pre-evaluation takes place instead.
This looks quite suitable for
DateTime.Now
and its variants.But for
Guid
andRandom
, I find it more debatable: when pre-evaluated, the same value will be used for each row of result, whereas when converted to SQL, different values for each row will be used. So this could cause bugs when an application targets a different database than previously tested, without an immediate obvious exception.Maybe this will be a bad legacy, and maybe it would be better to throw a
NotSupportedException
in those cases, instead of falling back to pre-evaluation.For queries where this change is breaking, it can be fixed either on a case by case basis or globally.
On a case by case basis, moving the expression newly converted to SQL out of the LINQ query thanks to a local variable will cause it to be evaluated once per request like it was prior to this PR.
Globally, a new boolean setting
linqtohql.legacy_preevaluation
allows to disable this new feature.Fixes #959.