Skip to content

Commit a951c3f

Browse files
feat: add option to override DateTime to NodaTime.Instant in MySQL
1 parent f6c2f12 commit a951c3f

29 files changed

Lines changed: 454 additions & 63 deletions

Drivers/MySqlConnectorDriver.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,29 @@ public sealed partial class MySqlConnectorDriver(
124124
},
125125
readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetFieldValue<TimeSpan>({ordinal})"
126126
),
127+
["Instant"] = new(
128+
[],
129+
readerFn: (ordinal, _) => $$"""
130+
(new Func<MySqlDataReader, int, Instant>((r, o) =>
131+
{
132+
var dt = {{Variable.Reader.AsVarName()}}.GetDateTime(o);
133+
if (dt.Kind != DateTimeKind.Utc)
134+
dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
135+
return dt.ToInstant();
136+
}))({{Variable.Reader.AsVarName()}}, {{ordinal}})
137+
""",
138+
writerFn: (el, _, notNull, isDapper, isLegacy) =>
139+
{
140+
if (notNull)
141+
return $"DateTime.SpecifyKind({el}.ToDateTimeUtc(), DateTimeKind.Unspecified)";
142+
var nullValue = isDapper ? "null" : "(object)DBNull.Value";
143+
return $"{el} is null ? {nullValue} : (DateTime?) DateTime.SpecifyKind({el}.Value.ToDateTimeUtc(), DateTimeKind.Unspecified)";
144+
},
145+
usingDirectives: ["System", "NodaTime", "NodaTime.Extensions"],
146+
sqlMapper: "SqlMapper.AddTypeHandler(typeof(Instant), new NodaInstantTypeHandler());",
147+
sqlMapperImpl: NodaInstantTypeHandler
148+
),
149+
127150

