Skip to content

Commit 5b32fc4

Browse files
committed
CompiledQueryRunner: Improved check whether query is cacheable
1 parent 7b35e7a commit 5b32fc4

File tree

2 files changed

+75
-36
lines changed

2 files changed

+75
-36
lines changed

Orm/Xtensive.Orm/Orm/Internals/CompiledQueryRunner.cs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created: 2012.01.27
66

77
using System;
8+
using System.Collections.Generic;
89
using System.Linq;
910
using System.Linq.Expressions;
1011
using System.Reflection;
@@ -20,14 +21,16 @@ namespace Xtensive.Orm.Internals
2021
{
2122
internal class CompiledQueryRunner
2223
{
23-
private static readonly Func<FieldInfo, bool> FieldIsSimple = fieldInfo => IsSimpleType(fieldInfo.FieldType);
24+
private static readonly Func<FieldInfo, IReadOnlySet<Type>, bool> IsFieldReadyToCache = (fieldInfo, supportedTypes) =>
25+
IsTypeCacheable(fieldInfo.FieldType, supportedTypes);
2426

2527
private readonly Domain domain;
2628
private readonly Session session;
2729
private readonly QueryEndpoint endpoint;
2830
private readonly object queryKey;
2931
private readonly object queryTarget;
3032
private readonly ParameterContext outerContext;
33+
private readonly IReadOnlySet<Type> supportedTypes;
3134

3235
private Parameter queryParameter;
3336
private ExtendedExpressionReplacer queryParameterReplacer;
@@ -200,23 +203,49 @@ private bool AllocateParameterAndReplacer()
200203
return null;
201204
});
202205

206+
203207
return !TypeHelper.IsClosure(closureType)
204-
|| closureType.GetFields().All(FieldIsSimple);
208+
|| closureType.GetFields().All(f => IsFieldReadyToCache(f, supportedTypes));
205209
}
206210

207-
private static bool IsSimpleType(Type type)
211+
private static bool IsTypeCacheable(Type type, IReadOnlySet<Type> supportedTypes)
208212
{
209-
var typeInfo = type.GetTypeInfo();
210-
if (typeInfo.IsGenericType) {
211-
var genericDef = typeInfo.GetGenericTypeDefinition();
212-
return (genericDef == WellKnownTypes.NullableOfT || genericDef.IsAssignableTo(WellKnownTypes.IReadOnlyListOfT))
213-
&& IsSimpleType(typeInfo.GetGenericArguments()[0]);
213+
var type1 = type.StripNullable();
214+
if (type1.IsGenericType) {
215+
// IReadOnlyList<T> implementations + ValueTuple<> with different number of argument types
216+
if (type1.IsValueTuple() && type1.GetGenericArguments().All(t => IsTypeCacheable(t, supportedTypes))) {
217+
return true;
218+
}
219+
var genericDef = type1.GetGenericTypeDefinition();
220+
return genericDef.IsAssignableTo(WellKnownTypes.IReadOnlyListOfT) && IsTypeCacheable(type1.GetGenericArguments()[0], supportedTypes);
214221
}
215-
else if (typeInfo.IsArray) {
216-
return IsSimpleType(typeInfo.GetElementType());
222+
else if (type1.IsArray) {
223+
return IsTypeCacheable(type1.GetElementType(), supportedTypes);
217224
}
218225
else {
219-
return typeInfo.IsPrimitive || typeInfo.IsEnum || type == WellKnownTypes.String || type == WellKnownTypes.Decimal;
226+
// enums are handled by their base type so no need to check them
227+
return Type.GetTypeCode(type1) switch {
228+
TypeCode.Boolean => true,
229+
TypeCode.Byte => true,
230+
TypeCode.SByte => true,
231+
TypeCode.Int16 => true,
232+
TypeCode.UInt16 => true,
233+
TypeCode.Int32 => true,
234+
TypeCode.UInt32 => true,
235+
TypeCode.Int64 => true,
236+
TypeCode.UInt64 => true,
237+
TypeCode.Single => true,
238+
TypeCode.Double => true,
239+
TypeCode.Decimal => true,
240+
TypeCode.Char => true,
241+
TypeCode.String => true,
242+
TypeCode.DateTime => true,
243+
TypeCode.Object => type1 == WellKnownTypes.Guid
244+
|| type1 == WellKnownTypes.TimeSpan
245+
|| type1 == WellKnownTypes.DateTimeOffset
246+
|| supportedTypes.Contains(type1),
247+
_ => false
248+
};
220249
}
221250
}
222251

@@ -245,6 +274,7 @@ public CompiledQueryRunner(QueryEndpoint endpoint, object queryKey, object query
245274
this.queryKey = new Pair<object, string>(queryKey, session.StorageNodeId);
246275
this.queryTarget = queryTarget;
247276
this.outerContext = outerContext;
277+
supportedTypes = domain.StorageProviderInfo.SupportedTypes;
248278
}
249279
}
250280
}

