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;
}