128151
/* Unstructured data types */
129152
["JsonElement"] = new(

end2end/EndToEndScaffold/Templates/MySqlTests.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,23 +174,48 @@ void AssertSingularEquals(QuerySql.GetMysqlNumericTypesRow x, QuerySql.GetMysqlN
174174
[KnownTestType.MySqlDateTimeDataTypes] = new TestImpl
175175
{
176176
Impl = $$"""
177+
178+
private static IEnumerable<TestCaseData> MySqlDateTimeTypesTestCases
179+
{
180+
get
181+
{
182+
yield return new TestCaseData(
183+
(short) 1999,
184+
DateTime.Parse("2000-1-30"),
185+
DateTime.Parse("1983-11-3 02:01:22"),
186+
DateTime.Parse("2010-1-30 08:11:00"),
187+
TimeSpan.Parse("02:01:22"),
188+
Instant.FromUtc(2025, 10, 15, 19, 55, 2)
189+
).SetName("DateTimeTypes with values");
190+
yield return new TestCaseData(
191+
null,
192+
null,
193+
null,
194+
null,
195+
null,
196+
null
197+
).SetName("DateTimeTypes with null values");
198+
}
199+
}
200+
177201
[Test]
178-
[TestCase(1999, "2000-1-30", "1983-11-3 02:01:22", "2010-1-30 08:11:00", "02:01:22")]
179-
[TestCase(null, null, null, null, null)]
202+
[TestCaseSource(nameof(MySqlDateTimeTypesTestCases))]
180203
public async Task TestMySqlDateTimeTypes(
181204
short? cYear,
182205
DateTime? cDate,
183206
DateTime? cDatetime,
184207
DateTime? cTimestamp,
185-
TimeSpan? cTime)
208+
TimeSpan? cTime,
209+
Instant? cTimestampNodaInstantOverride)
186210
{
187211
await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArgs
188212
{
189213
CYear = cYear,
190214
CDate = cDate,
191215
CDatetime = cDatetime,
192216
CTimestamp = cTimestamp,
193-
CTime = cTime
217+
CTime = cTime,
218+
CTimestampNodaInstantOverride = cTimestampNodaInstantOverride
194219
});
195220
196221
var expected = new QuerySql.GetMysqlDatetimeTypesRow
@@ -199,7 +224,8 @@ await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArg
199224
CDate = cDate,
200225
CDatetime = cDatetime,
201226
CTimestamp = cTimestamp,
202-
CTime = cTime
227+
CTime = cTime,
228+
CTimestampNodaInstantOverride = cTimestampNodaInstantOverride
203229
};
204230
var actual = await QuerySql.GetMysqlDatetimeTypes();
205231
AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}});
@@ -211,6 +237,7 @@ void AssertSingularEquals(QuerySql.GetMysqlDatetimeTypesRow x, QuerySql.GetMysql
211237
Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime));
212238
Assert.That(x.CTimestamp, Is.EqualTo(y.CTimestamp));
213239
Assert.That(x.CTime, Is.EqualTo(y.CTime));
240+
Assert.That(x.CTimestampNodaInstantOverride, Is.EqualTo(y.CTimestampNodaInstantOverride));
214241
}
215242
}
216243
"""

end2end/EndToEndTests/MySqlConnectorDapperTester.generated.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,19 +519,28 @@ void AssertSingularEquals(QuerySql.GetMysqlNumericTypesRow x, QuerySql.GetMysqlN
519519
}
520520
}
521521

522+
private static IEnumerable<TestCaseData> MySqlDateTimeTypesTestCases
523+
{
524+
get
525+
{
526+
yield return new TestCaseData((short)1999, DateTime.Parse("2000-1-30"), DateTime.Parse("1983-11-3 02:01:22"), DateTime.Parse("2010-1-30 08:11:00"), TimeSpan.Parse("02:01:22"), Instant.FromUtc(2025, 1, 1, 1, 1, 1)).SetName("DateTimeTypes with values");
527+
yield return new TestCaseData(null, null, null, null, null, null).SetName("DateTimeTypes with null values");
528+
}
529+
}
530+
522531
[Test]
523-
[TestCase(1999, "2000-1-30", "1983-11-3 02:01:22", "2010-1-30 08:11:00", "02:01:22")]
524-
[TestCase(null, null, null, null, null)]
525-
public async Task TestMySqlDateTimeTypes(short? cYear, DateTime? cDate, DateTime? cDatetime, DateTime? cTimestamp, TimeSpan? cTime)
532+
[TestCaseSource(nameof(MySqlDateTimeTypesTestCases))]
533+
public async Task TestMySqlDateTimeTypes(short? cYear, DateTime? cDate, DateTime? cDatetime, DateTime? cTimestamp, TimeSpan? cTime, Instant? cTimestampNodaInstantOverride)
526534
{
527-
await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArgs { CYear = cYear, CDate = cDate, CDatetime = cDatetime, CTimestamp = cTimestamp, CTime = cTime });
535+
await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArgs { CYear = cYear, CDate = cDate, CDatetime = cDatetime, CTimestamp = cTimestamp, CTime = cTime, CTimestampNodaInstantOverride = cTimestampNodaInstantOverride });
528536
var expected = new QuerySql.GetMysqlDatetimeTypesRow
529537
{
530538
CYear = cYear,
531539
CDate = cDate,
532540
CDatetime = cDatetime,
533541
CTimestamp = cTimestamp,
534-
CTime = cTime
542+
CTime = cTime,
543+
CTimestampNodaInstantOverride = cTimestampNodaInstantOverride
535544
};
536545
var actual = await QuerySql.GetMysqlDatetimeTypes();
537546
AssertSingularEquals(expected, actual);
@@ -542,6 +551,7 @@ void AssertSingularEquals(QuerySql.GetMysqlDatetimeTypesRow x, QuerySql.GetMysql
542551
Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime));
543552
Assert.That(x.CTimestamp, Is.EqualTo(y.CTimestamp));
544553
Assert.That(x.CTime, Is.EqualTo(y.CTime));
554+
Assert.That(x.CTimestampNodaInstantOverride, Is.EqualTo(y.CTimestampNodaInstantOverride));
545555
}
546556
}
547557

end2end/EndToEndTests/MySqlConnectorTester.generated.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,19 +519,28 @@ void AssertSingularEquals(QuerySql.GetMysqlNumericTypesRow x, QuerySql.GetMysqlN
519519
}
520520
}
521521

