Skip to content

Commit 74c49c5

Browse files
committed
PostgreSql: TimeOnly construction with hours overflow check
1 parent 8b628b7 commit 74c49c5

2 files changed

Lines changed: 46 additions & 12 deletions

File tree

Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/Compiler.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,24 @@ internal class Compiler : v9_1.Compiler
2020
protected override SqlExpression ConstructTime(IReadOnlyList<SqlExpression> arguments)
2121
{
2222
if (arguments.Count == 4) {
23-
return MakeTime(arguments[0], arguments[1], arguments[2], arguments[3]);
23+
return MakeTime(arguments[0], arguments[1], arguments[2], arguments[3], true);
24+
}
25+
else if (arguments.Count == 1) {
26+
var ticks = arguments[0];
27+
if (SqlHelper.IsTimeSpanTicks(ticks, out var sourceInterval)) {
28+
// try to optimize and reduce calculations when TimeSpan.Ticks where used for TimeOnly(ticks) ctor
29+
return SqlDml.Cast(SqlDml.Cast(sourceInterval, SqlType.VarChar), SqlType.Time);
30+
}
31+
else {
32+
var hour = SqlDml.Cast(ticks / 36000000000, SqlType.Int32);
33+
var minute = SqlDml.Cast((ticks / 600000000) % 60, SqlType.Int32);
34+
var second = SqlDml.Cast((ticks / 10000000) % 60, SqlType.Int32);
35+
var microsecond = SqlDml.Cast((ticks % 10000000) / 10, SqlType.Int32);
36+
return MakeTime(hour, minute, second, microsecond, false);
37+
}
2438
}
2539
else {
26-
return base.ConstructTime(arguments);
40+
throw new InvalidOperationException("Unsupported count of parameters");
2741
}
2842
}
2943
#endif
@@ -36,8 +50,10 @@ protected static SqlUserFunctionCall MakeDate(SqlExpression year, SqlExpression
3650
SqlDml.FunctionCall("MAKE_DATE", year, month, day);
3751

3852
protected static SqlUserFunctionCall MakeTime(
39-
SqlExpression hours, SqlExpression minutes, SqlExpression seconds, SqlExpression milliseconds) =>
40-
SqlDml.FunctionCall("MAKE_TIME", hours, minutes, seconds + (SqlDml.Cast(milliseconds, SqlType.Double) / 1000));
53+
SqlExpression hours, SqlExpression minutes, SqlExpression seconds, SqlExpression secondFractions, in bool isMilliseconds) =>
54+
(isMilliseconds)
55+
? SqlDml.FunctionCall("MAKE_TIME", hours, minutes, seconds + (SqlDml.Cast(secondFractions, SqlType.Double) / 1000))
56+
: SqlDml.FunctionCall("MAKE_TIME", hours, minutes, seconds + (SqlDml.Cast(secondFractions, SqlType.Double) / 1000000));
4157
#endif
4258

4359
// Constructors

Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Compiler.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -415,22 +415,40 @@ protected virtual SqlExpression ConstructDate(IReadOnlyList<SqlExpression> argum
415415

416416
protected virtual SqlExpression ConstructTime(IReadOnlyList<SqlExpression> arguments)
417417
{
418+
SqlExpression hour, minute, second, microsecond;
418419
if (arguments.Count == 4) {
419-
return ZeroTimeLiteral
420-
+ (OneHourInterval * (arguments[0]))
421-
+ (OneMinuteInterval * (arguments[1]))
422-
+ (OneSecondInterval * (arguments[2] + (SqlDml.Cast(arguments[3], SqlType.Double) / 1000)));
420+
hour = arguments[0];
421+
minute = arguments[1];
422+
second = arguments[2];
423+
microsecond = arguments[3] * 1000;
423424
}
424425
else if (arguments.Count == 1) {
425426
var ticks = arguments[0];
426-
if (SqlHelper.IsTimeSpanTicks(ticks, out var intervalExpr)) {
427-
return ZeroTimeLiteral + intervalExpr;
427+
if (SqlHelper.IsTimeSpanTicks(ticks, out var sourceInterval)) {
428+
// try to optimize and reduce calculations when TimeSpan.Ticks where used for TimeOnly(ticks) ctor
429+
return SqlDml.Cast(SqlDml.Cast(sourceInterval, SqlType.VarChar), SqlType.Time);
430+
}
431+
else {
432+
hour = SqlDml.Cast(ticks / 36000000000, SqlType.Int32);
433+
minute = SqlDml.Cast((ticks / 600000000) % 60, SqlType.Int32);
434+
second = SqlDml.Cast((ticks / 10000000) % 60, SqlType.Int32);
435+
microsecond = SqlDml.Cast((ticks % 10000000) / 10, SqlType.Int32);
428436
}
429-
return ZeroTimeLiteral + (ticks / SqlDml.Literal(10000000.0) * OneSecondInterval);
430437
}
431438
else {
432439
throw new InvalidOperationException("Unsupported count of parameters");
433-
}
440+
}
441+
442+
// Using string version of time allows to control hours overflow
443+
// we cannot add hours, minutes and other parts to 00:00:00.000000 time
444+
// because hours might step over 24 hours and start counting from 0.
445+
// Starting from v10 new function is in use, which controlls overflow
446+
var hourString = SqlDml.Cast(hour, new SqlValueType(SqlType.VarChar, 3));
447+
var minuteString = SqlDml.Cast(minute, new SqlValueType(SqlType.VarChar, 2));
448+
var secondString = SqlDml.Cast(second, new SqlValueType(SqlType.VarChar, 2));
449+
var microsecondString = SqlDml.Cast(microsecond, new SqlValueType(SqlType.VarChar, 7));
450+
var composedTimeString = SqlDml.Concat(hourString, SqlDml.Literal(":"), minuteString, SqlDml.Literal(":"), secondString, SqlDml.Literal("."), microsecondString);
451+
return SqlDml.Cast(composedTimeString, SqlType.Time);
434452
}
435453

436454
protected virtual SqlExpression TimeToNanoseconds(SqlExpression time)

0 commit comments

Comments
 (0)