diff --git a/src/NHibernate.Test/Async/Linq/DateTimeTests.cs b/src/NHibernate.Test/Async/Linq/DateTimeTests.cs index 8d0fd7c1598..4b67a5ebbcb 100644 --- a/src/NHibernate.Test/Async/Linq/DateTimeTests.cs +++ b/src/NHibernate.Test/Async/Linq/DateTimeTests.cs @@ -9,98 +9,261 @@ using System; +using System.Data; using System.Linq; +using NHibernate.Cfg; +using NHibernate.SqlTypes; +using NHibernate.Mapping.ByCode; using NUnit.Framework; +using NHibernate.Type; using NHibernate.Linq; +using System.Linq.Expressions; namespace NHibernate.Test.Linq { using System.Threading.Tasks; + using System.Threading; [TestFixture] - public class DateTimeTestsAsync : LinqTestCase + public class DateTimeTestsAsync : TestCase { + private bool DialectSupportsDateTimeOffset => TestDialect.SupportsSqlType(new SqlType(DbType.DateTimeOffset)); + private bool DialectSupportsDateTimeWithScale => TestDialect.SupportsSqlType(new SqlType(DbType.DateTime,(byte) 2)); + private readonly DateTimeTestsClass[] _referenceEntities = + [ + new() {Id =1, DateTimeValue = new DateTime(1998, 02, 26)}, + new() {Id =2, DateTimeValue = new DateTime(1998, 02, 26)}, + new() {Id =3, DateTimeValue = new DateTime(1998, 02, 26, 01, 01, 01)}, + new() {Id =4, DateTimeValue = new DateTime(1998, 02, 26, 02, 02, 02)}, + new() {Id =5, DateTimeValue = new DateTime(1998, 02, 26, 03, 03, 03)}, + new() {Id =6, DateTimeValue = new DateTime(1998, 02, 26, 04, 04, 04)}, + new() {Id =7, DateTimeValue = new DateTime(1998, 03, 01)}, + new() {Id =8, DateTimeValue = new DateTime(2000, 01, 01)} + ]; + + protected override string[] Mappings => default; + protected override void AddMappings(Configuration configuration) + { + var modelMapper = new ModelMapper(); + + modelMapper.Class(m => + { + m.Table("datetimetests"); + m.Lazy(false); + m.Id(p => p.Id, p => p.Generator(Generators.Assigned)); + m.Property(p => p.DateValue, c => c.Type()); + m.Property(p => p.DateTimeValue); + m.Property(p => p.DateTimeValueWithScale, c => c.Scale(2)); + if (DialectSupportsDateTimeOffset) + { + m.Property(p => p.DateTimeOffsetValue); + m.Property(p => p.DateTimeOffsetValueWithScale, c => c.Scale(2)); + } + }); + var mapping = modelMapper.CompileMappingForAllExplicitlyAddedEntities(); + configuration.AddMapping(mapping); + } + + protected override void OnSetUp() + { + foreach (var entity in _referenceEntities) + { + entity.DateValue = entity.DateTimeValue.Date; + entity.DateTimeValueWithScale = entity.DateTimeValue.AddSeconds(0.9); + entity.DateTimeOffsetValue = new DateTimeOffset(entity.DateTimeValue, TimeSpan.FromHours(3)); + entity.DateTimeOffsetValueWithScale = new DateTimeOffset(entity.DateTimeValue, TimeSpan.FromHours(3)); + } + + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + foreach (var entity in _referenceEntities) + { + session.Save(entity); + } + trans.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + session.Query().Delete(); + trans.Commit(); + } + } + + private void AssertDateTimeOffsetSupported() + { + if (!DialectSupportsDateTimeOffset) + { + Assert.Ignore("Dialect doesn't support DateTimeOffset"); + } + } + + private async Task AssertDateTimeWithScaleSupportedAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + if (!DialectSupportsDateTimeWithScale) + { + Assert.Ignore("Dialect doesn't support DateTime with scale (2)"); + } + using (var session = OpenSession()) + using (var trans = session.BeginTransaction()) + { + var entity1 = await (session.GetAsync(_referenceEntities[0].Id, cancellationToken)); + if (entity1.DateTimeValueWithScale != entity1.DateTimeValue.AddSeconds(0.9)) + { + Assert.Ignore("Current setup doesn't support DateTime with scale (2)"); + } + } + + } + + private Task AssertQueryAsync(Expression> where, CancellationToken cancellationToken = default(CancellationToken)) => AssertQueryAsync(where, x => x.Id, cancellationToken); + + private async Task AssertQueryAsync(Expression> where, Expression> select, CancellationToken cancellationToken = default(CancellationToken)) + { + using var session = OpenSession(); + var fromDb = await (session.Query().Where(where).Select(select).ToListAsync(cancellationToken)); + var fromMemory = _referenceEntities.AsQueryable().Where(where).Select(select).AsEnumerable().ToList(); //AsEnumerable added to avoid async generator + Assert.That(fromMemory, Has.Count.GreaterThan(0), "Inconclusive, since the query doesn't match anything in the defined set"); + Assert.That(fromDb, Has.Count.EqualTo(fromMemory.Count)); + Assert.That(fromDb, Is.EquivalentTo(fromMemory)); + } + [Test] public async Task CanQueryByYearAsync() { - var x = await ((from o in db.Orders - where o.OrderDate.Value.Year == 1998 - select o).ToListAsync()); + await (AssertQueryAsync(o => o.DateTimeValue.Year == 1998)); + } - Assert.AreEqual(270, x.Count()); + [Test] + public async Task CanQueryDateTimeBySecondAsync() + { + await (AssertQueryAsync(o => o.DateTimeValue.Second == 4)); } [Test] - public async Task CanQueryByDateAsync() + public async Task CanQueryDateTimeByMinuteAsync() { - var x = await ((from o in db.Orders - where o.OrderDate.Value.Date == new DateTime(1998, 02, 26) - select o).ToListAsync()); + await (AssertQueryAsync(o => o.DateTimeValue.Minute == 4)); + } - Assert.AreEqual(6, x.Count()); + [Test] + public async Task CanQueryDateTimeByHourAsync() + { + await (AssertQueryAsync(o => o.DateTimeValue.Hour == 4)); } [Test] - public async Task CanQueryByDateTimeAsync() + public async Task CanQueryDateTimeBySecondWhenValueContainsFractionalSecondsAsync() { - var x = await ((from o in db.Orders - where o.OrderDate.Value == new DateTime(1998, 02, 26) - select o).ToListAsync()); + await (AssertDateTimeWithScaleSupportedAsync()); + await (AssertQueryAsync(o => o.DateTimeValueWithScale.Second == 4)); + } - Assert.AreEqual(5, x.Count()); + [Test] + public async Task CanQueryDateTimeOffsetBySecondAsync() + { + AssertDateTimeOffsetSupported(); + await (AssertQueryAsync(o => o.DateTimeOffsetValue.Second == 4)); } [Test] - public async Task CanQueryByDateTime2Async() + public async Task CanQueryDateTimeOffsetByMinuteAsync() { - var x = await ((from o in db.Orders - where o.OrderDate.Value == new DateTime(1998, 02, 26, 0, 1, 0) - select o).ToListAsync()); + AssertDateTimeOffsetSupported(); + await (AssertQueryAsync(o => o.DateTimeOffsetValue.Minute == 4)); + } - Assert.AreEqual(1, x.Count()); + [Test] + public async Task CanQueryDateTimeOffsetByHourAsync() + { + AssertDateTimeOffsetSupported(); + await (AssertQueryAsync(o => o.DateTimeOffsetValue.Hour == 4)); } [Test] - public async Task CanSelectYearAsync() + public async Task CanQueryDateTimeOffsetBySecondWhenValueContainsFractionalSecondsAsync() { - var x = await ((from o in db.Orders - where o.OrderDate.Value.Year == 1998 - select o.OrderDate.Value.Year).ToListAsync()); + AssertDateTimeOffsetSupported(); + await (AssertQueryAsync(o => o.DateTimeOffsetValueWithScale.Second == 4)); + } - Assert.That(x, Has.All.EqualTo(1998)); - Assert.AreEqual(270, x.Count()); + [Test] + public async Task CanQueryByDateAsync() + { + await (AssertQueryAsync(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26))); } [Test] - public async Task CanSelectDateAsync() + public async Task CanQueryByDateTimeAsync() { - var x = await ((from o in db.Orders - where o.OrderDate.Value.Date == new DateTime(1998, 02, 26) - select o.OrderDate.Value.Date).ToListAsync()); + await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26))); + } - Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26))); - Assert.AreEqual(6, x.Count()); + [Test] + public async Task CanQueryByDateTime2Async() + { + await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1))); } [Test] - public async Task CanSelectDateTimeAsync() + public async Task CanSelectYearAsync() + { + await (AssertQueryAsync(o => o.DateTimeValue.Year == 1998, o => o.DateTimeValue.Year)); + } + + [Test] + public async Task CanSelectDateAsync() { - var x = await ((from o in db.Orders - where o.OrderDate.Value == new DateTime(1998, 02, 26) - select o.OrderDate.Value).ToListAsync()); + await (AssertQueryAsync(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26), o => o.DateTimeValue.Date)); + } - Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26))); - Assert.AreEqual(5, x.Count()); + [Test] + public async Task CanSelectDateTimeAsync() + { + await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26), o => o.DateTimeValue)); } [Test] public async Task CanSelectDateTime2Async() { - var x = await ((from o in db.Orders - where o.OrderDate.Value == new DateTime(1998, 02, 26, 0, 1, 0) - select o.OrderDate.Value).ToListAsync()); + await (AssertQueryAsync(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1), o => o.DateTimeValue)); + } + + [Test] + public async Task CanSelectDateTimeWithScaleAsync() + { + await (AssertDateTimeWithScaleSupportedAsync()); + await (AssertQueryAsync(o => o.DateTimeValueWithScale == _referenceEntities[0].DateTimeValueWithScale, o => o.DateTimeValueWithScale)); + } + + public class DateTimeTestsClass : IEquatable + { + public int Id { get; set; } + public DateTime DateTimeValue { get; set; } + public DateTime DateTimeValueWithScale { get; set; } + public DateTimeOffset DateTimeOffsetValue { get; set; } + public DateTimeOffset DateTimeOffsetValueWithScale { get; set; } + public DateTime DateValue { get; set; } + + public override bool Equals(object obj) + { + return Equals(obj as DateTimeTestsClass); + } + + public bool Equals(DateTimeTestsClass other) + { + return other is not null && + Id.Equals(other.Id); + } - Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26, 0, 1, 0))); - Assert.AreEqual(1, x.Count()); + public override int GetHashCode() + { + return HashCode.Combine(Id); + } } } } diff --git a/src/NHibernate.Test/Linq/DateTimeTests.cs b/src/NHibernate.Test/Linq/DateTimeTests.cs index 20ec87ff802..dc15154d845 100644 --- a/src/NHibernate.Test/Linq/DateTimeTests.cs +++ b/src/NHibernate.Test/Linq/DateTimeTests.cs @@ -1,94 +1,254 @@ -using System; +using System; +using System.Data; using System.Linq; +using System.Linq.Expressions; +using NHibernate.Cfg; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlTypes; +using NHibernate.Type; using NUnit.Framework; namespace NHibernate.Test.Linq { [TestFixture] - public class DateTimeTests : LinqTestCase + public class DateTimeTests : TestCase { + private bool DialectSupportsDateTimeOffset => TestDialect.SupportsSqlType(new SqlType(DbType.DateTimeOffset)); + private readonly DateTimeTestsClass[] _referenceEntities = + [ + new() {Id =1, DateTimeValue = new DateTime(1998, 02, 26)}, + new() {Id =2, DateTimeValue = new DateTime(1998, 02, 26)}, + new() {Id =3, DateTimeValue = new DateTime(1998, 02, 26, 01, 01, 01)}, + new() {Id =4, DateTimeValue = new DateTime(1998, 02, 26, 02, 02, 02)}, + new() {Id =5, DateTimeValue = new DateTime(1998, 02, 26, 03, 03, 03)}, + new() {Id =6, DateTimeValue = new DateTime(1998, 02, 26, 04, 04, 04)}, + new() {Id =7, DateTimeValue = new DateTime(1998, 03, 01)}, + new() {Id =8, DateTimeValue = new DateTime(2000, 01, 01)} + ]; + + private TimeSpan FractionalSecondsAdded => TimeSpan.FromMilliseconds(900); + + protected override string[] Mappings => default; + protected override void AddMappings(Configuration configuration) + { + var modelMapper = new ModelMapper(); + + modelMapper.Class(m => + { + m.Table("datetimetests"); + m.Lazy(false); + m.Id(p => p.Id, p => p.Generator(Generators.Assigned)); + m.Property(p => p.DateValue, c => c.Type()); + m.Property(p => p.DateTimeValue); + m.Property(p => p.DateTimeValueWithScale, c => c.Scale(2)); + if (DialectSupportsDateTimeOffset) + { + m.Property(p => p.DateTimeOffsetValue); + m.Property(p => p.DateTimeOffsetValueWithScale, c => c.Scale(2)); + } + }); + var mapping = modelMapper.CompileMappingForAllExplicitlyAddedEntities(); + configuration.AddMapping(mapping); + } + + protected override void OnSetUp() + { + foreach (var entity in _referenceEntities) + { + entity.DateValue = entity.DateTimeValue.Date; + entity.DateTimeValueWithScale = entity.DateTimeValue + FractionalSecondsAdded; + entity.DateTimeOffsetValue = new DateTimeOffset(entity.DateTimeValue, TimeSpan.FromHours(3)); + entity.DateTimeOffsetValueWithScale = new DateTimeOffset(entity.DateTimeValueWithScale, TimeSpan.FromHours(3)); + } + + using var session = OpenSession(); + using var trans = session.BeginTransaction(); + foreach (var entity in _referenceEntities) + { + session.Save(entity); + } + trans.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var trans = session.BeginTransaction(); + session.Query().Delete(); + trans.Commit(); + } + + private void AssertDateTimeOffsetSupported() + { + if (!DialectSupportsDateTimeOffset) + { + Assert.Ignore("Dialect doesn't support DateTimeOffset"); + } + } + + private void AssertDateTimeWithFractionalSecondsSupported() + { + //Ideally, the dialect should know whether this is supported or not + if (!TestDialect.SupportsDateTimeWithFractionalSeconds) + { + Assert.Ignore("Dialect doesn't support DateTime with factional seconds"); + } + + //But it sometimes doesn't + using var session = OpenSession(); + using var trans = session.BeginTransaction(); + var entity1 = session.Get(_referenceEntities[0].Id); + if (entity1.DateTimeValueWithScale != entity1.DateTimeValue + FractionalSecondsAdded) + { + Assert.Ignore("Current setup doesn't support DateTime with scale (2)"); + } + } + + private void AssertQuery(Expression> where) => AssertQuery(where, x => x.Id); + + private void AssertQuery(Expression> where, Expression> select) + { + using var session = OpenSession(); + var fromDb = session.Query().Where(where).Select(select).ToList(); + var fromMemory = _referenceEntities.AsQueryable().Where(where).Select(select).AsEnumerable().ToList(); //AsEnumerable added to avoid async generator + Assert.That(fromMemory, Has.Count.GreaterThan(0), "Inconclusive, since the query doesn't match anything in the defined set"); + Assert.That(fromDb, Has.Count.EqualTo(fromMemory.Count)); + Assert.That(fromDb, Is.EquivalentTo(fromMemory)); + } + [Test] public void CanQueryByYear() { - var x = (from o in db.Orders - where o.OrderDate.Value.Year == 1998 - select o).ToList(); + AssertQuery(o => o.DateTimeValue.Year == 1998); + } - Assert.AreEqual(270, x.Count()); + [Test] + public void CanQueryDateTimeBySecond() + { + AssertQuery(o => o.DateTimeValue.Second == 4); } [Test] - public void CanQueryByDate() + public void CanQueryDateTimeByMinute() { - var x = (from o in db.Orders - where o.OrderDate.Value.Date == new DateTime(1998, 02, 26) - select o).ToList(); + AssertQuery(o => o.DateTimeValue.Minute == 4); + } - Assert.AreEqual(6, x.Count()); + [Test] + public void CanQueryDateTimeByHour() + { + AssertQuery(o => o.DateTimeValue.Hour == 4); } [Test] - public void CanQueryByDateTime() + public void CanQueryDateTimeBySecondWhenValueContainsFractionalSeconds() { - var x = (from o in db.Orders - where o.OrderDate.Value == new DateTime(1998, 02, 26) - select o).ToList(); + AssertDateTimeWithFractionalSecondsSupported(); + AssertQuery(o => o.DateTimeValueWithScale.Second == 4); + } - Assert.AreEqual(5, x.Count()); + [Test] + public void CanQueryDateTimeOffsetBySecond() + { + AssertDateTimeOffsetSupported(); + AssertQuery(o => o.DateTimeOffsetValue.Second == 4); } [Test] - public void CanQueryByDateTime2() + public void CanQueryDateTimeOffsetByMinute() { - var x = (from o in db.Orders - where o.OrderDate.Value == new DateTime(1998, 02, 26, 0, 1, 0) - select o).ToList(); + AssertDateTimeOffsetSupported(); + AssertQuery(o => o.DateTimeOffsetValue.Minute == 4); + } - Assert.AreEqual(1, x.Count()); + [Test] + public void CanQueryDateTimeOffsetByHour() + { + AssertDateTimeOffsetSupported(); + AssertQuery(o => o.DateTimeOffsetValue.Hour == 4); } [Test] - public void CanSelectYear() + public void CanQueryDateTimeOffsetBySecondWhenValueContainsFractionalSeconds() { - var x = (from o in db.Orders - where o.OrderDate.Value.Year == 1998 - select o.OrderDate.Value.Year).ToList(); + AssertDateTimeOffsetSupported(); + AssertQuery(o => o.DateTimeOffsetValueWithScale.Second == 4); + } - Assert.That(x, Has.All.EqualTo(1998)); - Assert.AreEqual(270, x.Count()); + [Test] + public void CanQueryByDate() + { + AssertQuery(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26)); } [Test] - public void CanSelectDate() + public void CanQueryByDateTime() { - var x = (from o in db.Orders - where o.OrderDate.Value.Date == new DateTime(1998, 02, 26) - select o.OrderDate.Value.Date).ToList(); + AssertQuery(o => o.DateTimeValue == new DateTime(1998, 02, 26)); + } - Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26))); - Assert.AreEqual(6, x.Count()); + [Test] + public void CanQueryByDateTime2() + { + AssertQuery(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1)); } [Test] - public void CanSelectDateTime() + public void CanSelectYear() { - var x = (from o in db.Orders - where o.OrderDate.Value == new DateTime(1998, 02, 26) - select o.OrderDate.Value).ToList(); + AssertQuery(o => o.DateTimeValue.Year == 1998, o => o.DateTimeValue.Year); + } - Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26))); - Assert.AreEqual(5, x.Count()); + [Test] + public void CanSelectDate() + { + AssertQuery(o => o.DateTimeValue.Date == new DateTime(1998, 02, 26), o => o.DateTimeValue.Date); + } + + [Test] + public void CanSelectDateTime() + { + AssertQuery(o => o.DateTimeValue == new DateTime(1998, 02, 26), o => o.DateTimeValue); } [Test] public void CanSelectDateTime2() { - var x = (from o in db.Orders - where o.OrderDate.Value == new DateTime(1998, 02, 26, 0, 1, 0) - select o.OrderDate.Value).ToList(); + AssertQuery(o => o.DateTimeValue == new DateTime(1998, 02, 26, 1, 1, 1), o => o.DateTimeValue); + } - Assert.That(x, Has.All.EqualTo(new DateTime(1998, 02, 26, 0, 1, 0))); - Assert.AreEqual(1, x.Count()); + [Test] + public void CanSelectDateTimeWithScale() + { + AssertDateTimeWithFractionalSecondsSupported(); + AssertQuery(o => o.DateTimeValueWithScale == _referenceEntities[0].DateTimeValueWithScale, o => o.DateTimeValueWithScale); + } + + public class DateTimeTestsClass : IEquatable + { + public int Id { get; set; } + public DateTime DateTimeValue { get; set; } + public DateTime DateTimeValueWithScale { get; set; } + public DateTimeOffset DateTimeOffsetValue { get; set; } + public DateTimeOffset DateTimeOffsetValueWithScale { get; set; } + public DateTime DateValue { get; set; } + + public override bool Equals(object obj) + { + return Equals(obj as DateTimeTestsClass); + } + + public bool Equals(DateTimeTestsClass other) + { + return other is not null && + Id.Equals(other.Id); + } + + public override int GetHashCode() + { + return HashCode.Combine(Id); + } } } } diff --git a/src/NHibernate.Test/TestDialect.cs b/src/NHibernate.Test/TestDialect.cs index 316d7b68fd8..ec099144dde 100644 --- a/src/NHibernate.Test/TestDialect.cs +++ b/src/NHibernate.Test/TestDialect.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using NHibernate.Hql.Ast.ANTLR; using NHibernate.Id; @@ -61,7 +61,7 @@ public bool NativeGeneratorSupportsBulkInsertion /// Some databases do not support SELECT FOR UPDATE /// public virtual bool SupportsSelectForUpdate => true; - + /// /// Some databases do not support SELECT FOR UPDATE with paging /// @@ -218,5 +218,7 @@ public virtual bool SupportsSqlType(SqlType sqlType) /// Some databases (MySql) don't support using main table aliases in subquery inside join ON clause /// public virtual bool SupportsCorrelatedColumnsInSubselectJoin => true; + + public virtual bool SupportsDateTimeWithFractionalSeconds => _dialect.TimestampResolutionInTicks < TimeSpan.TicksPerSecond && SupportsSqlType(new SqlType(DbType.DateTime, (byte) 2)); } } diff --git a/src/NHibernate/Dialect/FirebirdDialect.cs b/src/NHibernate/Dialect/FirebirdDialect.cs index 7109480335c..7d688ca5fe3 100644 --- a/src/NHibernate/Dialect/FirebirdDialect.cs +++ b/src/NHibernate/Dialect/FirebirdDialect.cs @@ -537,6 +537,7 @@ private void RegisterDateTimeFunctions() RegisterFunction("addweek", new StandardSQLFunction("addweek", NHibernateUtil.DateTime)); RegisterFunction("addyear", new StandardSQLFunction("addyear", NHibernateUtil.DateTime)); RegisterFunction("getexacttimestamp", new NoArgSQLFunction("getexacttimestamp", NHibernateUtil.DateTime)); + RegisterFunction("secondtruncated", new SQLFunctionTemplate(NHibernateUtil.Int32, "cast(floor(extract(second from ?1)) as int)")); } private void RegisterStringAndCharFunctions() diff --git a/src/NHibernate/Dialect/Function/SQLFunctionRegistry.cs b/src/NHibernate/Dialect/Function/SQLFunctionRegistry.cs index 701dadd64f7..b611ccfdad6 100644 --- a/src/NHibernate/Dialect/Function/SQLFunctionRegistry.cs +++ b/src/NHibernate/Dialect/Function/SQLFunctionRegistry.cs @@ -7,6 +7,8 @@ public class SQLFunctionRegistry { private readonly Dialect dialect; private readonly IDictionary userFunctions; + //Temporary alias support + private static Dictionary _functionAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "secondtruncated", "second" } }; public SQLFunctionRegistry(Dialect dialect, IDictionary userFunctions) { @@ -20,8 +22,11 @@ public SQLFunctionRegistry(Dialect dialect, IDictionary us /// public ISQLFunction FindSQLFunction(string functionName) { - ISQLFunction result; - if (!userFunctions.TryGetValue(functionName, out result)) + if (!userFunctions.ContainsKey(functionName) && !dialect.Functions.ContainsKey(functionName) && _functionAliases.TryGetValue(functionName, out var sqlFunction)) + { + functionName = sqlFunction; + } + if (!userFunctions.TryGetValue(functionName, out ISQLFunction result)) { dialect.Functions.TryGetValue(functionName, out result); } @@ -30,6 +35,10 @@ public ISQLFunction FindSQLFunction(string functionName) public bool HasFunction(string functionName) { + if (!userFunctions.ContainsKey(functionName) && !dialect.Functions.ContainsKey(functionName) && _functionAliases.TryGetValue(functionName, out var sqlFunction)) + { + functionName = sqlFunction; + } return userFunctions.ContainsKey(functionName) || dialect.Functions.ContainsKey(functionName); } } diff --git a/src/NHibernate/Dialect/Oracle8iDialect.cs b/src/NHibernate/Dialect/Oracle8iDialect.cs index bd11e9bcff5..c697339df73 100644 --- a/src/NHibernate/Dialect/Oracle8iDialect.cs +++ b/src/NHibernate/Dialect/Oracle8iDialect.cs @@ -265,9 +265,11 @@ protected virtual void RegisterFunctions() // Cast is needed because EXTRACT treats DATE not as legacy Oracle DATE but as ANSI DATE, without time elements. // Therefore, you can extract only YEAR, MONTH, and DAY from a DATE value. + // Oracle returns the seconds with fractional precision. It has to be truncated to return the actual second part RegisterFunction("second", new SQLFunctionTemplate(NHibernateUtil.Int32, "extract(second from cast(?1 as timestamp))")); RegisterFunction("minute", new SQLFunctionTemplate(NHibernateUtil.Int32, "extract(minute from cast(?1 as timestamp))")); RegisterFunction("hour", new SQLFunctionTemplate(NHibernateUtil.Int32, "extract(hour from cast(?1 as timestamp))")); + RegisterFunction("secondtruncated", new SQLFunctionTemplate(NHibernateUtil.Int32, "cast(floor(extract(second from cast(?1 as timestamp))) as int)")); RegisterFunction("date", new StandardSQLFunction("trunc", NHibernateUtil.Date)); diff --git a/src/NHibernate/Dialect/PostgreSQLDialect.cs b/src/NHibernate/Dialect/PostgreSQLDialect.cs index 725d530a22a..3053ce17267 100644 --- a/src/NHibernate/Dialect/PostgreSQLDialect.cs +++ b/src/NHibernate/Dialect/PostgreSQLDialect.cs @@ -105,6 +105,8 @@ public PostgreSQLDialect() // and NHibernate.TestDatabaseSetup does install it. RegisterFunction("new_uuid", new NoArgSQLFunction("uuid_generate_v4", NHibernateUtil.Guid)); + RegisterFunction("secondtruncated", new SQLFunctionTemplate(NHibernateUtil.Int32, "cast(floor(extract(second from ?1)) as int)")); + RegisterKeywords(); } diff --git a/src/NHibernate/Linq/Functions/DateTimePropertiesHqlGenerator.cs b/src/NHibernate/Linq/Functions/DateTimePropertiesHqlGenerator.cs index a8b26787f47..e5b1e95d703 100644 --- a/src/NHibernate/Linq/Functions/DateTimePropertiesHqlGenerator.cs +++ b/src/NHibernate/Linq/Functions/DateTimePropertiesHqlGenerator.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Linq.Expressions; using System.Reflection; +using NHibernate.Engine; using NHibernate.Hql.Ast; using NHibernate.Linq.Visitors; using NHibernate.Util; @@ -33,8 +34,13 @@ public DateTimePropertiesHqlGenerator() public override HqlTreeNode BuildHql(MemberInfo member, Expression expression, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { - return treeBuilder.MethodCall(member.Name.ToLowerInvariant(), + var functionName = member.Name.ToLowerInvariant(); + if (functionName == "second") + { + functionName = "secondtruncated"; + } + return treeBuilder.MethodCall(functionName, visitor.Visit(expression).AsExpression()); } } -} \ No newline at end of file +}