Skip to content

Commit 647daed

Browse files
committed
MySQL: Add DateConstruct/TimeConstruct functions support
- updated Nuget package of client library to one that has native reading/binding of DateOnly and TimeOnly values - when placeholder is of TimeOnly type then it is wrapped by sql function TIME(), MySQL has some issues with values passed via parameter, even if DbType of DbParameter is set correctly. - 5.6 and above uses built-in fuction MAKETIME instead of general approach, should be faster
1 parent 6e304c3 commit 647daed

5 files changed

Lines changed: 130 additions & 20 deletions

File tree

Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -167,17 +167,32 @@ public override void Visit(SqlFunctionCall node)
167167
Visit(CastToLong(node.Arguments[0]));
168168
return;
169169
case SqlFunctionType.DateTimeAddMonths:
170-
Visit(DateAddMonth(node.Arguments[0], node.Arguments[1]));
170+
Visit(DateTimeAddMonth(node.Arguments[0], node.Arguments[1]));
171171
return;
172172
case SqlFunctionType.DateTimeAddYears:
173-
Visit(DateAddYear(node.Arguments[0], node.Arguments[1]));
173+
Visit(DateTimeAddYear(node.Arguments[0], node.Arguments[1]));
174174
return;
175175
case SqlFunctionType.DateTimeConstruct:
176-
Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)),
176+
Visit(DateTimeAddDay(DateTimeAddMonth(DateTimeAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)),
177177
node.Arguments[0] - 2001),
178178
node.Arguments[1] - 1),
179179
node.Arguments[2] - 1));
180180
return;
181+
#if NET6_0_OR_GREATER //DO_DATEONLY
182+
case SqlFunctionType.DateConstruct:
183+
Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateOnly(2001, 1, 1)),
184+
node.Arguments[0] - 2001),
185+
node.Arguments[1] - 1),
186+
node.Arguments[2] - 1));
187+
return;
188+
case SqlFunctionType.TimeConstruct:
189+
Visit(SqlDml.FunctionCall("TIME", TimeAddMillisecond(TimeAddSecond(TimeAddMinute(TimeAddHour(SqlDml.Literal(new DateTime(2001, 1, 1)),
190+
node.Arguments[0]),
191+
node.Arguments[1]),
192+
node.Arguments[2]),
193+
node.Arguments[3])));
194+
return;
195+
#endif
181196
case SqlFunctionType.DateTimeToStringIso:
182197
Visit(DateTimeToStringIso(node.Arguments[0]));
183198
return;
@@ -186,6 +201,20 @@ public override void Visit(SqlFunctionCall node)
186201
base.Visit(node);
187202
}
188203

204+
#if NET6_0_OR_GREATER //DO_DATEONLY
205+
public override void Visit(SqlPlaceholder node)
206+
{
207+
if(node.Id is Xtensive.Orm.Providers.QueryParameterBinding qpb && qpb.TypeMapping.Type==typeof(TimeOnly)) {
208+
_ = context.Output.Append("TIME(");
209+
base.Visit(node);
210+
_ = context.Output.Append(")");
211+
}
212+
else {
213+
base.Visit(node);
214+
}
215+
}
216+
#endif
217+
189218
/// <inheritdoc/>
190219
protected override void VisitSelectLimitOffset(SqlSelect node)
191220
{
@@ -217,13 +246,13 @@ protected virtual SqlExpression DateTimeSubtractDateTime(SqlExpression date1, Sq
217246
{
218247
return (CastToDecimal(DateDiffDay(date1, date2), 18, 0) * NanosecondsPerDay)
219248
+
220-
(CastToDecimal(DateDiffMicrosecond(DateAddDay(date2, DateDiffDay(date1, date2)), date1), 18, 0) * NanosecondsPerMicrosecond);
249+
(CastToDecimal(DateTimeDiffMicrosecond(DateTimeAddDay(date2, DateDiffDay(date1, date2)), date1), 18, 0) * NanosecondsPerMicrosecond);
221250
}
222251

223252
protected virtual SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpression interval)
224253
{
225-
return DateAddMicrosecond(
226-
DateAddDay(date, ((interval - (interval % NanosecondsPerDay)) + ((interval % NanosecondsPerDay) > (NanosecondsPerDay / 2) ? 0 : 1)) / NanosecondsPerDay),
254+
return DateTimeAddMicrosecond(
255+
DateTimeAddDay(date, ((interval - (interval % NanosecondsPerDay)) + ((interval % NanosecondsPerDay) > (NanosecondsPerDay / 2) ? 0 : 1)) / NanosecondsPerDay),
227256
(interval / NanosecondsPerMillisecond * NanosecondsPerMicrosecond) % (MillisecondsPerDay * NanosecondsPerMicrosecond));
228257
}
229258

@@ -237,20 +266,42 @@ private static SqlCast CastToDecimal(SqlExpression arg, short precision, short s
237266
private static SqlUserFunctionCall DateDiffDay(SqlExpression date1, SqlExpression date2) =>
238267
SqlDml.FunctionCall("DATEDIFF", date1, date2);
239268

240-
private static SqlUserFunctionCall DateDiffMicrosecond(SqlExpression date1, SqlExpression date2) =>
241-
SqlDml.FunctionCall("TIMESTAMPDIFF", SqlDml.Native("MICROSECOND"), date1, date2);
269+
private static SqlUserFunctionCall DateTimeDiffMicrosecond(SqlExpression datetime1, SqlExpression datetime2) =>
270+
SqlDml.FunctionCall("TIMESTAMPDIFF", SqlDml.Native("MICROSECOND"), datetime1, datetime2);
271+
272+
private static SqlUserFunctionCall DateTimeAddYear(SqlExpression datetime, SqlExpression years) =>
273+
SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("YEAR"), years, datetime);
274+
275+
private static SqlUserFunctionCall DateTimeAddMonth(SqlExpression datetime, SqlExpression months) =>
276+
SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("MONTH"), months, datetime);
277+
278+
private static SqlUserFunctionCall DateTimeAddDay(SqlExpression datetime, SqlExpression days) =>
279+
SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("DAY"), days, datetime);
242280

