diff --git a/src/NHibernate.Test/NHSpecificTest/GH2529/DateTimeAddYearsMethodHqlGenerator.cs b/src/NHibernate.Test/NHSpecificTest/GH2529/DateTimeAddYearsMethodHqlGenerator.cs new file mode 100644 index 00000000000..0b2de6f1785 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2529/DateTimeAddYearsMethodHqlGenerator.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Hql.Ast; +using NHibernate.Linq.Functions; +using NHibernate.Linq.Visitors; +using NHibernate.Util; + +namespace NHibernate.Test.NHSpecificTest.GH2529 +{ + public class DateTimeAddYearsMethodHqlGenerator : BaseHqlGeneratorForMethod + { + public DateTimeAddYearsMethodHqlGenerator() + { + SupportedMethods = new[] { + ReflectHelper.GetMethodDefinition((DateTime x) => x.AddYears(0)) + }; + } + + public override HqlTreeNode BuildHql( + MethodInfo method, + Expression targetObject, + ReadOnlyCollection arguments, + HqlTreeBuilder treeBuilder, + IHqlExpressionVisitor visitor + ) + { + return treeBuilder.MethodCall( + nameof(DateTime.AddYears), + visitor.Visit(targetObject) + .AsExpression(), + visitor.Visit(arguments[0]) + .AsExpression() + ); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2529/DateTimeDayOfYearPropertyHqlGenerator.cs b/src/NHibernate.Test/NHSpecificTest/GH2529/DateTimeDayOfYearPropertyHqlGenerator.cs new file mode 100644 index 00000000000..9c001b749af --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2529/DateTimeDayOfYearPropertyHqlGenerator.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using NHibernate.Hql.Ast; +using NHibernate.Linq.Functions; +using NHibernate.Linq.Visitors; +using NHibernate.Util; + +namespace NHibernate.Test.NHSpecificTest.GH2529 +{ + public class DateTimeDayOfYearPropertyHqlGenerator : BaseHqlGeneratorForProperty + { + public DateTimeDayOfYearPropertyHqlGenerator() + { + SupportedProperties = new[] + { + ReflectHelper.GetProperty((DateTime x) => x.DayOfYear) + }; + } + + public override HqlTreeNode BuildHql( + MemberInfo member, + Expression expression, + HqlTreeBuilder treeBuilder, + IHqlExpressionVisitor visitor + ) + { + return treeBuilder.MethodCall( + nameof(DateTime.DayOfYear), + visitor.Visit(expression) + .AsExpression() + ); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2529/EnhancedLinqToHqlGeneratorsRegistry.cs b/src/NHibernate.Test/NHSpecificTest/GH2529/EnhancedLinqToHqlGeneratorsRegistry.cs new file mode 100644 index 00000000000..5e814249059 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2529/EnhancedLinqToHqlGeneratorsRegistry.cs @@ -0,0 +1,13 @@ +using NHibernate.Linq.Functions; + +namespace NHibernate.Test.NHSpecificTest.GH2529 +{ + public class EnhancedLinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry + { + public EnhancedLinqToHqlGeneratorsRegistry() + { + this.Merge(new DateTimeDayOfYearPropertyHqlGenerator()); + this.Merge(new DateTimeAddYearsMethodHqlGenerator()); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2529/EnhancedMsSql2008Dialect.cs b/src/NHibernate.Test/NHSpecificTest/GH2529/EnhancedMsSql2008Dialect.cs new file mode 100644 index 00000000000..cc4288a1bcd --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2529/EnhancedMsSql2008Dialect.cs @@ -0,0 +1,30 @@ +using System; +using NHibernate.Dialect; +using NHibernate.Dialect.Function; + +namespace NHibernate.Test.NHSpecificTest.GH2529 +{ + public class EnhancedMsSql2008Dialect : MsSql2008Dialect + { + protected override void RegisterFunctions() + { + base.RegisterFunctions(); + + RegisterFunction( + nameof(DateTime.DayOfYear), + new SQLFunctionTemplate( + NHibernateUtil.Int32, + "datepart(dy, ?1)" + ) + ); + + RegisterFunction( + nameof(DateTime.AddYears), + new SQLFunctionTemplate( + NHibernateUtil.DateTime, + "dateadd(year,?2,?1)" + ) + ); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2529/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH2529/FixtureByCode.cs new file mode 100644 index 00000000000..1ad272e467f --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2529/FixtureByCode.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH2529 +{ + /// + /// Fixture using 'by code' mappings + /// + /// + /// This fixture is identical to except the mapping is performed + /// by code in the GetMappings method, and does not require the Mappings.hbm.xml file. Use this approach + /// if you prefer. + /// + [TestFixture] + public class ByCodeFixture : TestCaseMappingByCode + { + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + + configuration.LinqToHqlGeneratorsRegistry(); + configuration.SetProperty(Cfg.Environment.Dialect, typeof(EnhancedMsSql2008Dialect).AssemblyQualifiedName); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect is EnhancedMsSql2008Dialect; + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Table("Users"); + rc.Id(x => x.Id, m => m.Generator(Generators.Identity)); + rc.Property(x => x.Birthday); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test, Explicit] + public void List_DealsWithYearTransitionProperly() + { + // arrange + DateTimeOffset today = new DateTimeOffset(2001, 12, 27, 08, 00, 0, TimeSpan.Zero); + + CreateUser(birthday: new DateTime(1991, 12, 31)); + CreateUser(birthday: new DateTime(1992, 01, 01)); + + // act + IReadOnlyCollection result = List( + today + ); + + // arrange + Assert.That(result.Count, Is.EqualTo(2)); + BirthdayListItem firstBirthday = result.First(); + BirthdayListItem secondBirthday = result.Skip(1).Single(); + Assert.That(firstBirthday.IsThisYearsBirthdayInThePastNHibernate, Is.EqualTo(firstBirthday.IsThisYearsBirthdayInThePastManual)); + Assert.That(secondBirthday.IsThisYearsBirthdayInThePastNHibernate, Is.EqualTo(secondBirthday.IsThisYearsBirthdayInThePastManual)); + } + + [Test, Explicit] + public void List_WorksForTomorrow() + { + // arrange + DateTimeOffset today = new DateTime(2000, 05, 11, 00, 00, 0, DateTimeKind.Utc); + CreateUser(birthday: today.AddDays(1).DateTime); + + // act + IReadOnlyCollection result = List( + today + ); + + // arrange + Assert.That(result.Count, Is.EqualTo(1)); + BirthdayListItem firstBirthday = result.First(); + Assert.That(firstBirthday.IsThisYearsBirthdayInThePastNHibernate, Is.EqualTo(firstBirthday.IsThisYearsBirthdayInThePastManual)); + } + + [Test] + public void List_Both() + { + // Running these methods individual passes the test + // Running them in sequence fails on List_WorksForTomorrow + List_DealsWithYearTransitionProperly(); + DeleteAllUsers(); + List_WorksForTomorrow(); + } + + private User CreateUser( + DateTime birthday + ) + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + var user = new User + { + Birthday = birthday, + }; + + session.Save(user); + transaction.Commit(); + + return user; + } + } + + private void DeleteAllUsers() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + string queryString = $"DELETE {typeof(User).FullName}"; + IQuery query = session.CreateQuery(queryString); + query.ExecuteUpdate(); + + transaction.Commit(); + } + } + + private IReadOnlyCollection List( + DateTimeOffset today + ) + { + using (ISession session = OpenSession()) + { + return session.Query() + .Where(x => x.Birthday != null) + .Select(x => new + { + BirthdayThisYear = x.Birthday.Value.AddYears(today.Year - x.Birthday.Value.Year), + BirthdayNextYear = x.Birthday.Value.AddYears((today.Year - x.Birthday.Value.Year) + 1), + IsThisYearsBirthdayInThePast = x.Birthday.Value.AddYears(today.Year - x.Birthday.Value.Year).DayOfYear < today.DayOfYear, + }) + .OrderBy(x => x.IsThisYearsBirthdayInThePast ? x.BirthdayNextYear : x.BirthdayThisYear) + .ToArray() + .Select(x => new BirthdayListItem + { + IsThisYearsBirthdayInThePastNHibernate = x.IsThisYearsBirthdayInThePast, + IsThisYearsBirthdayInThePastManual = x.BirthdayThisYear.DayOfYear < today.DayOfYear, + }) + .ToArray(); + } + } + + public class BirthdayListItem + { + public bool IsThisYearsBirthdayInThePastNHibernate { get; set; } + public bool IsThisYearsBirthdayInThePastManual { get; set; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2529/User.cs b/src/NHibernate.Test/NHSpecificTest/GH2529/User.cs new file mode 100644 index 00000000000..14ccd62590f --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2529/User.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2529 +{ + public class User + { + public virtual int Id { get; set; } + + public virtual DateTime? Birthday { get; set; } + } +} diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 061199e776f..ae8f1423d5a 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -99,4 +99,7 @@ + + +