Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ChangeLog/7.1.5_dev.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
[main] Addressed certain cases of overlaping server-side error by new one when temporary tables are used in query
[main] Addressed certain cases of overlaping server-side error by new one when temporary tables are used in query
[postgresql] Improved .Milliseconds translation for types which have this part
[postgresql] Improved TimeSpan.TotalMilliseconds translation
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public override void Visit(SqlFunctionCall node)
((node.Arguments[0] / SqlDml.Literal(nanosecondsPerSecond)) * OneSecondInterval).AcceptVisitor(this);
return;
case SqlFunctionType.IntervalToMilliseconds:
SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this);
VisitIntervalToMilliseconds(node);
return;
case SqlFunctionType.IntervalToNanoseconds:
SqlHelper.IntervalToNanoseconds(node.Arguments[0]).AcceptVisitor(this);
Expand Down Expand Up @@ -296,6 +296,11 @@ public override void Visit(SqlCustomFunctionCall node)
base.Visit(node);
}

protected virtual void VisitIntervalToMilliseconds(SqlFunctionCall node)
{
SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this);
}

private static SqlExpression DateTimeToStringIso(SqlExpression dateTime, in string isoFormat) =>
SqlDml.FunctionCall("TO_CHAR", dateTime, isoFormat);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,11 @@ public override void Translate(SqlCompilerContext context, SqlExtract node, Extr
}
switch (section) {
case ExtractSection.Entry:
_ = context.Output.AppendOpeningPunctuation(isSecond ? "(trunc(extract(" : "(extract(");
_ = context.Output.AppendOpeningPunctuation(isSecond || isMillisecond ? "(trunc(extract(" : "(extract(");
break;
case ExtractSection.Exit:
_ = context.Output.Append(isMillisecond
? ")::int8 % 1000)"
? "))::int8 % 1000)"
: isSecond ? ")))" : ")::int8)"
);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@
// Created by: Denis Krjuchkov
// Created: 2012.06.06

using Xtensive.Sql.Dml;

namespace Xtensive.Sql.Drivers.PostgreSql.v9_0
{
internal class Compiler : v8_4.Compiler
{
// Constructors
protected override void VisitIntervalToMilliseconds(SqlFunctionCall node)
{
AppendSpaceIfNecessary();
_ = context.Output.Append("(TRUNC(EXTRACT(EPOCH FROM (");
node.Arguments[0].AcceptVisitor(this);
_ = context.Output.Append(")) * 1000))");

}

public Compiler(SqlDriver driver)
: base(driver)
Expand Down
198 changes: 198 additions & 0 deletions Orm/Xtensive.Orm.Tests.Sql/PostgreSql/IntervalToMillisecondsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright (C) 2025 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.

using System;
using NUnit.Framework;
using Xtensive.Sql;
using Xtensive.Sql.Dml;

namespace Xtensive.Orm.Tests.Sql.PostgreSql
{
public sealed class IntervalToMillisecondsTest : SqlTest
{
private const string IdColumnName = "Id";
private const string ValueColumnName = "Value";
private const string TableName = "IntervalToMsTest";

private TypeMapping longMapping;
private TypeMapping timeSpanMapping;
private TypeMapping doubleMapping;

private SqlSelect selectQuery;

private static TimeSpan[] TestValues
{
get => new[] {
TimeSpan.MinValue,
TimeSpan.MaxValue,
TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)),
TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)),
TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)),
TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)),
TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)),
TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)),
TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)),
TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)),
TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))),
TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))),
TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))),
TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))),
TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))),
TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))),
TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))),
TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))),
TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))),
TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))),
TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))),
TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))),
TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))),
TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
TimeSpan.FromDays(366).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),
TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))),

TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)).Negate(),
TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)).Negate(),
TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)).Negate(),
TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)).Negate(),
TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)).Negate(),
TimeSpan.FromMinutes(43).Add(TimeSpan.FromSeconds(43)).Negate(),
TimeSpan.FromMinutes(55).Add(TimeSpan.FromSeconds(55)).Negate(),
TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)).Negate(),
TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1))).Negate(),
TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10))).Negate(),
TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15))).Negate(),
TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27))).Negate(),
TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30))).Negate(),
TimeSpan.FromDays(1).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(),
TimeSpan.FromDays(30).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(),
TimeSpan.FromDays(15).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(),
TimeSpan.FromDays(20).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(),
TimeSpan.FromDays(23).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
TimeSpan.FromDays(28).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
TimeSpan.FromDays(29).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
TimeSpan.FromDays(32).Add(TimeSpan.FromHours(1).Add(TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(1)))).Negate(),
TimeSpan.FromDays(40).Add(TimeSpan.FromHours(10).Add(TimeSpan.FromMinutes(10).Add(TimeSpan.FromSeconds(10)))).Negate(),
TimeSpan.FromDays(65).Add(TimeSpan.FromHours(15).Add(TimeSpan.FromMinutes(15).Add(TimeSpan.FromSeconds(15)))).Negate(),
TimeSpan.FromDays(181).Add(TimeSpan.FromHours(20).Add(TimeSpan.FromMinutes(27).Add(TimeSpan.FromSeconds(27)))).Negate(),
TimeSpan.FromDays(182).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
TimeSpan.FromDays(360).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
TimeSpan.FromDays(363).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
TimeSpan.FromDays(364).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
TimeSpan.FromDays(365).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
TimeSpan.FromDays(366).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate(),
TimeSpan.FromDays(730).Add(TimeSpan.FromHours(23).Add(TimeSpan.FromMinutes(30).Add(TimeSpan.FromSeconds(30)))).Negate()
};
}

protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql);

protected override void TestFixtureSetUp()
{
base.TestFixtureSetUp();

longMapping = Driver.TypeMappings[typeof(long)];
timeSpanMapping = Driver.TypeMappings[typeof(TimeSpan)];
doubleMapping = Driver.TypeMappings[typeof(double)];

var dropTableCommand = Connection
.CreateCommand(
$"DROP TABLE IF EXISTS \"{TableName}\";");
using (dropTableCommand) {
_ = dropTableCommand.ExecuteNonQuery();
}

var createTableCommand = Connection
.CreateCommand(
$"CREATE TABLE IF NOT EXISTS \"{TableName}\" (\"{IdColumnName}\" bigint CONSTRAINT PK_{TableName} PRIMARY KEY, \"{ValueColumnName}\" interval);");
using (createTableCommand) {
_ = createTableCommand.ExecuteNonQuery();
}

var schema = ExtractDefaultSchema();
var tableRef = SqlDml.TableRef(schema.Tables[TableName]);
var selectTotalMsQuery = SqlDml.Select(tableRef);
selectTotalMsQuery.Columns.Add(tableRef[IdColumnName], "id");
selectTotalMsQuery.Columns.Add(tableRef[ValueColumnName], "timespan");
selectTotalMsQuery.Columns.Add(SqlDml.IntervalToMilliseconds(tableRef[ValueColumnName]), "totalMs");
selectTotalMsQuery.Where = tableRef[IdColumnName] == SqlDml.ParameterRef("pId");
selectQuery = selectTotalMsQuery;
}

protected override void TestFixtureTearDown()
{
longMapping = null;
timeSpanMapping = null;
doubleMapping = null;
selectQuery = null;

base.TestFixtureTearDown();
}


[Test]
[TestCaseSource(nameof(TestValues))]
public void MainTest(TimeSpan testCase)
{
TestValue(testCase);
}


