diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs new file mode 100644 index 00000000000..4ddde3d2a41 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +// +// 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 NHibernate.Dialect; +using NHibernate.SqlTypes; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam +{ + using System.Threading.Tasks; + [TestFixture] + public class SqlQueryParamTypeFixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return + //Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String + (Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String)) + || Dialect is SQLiteDialect); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public async Task AppliesParameterTypeFromQueryParamAsync() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + await (s.GetNamedQuery("entityIdByName").SetParameter("name", "Bob").UniqueResultAsync()); + Assert.That(log.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Entity.cs new file mode 100644 index 00000000000..3dba8b9c90b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Entity.cs @@ -0,0 +1,10 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam +{ + class Entity + { + public virtual long Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Mappings.hbm.xml new file mode 100644 index 00000000000..028a77a8a61 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Mappings.hbm.xml @@ -0,0 +1,14 @@ + + + + + + + + + + select s.Id from Entity s where s.Name = :name + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs new file mode 100644 index 00000000000..c7c5f1967f0 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs @@ -0,0 +1,50 @@ +using System.Data; +using NHibernate.Dialect; +using NHibernate.SqlTypes; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam +{ + [TestFixture] + public class SqlQueryParamTypeFixture : BugTestCase + { + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return + //Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String + (Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String)) + || Dialect is SQLiteDialect); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public void AppliesParameterTypeFromQueryParam() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + s.GetNamedQuery("entityIdByName").SetParameter("name", "Bob").UniqueResult(); + Assert.That(log.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } +} diff --git a/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs index e9bb3f89081..d802bb534c2 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using NHibernate.Cfg.MappingSchema; using NHibernate.Engine; using NHibernate.Util; @@ -33,7 +34,11 @@ public void AddSqlQuery(HbmSqlQuery querySchema) ? querySchema.cachemode.ToCacheMode() : null; - var parameterTypes = new LinkedHashMap(); + var parameterTypes = + querySchema.Items.EmptyIfNull().OfType() + .Where(x => !string.IsNullOrEmpty(x.type)) + .ToDictionary(x => x.name, x => x.type); + var synchronizedTables = GetSynchronizedTables(querySchema); NamedSQLQueryDefinition namedQuery; diff --git a/src/NHibernate/Engine/Query/QueryPlanCache.cs b/src/NHibernate/Engine/Query/QueryPlanCache.cs index c2b8cd9e1f6..33b260ed39e 100644 --- a/src/NHibernate/Engine/Query/QueryPlanCache.cs +++ b/src/NHibernate/Engine/Query/QueryPlanCache.cs @@ -5,6 +5,7 @@ using NHibernate.Engine.Query.Sql; using NHibernate.Hql; using NHibernate.Linq; +using NHibernate.Type; using NHibernate.Util; namespace NHibernate.Engine.Query @@ -40,6 +41,11 @@ public QueryPlanCache(ISessionFactoryImplementor factory) } public ParameterMetadata GetSQLParameterMetadata(string query) + { + return GetSQLParameterMetadata(query, CollectionHelper.EmptyDictionary()); + } + + public ParameterMetadata GetSQLParameterMetadata(string query, IDictionary parameterTypes) { var metadata = (ParameterMetadata)sqlParamMetadataCache[query]; if (metadata == null) @@ -49,7 +55,7 @@ public ParameterMetadata GetSQLParameterMetadata(string query) // retrieval for a native-sql query depends on all of the return // types having been set, which might not be the case up-front when // param metadata would be most useful - metadata = BuildNativeSQLParameterMetadata(query); + metadata = BuildNativeSQLParameterMetadata(query, parameterTypes); sqlParamMetadataCache.Put(query, metadata); } return metadata; @@ -170,14 +176,14 @@ public NativeSQLQueryPlan GetNativeSQLQueryPlan(NativeSQLQuerySpecification spec return plan; } - private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString) + private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString, + IDictionary parameterTypes) { ParamLocationRecognizer recognizer = ParamLocationRecognizer.ParseLocations(sqlString); var ordinalDescriptors = new OrdinalParameterDescriptor[recognizer.OrdinalParameterLocationList.Count]; - for (int i = 0; i < recognizer.OrdinalParameterLocationList.Count; i++) + for (int i = 0; i < ordinalDescriptors.Length; i++) { - int position = recognizer.OrdinalParameterLocationList[i]; ordinalDescriptors[i] = new OrdinalParameterDescriptor(i, null); } @@ -187,8 +193,14 @@ private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString) { string name = entry.Key; ParamLocationRecognizer.NamedParameterDescription description = entry.Value; + IType expectedType = null; + if (parameterTypes.TryGetValue(name, out var type) && !string.IsNullOrEmpty(type)) + { + expectedType = TypeFactory.HeuristicType(type); + } + namedParamDescriptorMap[name] = - new NamedParameterDescriptor(name, null, description.JpaStyle); + new NamedParameterDescriptor(name, expectedType, description.JpaStyle); } return new ParameterMetadata(ordinalDescriptors, namedParamDescriptorMap); diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index d2fd81b1b72..4fe9c9ae598 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -299,7 +299,7 @@ public virtual IQuery GetNamedSQLQuery(string name) throw new MappingException("Named SQL query not known: " + name); } var query = new SqlQueryImpl(nsqlqd, this, - _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString)); + _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString, nsqlqd.ParameterTypes)); query.SetComment("named native SQL query " + name); InitQuery(query, nsqlqd); return query; @@ -378,7 +378,7 @@ public virtual IQuery GetNamedQuery(string queryName) throw new MappingException("Named query not known: " + queryName); } query = new SqlQueryImpl(nsqlqd, this, - _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString)); + _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString, nsqlqd.ParameterTypes)); query.SetComment("named native SQL query " + queryName); nqd = nsqlqd; }