From 522b1a011c3c3e0ce7da2490b5149700eac6b752 Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Mon, 8 May 2023 09:54:57 +0000 Subject: [PATCH 1/3] Backport support for null datetime parameters for Npgsql 6+ (#3299) Fixes #3291 in 5.3.x --- .../Async/NHSpecificTest/GH3291/Fixture.cs | 79 +++++++++++++++++++ .../NHSpecificTest/GH3291/Fixture.cs | 67 ++++++++++++++++ .../NHSpecificTest/GH3291/Mappings.hbm.xml | 11 +++ .../NHSpecificTest/GH3291/Person.cs | 11 +++ .../TestDatabaseSetup.cs | 6 +- src/NHibernate/Driver/NpgsqlDriver.cs | 21 ++++- 6 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml create mode 100644 src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs 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..0d898115b80 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs @@ -0,0 +1,79 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using NHibernate.Criterion; +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 var _ = session.BeginTransaction(); + + DateTime? dateOfSearch = null; + + var result = await (( + from person in session.Query() + 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 var _ = 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()); + + 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..fa01d89b8f9 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using NHibernate.Criterion; +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 var _ = session.BeginTransaction(); + + DateTime? dateOfSearch = null; + + var result = ( + from person in session.Query() + 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 var _ = session.BeginTransaction(); + + DateTime? dateOfSearch = null; + + var result = + session.CreateQuery("from Person where :DateOfSearch is null OR DateOfBirth > :DateOfSearch") + .SetParameter("DateOfSearch", dateOfSearch, NHibernateUtil.DateTime) + .List(); + + 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 @@ + + + + + + + + + + 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..69c39821e1c 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,28 @@ 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.Value is DateTime) + { + // Let Npgsql 6 driver to decide parameter type + parameter.ResetDbType(); + } + } } + + base.AdjustCommand(command); } // Prior to v3, Npgsql was expecting DateTime for time. From 0d9d46b5b7be1061e5d562fc73cdc11c5be47e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Mon, 8 May 2023 12:44:56 +0200 Subject: [PATCH 2/3] Fix tests for 5.3.x language support --- .../Async/NHSpecificTest/GH3291/Fixture.cs | 75 ++++++++++--------- .../NHSpecificTest/GH3291/Fixture.cs | 67 +++++++++-------- 2 files changed, 74 insertions(+), 68 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs index 0d898115b80..3420e7e2199 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs @@ -10,7 +10,6 @@ using System; using System.Linq; -using NHibernate.Criterion; using NUnit.Framework; using NHibernate.Linq; @@ -22,58 +21,62 @@ public class FixtureAsync : BugTestCase { protected override void OnSetUp() { - using var session = OpenSession(); - using var transaction = session.BeginTransaction(); + 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 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); - var e2 = new Person { Name = "Sally", DateOfBirth = new DateTime(2018, 9, 30) }; - session.Save(e2); - - transaction.Commit(); + transaction.Commit(); + } } protected override void OnTearDown() { - using var session = OpenSession(); - using var transaction = session.BeginTransaction(); - - session.CreateQuery("delete from System.Object").ExecuteUpdate(); + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); - transaction.Commit(); + transaction.Commit(); + } } [Test] public async Task LinqAsync() { - using var session = OpenSession(); - using var _ = session.BeginTransaction(); - - DateTime? dateOfSearch = null; - - var result = await (( - from person in session.Query() - where dateOfSearch == null || person.DateOfBirth > dateOfSearch - select person).ToListAsync()); - - Assert.That(result, Has.Count.EqualTo(2)); + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + DateTime? dateOfSearch = null; + + var result = await (( + from person in session.Query() + 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 var _ = 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()); - - Assert.That(result, Has.Count.EqualTo(2)); + 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()); + + 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 index fa01d89b8f9..1cf8f418f67 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using NHibernate.Criterion; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.GH3291 @@ -10,58 +9,62 @@ public class Fixture : BugTestCase { protected override void OnSetUp() { - using var session = OpenSession(); - using var transaction = session.BeginTransaction(); + 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 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); - var e2 = new Person { Name = "Sally", DateOfBirth = new DateTime(2018, 9, 30) }; - session.Save(e2); - - transaction.Commit(); + transaction.Commit(); + } } protected override void OnTearDown() { - using var session = OpenSession(); - using var transaction = session.BeginTransaction(); - - session.CreateQuery("delete from System.Object").ExecuteUpdate(); + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); - transaction.Commit(); + transaction.Commit(); + } } [Test] public void Linq() { - using var session = OpenSession(); - using var _ = session.BeginTransaction(); + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + DateTime? dateOfSearch = null; - DateTime? dateOfSearch = null; + var result = ( + from person in session.Query() + where dateOfSearch == null || person.DateOfBirth > dateOfSearch + select person).ToList(); - var result = ( - from person in session.Query() - where dateOfSearch == null || person.DateOfBirth > dateOfSearch - select person).ToList(); - - Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result, Has.Count.EqualTo(2)); + } } [Test] public void Hql() { - using var session = OpenSession(); - using var _ = session.BeginTransaction(); - - DateTime? dateOfSearch = null; + 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(); + var result = + session.CreateQuery("from Person where :DateOfSearch is null OR DateOfBirth > :DateOfSearch") + .SetParameter("DateOfSearch", dateOfSearch, NHibernateUtil.DateTime) + .List(); - Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result, Has.Count.EqualTo(2)); + } } } } From 27a7a7be05426c07eaac0453972f17408fc927fa Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Tue, 9 May 2023 09:08:47 +0000 Subject: [PATCH 3/3] Backport Npgsql 6 parameter DbType handling for datetime (#3301) Fixes #3291 --- src/NHibernate/Driver/NpgsqlDriver.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/NHibernate/Driver/NpgsqlDriver.cs b/src/NHibernate/Driver/NpgsqlDriver.cs index 69c39821e1c..9a4a4b06771 100644 --- a/src/NHibernate/Driver/NpgsqlDriver.cs +++ b/src/NHibernate/Driver/NpgsqlDriver.cs @@ -88,10 +88,19 @@ public override void AdjustCommand(DbCommand command) for (var i = 0; i < command.Parameters.Count; i++) { var parameter = command.Parameters[i]; - if (parameter.Value is DateTime) + if (parameter.DbType == DbType.DateTime && + parameter.Value is DateTime dateTime && + dateTime.Kind != DateTimeKind.Utc) { - // Let Npgsql 6 driver to decide parameter type - parameter.ResetDbType(); + // 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; } } }