Skip to content

Commit f6c2f12

Browse files
feat: add option to override DateTime to NodaTime.Instant in Postgres
1 parent 98dfbcc commit f6c2f12

13 files changed

Lines changed: 186 additions & 96 deletions

File tree

Drivers/DbDriver.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ private class NodaInstantTypeHandler : SqlMapper.TypeHandler<Instant>
7575
public override Instant Parse(object value)
7676
{
7777
if (value is DateTime dt)
78+
{
79+
if (dt.Kind != DateTimeKind.Utc)
80+
dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
7881
return dt.ToInstant();
82+
}
7983
throw new DataException($"Cannot convert {value?.GetType()} to Instant");
8084
}
8185

Drivers/NpgsqlDriver.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,21 @@ public NpgsqlDriver(
139139
),
140140
["Instant"] = new(
141141
[],
142-
readerFn: (ordinal, _) => $"{Variable.Reader.AsVarName()}.GetDateTime({ordinal}).ToInstant()",
142+
readerFn: (ordinal, _) => $$"""
143+
(new Func<NpgsqlDataReader, int, Instant>((r, o) =>
144+
{
145+
var dt = {{Variable.Reader.AsVarName()}}.GetDateTime(o);
146+
if (dt.Kind != DateTimeKind.Utc)
147+
dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
148+
return dt.ToInstant();
149+
}))({{Variable.Reader.AsVarName()}}, {{ordinal}})
150+
""",
143151
writerFn: (el, _, notNull, isDapper, isLegacy) =>
144152
{
145153
if (notNull)
146-
return $"{el}.ToDateTimeUtc().ToLocalTime()";
154+
return $"DateTime.SpecifyKind({el}.ToDateTimeUtc(), DateTimeKind.Unspecified)";
147155
var nullValue = isDapper ? "null" : "(object)DBNull.Value";
148-
return $"{el}?.ToDateTimeUtc().ToLocalTime() ?? {nullValue}";
156+
return $"{el} is null ? {nullValue} : (DateTime?) DateTime.SpecifyKind({el}.Value.ToDateTimeUtc(), DateTimeKind.Unspecified)";
149157
},
150158
usingDirectives: ["System", "NodaTime", "NodaTime.Extensions"],
151159
sqlMapper: "SqlMapper.AddTypeHandler(typeof(Instant), new NodaInstantTypeHandler());",

end2end/EndToEndScaffold/Templates/PostgresTests.cs

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ void AssertSingularEquals(QuerySql.GetPostgresDateTimeTypesCntRow x, QuerySql.Ge
493493
{
494494
Impl = $$"""
495495
[Test]
496-
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}", "$.\"name\"")]
496+
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\" :1983}", "$.\"name\"")]
497497
[TestCase(null, null)]
498498
public async Task TestPostgresJsonDataTypes(
499499
string cJson,
@@ -524,14 +524,18 @@ await QuerySql.InsertPostgresSpecialTypes(new QuerySql.InsertPostgresSpecialType
524524
525525
void AssertSingularEquals(QuerySql.GetPostgresSpecialTypesRow x, QuerySql.GetPostgresSpecialTypesRow y)
526526
{
527-
Assert.That(x.CJson.HasValue, Is.EqualTo(y.CJson.HasValue));
528-
if (x.CJson.HasValue)
529-
Assert.That(x.CJson.Value.GetRawText(), Is.EqualTo(y.CJson.Value.GetRawText()));
530-
Assert.That(x.CJsonb.HasValue, Is.EqualTo(y.CJsonb.HasValue));
531-
if (x.CJsonb.HasValue)
532-
Assert.That(x.CJsonb.Value.GetRawText(), Is.EqualTo(y.CJsonb.Value.GetRawText()));
533-
Assert.That(x.CJsonStringOverride, Is.EqualTo(y.CJsonStringOverride));
534-
Assert.That(x.CJsonpath, Is.EqualTo(y.CJsonpath));
527+
AssertJsonElementEquals(y.CJson, x.CJson);
528+
AssertJsonElementEquals(y.CJsonb, x.CJsonb);
529+
Assert.That(y.CJsonStringOverride, Is.EqualTo(x.CJsonStringOverride));
530+
Assert.That(y.CJsonpath, Is.EqualTo(x.CJsonpath));
531+
}
532+
533+
void AssertJsonElementEquals(JsonElement? x, JsonElement? y)
534+
{
535+
var options = new JsonSerializerOptions { WriteIndented = false };
536+
Assert.That(y.HasValue, Is.EqualTo(x.HasValue));
537+
if (y.HasValue)
538+
Assert.That(JsonSerializer.Serialize(y.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.Value, options)));
535539
}
536540
}
537541
"""
@@ -568,17 +572,17 @@ public async Task TestPostgresJsonCopyFrom(int batchSize, string cJson)
568572
569573
void AssertSingularEquals(QuerySql.GetPostgresSpecialTypesCntRow x, QuerySql.GetPostgresSpecialTypesCntRow y)
570574
{
571-
var options = new JsonSerializerOptions
572-
{
573-
WriteIndented = false
574-
};
575575
Assert.That(y.Cnt, Is.EqualTo(x.Cnt));
576-
Assert.That(y.CJson.HasValue, Is.EqualTo(x.CJson.HasValue));
577-
if (y.CJson.HasValue)
578-
Assert.That(JsonSerializer.Serialize(y.CJson.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.CJson.Value, options)));
579-
Assert.That(y.CJsonb.HasValue, Is.EqualTo(x.CJsonb.HasValue));
580-
if (y.CJsonb.HasValue)
581-
Assert.That(JsonSerializer.Serialize(y.CJsonb.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.CJsonb.Value, options)));
576+
AssertJsonElementEquals(y.CJson, x.CJson);
577+
AssertJsonElementEquals(y.CJsonb, x.CJsonb);
578+
}
579+
580+
void AssertJsonElementEquals(JsonElement? x, JsonElement? y)
581+
{
582+
var options = new JsonSerializerOptions { WriteIndented = false };
583+
Assert.That(y.HasValue, Is.EqualTo(x.HasValue));
584+
if (y.HasValue)
585+
Assert.That(JsonSerializer.Serialize(y.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.Value, options)));
582586
}
583587
}
584588
"""

end2end/EndToEndTests/NpgsqlDapperTester.generated.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ void AssertSingularEquals(QuerySql.GetPostgresGeoTypesRow x, QuerySql.GetPostgre
682682
}
683683

684684
[Test]
685-
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}", "$.\"name\"")]
685+
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\" :1983}", "$.\"name\"")]
686686
[TestCase(null, null)]
687687
public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
688688
{
@@ -701,14 +701,21 @@ public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
701701
AssertSingularEquals(expected, actual);
702702
void AssertSingularEquals(QuerySql.GetPostgresSpecialTypesRow x, QuerySql.GetPostgresSpecialTypesRow y)
703703
{
704-
Assert.That(x.CJson.HasValue, Is.EqualTo(y.CJson.HasValue));
705-
if (x.CJson.HasValue)
706-
Assert.That(x.CJson.Value.GetRawText(), Is.EqualTo(y.CJson.Value.GetRawText()));
707-
Assert.That(x.CJsonb.HasValue, Is.EqualTo(y.CJsonb.HasValue));
708-
if (x.CJsonb.HasValue)
709-
Assert.That(x.CJsonb.Value.GetRawText(), Is.EqualTo(y.CJsonb.Value.GetRawText()));
710-
Assert.That(x.CJsonStringOverride, Is.EqualTo(y.CJsonStringOverride));
711-
Assert.That(x.CJsonpath, Is.EqualTo(y.CJsonpath));
704+
AssertJsonElementEquals(y.CJson, x.CJson);
705+
AssertJsonElementEquals(y.CJsonb, x.CJsonb);
706+
Assert.That(y.CJsonStringOverride, Is.EqualTo(x.CJsonStringOverride));
707+
Assert.That(y.CJsonpath, Is.EqualTo(x.CJsonpath));
708+
}
709+
710+
void AssertJsonElementEquals(JsonElement? x, JsonElement? y)
711+
{
712+
var options = new JsonSerializerOptions
713+
{
714+
WriteIndented = false
715+
};
716+
Assert.That(y.HasValue, Is.EqualTo(x.HasValue));
717+
if (y.HasValue)
718+
Assert.That(JsonSerializer.Serialize(y.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.Value, options)));
712719
}
713720
}
714721

@@ -941,18 +948,21 @@ public async Task TestPostgresJsonCopyFrom(int batchSize, string cJson)
941948
var actual = await QuerySql.GetPostgresSpecialTypesCnt();
942949
AssertSingularEquals(expected, actual);
943950
void AssertSingularEquals(QuerySql.GetPostgresSpecialTypesCntRow x, QuerySql.GetPostgresSpecialTypesCntRow y)
951+
{
952+
Assert.That(y.Cnt, Is.EqualTo(x.Cnt));
953+
AssertJsonElementEquals(y.CJson, x.CJson);
954+
AssertJsonElementEquals(y.CJsonb, x.CJsonb);
955+
}
956+
957+
void AssertJsonElementEquals(JsonElement? x, JsonElement? y)
944958
{
945959
var options = new JsonSerializerOptions
946960
{
947961
WriteIndented = false
948962
};
949-
Assert.That(y.Cnt, Is.EqualTo(x.Cnt));
950-
Assert.That(y.CJson.HasValue, Is.EqualTo(x.CJson.HasValue));
951-
if (y.CJson.HasValue)
952-
Assert.That(JsonSerializer.Serialize(y.CJson.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.CJson.Value, options)));
953-
Assert.That(y.CJsonb.HasValue, Is.EqualTo(x.CJsonb.HasValue));
954-
if (y.CJsonb.HasValue)
955-
Assert.That(JsonSerializer.Serialize(y.CJsonb.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.CJsonb.Value, options)));
963+
Assert.That(y.HasValue, Is.EqualTo(x.HasValue));
964+
if (y.HasValue)
965+
Assert.That(JsonSerializer.Serialize(y.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.Value, options)));
956966
}
957967
}
958968

end2end/EndToEndTests/NpgsqlTester.generated.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ void AssertSingularEquals(QuerySql.GetPostgresGeoTypesRow x, QuerySql.GetPostgre
682682
}
683683

684684
[Test]
685-
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}", "$.\"name\"")]
685+
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\" :1983}", "$.\"name\"")]
686686
[TestCase(null, null)]
687687
public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
688688
{
@@ -701,14 +701,21 @@ public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
701701
AssertSingularEquals(expected, actual.Value);
702702
void AssertSingularEquals(QuerySql.GetPostgresSpecialTypesRow x, QuerySql.GetPostgresSpecialTypesRow y)
703703
{
704-
Assert.That(x.CJson.HasValue, Is.EqualTo(y.CJson.HasValue));
705-
if (x.CJson.HasValue)
706-
Assert.That(x.CJson.Value.GetRawText(), Is.EqualTo(y.CJson.Value.GetRawText()));
707-
Assert.That(x.CJsonb.HasValue, Is.EqualTo(y.CJsonb.HasValue));
708-
if (x.CJsonb.HasValue)
709-
Assert.That(x.CJsonb.Value.GetRawText(), Is.EqualTo(y.CJsonb.Value.GetRawText()));
710-
Assert.That(x.CJsonStringOverride, Is.EqualTo(y.CJsonStringOverride));
711-
Assert.That(x.CJsonpath, Is.EqualTo(y.CJsonpath));
704+
AssertJsonElementEquals(y.CJson, x.CJson);
705+
AssertJsonElementEquals(y.CJsonb, x.CJsonb);
706+
Assert.That(y.CJsonStringOverride, Is.EqualTo(x.CJsonStringOverride));
707+
Assert.That(y.CJsonpath, Is.EqualTo(x.CJsonpath));
708+
}
709+
710+
void AssertJsonElementEquals(JsonElement? x, JsonElement? y)
711+
{
712+
var options = new JsonSerializerOptions
713+
{
714+
WriteIndented = false
715+
};
716+
Assert.That(y.HasValue, Is.EqualTo(x.HasValue));
717+
if (y.HasValue)
718+
Assert.That(JsonSerializer.Serialize(y.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.Value, options)));
712719
}
713720
}
714721

@@ -941,18 +948,21 @@ public async Task TestPostgresJsonCopyFrom(int batchSize, string cJson)
941948
var actual = await QuerySql.GetPostgresSpecialTypesCnt();
942949
AssertSingularEquals(expected, actual.Value);
943950
void AssertSingularEquals(QuerySql.GetPostgresSpecialTypesCntRow x, QuerySql.GetPostgresSpecialTypesCntRow y)
951+
{
952+
Assert.That(y.Cnt, Is.EqualTo(x.Cnt));
953+
AssertJsonElementEquals(y.CJson, x.CJson);
954+
AssertJsonElementEquals(y.CJsonb, x.CJsonb);
955+
}
956+
957+
void AssertJsonElementEquals(JsonElement? x, JsonElement? y)
944958
{
945959
var options = new JsonSerializerOptions
946960
{
947961
WriteIndented = false
948962
};
949-
Assert.That(y.Cnt, Is.EqualTo(x.Cnt));
950-
Assert.That(y.CJson.HasValue, Is.EqualTo(x.CJson.HasValue));
951-
if (y.CJson.HasValue)
952-
Assert.That(JsonSerializer.Serialize(y.CJson.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.CJson.Value, options)));
953-
Assert.That(y.CJsonb.HasValue, Is.EqualTo(x.CJsonb.HasValue));
954-
if (y.CJsonb.HasValue)
955-
Assert.That(JsonSerializer.Serialize(y.CJsonb.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.CJsonb.Value, options)));
963+
Assert.That(y.HasValue, Is.EqualTo(x.HasValue));
964+
if (y.HasValue)
965+
Assert.That(JsonSerializer.Serialize(y.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.Value, options)));
956966
}
957967
}
958968

end2end/EndToEndTestsLegacy/NpgsqlDapperTester.generated.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ void AssertSingularEquals(QuerySql.GetPostgresGeoTypesRow x, QuerySql.GetPostgre
682682
}
683683

684684
[Test]
685-
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}", "$.\"name\"")]
685+
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\" :1983}", "$.\"name\"")]
686686
[TestCase(null, null)]
687687
public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
688688
{
@@ -701,14 +701,21 @@ public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
701701
AssertSingularEquals(expected, actual);
702702
void AssertSingularEquals(QuerySql.GetPostgresSpecialTypesRow x, QuerySql.GetPostgresSpecialTypesRow y)
703703
{
704-
Assert.That(x.CJson.HasValue, Is.EqualTo(y.CJson.HasValue));
705-
if (x.CJson.HasValue)
706-
Assert.That(x.CJson.Value.GetRawText(), Is.EqualTo(y.CJson.Value.GetRawText()));
707-
Assert.That(x.CJsonb.HasValue, Is.EqualTo(y.CJsonb.HasValue));
708-
if (x.CJsonb.HasValue)
709-
Assert.That(x.CJsonb.Value.GetRawText(), Is.EqualTo(y.CJsonb.Value.GetRawText()));
710-
Assert.That(x.CJsonStringOverride, Is.EqualTo(y.CJsonStringOverride));
711-
Assert.That(x.CJsonpath, Is.EqualTo(y.CJsonpath));
704+
AssertJsonElementEquals(y.CJson, x.CJson);
705+
AssertJsonElementEquals(y.CJsonb, x.CJsonb);
706+
Assert.That(y.CJsonStringOverride, Is.EqualTo(x.CJsonStringOverride));
707+
Assert.That(y.CJsonpath, Is.EqualTo(x.CJsonpath));
708+
}
709+
710+
void AssertJsonElementEquals(JsonElement? x, JsonElement? y)
711+
{
712+
var options = new JsonSerializerOptions
713+
{
714+
WriteIndented = false
715+
};
716+
Assert.That(y.HasValue, Is.EqualTo(x.HasValue));
717+
if (y.HasValue)
718+
Assert.That(JsonSerializer.Serialize(y.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.Value, options)));
712719
}
713720
}
714721

@@ -941,18 +948,21 @@ public async Task TestPostgresJsonCopyFrom(int batchSize, string cJson)
941948
var actual = await QuerySql.GetPostgresSpecialTypesCnt();
942949
AssertSingularEquals(expected, actual);
943950
void AssertSingularEquals(QuerySql.GetPostgresSpecialTypesCntRow x, QuerySql.GetPostgresSpecialTypesCntRow y)
951+
{
952+
Assert.That(y.Cnt, Is.EqualTo(x.Cnt));
953+
AssertJsonElementEquals(y.CJson, x.CJson);
954+
AssertJsonElementEquals(y.CJsonb, x.CJsonb);
955+
}
956+
957+
void AssertJsonElementEquals(JsonElement? x, JsonElement? y)
944958
{
945959
var options = new JsonSerializerOptions
946960
{
947961
WriteIndented = false
948962
};
949-
Assert.That(y.Cnt, Is.EqualTo(x.Cnt));
950-
Assert.That(y.CJson.HasValue, Is.EqualTo(x.CJson.HasValue));
951-
if (y.CJson.HasValue)
952-
Assert.That(JsonSerializer.Serialize(y.CJson.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.CJson.Value, options)));
953-
Assert.That(y.CJsonb.HasValue, Is.EqualTo(x.CJsonb.HasValue));
954-
if (y.CJsonb.HasValue)
955-
Assert.That(JsonSerializer.Serialize(y.CJsonb.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.CJsonb.Value, options)));
963+
Assert.That(y.HasValue, Is.EqualTo(x.HasValue));
964+
if (y.HasValue)
965+
Assert.That(JsonSerializer.Serialize(y.Value, options), Is.EqualTo(JsonSerializer.Serialize(x.Value, options)));
956966
}
957967
}
958968

0 commit comments

Comments
 (0)