Skip to content

Commit 197ce00

Browse files
committed
Sqlite: Support for DateConstruct/TimeConstruct
Also, reading/writing of DateOnly/TimeOnly values done correctly
1 parent a2bbcbc commit 197ce00

3 files changed

Lines changed: 120 additions & 28 deletions

File tree

Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2011-2022 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
@@ -23,7 +23,9 @@ internal class Compiler : SqlCompiler
2323
private static readonly int StartOffsetIndex = DateTimeOffsetExampleString.IndexOf('+');
2424

2525
private const long NanosecondsPerMillisecond = 1000000L;
26-
private const string DateFormat = "%Y-%m-%d 00:00:00.000";
26+
private const string DateWithZeroTimeFormat = "%Y-%m-%d 00:00:00.000";
27+
private const string DateFormat = "%Y-%m-%d";
28+
private const string TimeFormat = "%H:%M:%f";
2729
private const string DateTimeFormat = "%Y-%m-%d %H:%M:%f";
2830
private const string DateTimeIsoFormat = "%Y-%m-%dT%H:%M:%S";
2931
private const string DateTimeOffsetExampleString = "2001-02-03 04:05:06.789+02.45";
@@ -131,15 +133,16 @@ public override void Visit(SqlExtract node)
131133
/// <inheritdoc/>
132134
public override void Visit(SqlFunctionCall node)
133135
{
136+
var arguments = node.Arguments;
134137
switch (node.FunctionType) {
135138
case SqlFunctionType.CharLength:
136-
(SqlDml.FunctionCall("LENGTH", node.Arguments) / 2).AcceptVisitor(this);
139+
(SqlDml.FunctionCall("LENGTH", arguments) / 2).AcceptVisitor(this);
137140
return;
138141
case SqlFunctionType.PadLeft:
139142
case SqlFunctionType.PadRight:
140143
return;
141144
case SqlFunctionType.Concat:
142-
var nod = node.Arguments[0];
145+
var nod = arguments[0];
143146
return;
144147
case SqlFunctionType.Round:
145148
// Round should always be called with 2 arguments
@@ -149,40 +152,54 @@ public override void Visit(SqlFunctionCall node)
149152
}
150153
break;
151154
case SqlFunctionType.Truncate:
152-
Visit(CastToLong(node.Arguments[0]));
155+
Visit(CastToLong(arguments[0]));
153156
return;
154157
case SqlFunctionType.IntervalConstruct:
155-
Visit(CastToLong(node.Arguments[0]));
158+
Visit(CastToLong(arguments[0]));
156159
return;
157160
case SqlFunctionType.IntervalToNanoseconds:
158-
Visit(CastToLong(node.Arguments[0]));
161+
Visit(CastToLong(arguments[0]));
159162
return;
160163
case SqlFunctionType.IntervalToMilliseconds:
161-
Visit(CastToLong(node.Arguments[0] / NanosecondsPerMillisecond));
164+
Visit(CastToLong(arguments[0] / NanosecondsPerMillisecond));
162165
return;
163166
case SqlFunctionType.DateTimeAddMonths:
164-
DateAddMonth(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this);
167+
DateTimeAddMonth(arguments[0], arguments[1]).AcceptVisitor(this);
165168
return;
166169
case SqlFunctionType.DateTimeAddYears:
167-
DateAddYear(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this);
170+
DateTimeAddYear(arguments[0], arguments[1]).AcceptVisitor(this);
168171
return;
169172
case SqlFunctionType.DateTimeTruncate:
170-
DateTimeTruncate(node.Arguments[0]).AcceptVisitor(this);
173+
DateTimeTruncate(arguments[0]).AcceptVisitor(this);
171174
return;
172175
case SqlFunctionType.DateTimeConstruct:
173-
DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)),
174-
node.Arguments[0] - 2001),
175-
node.Arguments[1] - 1),
176-
node.Arguments[2] - 1).AcceptVisitor(this);
177-
return;
176+
DateTimeAddDay(DateTimeAddMonth(DateTimeAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)),
177+
arguments[0] - 2001),
178+
arguments[1] - 1),
179+
arguments[2] - 1).AcceptVisitor(this);
180+
return;
181+
#if NET6_0_OR_GREATER //DO_DATEONLY
182+
case SqlFunctionType.DateConstruct:
183+
DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateOnly(2001, 1, 1)),
184+
arguments[0] - 2001),
185+
arguments[1] - 1),
186+
arguments[2] - 1).AcceptVisitor(this);
187+
return;
188+
case SqlFunctionType.TimeConstruct:
189+
TimeAddSeconds(TimeAddMinutes(TimeAddHours(SqlDml.Literal(new TimeOnly(0, 0, 0, 0)),
190+
arguments[0]),
191+
arguments[1]),
192+
arguments[2], arguments[3]).AcceptVisitor(this);
193+
return;
194+
#endif
178195
case SqlFunctionType.DateTimeToStringIso:
179196
DateTimeToStringIso(node.Arguments[0]).AcceptVisitor(this);
180197
return;
181198
case SqlFunctionType.DateTimeOffsetAddMonths:
182-
SqlDml.Concat(DateAddMonth(DateTimeOffsetExtractDateTimeAsString(node.Arguments[0]), node.Arguments[1]), DateTimeOffsetExtractOffsetAsString(node.Arguments[0])).AcceptVisitor(this);
199+
SqlDml.Concat(DateTimeAddMonth(DateTimeOffsetExtractDateTimeAsString(node.Arguments[0]), node.Arguments[1]), DateTimeOffsetExtractOffsetAsString(node.Arguments[0])).AcceptVisitor(this);
183200
return;
184201
case SqlFunctionType.DateTimeOffsetAddYears:
185-
SqlDml.Concat(DateAddYear(DateTimeOffsetExtractDateTimeAsString(node.Arguments[0]), node.Arguments[1]), DateTimeOffsetExtractOffsetAsString(node.Arguments[0])).AcceptVisitor(this);
202+
SqlDml.Concat(DateTimeAddYear(DateTimeOffsetExtractDateTimeAsString(node.Arguments[0]), node.Arguments[1]), DateTimeOffsetExtractOffsetAsString(node.Arguments[0])).AcceptVisitor(this);
186203
return;
187204
case SqlFunctionType.DateTimeOffsetConstruct:
188205
SqlDml.Concat(node.Arguments[0], OffsetToOffsetAsString(node.Arguments[1])).AcceptVisitor(this);
@@ -351,10 +368,10 @@ private void VisitDateTimeOffset(SqlExtract node)
351368
}
352369

