Skip to content

Commit 4308ec9

Browse files
authored
Fix NamedSQLQuery ignores query-param type (#3404)
Fixes #3311
1 parent d1a7e10 commit 4308ec9

File tree

7 files changed

+160
-8
lines changed

7 files changed

+160
-8
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Data;
12+
using NHibernate.Dialect;
13+
using NHibernate.SqlTypes;
14+
using NUnit.Framework;
15+
16+
namespace NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam
17+
{
18+
using System.Threading.Tasks;
19+
[TestFixture]
20+
public class SqlQueryParamTypeFixtureAsync : BugTestCase
21+
{
22+
protected override void OnSetUp()
23+
{
24+
using var session = OpenSession();
25+
using var transaction = session.BeginTransaction();
26+
var e1 = new Entity {Name = "Bob"};
27+
session.Save(e1);
28+
29+
var e2 = new Entity {Name = "Sally"};
30+
session.Save(e2);
31+
32+
transaction.Commit();
33+
}
34+
35+
protected override bool AppliesTo(Dialect.Dialect dialect)
36+
{
37+
return
38+
//Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String
39+
(Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String))
40+
|| Dialect is SQLiteDialect);
41+
}
42+
43+
protected override void OnTearDown()
44+
{
45+
using var session = OpenSession();
46+
using var transaction = session.BeginTransaction();
47+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
48+
49+
transaction.Commit();
50+
}
51+
52+
[Test]
53+
public async Task AppliesParameterTypeFromQueryParamAsync()
54+
{
55+
using var log = new SqlLogSpy();
56+
using var s = OpenSession();
57+
await (s.GetNamedQuery("entityIdByName").SetParameter("name", "Bob").UniqueResultAsync<object>());
58+
Assert.That(log.GetWholeLog(), Does.Contain("Type: AnsiString"));
59+
}
60+
}
61+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam
4+
{
5+
class Entity
6+
{
7+
public virtual long Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
3+
namespace="NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam">
4+
5+
<class name="Entity">
6+
<id name="Id" generator="native" />
7+
<property name="Name" type="AnsiString" />
8+
</class>
9+
<sql-query name="entityIdByName">
10+
<query-param name="name" type="AnsiString" />
11+
select s.Id from Entity s where s.Name = :name
12+
</sql-query>
13+
14+
</hibernate-mapping>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System.Data;
2+
using NHibernate.Dialect;
3+
using NHibernate.SqlTypes;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam
7+
{
8+
[TestFixture]
9+
public class SqlQueryParamTypeFixture : BugTestCase
10+
{
11+
protected override void OnSetUp()
12+
{
13+
using var session = OpenSession();
14+
using var transaction = session.BeginTransaction();
15+
var e1 = new Entity {Name = "Bob"};
16+
session.Save(e1);
17+
18+
var e2 = new Entity {Name = "Sally"};
19+
session.Save(e2);
20+
21+
transaction.Commit();
22+
}
23+
24+
protected override bool AppliesTo(Dialect.Dialect dialect)
25+
{
26+
return
27+
//Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String
28+
(Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String))
29+
|| Dialect is SQLiteDialect);
30+
}
31+
32+
protected override void OnTearDown()
33+
{
34+
using var session = OpenSession();
35+
using var transaction = session.BeginTransaction();
36+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
37+
38+
transaction.Commit();
39+
}
40+
41+
[Test]
42+
public void AppliesParameterTypeFromQueryParam()
43+
{
44+
using var log = new SqlLogSpy();
45+
using var s = OpenSession();
46+
s.GetNamedQuery("entityIdByName").SetParameter("name", "Bob").UniqueResult<object>();
47+
Assert.That(log.GetWholeLog(), Does.Contain("Type: AnsiString"));
48+
}
49+
}
50+
}

src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using NHibernate.Cfg.MappingSchema;
45
using NHibernate.Engine;
56
using NHibernate.Util;
@@ -33,7 +34,11 @@ public void AddSqlQuery(HbmSqlQuery querySchema)
3334
? querySchema.cachemode.ToCacheMode()
3435
: null;
3536