Orm/Xtensive.Orm/Reflection/TypeHelper.cs

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,47 +42,42 @@ public int GetHashCode((Type, Type[]) obj)
4242
}
4343
}
4444

45-
private const string invokeMethodName = "Invoke";
46-
47-
private static readonly ConcurrentDictionary<(Type, Type[]), ConstructorInfo> constructorInfoByTypes =
48-
new ConcurrentDictionary<(Type, Type[]), ConstructorInfo>(new TypesEqualityComparer());
45+
private const string InvokeMethodName = "Invoke";
4946

5047
private static readonly object EmitLock = new object();
5148
private static readonly int NullableTypeMetadataToken = WellKnownTypes.NullableOfT.MetadataToken;
52-
private static readonly Module NullableTypeModule = WellKnownTypes.NullableOfT.Module;
49+
private static readonly int ValueTuple1 = typeof(ValueTuple<>).MetadataToken;
50+
private static readonly int ValueTuple8 = typeof(ValueTuple<,,,,,,,>).MetadataToken;
51+
private static readonly Module SystemCoreLibModule = WellKnownTypes.NullableOfT.Module;
5352
private static readonly Type CompilerGeneratedAttributeType = typeof(CompilerGeneratedAttribute);
5453
private static readonly string TypeHelperNamespace = typeof(TypeHelper).Namespace;
5554

56-
private static readonly ConcurrentDictionary<Type, Type[]> OrderedInterfaces =
57-
new ConcurrentDictionary<Type, Type[]>();
55+
private static readonly ConcurrentDictionary<(Type, Type[]), ConstructorInfo> ConstructorInfoByTypes =
56+
new(new TypesEqualityComparer());
57+
58+
private static readonly ConcurrentDictionary<Type, Type[]> OrderedInterfaces = new();
59+
60+
private static readonly ConcurrentDictionary<Type, Type[]> OrderedCompatibles = new();
61+
62+
private static readonly ConcurrentDictionary<Pair<Type, Type>, InterfaceMapping> interfaceMaps = new();
5863

59-
private static readonly ConcurrentDictionary<Type, Type[]> OrderedCompatibles =
60-
new ConcurrentDictionary<Type, Type[]>();
64+
private static readonly ConcurrentDictionary<(MethodInfo, Type), MethodInfo> GenericMethodInstances1 = new();
6165

62-
private static readonly ConcurrentDictionary<Pair<Type, Type>, InterfaceMapping> interfaceMaps =
63-
new ConcurrentDictionary<Pair<Type, Type>, InterfaceMapping>();
66+
private static readonly ConcurrentDictionary<(MethodInfo, Type, Type), MethodInfo> GenericMethodInstances2 = new();
6467

65-
private static readonly ConcurrentDictionary<(MethodInfo, Type), MethodInfo> GenericMethodInstances1 =
66-
new ConcurrentDictionary<(MethodInfo, Type), MethodInfo>();
68+
private static readonly ConcurrentDictionary<(Type, Type), Type> GenericTypeInstances1 = new();
69+
70+
private static readonly ConcurrentDictionary<(Type, Type, Type), Type> GenericTypeInstances2 = new();
6771

6872
private static readonly Func<(MethodInfo genericDefinition, Type typeArgument), MethodInfo> GenericMethodFactory1 =
6973
key => key.genericDefinition.MakeGenericMethod(key.typeArgument);
7074