353370
private static SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpression interval) =>
354-
DateAddSeconds(date, interval / Convert.ToDouble(NanosecondsPerSecond));
371+
DateTimeAddSeconds(date, interval / Convert.ToDouble(NanosecondsPerSecond));
355372

356373
private static SqlExpression DateTimeTruncate(SqlExpression date) =>
357-
DateTime(SqlDml.FunctionCall("STRFTIME", DateFormat, date));
374+
DateTime(SqlDml.FunctionCall("STRFTIME", DateWithZeroTimeFormat, date));
358375

359376
private static SqlExpression DateTime(SqlExpression date) => SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date);
360377

@@ -405,20 +422,38 @@ private static SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression dateTim
405422
private static SqlExpression DateTimeToStringIso(SqlExpression dateTime) =>
406423
SqlDml.FunctionCall("STRFTIME", DateTimeIsoFormat, dateTime);
407424

408-
private static SqlExpression DateAddYear(SqlExpression date, SqlExpression years) =>
425+
private static SqlExpression DateTimeAddYear(SqlExpression date, SqlExpression years) =>
409426
SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(years, " ", "YEARS"));
410-
411427

412-
private static SqlExpression DateAddMonth(SqlExpression date, SqlExpression months) =>
428+
private static SqlExpression DateTimeAddMonth(SqlExpression date, SqlExpression months) =>
413429
SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(months, " ", "MONTHS"));
414430

415-
private static SqlExpression DateAddDay(SqlExpression date, SqlExpression days) =>
431+
private static SqlExpression DateTimeAddDay(SqlExpression date, SqlExpression days) =>
416432
SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(days, " ", "DAYS"));
417433

418-
419-
private static SqlExpression DateAddSeconds(SqlExpression date, SqlExpression seconds) =>
434+
private static SqlExpression DateTimeAddSeconds(SqlExpression date, SqlExpression seconds) =>
420435
SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(seconds, " ", "SECONDS"));
421436

437+
#if NET6_0_OR_GREATER // DO_DATEONLY
438+
private static SqlExpression DateAddYear(SqlExpression date, SqlExpression years) =>
439+
SqlDml.FunctionCall("STRFTIME", DateFormat, date, SqlDml.Concat(years, " ", "YEARS"));
440+
441+
private static SqlExpression DateAddMonth(SqlExpression date, SqlExpression months) =>
442+
SqlDml.FunctionCall("STRFTIME", DateFormat, date, SqlDml.Concat(months, " ", "MONTHS"));
443+
444+
private static SqlExpression DateAddDay(SqlExpression date, SqlExpression days) =>
445+
SqlDml.FunctionCall("STRFTIME", DateFormat, date, SqlDml.Concat(days, " ", "DAYS"));
446+
447+
private static SqlExpression TimeAddHours(SqlExpression date, SqlExpression seconds) =>
448+
SqlDml.FunctionCall("STRFTIME", TimeFormat, date, SqlDml.Concat(seconds, " ", "HOURS"));
449+
450+
private static SqlExpression TimeAddMinutes(SqlExpression date, SqlExpression seconds) =>
451+
SqlDml.FunctionCall("STRFTIME", TimeFormat, date, SqlDml.Concat(seconds, " ", "MINUTES"));
452+
453+
private static SqlExpression TimeAddSeconds(SqlExpression date, SqlExpression seconds, SqlExpression milliseconds) =>
454+
SqlDml.FunctionCall("STRFTIME", TimeFormat, date, SqlDml.Concat(seconds, ".", milliseconds, " ", "SECONDS"));
455+
#endif
456+
422457
private static SqlExpression DateGetMilliseconds(SqlExpression date) =>
423458
CastToLong(SqlDml.FunctionCall("STRFTIME", "%f", date) * MillisecondsPerSecond) -
424459
CastToLong(SqlDml.FunctionCall("STRFTIME", "%S", date) * MillisecondsPerSecond);

Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Translator.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2011-2022 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
@@ -22,6 +22,14 @@ internal class Translator : SqlTranslator
2222

