From 02a4f71170c68d83d380be6bee840b572ed4f638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Tue, 3 Oct 2017 18:43:40 +0200 Subject: [PATCH] NH-4087 - Fix decimal parameter with scale above 5 * Use dialect configurable default scale/precision * Fixes #1187 --- .../Async/DriverTest/OdbcDriverFixture.cs | 117 +++++++++++++ .../DriverTest/SqlClientDriverFixture.cs | 101 +++++++---- .../Async/TypesTest/DecimalTypeFixture.cs | 130 ++++++++++++-- .../DriverTest/MultiTypeEntity.hbm.xml | 7 +- .../DriverTest/OdbcDriverFixture.cs | 106 ++++++++++++ .../DriverTest/SqlClientDriverFixture.cs | 162 ++++++++++++++---- src/NHibernate.Test/TestDialect.cs | 3 + .../TestDialects/SQLiteTestDialect.cs | 27 +-- src/NHibernate.Test/TypesTest/DecimalClass.cs | 27 +-- .../TypesTest/DecimalClass.hbm.xml | 23 +-- .../TypesTest/DecimalTypeFixture.cs | 150 ++++++++++++---- src/NHibernate/Dialect/Dialect.cs | 6 +- src/NHibernate/Driver/FirebirdClientDriver.cs | 2 +- src/NHibernate/Driver/SqlClientDriver.cs | 81 +++++++-- 14 files changed, 760 insertions(+), 182 deletions(-) create mode 100644 src/NHibernate.Test/Async/DriverTest/OdbcDriverFixture.cs create mode 100644 src/NHibernate.Test/DriverTest/OdbcDriverFixture.cs diff --git a/src/NHibernate.Test/Async/DriverTest/OdbcDriverFixture.cs b/src/NHibernate.Test/Async/DriverTest/OdbcDriverFixture.cs new file mode 100644 index 00000000000..a0f0f8664f9 --- /dev/null +++ b/src/NHibernate.Test/Async/DriverTest/OdbcDriverFixture.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// 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.Collections; +using NHibernate.Dialect; +using NHibernate.Driver; +using NHibernate.Engine; +using NUnit.Framework; + +namespace NHibernate.Test.DriverTest +{ + using System.Threading.Tasks; + [TestFixture] + public class OdbcDriverFixtureAsync : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] { "DriverTest.MultiTypeEntity.hbm.xml" }; + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect is MsSql2000Dialect; + } + + protected override bool AppliesTo(ISessionFactoryImplementor factory) + { + return factory.ConnectionProvider.Driver is OdbcDriver; + } + + protected override void OnTearDown() + { + base.OnTearDown(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from MultiTypeEntity").ExecuteUpdate(); + t.Commit(); + } + } + + [Test] + public async Task CrudAsync() + { + // Should use default dimension for CRUD op because the mapping does not + // have dimensions specified. + object savedId; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + savedId = await (s.SaveAsync( + new MultiTypeEntity + { + StringProp = "a", + StringClob = "a", + BinaryBlob = new byte[] { 1, 2, 3 }, + Binary = new byte[] { 4, 5, 6 }, + Currency = 123.4m, + Double = 123.5d, + Decimal = 789.5m, + DecimalHighScale = 1234567890.0123456789m + })); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var m = await (s.GetAsync(savedId)); + + Assert.That(m.StringProp, Is.EqualTo("a"), "StringProp"); + Assert.That(m.StringClob, Is.EqualTo("a"), "StringClob"); + Assert.That(m.BinaryBlob, Is.EqualTo(new byte[] { 1, 2, 3 }), "BinaryBlob"); + Assert.That(m.Binary, Is.EqualTo(new byte[] { 4, 5, 6 }), "BinaryBlob"); + Assert.That(m.Currency, Is.EqualTo(123.4m), "Currency"); + Assert.That(m.Double, Is.EqualTo(123.5d).Within(0.0001d), "Double"); + Assert.That(m.Decimal, Is.EqualTo(789.5m), "Decimal"); + Assert.That(m.DecimalHighScale, Is.EqualTo(1234567890.0123456789m), "DecimalHighScale"); + + m.StringProp = "b"; + m.StringClob = "b"; + m.BinaryBlob = new byte[] { 4, 5, 6 }; + m.Binary = new byte[] { 7, 8, 9 }; + m.Currency = 456.78m; + m.Double = 987.6d; + m.Decimal = 1323456.45m; + m.DecimalHighScale = 9876543210.0123456789m; + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var m = await (s.LoadAsync(savedId)); + + Assert.That(m.StringProp, Is.EqualTo("b"), "StringProp"); + Assert.That(m.StringClob, Is.EqualTo("b"), "StringClob"); + Assert.That(m.BinaryBlob, Is.EqualTo(new byte[] { 4, 5, 6 }), "BinaryBlob"); + Assert.That(m.Binary, Is.EqualTo(new byte[] { 7, 8, 9 }), "BinaryBlob"); + Assert.That(m.Currency, Is.EqualTo(456.78m), "Currency"); + Assert.That(m.Double, Is.EqualTo(987.6d).Within(0.0001d), "Double"); + Assert.That(m.Decimal, Is.EqualTo(1323456.45m), "Decimal"); + Assert.That(m.DecimalHighScale, Is.EqualTo(9876543210.0123456789m), "DecimalHighScale"); + + await (t.CommitAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/DriverTest/SqlClientDriverFixture.cs b/src/NHibernate.Test/Async/DriverTest/SqlClientDriverFixture.cs index 2bb3106f6a1..62fe9232900 100644 --- a/src/NHibernate.Test/Async/DriverTest/SqlClientDriverFixture.cs +++ b/src/NHibernate.Test/Async/DriverTest/SqlClientDriverFixture.cs @@ -10,9 +10,13 @@ using System; using System.Collections; +using System.Data; using NHibernate.Dialect; using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.SqlTypes; using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; namespace NHibernate.Test.DriverTest { @@ -21,19 +25,30 @@ namespace NHibernate.Test.DriverTest [TestFixture] public class SqlClientDriverFixtureAsync : TestCase { - protected override string MappingsAssembly + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] { "DriverTest.MultiTypeEntity.hbm.xml" }; + + protected override bool AppliesTo(Dialect.Dialect dialect) { - get { return "NHibernate.Test"; } + return dialect is MsSql2008Dialect; } - protected override IList Mappings + protected override bool AppliesTo(ISessionFactoryImplementor factory) { - get { return new[] { "DriverTest.MultiTypeEntity.hbm.xml" }; } + return factory.ConnectionProvider.Driver is SqlClientDriver; } - protected override bool AppliesTo(Dialect.Dialect dialect) + protected override void OnTearDown() { - return dialect is MsSql2008Dialect; + base.OnTearDown(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from MultiTypeEntity").ExecuteUpdate(); + t.Commit(); + } } [Test] @@ -42,40 +57,63 @@ public async Task CrudAsync() // Should use default dimension for CRUD op because the mapping does not // have dimensions specified. object savedId; - using (ISession s = OpenSession()) - using (ITransaction t = s.BeginTransaction()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - savedId = await (s.SaveAsync(new MultiTypeEntity - { - StringProp = "a", - StringClob = "a", - BinaryBlob = new byte[]{1,2,3}, - Binary = new byte[] { 4, 5, 6 }, - Currency = 123.4m, - Double = 123.5d, - Decimal = 789.5m - })); + savedId = await (s.SaveAsync( + new MultiTypeEntity + { + StringProp = "a", + StringClob = "a", + BinaryBlob = new byte[] { 1, 2, 3 }, + Binary = new byte[] { 4, 5, 6 }, + Currency = 123.4m, + Double = 123.5d, + Decimal = 789.5m, + DecimalHighScale = 1234567890.0123456789m + })); await (t.CommitAsync()); } - using (ISession s = OpenSession()) - using (ITransaction t = s.BeginTransaction()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { var m = await (s.GetAsync(savedId)); + + Assert.That(m.StringProp, Is.EqualTo("a"), "StringProp"); + Assert.That(m.StringClob, Is.EqualTo("a"), "StringClob"); + Assert.That(m.BinaryBlob, Is.EqualTo(new byte[] { 1, 2, 3 }), "BinaryBlob"); + Assert.That(m.Binary, Is.EqualTo(new byte[] { 4, 5, 6 }), "BinaryBlob"); + Assert.That(m.Currency, Is.EqualTo(123.4m), "Currency"); + Assert.That(m.Double, Is.EqualTo(123.5d).Within(0.0001d), "Double"); + Assert.That(m.Decimal, Is.EqualTo(789.5m), "Decimal"); + Assert.That(m.DecimalHighScale, Is.EqualTo(1234567890.0123456789m), "DecimalHighScale"); + m.StringProp = "b"; m.StringClob = "b"; - m.BinaryBlob = new byte[] {4,5,6}; - m.Binary = new byte[] {7,8,9}; + m.BinaryBlob = new byte[] { 4, 5, 6 }; + m.Binary = new byte[] { 7, 8, 9 }; m.Currency = 456.78m; m.Double = 987.6d; m.Decimal = 1323456.45m; + m.DecimalHighScale = 9876543210.0123456789m; await (t.CommitAsync()); } - using (ISession s = OpenSession()) - using (ITransaction t = s.BeginTransaction()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - await (s.CreateQuery("delete from MultiTypeEntity").ExecuteUpdateAsync()); + var m = await (s.LoadAsync(savedId)); + + Assert.That(m.StringProp, Is.EqualTo("b"), "StringProp"); + Assert.That(m.StringClob, Is.EqualTo("b"), "StringClob"); + Assert.That(m.BinaryBlob, Is.EqualTo(new byte[] { 4, 5, 6 }), "BinaryBlob"); + Assert.That(m.Binary, Is.EqualTo(new byte[] { 7, 8, 9 }), "BinaryBlob"); + Assert.That(m.Currency, Is.EqualTo(456.78m), "Currency"); + Assert.That(m.Double, Is.EqualTo(987.6d).Within(0.0001d), "Double"); + Assert.That(m.Decimal, Is.EqualTo(1323456.45m), "Decimal"); + Assert.That(m.DecimalHighScale, Is.EqualTo(9876543210.0123456789m), "DecimalHighScale"); + await (t.CommitAsync()); } } @@ -83,9 +121,6 @@ public async Task CrudAsync() [Test] public async Task QueryPlansAreReusedAsync() { - if (!(Sfi.ConnectionProvider.Driver is SqlClientDriver)) - Assert.Ignore("Test designed for SqlClientDriver only"); - using (ISession s = OpenSession()) using (ITransaction t = s.BeginTransaction()) { @@ -102,7 +137,7 @@ public async Task QueryPlansAreReusedAsync() var beforeCount = await (countPlansCommand.UniqueResultAsync()); var insertCount = 10; - for (var i=0; i()); - Assert.That(afterCount - beforeCount, Is.LessThan(insertCount - 1), - string.Format("Excessive query plans created: before={0} after={1}", beforeCount, afterCount)); + Assert.That( + afterCount - beforeCount, + Is.LessThan(insertCount - 1), + $"Excessive query plans created: before={beforeCount} after={afterCount}"); await (t.RollbackAsync()); } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Async/TypesTest/DecimalTypeFixture.cs b/src/NHibernate.Test/Async/TypesTest/DecimalTypeFixture.cs index d3d4fb2cfbd..189d89d9e09 100644 --- a/src/NHibernate.Test/Async/TypesTest/DecimalTypeFixture.cs +++ b/src/NHibernate.Test/Async/TypesTest/DecimalTypeFixture.cs @@ -8,7 +8,8 @@ //------------------------------------------------------------------------------ -using System; +using NHibernate.Cfg; +using NHibernate.Dialect; using NHibernate.Type; using NUnit.Framework; @@ -21,34 +22,125 @@ namespace NHibernate.Test.TypesTest [TestFixture] public class DecimalTypeFixtureAsync : TypeFixtureBase { - protected override string TypeName + protected override string TypeName => "Decimal"; + private const int _highScaleId = 2; + private const decimal _highScaleTestedValue = 123456789.123456789m; + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return base.AppliesTo(dialect) && !TestDialect.HasBrokenDecimalType; + } + + protected override void Configure(Configuration configuration) { - get { return "Decimal"; } + base.Configure(configuration); + + if (Dialect is FirebirdDialect) + { + configuration.SetProperty(Environment.QueryDefaultCastPrecision, "18"); + configuration.SetProperty(Environment.QueryDefaultCastScale, "9"); + } + } + + protected override void OnSetUp() + { + base.OnSetUp(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(new DecimalClass { Id = _highScaleId, HighScaleDecimalValue = _highScaleTestedValue }); + t.Commit(); + } + } + + protected override void OnTearDown() + { + base.OnTearDown(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from DecimalClass").ExecuteUpdate(); + t.Commit(); + } } [Test] public async Task ReadWriteAsync() { - decimal expected = 5.64351M; + const decimal expected = 5.64351M; - DecimalClass basic = new DecimalClass(); - basic.Id = 1; - basic.DecimalValue = expected; + var basic = new DecimalClass + { + Id = 1, + DecimalValue = expected + }; - ISession s = OpenSession(); - await (s.SaveAsync(basic)); - await (s.FlushAsync()); - s.Close(); + using (var s = OpenSession()) + { + await (s.SaveAsync(basic)); + await (s.FlushAsync()); + } - s = OpenSession(); - basic = (DecimalClass) await (s.LoadAsync(typeof(DecimalClass), 1)); + using (var s = OpenSession()) + { + basic = await (s.LoadAsync(1)); - Assert.AreEqual(expected, basic.DecimalValue); - Assert.AreEqual(5.643510M, basic.DecimalValue); + Assert.That(basic.DecimalValue, Is.EqualTo(expected)); + Assert.That(basic.DecimalValue, Is.EqualTo(5.643510M)); + } + } + + [Test] + public async Task HighScaleParameterSelectAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var selectedValue = await (s + .CreateQuery("select dc.HighScaleDecimalValue + :d1 from DecimalClass dc") + .SetMaxResults(1) + .SetDecimal("d1", _highScaleTestedValue) + .UniqueResultAsync()); + Assert.That(selectedValue, Is.EqualTo(_highScaleTestedValue * 2)); + await (t.CommitAsync()); + } + } + + [Test] + public async Task HighScaleParameterFilterAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var count = await (s + .CreateQuery("select count(*) from DecimalClass dc where dc.HighScaleDecimalValue = :d1") + .SetDecimal("d1", _highScaleTestedValue) + .UniqueResultAsync()); + Assert.That(count, Is.GreaterThanOrEqualTo(1)); + await (t.CommitAsync()); + } + } + + [Test] + public async Task HighScaleParameterInequalityAsync() + { + if (!TestDialect.SupportsNonDataBoundCondition) + Assert.Ignore("Dialect does not support parameters comparison."); - await (s.DeleteAsync(basic)); - await (s.FlushAsync()); - s.Close(); + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var count = await (s + .CreateQuery("select count(*) from DecimalClass dc where :d1 != :d2") + .SetDecimal("d1", 123456789.123456789m) + // If truncation occurs before the last digit, the test will fail. + .SetDecimal("d2", 123456789.123456780m) + .UniqueResultAsync()); + Assert.That(count, Is.GreaterThanOrEqualTo(1)); + await (t.CommitAsync()); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DriverTest/MultiTypeEntity.hbm.xml b/src/NHibernate.Test/DriverTest/MultiTypeEntity.hbm.xml index e0d1adaf925..7426eb4ac04 100644 --- a/src/NHibernate.Test/DriverTest/MultiTypeEntity.hbm.xml +++ b/src/NHibernate.Test/DriverTest/MultiTypeEntity.hbm.xml @@ -10,12 +10,13 @@ + - - - \ No newline at end of file + + + diff --git a/src/NHibernate.Test/DriverTest/OdbcDriverFixture.cs b/src/NHibernate.Test/DriverTest/OdbcDriverFixture.cs new file mode 100644 index 00000000000..086df5ca30b --- /dev/null +++ b/src/NHibernate.Test/DriverTest/OdbcDriverFixture.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections; +using NHibernate.Dialect; +using NHibernate.Driver; +using NHibernate.Engine; +using NUnit.Framework; + +namespace NHibernate.Test.DriverTest +{ + [TestFixture] + public class OdbcDriverFixture : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] { "DriverTest.MultiTypeEntity.hbm.xml" }; + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect is MsSql2000Dialect; + } + + protected override bool AppliesTo(ISessionFactoryImplementor factory) + { + return factory.ConnectionProvider.Driver is OdbcDriver; + } + + protected override void OnTearDown() + { + base.OnTearDown(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from MultiTypeEntity").ExecuteUpdate(); + t.Commit(); + } + } + + [Test] + public void Crud() + { + // Should use default dimension for CRUD op because the mapping does not + // have dimensions specified. + object savedId; + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + savedId = s.Save( + new MultiTypeEntity + { + StringProp = "a", + StringClob = "a", + BinaryBlob = new byte[] { 1, 2, 3 }, + Binary = new byte[] { 4, 5, 6 }, + Currency = 123.4m, + Double = 123.5d, + Decimal = 789.5m, + DecimalHighScale = 1234567890.0123456789m + }); + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var m = s.Get(savedId); + + Assert.That(m.StringProp, Is.EqualTo("a"), "StringProp"); + Assert.That(m.StringClob, Is.EqualTo("a"), "StringClob"); + Assert.That(m.BinaryBlob, Is.EqualTo(new byte[] { 1, 2, 3 }), "BinaryBlob"); + Assert.That(m.Binary, Is.EqualTo(new byte[] { 4, 5, 6 }), "BinaryBlob"); + Assert.That(m.Currency, Is.EqualTo(123.4m), "Currency"); + Assert.That(m.Double, Is.EqualTo(123.5d).Within(0.0001d), "Double"); + Assert.That(m.Decimal, Is.EqualTo(789.5m), "Decimal"); + Assert.That(m.DecimalHighScale, Is.EqualTo(1234567890.0123456789m), "DecimalHighScale"); + + m.StringProp = "b"; + m.StringClob = "b"; + m.BinaryBlob = new byte[] { 4, 5, 6 }; + m.Binary = new byte[] { 7, 8, 9 }; + m.Currency = 456.78m; + m.Double = 987.6d; + m.Decimal = 1323456.45m; + m.DecimalHighScale = 9876543210.0123456789m; + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var m = s.Load(savedId); + + Assert.That(m.StringProp, Is.EqualTo("b"), "StringProp"); + Assert.That(m.StringClob, Is.EqualTo("b"), "StringClob"); + Assert.That(m.BinaryBlob, Is.EqualTo(new byte[] { 4, 5, 6 }), "BinaryBlob"); + Assert.That(m.Binary, Is.EqualTo(new byte[] { 7, 8, 9 }), "BinaryBlob"); + Assert.That(m.Currency, Is.EqualTo(456.78m), "Currency"); + Assert.That(m.Double, Is.EqualTo(987.6d).Within(0.0001d), "Double"); + Assert.That(m.Decimal, Is.EqualTo(1323456.45m), "Decimal"); + Assert.That(m.DecimalHighScale, Is.EqualTo(9876543210.0123456789m), "DecimalHighScale"); + + t.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/DriverTest/SqlClientDriverFixture.cs b/src/NHibernate.Test/DriverTest/SqlClientDriverFixture.cs index f96ece69a8e..094a2137c6f 100644 --- a/src/NHibernate.Test/DriverTest/SqlClientDriverFixture.cs +++ b/src/NHibernate.Test/DriverTest/SqlClientDriverFixture.cs @@ -1,46 +1,58 @@ using System; using System.Collections; +using System.Data; using NHibernate.Dialect; using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.SqlTypes; using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; namespace NHibernate.Test.DriverTest { public class MultiTypeEntity { - public MultiTypeEntity() - { - DateTimeProp = DateTime.Now; - } public virtual int Id { get; set; } public virtual string StringProp { get; set; } public virtual string AnsiStringProp { get; set; } public virtual decimal Decimal { get; set; } + public virtual decimal DecimalHighScale { get; set; } public virtual decimal Currency { get; set; } public virtual double Double { get; set; } public virtual float Float { get; set; } public virtual byte[] BinaryBlob { get; set; } public virtual byte[] Binary { get; set; } public virtual string StringClob { get; set; } - public virtual DateTime DateTimeProp { get; set; } + public virtual DateTime DateTimeProp { get; set; } = DateTime.Now; } [TestFixture] public class SqlClientDriverFixture : TestCase { - protected override string MappingsAssembly + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override IList Mappings => new[] { "DriverTest.MultiTypeEntity.hbm.xml" }; + + protected override bool AppliesTo(Dialect.Dialect dialect) { - get { return "NHibernate.Test"; } + return dialect is MsSql2008Dialect; } - protected override IList Mappings + protected override bool AppliesTo(ISessionFactoryImplementor factory) { - get { return new[] { "DriverTest.MultiTypeEntity.hbm.xml" }; } + return factory.ConnectionProvider.Driver is SqlClientDriver; } - protected override bool AppliesTo(Dialect.Dialect dialect) + protected override void OnTearDown() { - return dialect is MsSql2008Dialect; + base.OnTearDown(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from MultiTypeEntity").ExecuteUpdate(); + t.Commit(); + } } [Test] @@ -49,40 +61,63 @@ public void Crud() // Should use default dimension for CRUD op because the mapping does not // have dimensions specified. object savedId; - using (ISession s = OpenSession()) - using (ITransaction t = s.BeginTransaction()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - savedId = s.Save(new MultiTypeEntity - { - StringProp = "a", - StringClob = "a", - BinaryBlob = new byte[]{1,2,3}, - Binary = new byte[] { 4, 5, 6 }, - Currency = 123.4m, - Double = 123.5d, - Decimal = 789.5m - }); + savedId = s.Save( + new MultiTypeEntity + { + StringProp = "a", + StringClob = "a", + BinaryBlob = new byte[] { 1, 2, 3 }, + Binary = new byte[] { 4, 5, 6 }, + Currency = 123.4m, + Double = 123.5d, + Decimal = 789.5m, + DecimalHighScale = 1234567890.0123456789m + }); t.Commit(); } - using (ISession s = OpenSession()) - using (ITransaction t = s.BeginTransaction()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { var m = s.Get(savedId); + + Assert.That(m.StringProp, Is.EqualTo("a"), "StringProp"); + Assert.That(m.StringClob, Is.EqualTo("a"), "StringClob"); + Assert.That(m.BinaryBlob, Is.EqualTo(new byte[] { 1, 2, 3 }), "BinaryBlob"); + Assert.That(m.Binary, Is.EqualTo(new byte[] { 4, 5, 6 }), "BinaryBlob"); + Assert.That(m.Currency, Is.EqualTo(123.4m), "Currency"); + Assert.That(m.Double, Is.EqualTo(123.5d).Within(0.0001d), "Double"); + Assert.That(m.Decimal, Is.EqualTo(789.5m), "Decimal"); + Assert.That(m.DecimalHighScale, Is.EqualTo(1234567890.0123456789m), "DecimalHighScale"); + m.StringProp = "b"; m.StringClob = "b"; - m.BinaryBlob = new byte[] {4,5,6}; - m.Binary = new byte[] {7,8,9}; + m.BinaryBlob = new byte[] { 4, 5, 6 }; + m.Binary = new byte[] { 7, 8, 9 }; m.Currency = 456.78m; m.Double = 987.6d; m.Decimal = 1323456.45m; + m.DecimalHighScale = 9876543210.0123456789m; t.Commit(); } - using (ISession s = OpenSession()) - using (ITransaction t = s.BeginTransaction()) + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) { - s.CreateQuery("delete from MultiTypeEntity").ExecuteUpdate(); + var m = s.Load(savedId); + + Assert.That(m.StringProp, Is.EqualTo("b"), "StringProp"); + Assert.That(m.StringClob, Is.EqualTo("b"), "StringClob"); + Assert.That(m.BinaryBlob, Is.EqualTo(new byte[] { 4, 5, 6 }), "BinaryBlob"); + Assert.That(m.Binary, Is.EqualTo(new byte[] { 7, 8, 9 }), "BinaryBlob"); + Assert.That(m.Currency, Is.EqualTo(456.78m), "Currency"); + Assert.That(m.Double, Is.EqualTo(987.6d).Within(0.0001d), "Double"); + Assert.That(m.Decimal, Is.EqualTo(1323456.45m), "Decimal"); + Assert.That(m.DecimalHighScale, Is.EqualTo(9876543210.0123456789m), "DecimalHighScale"); + t.Commit(); } } @@ -90,9 +125,6 @@ public void Crud() [Test] public void QueryPlansAreReused() { - if (!(Sfi.ConnectionProvider.Driver is SqlClientDriver)) - Assert.Ignore("Test designed for SqlClientDriver only"); - using (ISession s = OpenSession()) using (ITransaction t = s.BeginTransaction()) { @@ -109,7 +141,7 @@ public void QueryPlansAreReused() var beforeCount = countPlansCommand.UniqueResult(); var insertCount = 10; - for (var i=0; i(); - Assert.That(afterCount - beforeCount, Is.LessThan(insertCount - 1), - string.Format("Excessive query plans created: before={0} after={1}", beforeCount, afterCount)); + Assert.That( + afterCount - beforeCount, + Is.LessThan(insertCount - 1), + $"Excessive query plans created: before={beforeCount} after={afterCount}"); t.Rollback(); } } + + [Test] + public void DefaultPrecisionScale() + { + const byte defaultPrecision = 28; + const byte defaultScale = 10; + var driver = Sfi.ConnectionProvider.Driver; + try + { + using (var cmd = new System.Data.SqlClient.SqlCommand()) + { + var p = driver.GenerateParameter(cmd, "p", SqlTypeFactory.Decimal); + Assert.That(p.Precision, Is.EqualTo(defaultPrecision), "no defaults"); + Assert.That(p.Scale, Is.EqualTo(defaultScale), "no defaults"); + p = driver.GenerateParameter(cmd, "p", SqlTypeFactory.GetSqlType(DbType.Decimal, 24, 11)); + Assert.That(p.Precision, Is.EqualTo(24), "explicit without defaults"); + Assert.That(p.Scale, Is.EqualTo(11), "explicit without defaults"); + + var configuration = TestConfigurationHelper.GetDefaultConfiguration(); + configuration.SetProperty(Environment.QueryDefaultCastPrecision, "26"); + driver.Configure(configuration.Properties); + p = driver.GenerateParameter(cmd, "p", SqlTypeFactory.Decimal); + Assert.That(p.Precision, Is.EqualTo(26), "default precision 26"); + Assert.That(p.Scale, Is.EqualTo(defaultScale), "default precision 26"); + p = driver.GenerateParameter(cmd, "p", SqlTypeFactory.GetSqlType(DbType.Decimal, 24, 11)); + Assert.That(p.Precision, Is.EqualTo(24), "explicit 24 with default precision 26"); + Assert.That(p.Scale, Is.EqualTo(11), "explicit 24 with default precision 26"); + + configuration.Properties.Remove(Environment.QueryDefaultCastPrecision); + configuration.SetProperty(Environment.QueryDefaultCastScale, "8"); + driver.Configure(configuration.Properties); + p = driver.GenerateParameter(cmd, "p", SqlTypeFactory.Decimal); + Assert.That(p.Precision, Is.EqualTo(defaultPrecision), "default scale 8"); + Assert.That(p.Scale, Is.EqualTo(8), "default scale 8"); + p = driver.GenerateParameter(cmd, "p", SqlTypeFactory.GetSqlType(DbType.Decimal, 24, 11)); + Assert.That(p.Precision, Is.EqualTo(24), "explicit with default scale 8"); + Assert.That(p.Scale, Is.EqualTo(11), "explicit with default scale 8"); + + configuration.SetProperty(Environment.QueryDefaultCastPrecision, "34"); + configuration.SetProperty(Environment.QueryDefaultCastScale, "15"); + driver.Configure(configuration.Properties); + p = driver.GenerateParameter(cmd, "p", SqlTypeFactory.Decimal); + Assert.That(p.Precision, Is.EqualTo(34), "default 34,15"); + Assert.That(p.Scale, Is.EqualTo(15), "default 34,15"); + p = driver.GenerateParameter(cmd, "p", SqlTypeFactory.GetSqlType(DbType.Decimal, 24, 11)); + Assert.That(p.Precision, Is.EqualTo(24), "explicit with default 34,15"); + Assert.That(p.Scale, Is.EqualTo(11), "explicit with default 34,15"); + } + } + finally + { + driver.Configure(cfg.GetDerivedProperties()); + } + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/TestDialect.cs b/src/NHibernate.Test/TestDialect.cs index f1e73367118..9d6c321ad15 100644 --- a/src/NHibernate.Test/TestDialect.cs +++ b/src/NHibernate.Test/TestDialect.cs @@ -32,6 +32,9 @@ public TestDialect(Dialect.Dialect dialect) public virtual bool SupportsFullJoin => true; + /// + /// Does the dialect lack a true handling of decimal? + /// public virtual bool HasBrokenDecimalType => false; public virtual bool SupportsNullCharactersInUtfStrings => true; diff --git a/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs b/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs index 22c65d41f2c..95c77873d2f 100644 --- a/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs +++ b/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs @@ -7,10 +7,10 @@ namespace NHibernate.Test.TestDialects { public class SQLiteTestDialect : TestDialect { - public SQLiteTestDialect(Dialect.Dialect dialect) - : base(dialect) - { - } + public SQLiteTestDialect(Dialect.Dialect dialect) + : base(dialect) + { + } public override bool SupportsOperatorAll { @@ -32,14 +32,17 @@ public override bool SupportsFullJoin get { return false; } } - public override bool HasBrokenDecimalType - { - get { return true; } - } + /// + /// SqlLite stores them as float instead. + /// + public override bool HasBrokenDecimalType + { + get { return true; } + } - public override bool SupportsHavingWithoutGroupBy - { - get { return false; } - } + public override bool SupportsHavingWithoutGroupBy + { + get { return false; } + } } } diff --git a/src/NHibernate.Test/TypesTest/DecimalClass.cs b/src/NHibernate.Test/TypesTest/DecimalClass.cs index 25a35dcbf83..7c23d10e3c9 100644 --- a/src/NHibernate.Test/TypesTest/DecimalClass.cs +++ b/src/NHibernate.Test/TypesTest/DecimalClass.cs @@ -1,29 +1,10 @@ -using System; - namespace NHibernate.Test.TypesTest { - /// - /// Summary description for DecimalClass. - /// public class DecimalClass { - private int _id; - private decimal _decimalValue; - - public DecimalClass() - { - } - - public int Id - { - get { return _id; } - set { _id = value; } - } + public int Id { get; set; } - public decimal DecimalValue - { - get { return _decimalValue; } - set { _decimalValue = value; } - } + public decimal DecimalValue { get; set; } + public decimal HighScaleDecimalValue { get; set; } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/TypesTest/DecimalClass.hbm.xml b/src/NHibernate.Test/TypesTest/DecimalClass.hbm.xml index a381b8e4201..52047a9bf5b 100644 --- a/src/NHibernate.Test/TypesTest/DecimalClass.hbm.xml +++ b/src/NHibernate.Test/TypesTest/DecimalClass.hbm.xml @@ -1,15 +1,16 @@ - + - - - - - - - - + + + + + + + + + diff --git a/src/NHibernate.Test/TypesTest/DecimalTypeFixture.cs b/src/NHibernate.Test/TypesTest/DecimalTypeFixture.cs index cbfa79dfb76..170400fa1a6 100644 --- a/src/NHibernate.Test/TypesTest/DecimalTypeFixture.cs +++ b/src/NHibernate.Test/TypesTest/DecimalTypeFixture.cs @@ -1,4 +1,5 @@ -using System; +using NHibernate.Cfg; +using NHibernate.Dialect; using NHibernate.Type; using NUnit.Framework; @@ -10,9 +11,50 @@ namespace NHibernate.Test.TypesTest [TestFixture] public class DecimalTypeFixture : TypeFixtureBase { - protected override string TypeName + protected override string TypeName => "Decimal"; + + private readonly DecimalType _type = NHibernateUtil.Decimal; + private const int _highScaleId = 2; + private const decimal _highScaleTestedValue = 123456789.123456789m; + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return base.AppliesTo(dialect) && !TestDialect.HasBrokenDecimalType; + } + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + + if (Dialect is FirebirdDialect) + { + configuration.SetProperty(Environment.QueryDefaultCastPrecision, "18"); + configuration.SetProperty(Environment.QueryDefaultCastScale, "9"); + } + } + + protected override void OnSetUp() { - get { return "Decimal"; } + base.OnSetUp(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Save(new DecimalClass { Id = _highScaleId, HighScaleDecimalValue = _highScaleTestedValue }); + t.Commit(); + } + } + + protected override void OnTearDown() + { + base.OnTearDown(); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.CreateQuery("delete from DecimalClass").ExecuteUpdate(); + t.Commit(); + } } /// @@ -22,50 +64,100 @@ protected override string TypeName [Test] public void Equals() { - decimal lhs = 5.64351M; - decimal rhs = 5.64351M; + const decimal lhs = 5.64351M; + var rhs = 5.64351M; - DecimalType type = (DecimalType) NHibernateUtil.Decimal; - Assert.IsTrue(type.IsEqual(lhs, rhs)); + Assert.That(_type.IsEqual(lhs, rhs), Is.True); // Test that two decimal fields that are equal except one has a higher precision than // the other one are returned as Equal by the DecimalType. rhs = 5.643510M; - Assert.IsTrue(type.IsEqual(lhs, rhs)); + Assert.That(_type.IsEqual(lhs, rhs), Is.True); } [Test] public void ReadWrite() { - decimal expected = 5.64351M; + const decimal expected = 5.64351M; - DecimalClass basic = new DecimalClass(); - basic.Id = 1; - basic.DecimalValue = expected; + var basic = new DecimalClass + { + Id = 1, + DecimalValue = expected + }; - ISession s = OpenSession(); - s.Save(basic); - s.Flush(); - s.Close(); + using (var s = OpenSession()) + { + s.Save(basic); + s.Flush(); + } - s = OpenSession(); - basic = (DecimalClass) s.Load(typeof(DecimalClass), 1); + using (var s = OpenSession()) + { + basic = s.Load(1); - Assert.AreEqual(expected, basic.DecimalValue); - Assert.AreEqual(5.643510M, basic.DecimalValue); - - s.Delete(basic); - s.Flush(); - s.Close(); + Assert.That(basic.DecimalValue, Is.EqualTo(expected)); + Assert.That(basic.DecimalValue, Is.EqualTo(5.643510M)); + } } [Test] public void UnsavedValue() { - DecimalType type = (DecimalType) NHibernateUtil.Decimal; - object mappedValue = type.StringToObject("0"); - Assert.AreEqual(0m, mappedValue); - Assert.IsTrue(type.IsEqual(mappedValue, 0m), "'0' in the mapping file should have been converted to a 0m"); + var mappedValue = _type.StringToObject("0"); + Assert.That(mappedValue, Is.EqualTo(0m)); + Assert.IsTrue(_type.IsEqual(mappedValue, 0m), "'0' in the mapping file should have been converted to a 0m"); + } + + [Test] + public void HighScaleParameterSelect() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var selectedValue = s + .CreateQuery("select dc.HighScaleDecimalValue + :d1 from DecimalClass dc") + .SetMaxResults(1) + .SetDecimal("d1", _highScaleTestedValue) + .UniqueResult(); + Assert.That(selectedValue, Is.EqualTo(_highScaleTestedValue * 2)); + t.Commit(); + } + } + + [Test] + public void HighScaleParameterFilter() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var count = s + .CreateQuery("select count(*) from DecimalClass dc where dc.HighScaleDecimalValue = :d1") + .SetDecimal("d1", _highScaleTestedValue) + .UniqueResult(); + Assert.That(count, Is.GreaterThanOrEqualTo(1)); + t.Commit(); + } + } + + [Test] + public void HighScaleParameterInequality() + { + if (!TestDialect.SupportsNonDataBoundCondition) + Assert.Ignore("Dialect does not support parameters comparison."); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var count = s + .CreateQuery("select count(*) from DecimalClass dc where :d1 != :d2") + .SetDecimal("d1", 123456789.123456789m) + // If truncation occurs before the last digit, the test will fail. + .SetDecimal("d2", 123456789.123456780m) + .UniqueResult(); + Assert.That(count, Is.GreaterThanOrEqualTo(1)); + t.Commit(); + } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs index 0a07ed0134c..a4b5075f13c 100644 --- a/src/NHibernate/Dialect/Dialect.cs +++ b/src/NHibernate/Dialect/Dialect.cs @@ -248,9 +248,9 @@ public virtual string GetLongestTypeName(DbType dbType) return _typeNames.GetLongest(dbType); } - protected int DefaultCastLength { get; set; } - protected byte DefaultCastPrecision { get; set; } - protected byte DefaultCastScale { get; set; } + public int DefaultCastLength { get; protected set; } + public byte DefaultCastPrecision { get; protected set; } + public byte DefaultCastScale { get; protected set; } /// /// Get the name of the database type appropriate for casting operations diff --git a/src/NHibernate/Driver/FirebirdClientDriver.cs b/src/NHibernate/Driver/FirebirdClientDriver.cs index 730d11a388d..751dbd1630c 100644 --- a/src/NHibernate/Driver/FirebirdClientDriver.cs +++ b/src/NHibernate/Driver/FirebirdClientDriver.cs @@ -65,7 +65,7 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq { var convertedSqlType = sqlType; if (convertedSqlType.DbType == DbType.Currency) - convertedSqlType = new SqlType(DbType.Decimal); + convertedSqlType = SqlTypeFactory.Decimal; base.InitializeParameter(dbParam, name, convertedSqlType); } diff --git a/src/NHibernate/Driver/SqlClientDriver.cs b/src/NHibernate/Driver/SqlClientDriver.cs index e3baabbd6f1..a4101d6ba9d 100644 --- a/src/NHibernate/Driver/SqlClientDriver.cs +++ b/src/NHibernate/Driver/SqlClientDriver.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.SqlClient; @@ -21,11 +22,24 @@ public class SqlClientDriver : DriverBase, IEmbeddedBatcherFactoryProvider public const int MaxSizeForLengthLimitedAnsiString = 8000; public const int MaxSizeForLengthLimitedString = 4000; public const int MaxSizeForLengthLimitedBinary = 8000; + // Since v5.1 + [Obsolete("This member has no more usages and will be removed in a future version")] public const byte MaxPrecision = 28; + // Since v5.1 + [Obsolete("This member has no more usages and will be removed in a future version")] public const byte MaxScale = 5; public const byte MaxDateTime2 = 8; public const byte MaxDateTimeOffset = 10; + private Dialect.Dialect _dialect; + + public override void Configure(IDictionary settings) + { + base.Configure(settings); + + _dialect = Dialect.Dialect.GetDialect(settings); + } + /// /// Creates an uninitialized object for /// the SqlClientDriver. @@ -97,9 +111,50 @@ public override bool SupportsMultipleOpenReaders protected override void InitializeParameter(DbParameter dbParam, string name, SqlType sqlType) { base.InitializeParameter(dbParam, name, sqlType); - SetVariableLengthParameterSize(dbParam, sqlType); + + // Defaults size/precision/scale + switch (dbParam.DbType) + { + case DbType.AnsiString: + case DbType.AnsiStringFixedLength: + dbParam.Size = IsAnsiText(dbParam, sqlType) ? MaxSizeForAnsiClob : MaxSizeForLengthLimitedAnsiString; + break; + case DbType.Binary: + dbParam.Size = IsBlob(dbParam, sqlType) ? MaxSizeForBlob : MaxSizeForLengthLimitedBinary; + break; + case DbType.Decimal: + if (_dialect == null) + throw new InvalidOperationException("Dialect not available, is this driver used without having been configured?"); + dbParam.Precision = _dialect.DefaultCastPrecision; + dbParam.Scale = _dialect.DefaultCastScale; + break; + case DbType.String: + case DbType.StringFixedLength: + dbParam.Size = IsText(dbParam, sqlType) ? MaxSizeForClob : MaxSizeForLengthLimitedString; + break; + case DbType.DateTime2: + dbParam.Size = MaxDateTime2; + break; + case DbType.DateTimeOffset: + dbParam.Size = MaxDateTimeOffset; + break; + case DbType.Xml: + dbParam.Size = MaxSizeForXml; + break; + } + + // Do not override the default length for string using data from SqlType, since LIKE expressions needs + // larger columns. https://nhibernate.jira.com/browse/NH-3036 + + if (sqlType.PrecisionDefined) + { + dbParam.Precision = sqlType.Precision; + dbParam.Scale = sqlType.Scale; + } } + // Since v5.1 + [Obsolete("This method has no more usages and will be removed in a future version")] public static void SetVariableLengthParameterSize(DbParameter dbParam, SqlType sqlType) { SetDefaultParameterSize(dbParam, sqlType); @@ -118,13 +173,15 @@ public static void SetVariableLengthParameterSize(DbParameter dbParam, SqlType s } } + // Since v5.1 + [Obsolete("This method has no more usages and will be removed in a future version")] protected static void SetDefaultParameterSize(DbParameter dbParam, SqlType sqlType) { switch (dbParam.DbType) { case DbType.AnsiString: case DbType.AnsiStringFixedLength: - dbParam.Size = IsAnsiText(dbParam, sqlType) ? MaxSizeForAnsiClob : MaxSizeForLengthLimitedAnsiString; + dbParam.Size = IsAnsiText(dbParam, sqlType) ? MaxSizeForAnsiClob : MaxSizeForLengthLimitedAnsiString; break; case DbType.Binary: dbParam.Size = IsBlob(dbParam, sqlType) ? MaxSizeForBlob : MaxSizeForLengthLimitedBinary; @@ -149,16 +206,16 @@ protected static void SetDefaultParameterSize(DbParameter dbParam, SqlType sqlTy } } - /// - /// Interprets if a parameter is a Clob (for the purposes of setting its default size) - /// - /// The parameter - /// The of the parameter - /// True, if the parameter should be interpreted as a Clob, otherwise False - protected static bool IsAnsiText(DbParameter dbParam, SqlType sqlType) - { - return ((DbType.AnsiString == dbParam.DbType || DbType.AnsiStringFixedLength == dbParam.DbType) && sqlType.LengthDefined && (sqlType.Length > MaxSizeForLengthLimitedAnsiString)); - } + /// + /// Interprets if a parameter is a Clob (for the purposes of setting its default size) + /// + /// The parameter + /// The of the parameter + /// True, if the parameter should be interpreted as a Clob, otherwise False + protected static bool IsAnsiText(DbParameter dbParam, SqlType sqlType) + { + return ((DbType.AnsiString == dbParam.DbType || DbType.AnsiStringFixedLength == dbParam.DbType) && sqlType.LengthDefined && (sqlType.Length > MaxSizeForLengthLimitedAnsiString)); + } /// /// Interprets if a parameter is a Clob (for the purposes of setting its default size)