281+
private static SqlUserFunctionCall DateTimeAddMicrosecond(SqlExpression datetime, SqlExpression microseconds) =>
282+
SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("MICROSECOND"), microseconds, datetime);
283+
284+
#if NET6_0_OR_GREATER //DO_DATEONLY
243285
private static SqlUserFunctionCall DateAddYear(SqlExpression date, SqlExpression years) =>
244-
SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("YEAR"), years, date);
286+
SqlDml.FunctionCall("DATE_ADD", date, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(years, SqlDml.Native("YEAR"))));
245287

246288
private static SqlUserFunctionCall DateAddMonth(SqlExpression date, SqlExpression months) =>
247-
SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("MONTH"), months, date);
248-
289+
SqlDml.FunctionCall("DATE_ADD", date, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(months, SqlDml.Native("MONTH"))));
249290
private static SqlUserFunctionCall DateAddDay(SqlExpression date, SqlExpression days) =>
250-
SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("DAY"), days, date);
291+
SqlDml.FunctionCall("DATE_ADD", date, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(days, SqlDml.Native("DAY"))));
292+
293+
private static SqlUserFunctionCall TimeAddHour(SqlExpression time, SqlExpression hours) =>
294+
SqlDml.FunctionCall("DATE_ADD", time, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(hours, SqlDml.Native("HOUR"))));
295+
296+
private static SqlUserFunctionCall TimeAddMinute(SqlExpression time, SqlExpression minutes) =>
297+
SqlDml.FunctionCall("DATE_ADD", time, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(minutes, SqlDml.Native("MINUTE"))));
298+
299+
private static SqlUserFunctionCall TimeAddSecond(SqlExpression time, SqlExpression seconds) =>
300+
SqlDml.FunctionCall("DATE_ADD", time, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(seconds, SqlDml.Native("SECOND"))));
251301

252-
private static SqlUserFunctionCall DateAddMicrosecond(SqlExpression date, SqlExpression microseconds) =>
253-
SqlDml.FunctionCall("TIMESTAMPADD", SqlDml.Native("MICROSECOND"), microseconds, date);
302+
private static SqlUserFunctionCall TimeAddMillisecond(SqlExpression time, SqlExpression millisecond) =>
303+
SqlDml.FunctionCall("DATE_ADD", time, SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.RawConcat(millisecond * 1000, SqlDml.Native("MICROSECOND"))));
304+
#endif
254305

