Skip to content

Commit ffe8931

Browse files
authored
Merge pull request #425 from DataObjects-NET/7.1-pgsql-ms-extraction-fix
Improve TimeSpan.TotalMilliseconds extraction and .Milliseconds extraction for types which has this part
2 parents 6934acb + ed2075d commit ffe8931

File tree

6 files changed

+219
-20
lines changed

6 files changed

+219
-20
lines changed

ChangeLog/7.1.5_dev.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
[main] Addressed certain cases of overlaping server-side error by new one when temporary tables are used in query
1+
[main] Addressed certain cases of overlaping server-side error by new one when temporary tables are used in query
2+
[postgresql] Improved .Milliseconds translation for types which have this part
3+
[postgresql] Improved TimeSpan.TotalMilliseconds translation

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public override void Visit(SqlFunctionCall node)
122122
((node.Arguments[0] / SqlDml.Literal(nanosecondsPerSecond)) * OneSecondInterval).AcceptVisitor(this);
123123
return;
124124
case SqlFunctionType.IntervalToMilliseconds:
125-
SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this);
125+
VisitIntervalToMilliseconds(node);
126126
return;
127127
case SqlFunctionType.IntervalToNanoseconds:
128128
SqlHelper.IntervalToNanoseconds(node.Arguments[0]).AcceptVisitor(this);
@@ -296,6 +296,11 @@ public override void Visit(SqlCustomFunctionCall node)
296296
base.Visit(node);
297297
}
298298