2323
/// <inheritdoc/>
2424
public override string DateTimeFormatString => @"\'yyyy\-MM\-dd HH\:mm\:ss.fff\'";
25+
#if NET6_0_OR_GREATER //DO_DATEONLY
26+
27+
/// <inheritdoc/>
28+
public override string DateOnlyFormatString => @"\'yyyy\-MM\-dd\'";
29+
30+
/// <inheritdoc/>
31+
public override string TimeOnlyFormatString => @"\'HH\:mm\:ss.fff\'";
32+
#endif
2533

2634
public virtual string DateTimeOffsetFormatString => @"\'yyyy\-MM\-dd HH\:mm\:ss.fffK\'";
2735

Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/TypeMapper.cs

Lines changed: 50 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
@@ -9,24 +9,45 @@
99
using System.Data.Common;
1010
using System.Data.SQLite;
1111
using System.Globalization;
12+
using System.Security.AccessControl;
1213
using Xtensive.Sql.Info;
1314

1415
namespace Xtensive.Sql.Drivers.Sqlite.v3
1516
{
1617
internal class TypeMapper : Sql.TypeMapper
1718
{
19+
#if NET6_0_OR_GREATER
20+
private ValueRange<DateOnly> dateOnlyRange;
21+
private ValueRange<TimeOnly> timeOnlyRange;
22+
#endif
1823
private ValueRange<DateTime> dateTimeRange;
1924
private ValueRange<DateTimeOffset> dateTimeOffsetRange;
2025

2126
private const string DateTimeOffsetFormat = "yyyy-MM-dd HH:mm:ss.fffK";
2227
private const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss.fffffff";
28+
private const string DateFormat = "yyyy-MM-dd";
29+
private const string TimeFormat = "HH:mm:ss.fff";
2330

2431
public override object ReadBoolean(DbDataReader reader, int index)
2532
{
2633
var value = reader.GetDecimal(index);
2734
return SQLiteConvert.ToBoolean(value);
2835
}
2936

37+
#if NET6_0_OR_GREATER
38+
public override object ReadDateOnly(DbDataReader reader, int index)
39+
{
40+
var value = reader.GetString(index);
41+
return DateOnly.ParseExact(value, DateTimeOffsetFormat, CultureInfo.InvariantCulture);
42+
}
43+
44+
public override object ReadTimeOnly(DbDataReader reader, int index)
45+
{
46+
var value = reader.GetString(index);
47+
return TimeOnly.ParseExact(value, DateTimeOffsetFormat, CultureInfo.InvariantCulture);
48+
}
49+
#endif
50+
3051
public override object ReadDateTimeOffset(DbDataReader reader, int index)
3152
{
3253
var value = reader.GetString(index);
@@ -68,6 +89,30 @@ public override void BindDateTime(DbParameter parameter, object value)
6889
parameter.Value = correctValue.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
6990
}
7091

92+
#if NET6_0_OR_GREATER //DO_DATEONLY
93+
public override void BindDateOnly(DbParameter parameter, object value)
94+
{
95+
parameter.DbType = DbType.String;
96+
if (value == null) {
97+
parameter.Value = DBNull.Value;
98+
return;
99+
}
100+
var correctValue = ValueRangeValidator.Correct((DateOnly) value, dateOnlyRange);
101+
parameter.Value = correctValue.ToString(DateFormat, CultureInfo.InvariantCulture);
102+
}
103+
104+
public override void BindTimeOnly(DbParameter parameter, object value)
105+
{
106+
parameter.DbType = DbType.String;
107+
if (value == null) {
108+
parameter.Value = DBNull.Value;
109+
return;
110+
}
111+
var correctValue = ValueRangeValidator.Correct((TimeOnly) value, timeOnlyRange);
112+
parameter.Value = correctValue.ToString(TimeFormat, CultureInfo.InvariantCulture);
113+
}
114+
#endif
115+
71116
public override void BindDateTimeOffset(DbParameter parameter, object value)
72117
{
73118
parameter.DbType = DbType.String;
@@ -129,6 +174,10 @@ public override void Initialize()
129174
base.Initialize();
130175
dateTimeRange = (ValueRange<DateTime>) Driver.ServerInfo.DataTypes.DateTime.ValueRange;
131176
dateTimeOffsetRange = (ValueRange<DateTimeOffset>) Driver.ServerInfo.DataTypes.DateTimeOffset.ValueRange;
177+
#if NET6_0_OR_GREATER
178+
dateOnlyRange = (ValueRange<DateOnly>) Driver.ServerInfo.DataTypes.DateOnly.ValueRange;
179+
timeOnlyRange = (ValueRange<TimeOnly>) Driver.ServerInfo.DataTypes.TimeOnly.ValueRange;
180+
#endif
132181
}
133182

134183
// Constructors

0 commit comments

Comments
 (0)