522+
private static IEnumerable<TestCaseData> MySqlDateTimeTypesTestCases
523+
{
524+
get
525+
{
526+
yield return new TestCaseData((short)1999, DateTime.Parse("2000-1-30"), DateTime.Parse("1983-11-3 02:01:22"), DateTime.Parse("2010-1-30 08:11:00"), TimeSpan.Parse("02:01:22"), Instant.FromUtc(2025, 1, 1, 1, 1, 1)).SetName("DateTimeTypes with values");
527+
yield return new TestCaseData(null, null, null, null, null, null).SetName("DateTimeTypes with null values");
528+
}
529+
}
530+
522531
[Test]
523-
[TestCase(1999, "2000-1-30", "1983-11-3 02:01:22", "2010-1-30 08:11:00", "02:01:22")]
524-
[TestCase(null, null, null, null, null)]
525-
public async Task TestMySqlDateTimeTypes(short? cYear, DateTime? cDate, DateTime? cDatetime, DateTime? cTimestamp, TimeSpan? cTime)
532+
[TestCaseSource(nameof(MySqlDateTimeTypesTestCases))]
533+
public async Task TestMySqlDateTimeTypes(short? cYear, DateTime? cDate, DateTime? cDatetime, DateTime? cTimestamp, TimeSpan? cTime, Instant? cTimestampNodaInstantOverride)
526534
{
527-
await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArgs { CYear = cYear, CDate = cDate, CDatetime = cDatetime, CTimestamp = cTimestamp, CTime = cTime });
535+
await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArgs { CYear = cYear, CDate = cDate, CDatetime = cDatetime, CTimestamp = cTimestamp, CTime = cTime, CTimestampNodaInstantOverride = cTimestampNodaInstantOverride });
528536
var expected = new QuerySql.GetMysqlDatetimeTypesRow
529537
{
530538
CYear = cYear,
531539
CDate = cDate,
532540
CDatetime = cDatetime,
533541
CTimestamp = cTimestamp,
534-
CTime = cTime
542+
CTime = cTime,
543+
CTimestampNodaInstantOverride = cTimestampNodaInstantOverride
535544
};
536545
var actual = await QuerySql.GetMysqlDatetimeTypes();
537546
AssertSingularEquals(expected, actual.Value);
@@ -542,6 +551,7 @@ void AssertSingularEquals(QuerySql.GetMysqlDatetimeTypesRow x, QuerySql.GetMysql
542551
Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime));
543552
Assert.That(x.CTimestamp, Is.EqualTo(y.CTimestamp));
544553
Assert.That(x.CTime, Is.EqualTo(y.CTime));
554+
Assert.That(x.CTimestampNodaInstantOverride, Is.EqualTo(y.CTimestampNodaInstantOverride));
545555
}
546556
}
547557

end2end/EndToEndTestsLegacy/MySqlConnectorDapperTester.generated.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,19 +519,28 @@ void AssertSingularEquals(QuerySql.GetMysqlNumericTypesRow x, QuerySql.GetMysqlN
519519
}
520520
}
521521

522+
private static IEnumerable<TestCaseData> MySqlDateTimeTypesTestCases
523+
{
524+
get
525+
{
526+
yield return new TestCaseData((short)1999, DateTime.Parse("2000-1-30"), DateTime.Parse("1983-11-3 02:01:22"), DateTime.Parse("2010-1-30 08:11:00"), TimeSpan.Parse("02:01:22"), Instant.FromUtc(2025, 1, 1, 1, 1, 1)).SetName("DateTimeTypes with values");
527+
yield return new TestCaseData(null, null, null, null, null, null).SetName("DateTimeTypes with null values");
528+
}
529+
}
530+
522531
[Test]
523-
[TestCase(1999, "2000-1-30", "1983-11-3 02:01:22", "2010-1-30 08:11:00", "02:01:22")]
524-
[TestCase(null, null, null, null, null)]
525-
public async Task TestMySqlDateTimeTypes(short? cYear, DateTime? cDate, DateTime? cDatetime, DateTime? cTimestamp, TimeSpan? cTime)
532+
[TestCaseSource(nameof(MySqlDateTimeTypesTestCases))]
533+
public async Task TestMySqlDateTimeTypes(short? cYear, DateTime? cDate, DateTime? cDatetime, DateTime? cTimestamp, TimeSpan? cTime, Instant? cTimestampNodaInstantOverride)
526534
{
527-
await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArgs { CYear = cYear, CDate = cDate, CDatetime = cDatetime, CTimestamp = cTimestamp, CTime = cTime });
535+
await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArgs { CYear = cYear, CDate = cDate, CDatetime = cDatetime, CTimestamp = cTimestamp, CTime = cTime, CTimestampNodaInstantOverride = cTimestampNodaInstantOverride });
528536
var expected = new QuerySql.GetMysqlDatetimeTypesRow
529537
{
530538
CYear = cYear,
531539
CDate = cDate,
532540
CDatetime = cDatetime,
533541
CTimestamp = cTimestamp,
534-
CTime = cTime
542+
CTime = cTime,
543+
CTimestampNodaInstantOverride = cTimestampNodaInstantOverride
535544
};
536545
var actual = await QuerySql.GetMysqlDatetimeTypes();
537546
AssertSingularEquals(expected, actual);
@@ -542,6 +551,7 @@ void AssertSingularEquals(QuerySql.GetMysqlDatetimeTypesRow x, QuerySql.GetMysql
542551
Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime));
543552
Assert.That(x.CTimestamp, Is.EqualTo(y.CTimestamp));
544553
Assert.That(x.CTime, Is.EqualTo(y.CTime));
554+
Assert.That(x.CTimestampNodaInstantOverride, Is.EqualTo(y.CTimestampNodaInstantOverride));
545555
}
546556
}
547557

