Skip to content

Commit 99dc4a9

Browse files
Fix the any handling without meta-values
1 parent 241d2f5 commit 99dc4a9

File tree

15 files changed

+149
-84
lines changed

15 files changed

+149
-84
lines changed

doc/reference/modules/basic_mapping.xml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3364,8 +3364,8 @@
33643364
database column values to persistent classes that have identifier properties of the type specified by
33653365
<literal>id-type</literal>. If the meta-type is <literal>class</literal>, nothing else is required.
33663366
The class full name will be persisted in the database as the type of the associated entity.
3367-
On the other hand, if it is a basic type like <literal>string</literal> or
3368-
<literal>char</literal>, you must specify the mapping from values to classes.
3367+
On the other hand, if it is a basic type like <literal>int</literal> or <literal>char</literal>, you
3368+
must specify the mapping from values to classes. Here is an example with <literal>string</literal>.
33693369
</para>
33703370

33713371
<programlisting><![CDATA[<any name="being" id-type="long" meta-type="string">
@@ -3376,6 +3376,11 @@
33763376
<column name="id"/>
33773377
</any>]]></programlisting>
33783378

3379+
<para>
3380+
String types are a special case: they can be used without meta-values, in which case they will behave
3381+
much like the <literal>class</literal> meta-type.
3382+
</para>
3383+
33793384
<programlistingco>
33803385
<areaspec>
33813386
<area id="any1" coords="2 55"/>

src/NHibernate.Test/Async/CacheTest/SerializationFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ private object[] GetAllKnownTypeValues()
199199
{NHibernateUtil.TrueFalse, false},
200200
{NHibernateUtil.YesNo, true},
201201
{NHibernateUtil.Class, typeof(IType)},
202-
{NHibernateUtil.ClassMetaType, entityName},
202+
{NHibernateUtil.MetaType, entityName},
203203
{NHibernateUtil.Serializable, new MyEntity {Id = 1}},
204204
{NHibernateUtil.Object, new MyEntity {Id = 10}},
205205
{NHibernateUtil.AnsiChar, 'a'},

src/NHibernate.Test/Async/Legacy/FooBarTest.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5180,8 +5180,13 @@ public async Task AnyAsync()
51805180
s.Close();
51815181

51825182
s = OpenSession();
5183-
IList list = await (s.CreateQuery("from Bar bar where bar.Object.id = ? and bar.Object.class = ?")
5184-
.SetParameter(0, oid, NHibernateUtil.Int64).SetParameter(1, typeof(One).FullName, NHibernateUtil.ClassMetaType).ListAsync());
5183+
var list = await (s.CreateQuery("from Bar bar where bar.Object.id = ? and bar.Object.class = ?")
5184+
#pragma warning disable 618
5185+
.SetParameter(0, oid, NHibernateUtil.Int64).SetParameter(1, typeof(One).FullName, NHibernateUtil.ClassMetaType).ListAsync());
5186+
#pragma warning restore 618
5187+
Assert.AreEqual(1, list.Count);
5188+
list = await (s.CreateQuery("from Bar bar where bar.Object.id = ? and bar.Object.class = ?")
5189+
.SetParameter(0, oid, NHibernateUtil.Int64).SetParameter(1, typeof(One).FullName, NHibernateUtil.MetaType).ListAsync());
51855190
Assert.AreEqual(1, list.Count);
51865191

51875192
// this is a little different from h2.0.3 because the full type is stored, not

src/NHibernate.Test/CacheTest/SerializationFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ private object[] GetAllKnownTypeValues()
188188
{NHibernateUtil.TrueFalse, false},
189189
{NHibernateUtil.YesNo, true},
190190
{NHibernateUtil.Class, typeof(IType)},
191-
{NHibernateUtil.ClassMetaType, entityName},
191+
{NHibernateUtil.MetaType, entityName},
192192
{NHibernateUtil.Serializable, new MyEntity {Id = 1}},
193193
{NHibernateUtil.Object, new MyEntity {Id = 10}},
194194
{NHibernateUtil.AnsiChar, 'a'},