private void TestValue(TimeSpan testCase)
{
InsertValue(testCase.Ticks, testCase);
var rowFromDb = SelectValue(testCase.Ticks);
var trueTotalMilliseconds = testCase.TotalMilliseconds;
var databaseValueTotalMilliseconds = rowFromDb.Item2.TotalMilliseconds;
var extractedTotalMilliseconds = rowFromDb.Item3;

Assert.That(databaseValueTotalMilliseconds, Is.EqualTo(trueTotalMilliseconds));
Assert.That(extractedTotalMilliseconds, Is.EqualTo(trueTotalMilliseconds));
}

private void InsertValue(long id, TimeSpan testCase)
{
var command = Connection.CreateCommand($"INSERT INTO \"{TableName}\"(\"{IdColumnName}\", \"{ValueColumnName}\") VALUES (@pId, @pValue)");
var pId = Connection.CreateParameter();
pId.ParameterName = "pId";
longMapping.BindValue(pId, id);
_ = command.Parameters.Add(pId);

var pValue = Connection.CreateParameter();
pValue.ParameterName = "pValue";
timeSpanMapping.BindValue(pValue, testCase);
_ = command.Parameters.Add(pValue);
using (command) {
_ = command.ExecuteNonQuery();
}
}

private (long, TimeSpan, double) SelectValue(long id)
{
var command = Connection.CreateCommand(selectQuery);
var pId = Connection.CreateParameter();
pId.ParameterName = "pId";
longMapping.BindValue(pId, id);
_ = command.Parameters.Add(pId);

using (command)
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
var idFromDb = (long) longMapping.ReadValue(reader, 0);
var valueFromDb = (TimeSpan) timeSpanMapping.ReadValue(reader, 1);
var totalMs = (double) doubleMapping.ReadValue(reader, 2);
return (idFromDb, valueFromDb, totalMs);
}
}

return default;
}
}
}
16 changes: 0 additions & 16 deletions Orm/Xtensive.Orm/Sql/Dml/Expressions/SqlExtract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,13 @@ public override void ReplaceWith(SqlExpression expression)
internalValue = replacingExpression.internalValue;
typeMarker = replacingExpression.typeMarker;
typeHasTime = replacingExpression.typeHasTime;
//DateTimePart = replacingExpression.DateTimePart;
//DateTimeOffsetPart = replacingExpression.DateTimeOffsetPart;
//IntervalPart = replacingExpression.IntervalPart;
Operand = replacingExpression.Operand;
}

internal override object Clone(SqlNodeCloneContext context) =>
context.NodeMapping.TryGetValue(this, out var clone)
? clone
: context.NodeMapping[this] = new SqlExtract(internalValue, typeMarker, (SqlExpression)Operand.Clone(context));
//DateTimePart!=SqlDateTimePart.Nothing
//? new SqlExtract(DateTimePart, (SqlExpression) Operand.Clone(context))
//: IntervalPart!=SqlIntervalPart.Nothing
// ? new SqlExtract(IntervalPart, (SqlExpression) Operand.Clone(context))
// : new SqlExtract(DateTimeOffsetPart, (SqlExpression) Operand.Clone(context));

public override void AcceptVisitor(ISqlVisitor visitor)
{
Expand All @@ -94,10 +86,6 @@ internal SqlExtract(SqlDateTimePart dateTimePart, SqlExpression operand)
internalValue = dateTimePart.ToDtoPartFast();
typeMarker = DateTimeTypeId;
typeHasTime = true;

//DateTimePart = dateTimePart;
//DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing;
//IntervalPart = SqlIntervalPart.Nothing;
Operand = operand;
}

Expand All @@ -107,10 +95,6 @@ internal SqlExtract(SqlIntervalPart intervalPart, SqlExpression operand)
internalValue = intervalPart.ToDtoPartFast();
typeMarker = IntervalTypeId;
typeHasTime = true;

//DateTimePart = SqlDateTimePart.Nothing;
//DateTimeOffsetPart = SqlDateTimeOffsetPart.Nothing;
//IntervalPart = intervalPart;
Operand = operand;
}

Expand Down