end2end/EndToEndTestsLegacy/MySqlConnectorTester.generated.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,19 +519,28 @@ void AssertSingularEquals(QuerySql.GetMysqlNumericTypesRow x, QuerySql.GetMysqlN
519519
}
520520
}
521521

522+
private static IEnumerable<TestCaseData> MySqlDateTimeTypesTestCases
523+
{
524+
get
525+
{
526+
yield return new TestCaseData((short)1999, DateTime.Parse("2000-1-30"), DateTime.Parse("1983-11-3 02:01:22"), DateTime.Parse("2010-1-30 08:11:00"), TimeSpan.Parse("02:01:22"), Instant.FromUtc(2025, 1, 1, 1, 1, 1)).SetName("DateTimeTypes with values");
527+
yield return new TestCaseData(null, null, null, null, null, null).SetName("DateTimeTypes with null values");
528+
}
529+
}
530+
522531
[Test]
523-
[TestCase(1999, "2000-1-30", "1983-11-3 02:01:22", "2010-1-30 08:11:00", "02:01:22")]
524-
[TestCase(null, null, null, null, null)]
525-
public async Task TestMySqlDateTimeTypes(short? cYear, DateTime? cDate, DateTime? cDatetime, DateTime? cTimestamp, TimeSpan? cTime)
532+
[TestCaseSource(nameof(MySqlDateTimeTypesTestCases))]
533+
public async Task TestMySqlDateTimeTypes(short? cYear, DateTime? cDate, DateTime? cDatetime, DateTime? cTimestamp, TimeSpan? cTime, Instant? cTimestampNodaInstantOverride)
526534
{
527-
await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArgs { CYear = cYear, CDate = cDate, CDatetime = cDatetime, CTimestamp = cTimestamp, CTime = cTime });
535+
await QuerySql.InsertMysqlDatetimeTypes(new QuerySql.InsertMysqlDatetimeTypesArgs { CYear = cYear, CDate = cDate, CDatetime = cDatetime, CTimestamp = cTimestamp, CTime = cTime, CTimestampNodaInstantOverride = cTimestampNodaInstantOverride });
528536
var expected = new QuerySql.GetMysqlDatetimeTypesRow
529537
{
530538
CYear = cYear,
531539
CDate = cDate,
532540
CDatetime = cDatetime,
533541
CTimestamp = cTimestamp,
534-
CTime = cTime
542+
CTime = cTime,
543+
CTimestampNodaInstantOverride = cTimestampNodaInstantOverride
535544
};
536545
var actual = await QuerySql.GetMysqlDatetimeTypes();
537546
AssertSingularEquals(expected, actual);
@@ -542,6 +551,7 @@ void AssertSingularEquals(QuerySql.GetMysqlDatetimeTypesRow x, QuerySql.GetMysql
542551
Assert.That(x.CDatetime, Is.EqualTo(y.CDatetime));
543552
Assert.That(x.CTimestamp, Is.EqualTo(y.CTimestamp));
544553
Assert.That(x.CTime, Is.EqualTo(y.CTime));
554+
Assert.That(x.CTimestampNodaInstantOverride, Is.EqualTo(y.CTimestampNodaInstantOverride));
545555
}
546556
}
547557

