Skip to content

Commit bc55076

Browse files
bytefishsay25
andauthored
Add NodaTime Support to PostgreSQLCopyHelper (#68)
* Add NodaTime Support * Version Consolidation * Fix Project Type Guid * Add a section on enabling NodaTime support, which is required by Npgsql.NodaTime to Readme Co-authored-by: say25 <say25@cornell.edu>
1 parent 51b433f commit bc55076

15 files changed

Lines changed: 742 additions & 11 deletions

Directory.Build.props

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<IncludeSource>True</IncludeSource>
1616
<IncludeSymbols>True</IncludeSymbols>
1717
<VersionPrefix>2.0.0</VersionPrefix>
18+
<Version>2.7.0</Version>
1819
</PropertyGroup>
1920

2021
<!-- Language configuration -->
@@ -25,7 +26,7 @@
2526
<!-- Reference .NET Framework reference assemblies, allows building on environments without .NET Framework installed
2627
(e.g. Linux). Gets ignored on non-framework TFMs. -->
2728
<ItemGroup>
28-
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
29+
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" />
2930
</ItemGroup>
3031

31-
</Project>
32+
</Project>

Directory.Build.targets

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project>
2+
<ItemGroup>
3+
<PackageReference Update="Npgsql" Version="4.1.1" />
4+
<PackageReference Update="Npgsql.NodaTime" Version="4.1.1" />
5+
<!-- Microsoft -->
6+
<PackageReference Update="Microsoft.Bcl.AsyncInterfaces" Version="1.0.0" />
7+
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="1.0.0" />
8+
<PackageReference Update="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" />
9+
<!-- Tests -->
10+
<!-- Tests > Microsoft -->
11+
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.7.1" />
12+
<!-- Tests > NUnit -->
13+
<PackageReference Update="NUnit" Version="3.12.0" />
14+
<PackageReference Update="NUnit3TestAdapter" Version="3.17.0" />
15+
</ItemGroup>
16+
</Project>

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,32 @@ private async Task<ulong> WriteToDatabaseAsync(PostgreSQLCopyHelper<TestEntity>
127127
}
128128
```
129129

130+
## PostgreSQLCopyHelper.NodaTime: NodaTime Support ##
131+
132+
The [PostgreSQLCopyHelper.NodaTime](https://www.nuget.org/packages/PostgreSQLCopyHelper.NodaTime/) package extends PostgreSQLCopyHelper for [NodaTime](https://nodatime.org/) types.
133+
134+
To install PostgreSQLCopyHelper.NodaTime, run the following command in the Package Manager Console:
135+
136+
```
137+
PM> Install-Package PostgreSQLCopyHelper
138+
```
139+
140+
It uses the [Npgsql.NodaTime plugin](https://www.npgsql.org/doc/types/nodatime.html), which needs to be enabled by running:
141+
142+
```csharp
143+
using Npgsql;
144+
145+
// Place this at the beginning of your program to use NodaTime everywhere (recommended)
146+
NpgsqlConnection.GlobalTypeMapper.UseNodaTime();
147+
148+
// Or to temporarily use NodaTime on a single connection only:
149+
conn.TypeMapper.UseNodaTime();
150+
```
151+
152+
For more details see the Npgsql documentation:
153+
154+
* [https://www.npgsql.org/doc/types/nodatime.html](https://www.npgsql.org/doc/types/nodatime.html)
155+
130156
## Case-Sensitive Identifiers ##
131157

132158
By default the library does not apply quotes to identifiers, such as Table Names and Column Names. If you want PostgreSQL-conform quoting for identifiers,

azure-pipelines.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ resources:
2020

2121
variables:
2222
buildConfiguration: 'Release'
23-
DOTNET_SDK_VERSION: 3.0.x
23+
DOTNET_SDK_VERSION: 3.1.x
2424

2525
jobs:
2626

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// This file is used by Code Analysis to maintain SuppressMessage
2+
// attributes that are applied to this project.
3+
// Project-level suppressions either have no target or are given
4+
// a specific target and scoped to a namespace, type, member, etc.
5+
6+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "Test Library")]
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
2+
3+
using System.Collections.Generic;
4+
using NodaTime;
5+
using Npgsql;
6+
using NUnit.Framework;
7+
using PostgreSQLCopyHelper.Test;
8+
using PostgreSQLCopyHelper.Test.Extensions;
9+
10+
namespace PostgreSQLCopyHelper.NodaTime.Test
11+
{
12+
[TestFixture]
13+
public class NodaTimeExtensionsTest : TransactionalTestBase
14+
{
15+
public class AllNodaTypesEntity
16+
{
17+
public LocalTime LocalTime { get; set; }
18+
19+
public LocalDateTime LocalDateTime { get; set; }
20+
21+
public ZonedDateTime ZonedDateTime { get; set; }
22+
23+
public OffsetTime OffsetTime { get; set; }
24+
25+
public Instant Instant { get; set; }
26+
27+
public OffsetDateTime OffsetDateTime { get; set; }
28+
29+
public Period Period { get; set; }
30+
31+
public LocalDate LocalDate { get; set; }
32+
}
33+
34+
[Test]
35+
public void Test_Interval()
36+
{
37+
CreateTable("interval");
38+
39+
var begin = new LocalDateTime(2020, 1, 23, 0, 12);
40+
var end = new LocalDateTime(2020, 12, 8, 12, 44);
41+
42+
var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
43+
.MapInterval("col_noda", x => x.Period);
44+
45+
var entity = new AllNodaTypesEntity
46+
{
47+
Period = Period.Between(begin, end)
48+
};
49+
50+
var entities = new[]
51+
{
52+
entity
53+
};
54+
55+
subject.SaveAll(connection, entities);
56+
57+
// Check what's written to DB:
58+
var rows = GetAll();
59+
60+
var actual = (Period) rows[0][0];
61+
62+
Assert.AreEqual(entity.Period, actual);
63+
}
64+
65+
[Test]
66+
public void Test_LocalDate()
67+
{
68+
CreateTable("date");
69+
70+
var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
71+
.MapDate("col_noda", x => x.LocalDate);
72+
73+
var entity = new AllNodaTypesEntity
74+
{
75+
LocalDate = new LocalDate(2011, 1, 2)
76+
};
77+
78+
var entities = new[]
79+
{
80+
entity
81+
};
82+
83+
subject.SaveAll(connection, entities);
84+
85+
// Check what's written to DB:
86+
var rows = GetAll();
87+
88+
var actual = (LocalDate) rows[0][0];
89+
90+
Assert.AreEqual(entity.LocalDate, actual);
91+
}
92+
93+
[Test]
94+
public void Test_LocalDateTime()
95+
{
96+
CreateTable("timestamp");
97+
98+
var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
99+
.MapTimeStamp("col_noda", x => x.LocalDateTime);
100+
101+
var entity = new AllNodaTypesEntity
102+
{
103+
LocalDateTime = new LocalDateTime(2011, 1, 2, 21, 0, 0)
104+
};
105+
106+
var entities = new[]
107+
{
108+
entity
109+
};
110+
111+
subject.SaveAll(connection, entities);
112+
113+
// Check what's written to DB:
114+
var rows = GetAll();
115+
116+
var actual = (Instant) rows[0][0];
117+
118+
var localTime = entity.LocalDateTime;
119+
var zonedTime = localTime.InZoneStrictly(DateTimeZone.Utc);
120+
var expected = zonedTime.ToInstant();
121+
122+
Assert.AreEqual(expected, actual);
123+
}
124+
125+
[Test]
126+
public void Test_Instant()
127+
{
128+
CreateTable("timestamp");
129+
130+
var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
131+
.MapTimeStamp("col_noda", x => x.Instant);
132+
133+
var entity = new AllNodaTypesEntity
134+
{
135+
Instant = Instant.FromUtc(2011, 1, 2, 0, 0)
136+
};
137+
138+
var entities = new[]
139+
{
140+
entity
141+
};
142+
143+
subject.SaveAll(connection, entities);
144+
145+
// Check what's written to DB:
146+
var rows = GetAll();
147+
148+
var actual = (Instant) rows[0][0];
149+
150+
Assert.AreEqual(entity.Instant, actual);
151+
}
152+
153+
[Test]
154+
public void Test_OffsetTime()
155+
{
156+
CreateTable("timetz");
157+
158+
var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
159+
.MapTimeTz("col_noda", x => x.OffsetTime);
160+
161+
var entity = new AllNodaTypesEntity
162+
{
163+
OffsetTime = new OffsetTime(new LocalTime(12, 41), Offset.FromHours(2))
164+
};
165+
166+
var entities = new[]
167+
{
168+
entity
169+
};
170+
171+
subject.SaveAll(connection, entities);
172+
173+
// Check what's written to DB:
174+
var rows = GetAll();
175+
176+
var actual = (OffsetTime) rows[0][0];
177+
178+
Assert.AreEqual(entity.OffsetTime, actual);
179+
}
180+
181+
[Test]
182+
public void Test_OffsetDateTime()
183+
{
184+
CreateTable("timestamptz");
185+
186+
var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
187+
.MapTimeStampTz("col_noda", x => x.OffsetDateTime);
188+
189+
var entity = new AllNodaTypesEntity
190+
{
191+
OffsetDateTime = new OffsetDateTime(new LocalDateTime(2001, 11, 21, 0, 32), Offset.FromHours(2))
192+
};
193+
194+
var entities = new[]
195+
{
196+
entity
197+
};
198+
199+
subject.SaveAll(connection, entities);
200+
201+
// Check what's written to DB:
202+
var rows = GetAll();
203+
204+
var actual = (Instant) rows[0][0];
205+
206+
Assert.AreEqual(entity.OffsetDateTime.ToInstant(), actual);
207+
}
208+
209+
[Test]
210+
public void Test_LocalTime()
211+
{
212+
CreateTable("time");
213+
214+
var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
215+
.MapTime("col_noda", x => x.LocalTime);
216+
217+
var entity = new AllNodaTypesEntity
218+
{
219+
LocalTime = new LocalTime(2011, 1, 2)
220+
};
221+
222+
var entities = new[]
223+
{
224+
entity
225+
};
226+
227+
subject.SaveAll(connection, entities);
228+
229+
// Check what's written to DB:
230+
var rows = GetAll();
231+
232+
var actual = (LocalTime) rows[0][0];
233+
234+
Assert.AreEqual(entity.LocalTime, actual);
235+
}
236+
237+
[Test]
238+
public void Test_ZonedDateTime()
239+
{
240+
CreateTable("timestamptz");
241+
242+
var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
243+
.MapTimeStampTz("col_noda", x => x.ZonedDateTime);
244+
245+
var timezone = DateTimeZoneProviders.Tzdb.GetZoneOrNull("Africa/Kigali");
246+
var instant = Instant.FromUtc(2011, 1, 5, 22, 50, 0) + Duration.FromMilliseconds(193);
247+
248+
var entity = new AllNodaTypesEntity
249+
{
250+
ZonedDateTime = new ZonedDateTime(instant, timezone)
251+
};
252+
253+
var entities = new[]
254+
{
255+
entity
256+
};
257+
258+
subject.SaveAll(connection, entities);
259+
260+
// Check what's written to DB:
261+
var rows = GetAll();
262+
263+
// TODO: How does Postgres <-> NodaTime convert Timezones? There is a good test here, but
264+
// I couldn't see through it yet:
265+
//
266+
// https://github.com/npgsql/npgsql/blob/766658172f08abb0b87a6b7f01a7ea4b49952a29/test/Npgsql.PluginTests/NodaTimeTests.cs
267+
//
268+
var actual = (Instant) rows[0][0];
269+
270+
Assert.AreEqual(instant, actual);
271+
}
272+
273+
private int CreateTable(string postgresDbType)
274+
{
275+
var sqlStatement = $"CREATE TABLE sample.noda_time_test(col_noda {postgresDbType});";
276+
277+
var sqlCommand = new NpgsqlCommand(sqlStatement, connection);
278+
279+
return sqlCommand.ExecuteNonQuery();
280+
}
281+
282+
private IList<object[]> GetAll()
283+
{
284+
return connection.GetAll("sample", "noda_time_test");
285+
}
286+
}
287+
}

0 commit comments

Comments
 (0)