diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1300/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1300/Fixture.cs new file mode 100644 index 00000000000..11f20de84c2 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1300/Fixture.cs @@ -0,0 +1,212 @@ +//------------------------------------------------------------------------------ +// +// 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.Connection; +using NHibernate.Criterion; +using NHibernate.Dialect; +using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.Exceptions; +using NHibernate.Linq; +using NHibernate.Type; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1300 +{ + 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) + { + using (var cp = ConnectionProviderFactory.NewConnectionProvider(cfg.Properties)) + { + if (cp.Driver is SqlClientDriver) + { + configuration.SetProperty(Environment.ConnectionDriver, typeof(TestSqlClientDriver).AssemblyQualifiedName); + } + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + base.OnSetUp(); + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Bob" }; + session.Save(e1); + transaction.Commit(); + } + } + + [Test] + public async Task InsertShouldUseMappedSizeAsync() + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "1", AnsiName = "2", FullText = "3", AnsiFullText = "4" }; + await (session.SaveAsync(e1)); + await (transaction.CommitAsync()); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "1").SqlDbType, Is.EqualTo(SqlDbType.NVarChar)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "1").Size, Is.EqualTo(3)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "2").SqlDbType, Is.EqualTo(SqlDbType.VarChar)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "2").Size, Is.EqualTo(3)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "3").SqlDbType, Is.EqualTo(SqlDbType.NVarChar)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "3").Size, Is.EqualTo(MsSql2000Dialect.MaxSizeForClob)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "4").SqlDbType, Is.EqualTo(SqlDbType.VarChar)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "4").Size, Is.EqualTo(MsSql2000Dialect.MaxSizeForAnsiClob)); + } + } + + [Test] + public void InsertWithTooLongValuesShouldThrowAsync() + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var 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.That(sqlEx, Is.Not.Null); + 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 (var session = OpenSession()) + using (var 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.That(Driver.LastCommandParameters.First().Size, Is.EqualTo(3)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); + } + } + + [Test] + public async Task MappedAsShouldUseExplicitSizeAsync() + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + await (session.Query().Where(x => x.Name == "Bob".MappedAs(TypeFactory.Basic("AnsiString(200)"))).ToListAsync()); + + Assert.That(Driver.LastCommandParameters.First().Size, Is.EqualTo(200)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(SqlDbType.VarChar)); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public async Task HqlLikeShouldUseLargerSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + await (session.CreateQuery("from Entity where " + property + " like :name").SetParameter("name", "%Bob%").ListAsync(cancellationToken)); + + Assert.That(Driver.LastCommandParameters.First().Size, Is.GreaterThanOrEqualTo(5)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public async Task CriteriaEqualsShouldUseMappedSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Driver.ClearCommands(); + + await (session.CreateCriteria().Add(Restrictions.Eq(property, "Bob")) + .ListAsync(cancellationToken)); + + Assert.That(Driver.LastCommandParameters.First().Size, Is.GreaterThanOrEqualTo(3)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public async Task CriteriaLikeShouldUseLargerSizeAsync(string property, SqlDbType expectedDbType, CancellationToken cancellationToken = default(CancellationToken)) + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + await (session.CreateCriteria().Add(Restrictions.Like(property, "%Bob%")) + .ListAsync(cancellationToken)); + + Assert.That(Driver.LastCommandParameters.First().Size, Is.GreaterThanOrEqualTo(5)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); + } + } + private TestSqlClientDriver Driver => Sfi.ConnectionProvider.Driver as TestSqlClientDriver; + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1300/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH1300/Entity.cs new file mode 100644 index 00000000000..abd53ce2050 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1300/Entity.cs @@ -0,0 +1,11 @@ +namespace NHibernate.Test.NHSpecificTest.GH1300 +{ + class Entity + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual string AnsiName { get; set; } + public virtual string FullText { get; set; } + public virtual string AnsiFullText { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1300/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1300/Fixture.cs new file mode 100644 index 00000000000..8f994c19117 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1300/Fixture.cs @@ -0,0 +1,200 @@ +using System.Data; +using System.Data.SqlClient; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Connection; +using NHibernate.Criterion; +using NHibernate.Dialect; +using NHibernate.Driver; +using NHibernate.Engine; +using NHibernate.Exceptions; +using NHibernate.Linq; +using NHibernate.Type; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1300 +{ + [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) + { + using (var cp = ConnectionProviderFactory.NewConnectionProvider(cfg.Properties)) + { + if (cp.Driver is SqlClientDriver) + { + configuration.SetProperty(Environment.ConnectionDriver, typeof(TestSqlClientDriver).AssemblyQualifiedName); + } + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnSetUp() + { + base.OnSetUp(); + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "Bob" }; + session.Save(e1); + transaction.Commit(); + } + } + + [Test] + public void InsertShouldUseMappedSize() + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity { Name = "1", AnsiName = "2", FullText = "3", AnsiFullText = "4" }; + session.Save(e1); + transaction.Commit(); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "1").SqlDbType, Is.EqualTo(SqlDbType.NVarChar)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "1").Size, Is.EqualTo(3)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "2").SqlDbType, Is.EqualTo(SqlDbType.VarChar)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "2").Size, Is.EqualTo(3)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "3").SqlDbType, Is.EqualTo(SqlDbType.NVarChar)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "3").Size, Is.EqualTo(MsSql2000Dialect.MaxSizeForClob)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "4").SqlDbType, Is.EqualTo(SqlDbType.VarChar)); + Assert.That(Driver.LastCommandParameters.Single(x => (string) x.Value == "4").Size, Is.EqualTo(MsSql2000Dialect.MaxSizeForAnsiClob)); + } + } + + [Test] + public void InsertWithTooLongValuesShouldThrow() + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var 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.That(sqlEx, Is.Not.Null); + 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 (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + if (property == "Name") + { + session.Query().Where(x => x.Name == "Bob").ToList(); + } + else + { + session.Query().Where(x => x.AnsiName == "Bob").ToList(); + } + Assert.That(Driver.LastCommandParameters.First().Size, Is.EqualTo(3)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); + } + } + + [Test] + public void MappedAsShouldUseExplicitSize() + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Query().Where(x => x.Name == "Bob".MappedAs(TypeFactory.Basic("AnsiString(200)"))).ToList(); + + Assert.That(Driver.LastCommandParameters.First().Size, Is.EqualTo(200)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(SqlDbType.VarChar)); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public void HqlLikeShouldUseLargerSize(string property, SqlDbType expectedDbType) + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("from Entity where " + property + " like :name").SetParameter("name", "%Bob%").List(); + + Assert.That(Driver.LastCommandParameters.First().Size, Is.GreaterThanOrEqualTo(5)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public void CriteriaEqualsShouldUseMappedSize(string property, SqlDbType expectedDbType) + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + Driver.ClearCommands(); + + session.CreateCriteria().Add(Restrictions.Eq(property, "Bob")) + .List(); + + Assert.That(Driver.LastCommandParameters.First().Size, Is.GreaterThanOrEqualTo(3)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); + } + } + + [TestCase("Name", SqlDbType.NVarChar)] + [TestCase("AnsiName", SqlDbType.VarChar)] + public void CriteriaLikeShouldUseLargerSize(string property, SqlDbType expectedDbType) + { + Driver.ClearCommands(); + + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateCriteria().Add(Restrictions.Like(property, "%Bob%")) + .List(); + + Assert.That(Driver.LastCommandParameters.First().Size, Is.GreaterThanOrEqualTo(5)); + Assert.That(Driver.LastCommandParameters.First().SqlDbType, Is.EqualTo(expectedDbType)); + } + } + private TestSqlClientDriver Driver => Sfi.ConnectionProvider.Driver as TestSqlClientDriver; + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1300/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1300/Mappings.hbm.xml new file mode 100644 index 00000000000..6c828e207ea --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1300/Mappings.hbm.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH1300/TestSqlClientDriver.cs b/src/NHibernate.Test/NHSpecificTest/GH1300/TestSqlClientDriver.cs new file mode 100644 index 00000000000..40fe814c262 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1300/TestSqlClientDriver.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Data.Common; +using System.Data.SqlClient; +using System.Linq; +using NHibernate.Driver; + +namespace NHibernate.Test.NHSpecificTest.GH1300 +{ + 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..23121520d89 --- /dev/null +++ b/src/NHibernate/AdoNet/IParameterAdjuster.cs @@ -0,0 +1,22 @@ +using System.Data.Common; +using NHibernate.Driver; +using NHibernate.SqlTypes; + +namespace NHibernate.AdoNet +{ + /// + /// Supports adjusting a according to a and + /// the parameter's value. An may implement this interface. + /// + public interface IParameterAdjuster + { + /// + /// Adjust the provided parameter according to its and + /// . + /// + /// The parameter to adjust. + /// The parameter's . + /// The parameter's value. + 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..81d46097a50 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,10 +18,11 @@ namespace NHibernate.Driver /// public class SqlClientDriver #if NETFX - : DriverBase, IEmbeddedBatcherFactoryProvider + : DriverBase, #else - : ReflectionBasedDriver, IEmbeddedBatcherFactoryProvider + : ReflectionBasedDriver, #endif + IEmbeddedBatcherFactoryProvider, IParameterAdjuster { // Since v5.1 [Obsolete("Use MsSql2000Dialect.MaxSizeForAnsiClob")] @@ -301,5 +303,23 @@ public override bool SupportsMultipleQueries /// public override DateTime MinDate => new DateTime(1753, 1, 1); + + public virtual 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 b8a5ade41ad..267ccec6da5 100644 --- a/src/NHibernate/Type/AbstractStringType.cs +++ b/src/NHibernate/Type/AbstractStringType.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Globalization; +using NHibernate.Driver; using NHibernate.Engine; using NHibernate.SqlTypes; using NHibernate.UserTypes; @@ -57,6 +58,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;