Skip to content

Commit

Permalink
Add NodaTime Support to PostgreSQLCopyHelper (#68)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
bytefish and say25 committed Oct 27, 2020
1 parent 51b433f commit bc55076
Show file tree
Hide file tree
Showing 15 changed files with 742 additions and 11 deletions.
5 changes: 3 additions & 2 deletions Directory.Build.props
Expand Up @@ -15,6 +15,7 @@
<IncludeSource>True</IncludeSource>
<IncludeSymbols>True</IncludeSymbols>
<VersionPrefix>2.0.0</VersionPrefix>
<Version>2.7.0</Version>
</PropertyGroup>

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

</Project>
</Project>
16 changes: 16 additions & 0 deletions Directory.Build.targets
@@ -0,0 +1,16 @@
<Project>
<ItemGroup>
<PackageReference Update="Npgsql" Version="4.1.1" />
<PackageReference Update="Npgsql.NodaTime" Version="4.1.1" />
<!-- Microsoft -->
<PackageReference Update="Microsoft.Bcl.AsyncInterfaces" Version="1.0.0" />
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="1.0.0" />
<PackageReference Update="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" />
<!-- Tests -->
<!-- Tests > Microsoft -->
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<!-- Tests > NUnit -->
<PackageReference Update="NUnit" Version="3.12.0" />
<PackageReference Update="NUnit3TestAdapter" Version="3.17.0" />
</ItemGroup>
</Project>
26 changes: 26 additions & 0 deletions README.md
Expand Up @@ -127,6 +127,32 @@ private async Task<ulong> WriteToDatabaseAsync(PostgreSQLCopyHelper<TestEntity>
}
```

## PostgreSQLCopyHelper.NodaTime: NodaTime Support ##

The [PostgreSQLCopyHelper.NodaTime](https://www.nuget.org/packages/PostgreSQLCopyHelper.NodaTime/) package extends PostgreSQLCopyHelper for [NodaTime](https://nodatime.org/) types.

To install PostgreSQLCopyHelper.NodaTime, run the following command in the Package Manager Console:

```
PM> Install-Package PostgreSQLCopyHelper
```

It uses the [Npgsql.NodaTime plugin](https://www.npgsql.org/doc/types/nodatime.html), which needs to be enabled by running:

```csharp
using Npgsql;

// Place this at the beginning of your program to use NodaTime everywhere (recommended)
NpgsqlConnection.GlobalTypeMapper.UseNodaTime();

// Or to temporarily use NodaTime on a single connection only:
conn.TypeMapper.UseNodaTime();
```

For more details see the Npgsql documentation:

* [https://www.npgsql.org/doc/types/nodatime.html](https://www.npgsql.org/doc/types/nodatime.html)

## Case-Sensitive Identifiers ##

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,
Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Expand Up @@ -20,7 +20,7 @@ resources:

variables:
buildConfiguration: 'Release'
DOTNET_SDK_VERSION: 3.0.x
DOTNET_SDK_VERSION: 3.1.x

jobs:

Expand Down
6 changes: 6 additions & 0 deletions src/PostgreSQLCopyHelper.NodaTime.Test/GlobalSuppressions.cs
@@ -0,0 +1,6 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "Test Library")]
287 changes: 287 additions & 0 deletions src/PostgreSQLCopyHelper.NodaTime.Test/NodaTimeExtensionsTest.cs
@@ -0,0 +1,287 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using NodaTime;
using Npgsql;
using NUnit.Framework;
using PostgreSQLCopyHelper.Test;
using PostgreSQLCopyHelper.Test.Extensions;

namespace PostgreSQLCopyHelper.NodaTime.Test
{
[TestFixture]
public class NodaTimeExtensionsTest : TransactionalTestBase
{
public class AllNodaTypesEntity
{
public LocalTime LocalTime { get; set; }

public LocalDateTime LocalDateTime { get; set; }

public ZonedDateTime ZonedDateTime { get; set; }

public OffsetTime OffsetTime { get; set; }

public Instant Instant { get; set; }

public OffsetDateTime OffsetDateTime { get; set; }

public Period Period { get; set; }

public LocalDate LocalDate { get; set; }
}

[Test]
public void Test_Interval()
{
CreateTable("interval");

var begin = new LocalDateTime(2020, 1, 23, 0, 12);
var end = new LocalDateTime(2020, 12, 8, 12, 44);

var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
.MapInterval("col_noda", x => x.Period);

var entity = new AllNodaTypesEntity
{
Period = Period.Between(begin, end)
};

var entities = new[]
{
entity
};

subject.SaveAll(connection, entities);

// Check what's written to DB:
var rows = GetAll();

var actual = (Period) rows[0][0];

Assert.AreEqual(entity.Period, actual);
}

[Test]
public void Test_LocalDate()
{
CreateTable("date");

var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
.MapDate("col_noda", x => x.LocalDate);

var entity = new AllNodaTypesEntity
{
LocalDate = new LocalDate(2011, 1, 2)
};

var entities = new[]
{
entity
};

subject.SaveAll(connection, entities);

// Check what's written to DB:
var rows = GetAll();

var actual = (LocalDate) rows[0][0];

Assert.AreEqual(entity.LocalDate, actual);
}

[Test]
public void Test_LocalDateTime()
{
CreateTable("timestamp");

var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
.MapTimeStamp("col_noda", x => x.LocalDateTime);

var entity = new AllNodaTypesEntity
{
LocalDateTime = new LocalDateTime(2011, 1, 2, 21, 0, 0)
};

var entities = new[]
{
entity
};

subject.SaveAll(connection, entities);

// Check what's written to DB:
var rows = GetAll();

var actual = (Instant) rows[0][0];

var localTime = entity.LocalDateTime;
var zonedTime = localTime.InZoneStrictly(DateTimeZone.Utc);
var expected = zonedTime.ToInstant();

Assert.AreEqual(expected, actual);
}

[Test]
public void Test_Instant()
{
CreateTable("timestamp");

var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
.MapTimeStamp("col_noda", x => x.Instant);

var entity = new AllNodaTypesEntity
{
Instant = Instant.FromUtc(2011, 1, 2, 0, 0)
};

var entities = new[]
{
entity
};

subject.SaveAll(connection, entities);

// Check what's written to DB:
var rows = GetAll();

var actual = (Instant) rows[0][0];

Assert.AreEqual(entity.Instant, actual);
}

[Test]
public void Test_OffsetTime()
{
CreateTable("timetz");

var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
.MapTimeTz("col_noda", x => x.OffsetTime);

var entity = new AllNodaTypesEntity
{
OffsetTime = new OffsetTime(new LocalTime(12, 41), Offset.FromHours(2))
};

var entities = new[]
{
entity
};

subject.SaveAll(connection, entities);

// Check what's written to DB:
var rows = GetAll();

var actual = (OffsetTime) rows[0][0];

Assert.AreEqual(entity.OffsetTime, actual);
}

[Test]
public void Test_OffsetDateTime()
{
CreateTable("timestamptz");

var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
.MapTimeStampTz("col_noda", x => x.OffsetDateTime);

var entity = new AllNodaTypesEntity
{
OffsetDateTime = new OffsetDateTime(new LocalDateTime(2001, 11, 21, 0, 32), Offset.FromHours(2))
};

var entities = new[]
{
entity
};

subject.SaveAll(connection, entities);

// Check what's written to DB:
var rows = GetAll();

var actual = (Instant) rows[0][0];

Assert.AreEqual(entity.OffsetDateTime.ToInstant(), actual);
}

[Test]
public void Test_LocalTime()
{
CreateTable("time");

var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
.MapTime("col_noda", x => x.LocalTime);

var entity = new AllNodaTypesEntity
{
LocalTime = new LocalTime(2011, 1, 2)
};

var entities = new[]
{
entity
};

subject.SaveAll(connection, entities);

// Check what's written to DB:
var rows = GetAll();

var actual = (LocalTime) rows[0][0];

Assert.AreEqual(entity.LocalTime, actual);
}

[Test]
public void Test_ZonedDateTime()
{
CreateTable("timestamptz");

var subject = new PostgreSQLCopyHelper<AllNodaTypesEntity>("sample", "noda_time_test")
.MapTimeStampTz("col_noda", x => x.ZonedDateTime);

var timezone = DateTimeZoneProviders.Tzdb.GetZoneOrNull("Africa/Kigali");
var instant = Instant.FromUtc(2011, 1, 5, 22, 50, 0) + Duration.FromMilliseconds(193);

var entity = new AllNodaTypesEntity
{
ZonedDateTime = new ZonedDateTime(instant, timezone)
};

var entities = new[]
{
entity
};

subject.SaveAll(connection, entities);

// Check what's written to DB:
var rows = GetAll();

// TODO: How does Postgres <-> NodaTime convert Timezones? There is a good test here, but
// I couldn't see through it yet:
//
// https://github.com/npgsql/npgsql/blob/766658172f08abb0b87a6b7f01a7ea4b49952a29/test/Npgsql.PluginTests/NodaTimeTests.cs
//
var actual = (Instant) rows[0][0];

Assert.AreEqual(instant, actual);
}

private int CreateTable(string postgresDbType)
{
var sqlStatement = $"CREATE TABLE sample.noda_time_test(col_noda {postgresDbType});";

var sqlCommand = new NpgsqlCommand(sqlStatement, connection);

return sqlCommand.ExecuteNonQuery();
}

private IList<object[]> GetAll()
{
return connection.GetAll("sample", "noda_time_test");
}
}
}

0 comments on commit bc55076

Please sign in to comment.