255306
protected static SqlUserFunctionCall DateTimeToStringIso(SqlExpression dateTime) =>
256307
SqlDml.FunctionCall("DATE_FORMAT", dateTime, "%Y-%m-%dT%T");

Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ namespace Xtensive.Sql.Drivers.MySql.v5_0
2222
internal class Translator : SqlTranslator
2323
{
2424
public override string DateTimeFormatString => @"\'yyyy\-MM\-dd HH\:mm\:ss\.ffffff\'";
25+
#if NET6_0_OR_GREATER //DO_DATEONLY
26+
public override string DateOnlyFormatString => @"\'yyyy\-MM\-dd\'";
27+
public override string TimeOnlyFormatString => @"\'HH\:mm\:ss\.ffffff\'";
28+
#endif
2529

2630
public override string TimeSpanFormatString => string.Empty;
2731

@@ -106,6 +110,10 @@ public override void Translate(IOutput output, SqlFunctionType type)
106110
case SqlFunctionType.DateTimeAddYears:
107111
case SqlFunctionType.DateTimeAddMonths:
108112
case SqlFunctionType.DateTimeConstruct:
113+
#if NET6_0_OR_GREATER //DO_DATEONLY
114+
case SqlFunctionType.DateConstruct:
115+
case SqlFunctionType.TimeConstruct:
116+
#endif
109117
case SqlFunctionType.IntervalToMilliseconds:
110118
return;
111119
//string

Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/TypeMapper.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2011-2021 Xtensive LLC.
1+
// Copyright (C) 2011-2023 Xtensive LLC.
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44
// Created by: Malisa Ncube
@@ -14,7 +14,11 @@ namespace Xtensive.Sql.Drivers.MySql.v5_0
1414
{
1515
internal class TypeMapper : Sql.TypeMapper
1616
{
17+
#if NET6_0_OR_GREATER //DO_DATEONLY
18+
private static readonly Type[] CastRequiredTypes = new[] { typeof(Guid), typeof(TimeSpan), typeof(byte[]), typeof (DateOnly), typeof(TimeOnly) };
19+
#else
1720
private static readonly Type[] CastRequiredTypes = new[] { typeof(Guid), typeof(TimeSpan), typeof(byte[]) };
21+
#endif
1822

1923
/// <inheritdoc/>
2024
public override bool IsParameterCastRequired(Type type)
@@ -63,6 +67,20 @@ public override void BindGuid(DbParameter parameter, object value)
6367
parameter.Value = value==null ? (object) DBNull.Value : SqlHelper.GuidToString((Guid) value);
6468
}
6569

70+
#if NET6_0_OR_GREATER //DO_DATEONLY
71+
public override void BindDateOnly(DbParameter parameter, object value)
72+
{
73+
parameter.DbType = DbType.Date;
74+
parameter.Value = value ?? DBNull.Value;
75+
}
76+
77+
public override void BindTimeOnly(DbParameter parameter, object value)
78+
{
79+
parameter.DbType = DbType.Time;
80+
parameter.Value = value ?? DBNull.Value;
81+
}
82+
#endif
83+
6684
/// <inheritdoc/>
6785
public override SqlValueType MapByte(int? length, int? precision, int? scale)
6886
{
@@ -111,6 +129,19 @@ public override object ReadGuid(DbDataReader reader, int index)
111129
return SqlHelper.GuidFromString(reader.GetString(index));
112130
}
113131

132+
#if NET6_0_OR_GREATER
133+
public override object ReadDateOnly(DbDataReader reader, int index)
134+
{
135+
return reader.GetFieldValue<DateOnly>(index);
136+
}
137+
138+
public override object ReadTimeOnly(DbDataReader reader, int index)
139+
{
140+
return reader.GetFieldValue<TimeOnly>(index);
141+
}
142+
143+
#endif
144+
114145
// Constructors
115146

116147
[SecuritySafeCritical]

Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/Compiler.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
1-
// Copyright (C) 2003-2010 Xtensive LLC.
2-
// All rights reserved.
3-
// For conditions of distribution and use, see license.
1+
// Copyright (C) 2013-2023 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
44
// Created by: Alena Mikshina
55
// Created: 2013.12.30
66

7+
using System;
8+
using Xtensive.Sql.Dml;
9+
710
namespace Xtensive.Sql.Drivers.MySql.v5_6
811
{
912
internal class Compiler : v5_5.Compiler
1013
{
14+
#if NET6_0_OR_GREATER //DO_DATEONLY
15+
public override void Visit(SqlFunctionCall node)
16+
{
17+
if (node.FunctionType == SqlFunctionType.TimeConstruct) {
18+
var arguments = node.Arguments;
19+
Visit(MakeTime(arguments[0], arguments[1], arguments[2], arguments[3]));
20+
return;
21+
}
22+
base.Visit(node);
23+
}
24+
25+
protected SqlUserFunctionCall MakeTime(
26+
SqlExpression hours, SqlExpression minutes, SqlExpression seconds, SqlExpression milliseconds) =>
27+
SqlDml.FunctionCall("MAKETIME", hours, minutes, seconds + (milliseconds / 1000));
28+
29+
#endif
30+
1131
// Constructors
1232

1333
public Compiler(SqlDriver driver)

Orm/Xtensive.Orm.MySql/Xtensive.Orm.MySql.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
</EmbeddedResource>
3131
</ItemGroup>
3232
<ItemGroup>
33-
<PackageReference Include="Google.Protobuf" Version="3.15.0" />
34-
<PackageReference Include="MySql.Data" Version="8.0.19" />
33+
<PackageReference Include="Google.Protobuf" Version="3.19.4" />
34+
<PackageReference Include="MySql.Data" Version="8.0.30" />
3535
<PackageReference Include="System.Xml.XPath.XmlDocument" Version="4.3.0" />
3636
</ItemGroup>
3737
<ItemGroup>

0 commit comments

Comments
 (0)