src/NHibernate.Test/Legacy/FooBarTest.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5168,8 +5168,13 @@ public void Any()
51685168
s.Close();
51695169

51705170
s = OpenSession();
5171-
IList list = s.CreateQuery("from Bar bar where bar.Object.id = ? and bar.Object.class = ?")
5172-
.SetParameter(0, oid, NHibernateUtil.Int64).SetParameter(1, typeof(One).FullName, NHibernateUtil.ClassMetaType).List();
5171+
var list = s.CreateQuery("from Bar bar where bar.Object.id = ? and bar.Object.class = ?")
5172+
#pragma warning disable 618
5173+
.SetParameter(0, oid, NHibernateUtil.Int64).SetParameter(1, typeof(One).FullName, NHibernateUtil.ClassMetaType).List();
5174+
#pragma warning restore 618
5175+
Assert.AreEqual(1, list.Count);
5176+
list = s.CreateQuery("from Bar bar where bar.Object.id = ? and bar.Object.class = ?")
5177+
.SetParameter(0, oid, NHibernateUtil.Int64).SetParameter(1, typeof(One).FullName, NHibernateUtil.MetaType).List();
51735178
Assert.AreEqual(1, list.Count);
51745179

51755180
// this is a little different from h2.0.3 because the full type is stored, not

