diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs new file mode 100644 index 00000000000..3420e7e2199 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs @@ -0,0 +1,82 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.GH3291 +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Person { Name = "Bob", DateOfBirth = new DateTime(2009, 12, 23) }; + session.Save(e1); + + var e2 = new Person { Name = "Sally", DateOfBirth = new DateTime(2018, 9, 30) }; + session.Save(e2); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public async Task LinqAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + DateTime? dateOfSearch = null; + + var result = await (( + from person in session.Query<Person>() + where dateOfSearch == null || person.DateOfBirth > dateOfSearch + select person).ToListAsync()); + + Assert.That(result, Has.Count.EqualTo(2)); + } + } + + [Test] + public async Task HqlAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + DateTime? dateOfSearch = null; + + var result = + await (session.CreateQuery("from Person where :DateOfSearch is null OR DateOfBirth > :DateOfSearch") + .SetParameter("DateOfSearch", dateOfSearch, NHibernateUtil.DateTime) + .ListAsync<Person>()); + + Assert.That(result, Has.Count.EqualTo(2)); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs new file mode 100644 index 00000000000..1cf8f418f67 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3291 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Person { Name = "Bob", DateOfBirth = new DateTime(2009, 12, 23) }; + session.Save(e1); + + var e2 = new Person { Name = "Sally", DateOfBirth = new DateTime(2018, 9, 30) }; + session.Save(e2); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public void Linq() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + DateTime? dateOfSearch = null; + + var result = ( + from person in session.Query<Person>() + where dateOfSearch == null || person.DateOfBirth > dateOfSearch + select person).ToList(); + + Assert.That(result, Has.Count.EqualTo(2)); + } + } + + [Test] + public void Hql() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + DateTime? dateOfSearch = null; + + var result = + session.CreateQuery("from Person where :DateOfSearch is null OR DateOfBirth > :DateOfSearch") + .SetParameter("DateOfSearch", dateOfSearch, NHibernateUtil.DateTime) + .List<Person>(); + + Assert.That(result, Has.Count.EqualTo(2)); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml new file mode 100644 index 00000000000..1088c98b593 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8" ?> +<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test" + namespace="NHibernate.Test.NHSpecificTest.GH3291"> + + <class name="Person"> + <id name="Id" generator="guid.comb"/> + <property name="Name"/> + <property name="DateOfBirth" /> + </class> + +</hibernate-mapping> diff --git a/src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs b/src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs new file mode 100644 index 00000000000..d80efc2e094 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH3291 +{ + class Person + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual DateTime? DateOfBirth { get; set; } + } +} diff --git a/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs b/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs index 5dd5fc8fe22..deea09fb3a5 100644 --- a/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs +++ b/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs @@ -182,11 +182,7 @@ private static void SetupNpgsql(Cfg.Configuration cfg) using (var cmd = conn.CreateCommand()) { - cmd.CommandText = - @"CREATE OR REPLACE FUNCTION uuid_generate_v4() - RETURNS uuid - AS '$libdir/uuid-ossp', 'uuid_generate_v4' - VOLATILE STRICT LANGUAGE C;"; + cmd.CommandText = "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"; cmd.ExecuteNonQuery(); } diff --git a/src/NHibernate/Driver/NpgsqlDriver.cs b/src/NHibernate/Driver/NpgsqlDriver.cs index c638e256221..9a4a4b06771 100644 --- a/src/NHibernate/Driver/NpgsqlDriver.cs +++ b/src/NHibernate/Driver/NpgsqlDriver.cs @@ -1,3 +1,4 @@ +using System; using System.Data; using System.Data.Common; using NHibernate.AdoNet; @@ -74,14 +75,37 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq // Since the .NET currency type has 4 decimal places, we use a decimal type in PostgreSQL instead of its native 2 decimal currency type. dbParam.DbType = DbType.Decimal; } - else if (DriverVersionMajor < 6 || sqlType.DbType != DbType.DateTime) + else { dbParam.DbType = sqlType.DbType; } - else + } + + public override void AdjustCommand(DbCommand command) + { + if (DriverVersionMajor >= 6) { - // Let Npgsql 6 driver to decide parameter type + for (var i = 0; i < command.Parameters.Count; i++) + { + var parameter = command.Parameters[i]; + if (parameter.DbType == DbType.DateTime && + parameter.Value is DateTime dateTime && + dateTime.Kind != DateTimeKind.Utc) + { + // There are breaking changes in Npgsql 6 as following: + // UTC DateTime is now strictly mapped to timestamptz, + // while Local/Unspecified DateTime is now strictly mapped to timestamp. + // + // DbType.DateTime now maps to timestamptz, not timestamp. + // DbType.DateTime2 continues to map to timestamp + // + // See more details here: https://www.npgsql.org/doc/release-notes/6.0.html#detailed-notes + parameter.DbType = DbType.DateTime2; + } + } } + + base.AdjustCommand(command); } // Prior to v3, Npgsql was expecting DateTime for time.