36-
var parameterTypes = new LinkedHashMap<string,string>();
37+
var parameterTypes =
38+
querySchema.Items.EmptyIfNull().OfType<HbmQueryParam>()
39+
.Where(x => !string.IsNullOrEmpty(x.type))
40+
.ToDictionary(x => x.name, x => x.type);
41+
3742
var synchronizedTables = GetSynchronizedTables(querySchema);
3843

3944
NamedSQLQueryDefinition namedQuery;

src/NHibernate/Engine/Query/QueryPlanCache.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using NHibernate.Engine.Query.Sql;
66
using NHibernate.Hql;
77
using NHibernate.Linq;
8+
using NHibernate.Type;
89
using NHibernate.Util;
910

1011
namespace NHibernate.Engine.Query
@@ -40,6 +41,11 @@ public QueryPlanCache(ISessionFactoryImplementor factory)
4041
}
4142

4243
public ParameterMetadata GetSQLParameterMetadata(string query)
44+
{
45+
return GetSQLParameterMetadata(query, CollectionHelper.EmptyDictionary<string, string>());
46+
}
47+
48+
public ParameterMetadata GetSQLParameterMetadata(string query, IDictionary<string, string> parameterTypes)
4349
{
4450
var metadata = (ParameterMetadata)sqlParamMetadataCache[query];
4551
if (metadata == null)
@@ -49,7 +55,7 @@ public ParameterMetadata GetSQLParameterMetadata(string query)
4955
// retrieval for a native-sql query depends on all of the return
5056
// types having been set, which might not be the case up-front when
5157
// param metadata would be most useful
52-
metadata = BuildNativeSQLParameterMetadata(query);
58+
metadata = BuildNativeSQLParameterMetadata(query, parameterTypes);
5359
sqlParamMetadataCache.Put(query, metadata);
5460
}
5561
return metadata;
@@ -170,14 +176,14 @@ public NativeSQLQueryPlan GetNativeSQLQueryPlan(NativeSQLQuerySpecification spec
170176
return plan;
171177
}
172178

173-
private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString)
179+
private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString,
180+
IDictionary<string, string> parameterTypes)
174181
{
175182
ParamLocationRecognizer recognizer = ParamLocationRecognizer.ParseLocations(sqlString);
176183

177184
var ordinalDescriptors = new OrdinalParameterDescriptor[recognizer.OrdinalParameterLocationList.Count];
178-
for (int i = 0; i < recognizer.OrdinalParameterLocationList.Count; i++)
185+
for (int i = 0; i < ordinalDescriptors.Length; i++)
179186
{
180-
int position = recognizer.OrdinalParameterLocationList[i];
181187
ordinalDescriptors[i] = new OrdinalParameterDescriptor(i, null);
182188
}
183189

@@ -187,8 +193,14 @@ private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString)
187193
{
188194
string name = entry.Key;
189195
ParamLocationRecognizer.NamedParameterDescription description = entry.Value;
196+
IType expectedType = null;
197+
if (parameterTypes.TryGetValue(name, out var type) && !string.IsNullOrEmpty(type))
198+
{
199+
expectedType = TypeFactory.HeuristicType(type);
200+
}
201+
190202
namedParamDescriptorMap[name] =
191-
new NamedParameterDescriptor(name, null, description.JpaStyle);
203+
new NamedParameterDescriptor(name, expectedType, description.JpaStyle);
192204
}
193205

194206
return new ParameterMetadata(ordinalDescriptors, namedParamDescriptorMap);

src/NHibernate/Impl/AbstractSessionImpl.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ public virtual IQuery GetNamedSQLQuery(string name)
299299
throw new MappingException("Named SQL query not known: " + name);
300300
}
301301
var query = new SqlQueryImpl(nsqlqd, this,
302-
_factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString));
302+
_factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString, nsqlqd.ParameterTypes));
303303
query.SetComment("named native SQL query " + name);
304304
InitQuery(query, nsqlqd);
305305
return query;
@@ -378,7 +378,7 @@ public virtual IQuery GetNamedQuery(string queryName)
378378
throw new MappingException("Named query not known: " + queryName);
379379
}
380380
query = new SqlQueryImpl(nsqlqd, this,
381-
_factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString));
381+
_factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString, nsqlqd.ParameterTypes));
382382
query.SetComment("named native SQL query " + queryName);
383383
nqd = nsqlqd;
384384
}

0 commit comments

Comments
 (0)