299+
protected virtual void VisitIntervalToMilliseconds(SqlFunctionCall node)
300+
{
301+
SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this);
302+
}
303+
299304
private static SqlExpression DateTimeToStringIso(SqlExpression dateTime, in string isoFormat) =>
300305
SqlDml.FunctionCall("TO_CHAR", dateTime, isoFormat);
301306

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,11 @@ public override void Translate(SqlCompilerContext context, SqlExtract node, Extr
410410
}
411411
switch (section) {
412412
case ExtractSection.Entry:
413-
_ = context.Output.AppendOpeningPunctuation(isSecond ? "(trunc(extract(" : "(extract(");
413+
_ = context.Output.AppendOpeningPunctuation(isSecond || isMillisecond ? "(trunc(extract(" : "(extract(");
414414
break;
415415
case ExtractSection.Exit:
416416
_ = context.Output.Append(isMillisecond
417-
? ")::int8 % 1000)"
417+
? "))::int8 % 1000)"
418418
: isSecond ? ")))" : ")::int8)"
419419
);
420420
break;

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,21 @@
44
// Created by: Denis Krjuchkov
55
// Created: 2012.06.06
66

7+
using Xtensive.Sql.Dml;
8+
79
namespace Xtensive.Sql.Drivers.PostgreSql.v9_0
810
{
911
internal class Compiler : v8_4.Compiler
1012
{
1113
// Constructors
14+
protected override void VisitIntervalToMilliseconds(SqlFunctionCall node)
15+
{
16+
AppendSpaceIfNecessary();
17+
_ = context.Output.Append("(TRUNC(EXTRACT(EPOCH FROM (");
18+
node.Arguments[0].AcceptVisitor(this);
19+
_ = context.Output.Append(")) * 1000))");
20+
21+
}
1222

1323
public Compiler(SqlDriver driver)
1424
: base(driver)
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Copyright (C) 2025 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using NUnit.Framework;
7+
using Xtensive.Sql;
8+
using Xtensive.Sql.Dml;
9+
10+
namespace Xtensive.Orm.Tests.Sql.PostgreSql
11+
{
12+
public sealed class IntervalToMillisecondsTest : SqlTest
13+
{
14+
private const string IdColumnName = "Id";
15+
private const string ValueColumnName = "Value";
16+
private const string TableName = "IntervalToMsTest";
17+
18+
private TypeMapping longMapping;
19+
private TypeMapping timeSpanMapping;
20+
private TypeMapping doubleMapping;
21+
22+
private SqlSelect selectQuery;
23+
24+
private static TimeSpan[] TestValues
25+
{
26+
get => new[] {
27+
TimeSpan.MinValue,
28+
TimeSpan.MaxValue,
29+
TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)),
30+
TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)),
31+
TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)),
32+
TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)),
33+
TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)),
34+
TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)),
35+
TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)),
36+
TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)),
37+
TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))),
38+
TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))),
39+
TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))),
40+
TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))),
41+
TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))),
42+
TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))),
43+
TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))),
44+
TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))),
45+
TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))),
46+
TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
47+
TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
48+
TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
49+
TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))),
50+
TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))),
51+
TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))),
52+
TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))),
53+
TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
54+
TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
55+
TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
56+
TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
57+
TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
58+
TimeSpan.FromDays(366).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
59+
TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
60+
61+
TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)).Negate(),
62+
TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)).Negate(),
63+
TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)).Negate(),
64+
TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)).Negate(),
65+
TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)).Negate(),
66+
TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)).Negate(),
67+
TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)).Negate(),
68+
TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)).Negate(),
69+
TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))).Negate(),
70+
TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))).Negate(),
71+
TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))).Negate(),
72+
TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))).Negate(),
73+
TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))).Negate(),
74+
TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(),
75+
TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(),
76+
TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(),
77+
TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(),
78+
TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
79+
TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
80+
TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
81+
TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(),
82+
TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(),
83+
TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(),
84+
TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(),
85+
TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
86+
TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
87+
TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
88+
TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
89+
TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
90+
TimeSpan.FromDays(366).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
91+
TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate()
92+
};
93+
}
94+
95+
protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql);
96+
97+
protected override void TestFixtureSetUp()
98+
{
99+
base.TestFixtureSetUp();
100+
101+
longMapping = Driver.TypeMappings[typeof(long)];
102+
timeSpanMapping = Driver.TypeMappings[typeof(TimeSpan)];
103+
doubleMapping = Driver.TypeMappings[typeof(double)];
104+
105+
var dropTableCommand = Connection
106+
.CreateCommand(
107+
$"DROP TABLE IF EXISTS \"{TableName}\";");
108+
using (dropTableCommand) {
109+
_ = dropTableCommand.ExecuteNonQuery();
110+
}
111+
112+
var createTableCommand = Connection
113+
.CreateCommand(
114+
$"CREATE TABLE IF NOT EXISTS \"{TableName}\" (\"{IdColumnName}\" bigint CONSTRAINT PK_{TableName} PRIMARY KEY, \"{ValueColumnName}\" interval);");
115+
using (createTableCommand) {
116+
_ = createTableCommand.ExecuteNonQuery();
117+
}
118+
119+
var schema = ExtractDefaultSchema();
120+
var tableRef = SqlDml.TableRef(schema.Tables[TableName]);
121+
var selectTotalMsQuery = SqlDml.Select(tableRef);
122+
selectTotalMsQuery.Columns.Add(tableRef[IdColumnName], "id");
123+
selectTotalMsQuery.Columns.Add(tableRef[ValueColumnName], "timespan");
124+
selectTotalMsQuery.Columns.Add(SqlDml.IntervalToMilliseconds(tableRef[ValueColumnName]), "totalMs");
125+
selectTotalMsQuery.Where = tableRef[IdColumnName] == SqlDml.ParameterRef("pId");
126+
selectQuery = selectTotalMsQuery;
127+
}
128+
129+
protected override void TestFixtureTearDown()
130+
{
131+
longMapping = null;
132+
timeSpanMapping = null;
133+
doubleMapping = null;
134+
selectQuery = null;
135+
136+
base.TestFixtureTearDown();
137+
}
138+
139+
140+
[Test]
141+
[TestCaseSource(nameof(TestValues))]
142+
public void MainTest(TimeSpan testCase)
143+
{
144+
TestValue(testCase);
145+
}
146+
147+
148+
private void TestValue(TimeSpan testCase)
149+
{
150+
InsertValue(testCase.Ticks, testCase);
151+
var rowFromDb = SelectValue(testCase.Ticks);
152+
var trueTotalMilliseconds = testCase.TotalMilliseconds;
153+
var databaseValueTotalMilliseconds = rowFromDb.Item2.TotalMilliseconds;
154+
var extractedTotalMilliseconds = rowFromDb.Item3;
155+
156+
Assert.That(databaseValueTotalMilliseconds, Is.EqualTo(trueTotalMilliseconds));
157+
Assert.That(extractedTotalMilliseconds, Is.EqualTo(trueTotalMilliseconds));
158+
}
159+
160+
private void InsertValue(long id, TimeSpan testCase)
161+
{
162+
var command = Connection.CreateCommand($"INSERT INTO \"{TableName}\"(\"{IdColumnName}\", \"{ValueColumnName}\") VALUES (@pId, @pValue)");
163+
var pId = Connection.CreateParameter();
164+
pId.ParameterName = "pId";
165+
longMapping.BindValue(pId, id);
166+
_ = command.Parameters.Add(pId);
167+
168+
var pValue = Connection.CreateParameter();
169+
pValue.ParameterName = "pValue";
170+
timeSpanMapping.BindValue(pValue, testCase);
171+
_ = command.Parameters.Add(pValue);
172+
using (command) {
173+
_ = command.ExecuteNonQuery();
174+
}
175+
}
176+
177+
private (long, TimeSpan, double) SelectValue(long id)
178+
{
179+
var command = Connection.CreateCommand(selectQuery);
180+
var pId = Connection.CreateParameter();
181+
pId.ParameterName = "pId";
182+
longMapping.BindValue(pId, id);
183+
_ = command.Parameters.Add(pId);
184+
185+
using (command)
186+
using (var reader = command.ExecuteReader()) {
187+
while (reader.Read()) {
188+
var idFromDb = (long) longMapping.ReadValue(reader, 0);
189+
var valueFromDb = (TimeSpan) timeSpanMapping.ReadValue(reader, 1);
190+
var totalMs = (double) doubleMapping.ReadValue(reader, 2);
191+
return (idFromDb, valueFromDb, totalMs);
192+
}
193+
}
194+
195+
return default;
196+
}
197+
}
198+
}

Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,13 @@ public override void ReplaceWith(SqlExpression expression)
6565
internalValue = replacingExpression.internalValue;
6666
typeMarker = replacingExpression.typeMarker;
6767
typeHasTime = replacingExpression.typeHasTime;
68-
//DateTimePart = replacingExpression.DateTimePart;
69-
//DateTimeOffsetPart = replacingExpression.DateTimeOffsetPart;
70-
//IntervalPart = replacingExpression.IntervalPart;
7168
Operand = replacingExpression.Operand;
7269
}
7370

