diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3403/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3403/Fixture.cs new file mode 100644 index 00000000000..570be6870fb --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3403/Fixture.cs @@ -0,0 +1,188 @@ +//------------------------------------------------------------------------------ +// +// 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.Data; +using System.Data.SqlClient; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Criterion; +using NHibernate.Dialect; +using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.Exceptions; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3403 +{ + using System.Threading.Tasks; + using System.Threading; + [TestFixture] + public class FixtureAsync : BugTestCase + { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect is MsSql2000Dialect; + } + + protected override bool AppliesTo(ISessionFactoryImplementor factory) + { + return factory.ConnectionProvider.Driver is SqlClientDriver; + } + + protected override void Configure(Configuration configuration) + { + cfg.SetProperty(Environment.ConnectionDriver, typeof(TestSqlClientDriver).AssemblyQualifiedName); + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + base.OnSetUp(); + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Bob" }; + session.Save(e1); + transaction.Commit(); + } + } + + [Test] + public async Task InsertShouldUseMappedSizeAsync() + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Al", AnsiName = "Al" }; + await (session.SaveAsync(e1)); + await (transaction.CommitAsync()); + Assert.AreEqual(SqlDbType.NVarChar, Driver.LastCommandParameters.First().SqlDbType); + Assert.AreEqual(3, Driver.LastCommandParameters.First().Size); + Assert.AreEqual(SqlDbType.VarChar, Driver.LastCommandParameters.Last().SqlDbType); + Assert.AreEqual(3, Driver.LastCommandParameters.Last().Size); + } + } + + [Test] + public void InsertWithTooLongValuesShouldThrowAsync() + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Alal", AnsiName = "Alal" }; + + var ex = Assert.ThrowsAsync( + async () => + { + await (session.SaveAsync(e1)); + await (transaction.CommitAsync()); + }); + + var sqlEx = ex.InnerException as SqlException; + Assert.IsNotNull(sqlEx); + Assert.That(sqlEx.Number, Is.EqualTo(8152)); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public async Task LinqEqualsShouldUseMappedSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + if (property == "Name") + { + await (session.Query().Where(x => x.Name == "Bob").ToListAsync(cancellationToken)); + } + else + { + await (session.Query().Where(x => x.AnsiName == "Bob").ToListAsync(cancellationToken)); + } + Assert.AreEqual(3, Driver.LastCommandParameters.First().Size); + Assert.AreEqual(expectedDbType, Driver.LastCommandParameters.First().SqlDbType); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public async Task HqlLikeShouldUseLargerSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + await (session.CreateQuery("from Entity where " + property + " like :name").SetParameter("name", "%Bob%").ListAsync(cancellationToken)); + + Assert.GreaterOrEqual(Driver.LastCommandParameters.First().Size, 5); + Assert.AreEqual(expectedDbType, Driver.LastCommandParameters.First().SqlDbType); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public async Task CriteriaEqualsShouldUseMappedSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + Driver.ClearCommands(); + + await (session.CreateCriteria().Add(Restrictions.Eq(property, "Bob")) + .ListAsync(cancellationToken)); + + Assert.GreaterOrEqual(Driver.LastCommandParameters.First().Size, 3); + Assert.AreEqual(expectedDbType, Driver.LastCommandParameters.First().SqlDbType); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public async Task CriteriaLikeShouldUseLargerSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + await (session.CreateCriteria().Add(Restrictions.Like(property, "%Bob%")) + .ListAsync(cancellationToken)); + + Assert.GreaterOrEqual(Driver.LastCommandParameters.First().Size, 5); + Assert.AreEqual(expectedDbType, Driver.LastCommandParameters.First().SqlDbType); + } + } + private TestSqlClientDriver Driver + { + get { return Sfi.ConnectionProvider.Driver as TestSqlClientDriver; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3403/Entity.cs b/src/NHibernate.Test/NHSpecificTest/NH3403/Entity.cs new file mode 100644 index 00000000000..a815e99c7f7 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3403/Entity.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.NHSpecificTest.NH3403 +{ + class Entity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual string AnsiName { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3403/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3403/Fixture.cs new file mode 100644 index 00000000000..6059b51630f --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3403/Fixture.cs @@ -0,0 +1,176 @@ +using System.Data; +using System.Data.SqlClient; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Criterion; +using NHibernate.Dialect; +using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.Exceptions; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3403 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect is MsSql2000Dialect; + } + + protected override bool AppliesTo(ISessionFactoryImplementor factory) + { + return factory.ConnectionProvider.Driver is SqlClientDriver; + } + + protected override void Configure(Configuration configuration) + { + cfg.SetProperty(Environment.ConnectionDriver, typeof(TestSqlClientDriver).AssemblyQualifiedName); + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + base.OnSetUp(); + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Bob" }; + session.Save(e1); + transaction.Commit(); + } + } + + [Test] + public void InsertShouldUseMappedSize() + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Al", AnsiName = "Al" }; + session.Save(e1); + transaction.Commit(); + Assert.AreEqual(SqlDbType.NVarChar, Driver.LastCommandParameters.First().SqlDbType); + Assert.AreEqual(3, Driver.LastCommandParameters.First().Size); + Assert.AreEqual(SqlDbType.VarChar, Driver.LastCommandParameters.Last().SqlDbType); + Assert.AreEqual(3, Driver.LastCommandParameters.Last().Size); + } + } + + [Test] + public void InsertWithTooLongValuesShouldThrow() + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Alal", AnsiName = "Alal" }; + + var ex = Assert.Throws( + () => + { + session.Save(e1); + transaction.Commit(); + }); + + var sqlEx = ex.InnerException as SqlException; + Assert.IsNotNull(sqlEx); + Assert.That(sqlEx.Number, Is.EqualTo(8152)); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public void LinqEqualsShouldUseMappedSize(string property, SqlDbType expectedDbType) + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + if (property == "Name") + { + session.Query().Where(x => x.Name == "Bob").ToList(); + } + else + { + session.Query().Where(x => x.AnsiName == "Bob").ToList(); + } + Assert.AreEqual(3, Driver.LastCommandParameters.First().Size); + Assert.AreEqual(expectedDbType, Driver.LastCommandParameters.First().SqlDbType); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public void HqlLikeShouldUseLargerSize(string property, SqlDbType expectedDbType) + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.CreateQuery("from Entity where " + property + " like :name").SetParameter("name", "%Bob%").List(); + + Assert.GreaterOrEqual(Driver.LastCommandParameters.First().Size, 5); + Assert.AreEqual(expectedDbType, Driver.LastCommandParameters.First().SqlDbType); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public void CriteriaEqualsShouldUseMappedSize(string property, SqlDbType expectedDbType) + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + Driver.ClearCommands(); + + session.CreateCriteria().Add(Restrictions.Eq(property, "Bob")) + .List(); + + Assert.GreaterOrEqual(Driver.LastCommandParameters.First().Size, 3); + Assert.AreEqual(expectedDbType, Driver.LastCommandParameters.First().SqlDbType); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public void CriteriaLikeShouldUseLargerSize(string property, SqlDbType expectedDbType) + { + Driver.ClearCommands(); + + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.CreateCriteria().Add(Restrictions.Like(property, "%Bob%")) + .List(); + + Assert.GreaterOrEqual(Driver.LastCommandParameters.First().Size, 5); + Assert.AreEqual(expectedDbType, Driver.LastCommandParameters.First().SqlDbType); + } + } + private TestSqlClientDriver Driver + { + get { return Sfi.ConnectionProvider.Driver as TestSqlClientDriver; } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3403/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH3403/Mappings.hbm.xml new file mode 100644 index 00000000000..90e9cd8c42e --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3403/Mappings.hbm.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/NH3403/TestSqlClientDriver.cs b/src/NHibernate.Test/NHSpecificTest/NH3403/TestSqlClientDriver.cs new file mode 100644 index 00000000000..c89bae725da --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3403/TestSqlClientDriver.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using System.Linq; +using NHibernate.Driver; + +namespace NHibernate.Test.NHSpecificTest.NH3403 +{ + public class TestSqlClientDriver : SqlClientDriver + { + public List LastCommandParameters { get; private set; }=new List(); + + public override void AdjustCommand(DbCommand command) + { + base.AdjustCommand(command); + LastCommandParameters = command.Parameters.OfType().ToList(); + } + public void ClearCommands() + { + LastCommandParameters.Clear(); + } + } +} diff --git a/src/NHibernate/AdoNet/IParameterAdjuster.cs b/src/NHibernate/AdoNet/IParameterAdjuster.cs new file mode 100644 index 00000000000..4211b2a5e37 --- /dev/null +++ b/src/NHibernate/AdoNet/IParameterAdjuster.cs @@ -0,0 +1,11 @@ +using System.Data.Common; +using NHibernate.SqlTypes; +using NHibernate.Type; + +namespace NHibernate.AdoNet +{ + internal interface IParameterAdjuster + { + void AdjustParameterForValue(DbParameter parameter, SqlType sqlType, object value); + } +} diff --git a/src/NHibernate/Driver/DriverExtensions.cs b/src/NHibernate/Driver/DriverExtensions.cs new file mode 100644 index 00000000000..ef7b4a3df78 --- /dev/null +++ b/src/NHibernate/Driver/DriverExtensions.cs @@ -0,0 +1,16 @@ +using System.Data.Common; +using NHibernate.AdoNet; +using NHibernate.SqlTypes; +using NHibernate.Type; + +namespace NHibernate.Driver +{ + internal static class DriverExtensions + { + internal static void AdjustParameterForValue(this IDriver driver, DbParameter parameter, SqlType sqlType, object value) + { + var adjustingDriver = driver as IParameterAdjuster; + adjustingDriver?.AdjustParameterForValue(parameter, sqlType, value); + } + } +} diff --git a/src/NHibernate/Driver/SqlClientDriver.cs b/src/NHibernate/Driver/SqlClientDriver.cs index 679fe397bc4..e1d84514a6c 100644 --- a/src/NHibernate/Driver/SqlClientDriver.cs +++ b/src/NHibernate/Driver/SqlClientDriver.cs @@ -9,6 +9,7 @@ using NHibernate.Dialect; using NHibernate.Engine; using NHibernate.SqlTypes; +using NHibernate.Type; namespace NHibernate.Driver { @@ -17,9 +18,9 @@ namespace NHibernate.Driver /// public class SqlClientDriver #if NETFX - : DriverBase, IEmbeddedBatcherFactoryProvider + : DriverBase, IEmbeddedBatcherFactoryProvider, IParameterAdjuster #else - : ReflectionBasedDriver, IEmbeddedBatcherFactoryProvider + : ReflectionBasedDriver, IEmbeddedBatcherFactoryProvider, IParameterAdjuster #endif { // Since v5.1 @@ -301,5 +302,23 @@ public override bool SupportsMultipleQueries /// public override DateTime MinDate => new DateTime(1753, 1, 1); + + public void AdjustParameterForValue(DbParameter parameter, SqlType sqlType, object value) + { + if (value is string stringVal) + { + switch (parameter.DbType) + { + case DbType.AnsiString: + case DbType.AnsiStringFixedLength: + parameter.Size = IsAnsiText(parameter, sqlType) ? MsSql2000Dialect.MaxSizeForAnsiClob : Math.Max(stringVal.Length, sqlType.LengthDefined ? sqlType.Length : parameter.Size); + break; + case DbType.String: + case DbType.StringFixedLength: + parameter.Size = IsText(parameter, sqlType) ? MsSql2000Dialect.MaxSizeForClob : Math.Max(stringVal.Length, sqlType.LengthDefined ? sqlType.Length : parameter.Size); + break; + } + } + } } } diff --git a/src/NHibernate/Type/AbstractStringType.cs b/src/NHibernate/Type/AbstractStringType.cs index 505eed0ee0a..a34bb83bba4 100644 --- a/src/NHibernate/Type/AbstractStringType.cs +++ b/src/NHibernate/Type/AbstractStringType.cs @@ -1,5 +1,6 @@ using System; using System.Data.Common; +using NHibernate.Driver; using NHibernate.Engine; using NHibernate.SqlTypes; @@ -17,6 +18,9 @@ public override void Set(DbCommand cmd, object value, int index, ISessionImpleme { var parameter = cmd.Parameters[index]; + //Allow the driver to adjust the parameter for the value + session.Factory.ConnectionProvider.Driver.AdjustParameterForValue(parameter, SqlType, value); + // set the parameter value before the size check, since ODBC changes the size automatically parameter.Value = value;