examples/MySqlConnectorDapperExample/Models.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// auto-generated by sqlc - do not edit
2+
using NodaTime;
3+
using NodaTime.Extensions;
24
using System;
35
using System.Collections.Generic;
46
using System.Linq;
@@ -58,6 +60,7 @@ public class MysqlDatetimeType
5860
public DateTime? CDatetime { get; init; }
5961
public DateTime? CTimestamp { get; init; }
6062
public TimeSpan? CTime { get; init; }
63+
public DateTime? CTimestampNodaInstantOverride { get; init; }
6164
};
6265
public class MysqlBinaryType
6366
{

examples/MySqlConnectorDapperExample/QuerySql.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
using CsvHelper.TypeConversion;
1010
using Dapper;
1111
using MySqlConnector;
12+
using NodaTime;
13+
using NodaTime.Extensions;
1214
using System;
1315
using System.Collections.Generic;
1416
using System.Globalization;
@@ -952,14 +954,15 @@ public async Task TruncateMysqlStringTypes()
952954
await this.Transaction.Connection.ExecuteAsync(TruncateMysqlStringTypesSql, transaction: this.Transaction);
953955
}
954956

955-
private const string InsertMysqlDatetimeTypesSql = " INSERT INTO mysql_datetime_types ( c_year, c_date, c_datetime, c_timestamp, c_time ) VALUES (@c_year, @c_date, @c_datetime, @c_timestamp, @c_time)";
957+
private const string InsertMysqlDatetimeTypesSql = " INSERT INTO mysql_datetime_types ( c_year, c_date, c_datetime, c_timestamp, c_time, c_timestamp_noda_instant_override ) VALUES (@c_year, @c_date, @c_datetime, @c_timestamp, @c_time, @c_timestamp_noda_instant_override)";
956958
public class InsertMysqlDatetimeTypesArgs
957959
{
958960
public short? CYear { get; init; }
959961
public DateTime? CDate { get; init; }
960962
public DateTime? CDatetime { get; init; }
961963
public DateTime? CTimestamp { get; init; }
962964
public TimeSpan? CTime { get; init; }
965+
public Instant? CTimestampNodaInstantOverride { get; init; }
963966
};
964967
public async Task InsertMysqlDatetimeTypes(InsertMysqlDatetimeTypesArgs args)
965968
{
@@ -969,6 +972,7 @@ public async Task InsertMysqlDatetimeTypes(InsertMysqlDatetimeTypesArgs args)
969972
queryParams.Add("c_datetime", args.CDatetime);
970973
queryParams.Add("c_timestamp", args.CTimestamp);
971974
queryParams.Add("c_time", args.CTime);
975+
queryParams.Add("c_timestamp_noda_instant_override", args.CTimestampNodaInstantOverride is null ? null : (DateTime? )DateTime.SpecifyKind(args.CTimestampNodaInstantOverride.Value.ToDateTimeUtc(), DateTimeKind.Unspecified));
972976
if (this.Transaction == null)
973977
{
974978
using (var connection = new MySqlConnection(ConnectionString))
@@ -1036,14 +1040,15 @@ public async Task InsertMysqlDatetimeTypesBatch(List<InsertMysqlDatetimeTypesBat
10361040
}
10371041
}
10381042

1039-
private const string GetMysqlDatetimeTypesSql = "SELECT c_year, c_date, c_datetime, c_timestamp, c_time FROM mysql_datetime_types LIMIT 1";
1043+
private const string GetMysqlDatetimeTypesSql = "SELECT c_year, c_date, c_datetime, c_timestamp, c_time, c_timestamp_noda_instant_override FROM mysql_datetime_types LIMIT 1";
10401044
public class GetMysqlDatetimeTypesRow
10411045
{
10421046
public short? CYear { get; init; }
10431047
public DateTime? CDate { get; init; }
10441048
public DateTime? CDatetime { get; init; }
10451049
public DateTime? CTimestamp { get; init; }
10461050
public TimeSpan? CTime { get; init; }
1051+
public Instant? CTimestampNodaInstantOverride { get; init; }
10471052
};
10481053
public async Task<GetMysqlDatetimeTypesRow?> GetMysqlDatetimeTypes()
10491054
{

0 commit comments

Comments
 (0)