7471
internal override object Clone(SqlNodeCloneContext context) =>
7572
context.NodeMapping.TryGetValue(this, out var clone)
7673
? clone
7774
: context.NodeMapping[this] = new SqlExtract(internalValue, typeMarker, (SqlExpression)Operand.Clone(context));
78-
//DateTimePart!=SqlDateTimePart.Nothing
79-
//? new SqlExtract(DateTimePart, (SqlExpression) Operand.Clone(context))
80-
//: IntervalPart!=SqlIntervalPart.Nothing
81-
// ? new SqlExtract(IntervalPart, (SqlExpression) Operand.Clone(context))
82-
// : new SqlExtract(DateTimeOffsetPart, (SqlExpression) Operand.Clone(context));
8375

8476
public override void AcceptVisitor(ISqlVisitor visitor)
8577
{
@@ -94,10 +86,6 @@ internal SqlExtract(SqlDateTimePart dateTimePart, SqlExpression operand)
9486
internalValue = dateTimePart.ToDtoPartFast();
9587
typeMarker = DateTimeTypeId;
9688
typeHasTime = true;
97-
98-
//DateTimePart = dateTimePart;
99-
//DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing;
100-
//IntervalPart = SqlIntervalPart.Nothing;
10189
Operand = operand;
10290
}
10391

@@ -107,10 +95,6 @@ internal SqlExtract(SqlIntervalPart intervalPart, SqlExpression operand)
10795
internalValue = intervalPart.ToDtoPartFast();
10896
typeMarker = IntervalTypeId;
10997
typeHasTime = true;
110-
111-
//DateTimePart = SqlDateTimePart.Nothing;
112-
//DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing;
113-
//IntervalPart = intervalPart;
11498
Operand = operand;
11599
}
116100

0 commit comments

Comments
 (0)