src/NHibernate.Test/NHSpecificTest/GH1774/Implicit/ImplicitMetaTypeFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace NHibernate.Test.NHSpecificTest.GH1774.Implicit
44
{
5-
[TestFixture, Ignore("Not fixed yet")]
5+
[TestFixture]
66
public class ImplicitMetaTypeFixture : FixtureBase
77
{
88
}

src/NHibernate/Async/Type/ClassMetaType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace NHibernate.Type
1717
{
1818
using System.Threading.Tasks;
1919
using System.Threading;
20-
public partial class ClassMetaType : AbstractType, IMetaType
20+
public partial class ClassMetaType : AbstractType
2121
{
2222

2323
public override Task<object> NullSafeGetAsync(DbDataReader rs, string[] names, ISessionImplementor session, object owner, CancellationToken cancellationToken)

src/NHibernate/Async/Type/MetaType.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,24 @@
1313
using System.Data.Common;
1414
using NHibernate.Engine;
1515
using NHibernate.SqlTypes;
16-
using NHibernate.Util;
1716

1817
namespace NHibernate.Type
1918
{
2019
using System.Threading.Tasks;
2120
using System.Threading;
22-
public partial class MetaType : AbstractType, IMetaType
21+
public partial class MetaType : AbstractType
2322
{
2423

2524
public override async Task<object> NullSafeGetAsync(DbDataReader rs, string[] names, ISessionImplementor session, object owner, CancellationToken cancellationToken)
2625
{
2726
cancellationToken.ThrowIfCancellationRequested();
28-
object key = await (baseType.NullSafeGetAsync(rs, names, session, owner, cancellationToken)).ConfigureAwait(false);
29-
return key == null ? null : values[key];
27+
return GetValueForKey(await (_baseType.NullSafeGetAsync(rs, names, session, owner, cancellationToken)).ConfigureAwait(false));
3028
}
3129

32-
public override async Task<object> NullSafeGetAsync(DbDataReader rs,string name,ISessionImplementor session,object owner, CancellationToken cancellationToken)
30+
public override async Task<object> NullSafeGetAsync(DbDataReader rs, string name, ISessionImplementor session, object owner, CancellationToken cancellationToken)
3331
{
3432
cancellationToken.ThrowIfCancellationRequested();
35-
object key = await (baseType.NullSafeGetAsync(rs, name, session, owner, cancellationToken)).ConfigureAwait(false);
36-
return key == null ? null : values[key];
33+
return GetValueForKey(await (_baseType.NullSafeGetAsync(rs, name, session, owner, cancellationToken)).ConfigureAwait(false));
3734
}
3835

3936
public override Task NullSafeSetAsync(DbCommand st, object value, int index, bool[] settable, ISessionImplementor session, CancellationToken cancellationToken)
@@ -53,13 +50,25 @@ public override Task NullSafeSetAsync(DbCommand st, object value, int index, boo
5350
}
5451
}
5552

56-
public override Task NullSafeSetAsync(DbCommand st,object value,int index,ISessionImplementor session, CancellationToken cancellationToken)
53+
public override Task NullSafeSetAsync(DbCommand st, object value, int index, ISessionImplementor session, CancellationToken cancellationToken)
5754
{
5855
if (cancellationToken.IsCancellationRequested)
5956
{
6057
return Task.FromCanceled<object>(cancellationToken);
6158
}
62-
return baseType.NullSafeSetAsync(st, value == null ? null : keys[(string)value], index, session, cancellationToken);
59+
try
60+
{
61+
// "_keys?[(string) value]" is valid code provided "_keys[(string) value]" can never yield null. It is the
62+
// case because we use a dictionary interface which throws in case of missing key, and because it is not
63+
// possible to have a value associated to a null key since generic dictionaries do not support null keys.
64+
var key = value == null ? null : _keys?[(string) value] ?? value;
65+
66+
return _baseType.NullSafeSetAsync(st, key, index, session, cancellationToken);
67+
}
68+
catch (Exception ex)
69+
{
70+
return Task.FromException<object>(ex);
71+
}
6372
}
6473

6574
public override async Task<bool> IsDirtyAsync(object old, object current, bool[] checkable, ISessionImplementor session, CancellationToken cancellationToken)

src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryLogicOperatorNode.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,19 +276,19 @@ private void ProcessMetaTypeDiscriminatorIfNecessary(IASTNode lhs, IASTNode rhs)
276276
return;
277277
}
278278

279-
if (rhsNode is IdentNode && lhsNode.DataType is IMetaType lhsNodeMetaType)
279+
if (rhsNode is IdentNode && lhsNode.DataType is MetaType lhsNodeMetaType)
280280
{
281281
EvaluateType(rhsNode, lhsNodeMetaType);
282282
return;
283283
}
284284

285-
if (lhsNode is IdentNode && rhsNode.DataType is IMetaType rhsNodeMetaType)
285+
if (lhsNode is IdentNode && rhsNode.DataType is MetaType rhsNodeMetaType)
286286
{
287287
EvaluateType(lhsNode, rhsNodeMetaType);
288288
}
289289
}
290290

291-
private void EvaluateType(SqlNode node, IMetaType metaType)
291+
private void EvaluateType(SqlNode node, MetaType metaType)
292292
{
293293
var sessionFactory = SessionFactoryHelper.Factory;
294294

src/NHibernate/Mapping/Any.cs

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class Any : SimpleValue
1919

2020
public Any(Table table) : base(table)
2121
{
22+
_type = GetLazyType();
2223
}
2324

2425
/// <summary>
@@ -30,29 +31,45 @@ public virtual string IdentifierTypeName
3031
set { identifierTypeName = value; }
3132
}
3233

33-
private IType type;
34-
public override IType Type
34+
private Lazy<IType> GetLazyType()
3535
{
36-
get
37-
{
38-
if (type == null)
36+
return new Lazy<IType>(
37+
() =>
3938
{
40-
type =
41-
new AnyType(
42-
metaValues == null
43-
? ("class".Equals(metaTypeName) ? new ClassMetaType(): TypeFactory.HeuristicType(metaTypeName))
44-
: new MetaType(metaValues, TypeFactory.HeuristicType(metaTypeName)),
45-
TypeFactory.HeuristicType(identifierTypeName));
46-
}
47-
return type;
48-
}
39+
var identifierType = TypeFactory.HeuristicType(identifierTypeName);
40+
if (identifierType == null)
41+
throw new MappingException($"Identifier type '{identifierTypeName}' is invalid");
42+
43+
var hasMetaValues = metaValues != null && metaValues.Count > 0;
44+
45+
var metaType = !hasMetaValues && "class" == metaTypeName
46+
? NHibernateUtil.String
47+
: TypeFactory.HeuristicType(metaTypeName);
48+
49+
if (metaType == null)
50+
throw new MappingException($"Meta type '{metaTypeName}' is invalid");
51+
52+
if (!hasMetaValues && metaType.ReturnedClass != typeof(string))
53+
throw new MappingException(
54+
$"Using a non-string meta type ('{metaTypeName}') without providing meta values is invalid");
55+
56+
return new AnyType(
57+
new MetaType(metaValues, metaType),
58+
identifierType);
59+
});
4960
}
5061

62+
// Types may be used by many threads, we must use a thread safe lazy initialization.
63+
// (Or we should build this class in another way, but this would likely be a breaking change.)
64+
private Lazy<IType> _type;
65+
66+
public override IType Type => _type.Value;
67+
5168
public void ResetCachedType()
5269
{
5370
// this is required if the user is programatically modifying the Any instance
5471
// and need to reset the cached type instance;
55-
type = null;
72+
_type = GetLazyType();
5673
}
5774

5875
public override void SetTypeUsingReflection(string className, string propertyName, string access)

src/NHibernate/NHibernateUtil.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,15 @@ public static IType GuessType(System.Type type)
268268
/// NHibernate class meta type for association of kind <code>any</code>.
269269
/// </summary>
270270
/// <seealso cref="AnyType"/>
271+
[Obsolete("Use MetaType without meta-values instead.")]
271272
public static readonly ClassMetaType ClassMetaType = new ClassMetaType();
272273

274+
/// <summary>
275+
/// NHibernate meta type for association of kind <code>any</code> without meta-values.
276+
/// </summary>
277+
/// <seealso cref="AnyType"/>
278+
public static readonly MetaType MetaType = new MetaType(null, String);
279+
273280
/// <summary>
274281
/// NHibernate serializable type
275282
/// </summary>

src/NHibernate/Type/AnyType.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,22 @@ public partial class AnyType : AbstractType, IAbstractComponentType, IAssociatio
4646
private readonly IType identifierType;
4747
private readonly IType metaType;
4848

49-
/// <summary>
50-
///
51-
/// </summary>
52-
/// <param name="metaType"></param>
53-
/// <param name="identifierType"></param>
49+
private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(AnyType));
50+
5451
internal AnyType(IType metaType, IType identifierType)
5552
{
56-
this.identifierType = identifierType;
57-
this.metaType = metaType;
53+
this.identifierType = identifierType ?? throw new ArgumentNullException(nameof(identifierType));
54+
this.metaType = metaType ?? throw new ArgumentNullException(nameof(metaType));
55+
56+
if (!(metaType is MetaType))
57+
{
58+
Log.Warn("Using AnyType with a meta type which is not a MetaType is obsolete and may cause" +
59+
"querying issues.");
60+
}
5861
}
5962

60-
/// <summary></summary>
6163
internal AnyType()
62-
: this(NHibernateUtil.String, NHibernateUtil.Serializable)
64+
: this(NHibernateUtil.MetaType, NHibernateUtil.Serializable)
6365
{
6466
}
6567

src/NHibernate/Type/ClassMetaType.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace NHibernate.Type
1212
/// It work like a MetaType where the key is the entity-name it self
1313
/// </remarks>
1414
[Serializable]
15-
public partial class ClassMetaType : AbstractType, IMetaType
15+
[Obsolete("Use MetaType without meta-values instead.")]
16+
public partial class ClassMetaType : AbstractType
1617
{
1718
public override SqlType[] SqlTypes(IMapping mapping)
1819
{
@@ -100,10 +101,5 @@ public override bool[] ToColumnNullness(object value, IMapping mapping)
100101
{
101102
return NHibernateUtil.String.ToColumnNullness(value, mapping);
102103
}
103-
104-
string IMetaType.GetMetaValue(string className, Dialect.Dialect dialect)
105-
{
106-
return NHibernateUtil.String.ObjectToSQLString(className, dialect);
107-
}
108104
}
109105
}

src/NHibernate/Type/IMetaType.cs

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)