71-
private static readonly ConcurrentDictionary<(MethodInfo, Type, Type), MethodInfo> GenericMethodInstances2 =
72-
new ConcurrentDictionary<(MethodInfo, Type, Type), MethodInfo>();
73-
7475
private static readonly Func<(MethodInfo genericDefinition, Type typeArgument1, Type typeArgument2), MethodInfo> GenericMethodFactory2 =
7576
key => key.genericDefinition.MakeGenericMethod(key.typeArgument1, key.typeArgument2);
7677

77-
private static readonly ConcurrentDictionary<(Type, Type), Type> GenericTypeInstances1 =
78-
new ConcurrentDictionary<(Type, Type), Type>();
79-
8078
private static readonly Func<(Type genericDefinition, Type typeArgument), Type> GenericTypeFactory1 = key =>
8179
key.genericDefinition.MakeGenericType(key.typeArgument);
8280

83-
private static readonly ConcurrentDictionary<(Type, Type, Type), Type> GenericTypeInstances2 =
84-
new ConcurrentDictionary<(Type, Type, Type), Type>();
85-
8681
private static readonly Func<(Type genericDefinition, Type typeArgument1, Type typeArgument2), Type> GenericTypeFactory2 = key =>
8782
key.genericDefinition.MakeGenericType(key.typeArgument1, key.typeArgument2);
8883

@@ -646,7 +641,7 @@ public static ConstructorInfo GetConstructor(this Type type, object[] arguments)
646641
GetSingleConstructor(type, arguments.Select(a => a?.GetType()).ToArray());
647642

648643
public static ConstructorInfo GetSingleConstructor(this Type type, Type[] argumentTypes) =>
649-
constructorInfoByTypes.GetOrAdd((type, argumentTypes), ConstructorExtractor);
644+
ConstructorInfoByTypes.GetOrAdd((type, argumentTypes), ConstructorExtractor);
650645

651646
private static readonly Func<(Type, Type[]), ConstructorInfo> ConstructorExtractor = t => {
652647
(var type, var argumentTypes) = t;
@@ -899,7 +894,7 @@ private static string GetShortNameBase(this Type type)
899894
/// <returns><see langword="True"/> if type is nullable type;
900895
/// otherwise, <see langword="false"/>.</returns>
901896
public static bool IsNullable(this Type type) =>
902-
(type.MetadataToken ^ NullableTypeMetadataToken) == 0 && ReferenceEquals(type.Module, NullableTypeModule);
897+
(type.MetadataToken ^ NullableTypeMetadataToken) == 0 && ReferenceEquals(type.Module, SystemCoreLibModule);
903898

904899
/// <summary>
905900
/// Indicates whether <typeparamref name="T"/> type is a <see cref="Nullable{T}"/> type.
@@ -931,7 +926,7 @@ public static bool IsNullable(this Type type) =>
931926
/// </summary>
932927
/// <param name="delegateType">Type of the delegate to get the "Invoke" method of.</param>
933928
/// <returns><see cref="MethodInfo"/> object describing the delegate "Invoke" method.</returns>
934-
public static MethodInfo GetInvokeMethod(this Type delegateType) => delegateType.GetMethod(invokeMethodName);
929+
public static MethodInfo GetInvokeMethod(this Type delegateType) => delegateType.GetMethod(InvokeMethodName);
935930

936931

937932
/// <summary>
@@ -1167,6 +1162,20 @@ public static bool IsNumericType(this Type type)
11671162
}
11681163
}
11691164

1165+
/// <summary>
1166+
/// Determines whether <paramref name="type"/> is generic form of <see cref="ValueTuple"/>
1167+
/// </summary>
1168+
/// <param name="type"></param>
1169+
/// <returns></returns>
1170+
internal static bool IsValueTuple(this Type type)
1171+
{
1172+
// this stands on the theory that tokens for all generic versions of ValueTuple
1173+
// go one after another.
1174+
var currentToken = type.MetadataToken;
1175+
return ((currentToken >= ValueTuple1) && currentToken <= ValueTuple8)
1176+
&& ReferenceEquals(type.Module, SystemCoreLibModule);
1177+
}
1178+
11701179
#region Private \ internal methods
11711180

11721181
/// <summary>

0 commit comments

Comments
 (0)