Skip to content

Commit

Permalink
Fix NamedSQLQuery ignores query-param type (#3404)
Browse files Browse the repository at this point in the history
Fixes #3311
  • Loading branch information
bahusoid authored Aug 13, 2023
1 parent d1a7e10 commit 4308ec9
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


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<object>());
Assert.That(log.GetWholeLog(), Does.Contain("Type: AnsiString"));
}
}
}
10 changes: 10 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Entity.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
namespace="NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam">

<class name="Entity">
<id name="Id" generator="native" />
<property name="Name" type="AnsiString" />
</class>
<sql-query name="entityIdByName">
<query-param name="name" type="AnsiString" />
select s.Id from Entity s where s.Name = :name
</sql-query>

</hibernate-mapping>
Original file line number Diff line number Diff line change
@@ -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<object>();
Assert.That(log.GetWholeLog(), Does.Contain("Type: AnsiString"));
}
}
}
7 changes: 6 additions & 1 deletion src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Engine;
using NHibernate.Util;
Expand Down Expand Up @@ -33,7 +34,11 @@ public void AddSqlQuery(HbmSqlQuery querySchema)
? querySchema.cachemode.ToCacheMode()
: null;

var parameterTypes = new LinkedHashMap<string,string>();
var parameterTypes =
querySchema.Items.EmptyIfNull().OfType<HbmQueryParam>()
.Where(x => !string.IsNullOrEmpty(x.type))
.ToDictionary(x => x.name, x => x.type);

var synchronizedTables = GetSynchronizedTables(querySchema);

NamedSQLQueryDefinition namedQuery;
Expand Down
22 changes: 17 additions & 5 deletions src/NHibernate/Engine/Query/QueryPlanCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -40,6 +41,11 @@ public QueryPlanCache(ISessionFactoryImplementor factory)
}

public ParameterMetadata GetSQLParameterMetadata(string query)
{
return GetSQLParameterMetadata(query, CollectionHelper.EmptyDictionary<string, string>());
}

public ParameterMetadata GetSQLParameterMetadata(string query, IDictionary<string, string> parameterTypes)
{
var metadata = (ParameterMetadata)sqlParamMetadataCache[query];
if (metadata == null)
Expand All @@ -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;
Expand Down Expand Up @@ -170,14 +176,14 @@ public NativeSQLQueryPlan GetNativeSQLQueryPlan(NativeSQLQuerySpecification spec
return plan;
}

private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString)
private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString,
IDictionary<string, string> 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);
}

Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/NHibernate/Impl/AbstractSessionImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 4308ec9

Please sign in to comment.