Skip to content

Commit b652b72

Browse files
committed
Cache only required parts of NhLinqExpression in QueryExpressionPlan
1 parent 8f3ab01 commit b652b72

File tree

8 files changed

+79
-33
lines changed

8 files changed

+79
-33
lines changed

src/NHibernate.Test/NHSpecificTest/GH3030/ByCodeFixture.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ public class ByCodeFixture : TestCaseMappingByCode
1212
protected override HbmMapping GetMappings()
1313
{
1414
var mapper = new ModelMapper();
15-
mapper.Class<Entity>(rc =>
16-
{
17-
rc.Table("Entity");
18-
rc.Id(x => x.Id, m => m.Generator(Generators.Assigned));
19-
});
15+
mapper.Class<Entity>(
16+
rc =>
17+
{
18+
rc.Table("Entity");
19+
rc.Id(x => x.Id, m => m.Generator(Generators.Assigned));
20+
});
2021

2122
return mapper.CompileMappingForAllExplicitlyAddedEntities();
2223
}
@@ -35,7 +36,7 @@ protected override void OnTearDown()
3536
}
3637

3738
[Test]
38-
public void LinqLeaksMemory()
39+
public void LinqShouldNotLeakEntityParameters()
3940
{
4041
WeakReference sessionReference = null;
4142
WeakReference firstReference = null;
@@ -64,8 +65,8 @@ public void LinqLeaksMemory()
6465
Assert.That(firstReference.Target, Is.Null);
6566
Assert.That(secondReference.Target, Is.Null);
6667
}
67-
68-
public class Entity
68+
69+
public class Entity
6970
{
7071
public virtual int Id { get; set; }
7172
}

src/NHibernate/Engine/Query/QueryExpressionPlan.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using NHibernate.Hql;
4-
using NHibernate.Linq;
54

65
namespace NHibernate.Engine.Query
76
{

src/NHibernate/Engine/Query/QueryPlanCache.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@ public IQueryExpressionPlan GetHQLQueryPlan(IQueryExpression queryExpression, bo
6666
{
6767
log.Debug("unable to locate HQL query plan in cache; generating ({0})", queryExpression.Key);
6868
}
69+
6970
plan = new QueryExpressionPlan(queryExpression, shallow, enabledFilters, factory);
7071
// 6.0 TODO: add "CanCachePlan { get; }" to IQueryExpression interface
7172
if (!(queryExpression is ICacheableQueryExpression linqExpression) || linqExpression.CanCachePlan)
72-
planCache.Put(key, plan);
73+
planCache.Put(key, PreparePlanToCache(plan));
7374
else
7475
log.Debug("Query plan not cacheable");
7576
}
@@ -85,23 +86,30 @@ public IQueryExpressionPlan GetHQLQueryPlan(IQueryExpression queryExpression, bo
8586
return plan;
8687
}
8788

89+
private QueryExpressionPlan PreparePlanToCache(QueryExpressionPlan plan)
90+
{
91+
if (plan.QueryExpression is NhLinqExpression planExpression)
92+
{
93+
return plan.Copy(new NhLinqExpressionCache(planExpression));
94+
}
95+
96+
return plan;
97+
}
98+
8899
private static QueryExpressionPlan CopyIfRequired(QueryExpressionPlan plan, IQueryExpression queryExpression)
89100
{
90-
var planExpression = plan.QueryExpression as NhLinqExpression;
91-
var expression = queryExpression as NhLinqExpression;
92-
if (planExpression != null && expression != null)
101+
if (plan.QueryExpression is NhLinqExpressionCache cache && queryExpression is NhLinqExpression expression)
93102
{
94103
//NH-3413
95104
//Here we have to use original expression.
96105
//In most cases NH do not translate expression in second time, but
97106
// for cases when we have list parameters in query, like @p1.Contains(...),
98107
// it does, and then it uses parameters from first try.
99-
//TODO: cache only required parts of QueryExpression
100108

101109
//NH-3436
102110
// We have to return new instance plan with it's own query expression
103-
// because other treads can override queryexpression of current plan during execution of query if we will use cached instance of plan
104-
expression.CopyExpressionTranslation(planExpression);
111+
// because other treads can override query expression of current plan during execution of query if we will use cached instance of plan
112+
expression.CopyExpressionTranslation(cache);
105113
plan = plan.Copy(expression);
106114
}
107115

@@ -124,7 +132,7 @@ public IQueryExpressionPlan GetFilterQueryPlan(IQueryExpression queryExpression,
124132
plan = new FilterQueryPlan(queryExpression, collectionRole, shallow, enabledFilters, factory);
125133
// 6.0 TODO: add "CanCachePlan { get; }" to IQueryExpression interface
126134
if (!(queryExpression is ICacheableQueryExpression linqExpression) || linqExpression.CanCachePlan)
127-
planCache.Put(key, plan);
135+
planCache.Put(key, PreparePlanToCache(plan));
128136
else
129137
log.Debug("Query plan not cacheable");
130138
}

src/NHibernate/Hql/Ast/ANTLR/ASTQueryTranslatorFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ static IQueryTranslator[] CreateQueryTranslators(
3333

3434
var translators = polymorphicParsers
3535
.ToArray(hql => queryExpression is NhLinqExpression linqExpression
36-
? new QueryTranslatorImpl(queryIdentifier, hql, filters, factory, linqExpression.NamedParameters)
36+
? new QueryTranslatorImpl(queryIdentifier, hql, filters, factory, linqExpression.GetNamedParameterTypes())
3737
: new QueryTranslatorImpl(queryIdentifier, hql, filters, factory));
3838

3939
foreach (var translator in translators)

src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,17 +1086,17 @@ IASTNode GenerateNamedParameter(IASTNode delimiterNode, IASTNode nameNode)
10861086
);
10871087

10881088
parameter.HqlParameterSpecification = paramSpec;
1089-
if (_qti.TryGetNamedParameter(name, out var namedParameter))
1089+
if (_qti.TryGetNamedParameterType(name, out var type, out var isGuessedType))
10901090
{
10911091
// Add the parameter type information so that we are able to calculate functions return types
10921092
// when the parameter is used as an argument.
1093-
if (namedParameter.IsGuessedType)
1093+
if (isGuessedType)
10941094
{
1095-
_guessedParameterTypes[paramSpec] = namedParameter.Type;
1096-
parameter.GuessedType = namedParameter.Type;
1095+
_guessedParameterTypes[paramSpec] = type;
1096+
parameter.GuessedType = type;
10971097
}
10981098
else
1099-
parameter.ExpectedType = namedParameter.Type;
1099+
parameter.ExpectedType = type;
11001100
}
11011101

11021102
_parameters.Add(paramSpec);

src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public partial class QueryTranslatorImpl : IFilterTranslator
3030
private readonly string _queryIdentifier;
3131
private readonly IASTNode _stageOneAst;
3232
private readonly ISessionFactoryImplementor _factory;
33-
private readonly IDictionary<string, NamedParameter> _namedParameters;
33+
private readonly IDictionary<string, Tuple<IType, bool>> _namedParameters;
3434

3535
private bool _shallowQuery;
3636
private bool _compiled;
@@ -70,7 +70,7 @@ internal QueryTranslatorImpl(
7070
IASTNode parsedQuery,
7171
IDictionary<string, IFilter> enabledFilters,
7272
ISessionFactoryImplementor factory,
73-
IDictionary<string, NamedParameter> namedParameters)
73+
IDictionary<string, Tuple<IType, bool>> namedParameters)
7474
{
7575
_queryIdentifier = queryIdentifier;
7676
_stageOneAst = parsedQuery;
@@ -474,15 +474,18 @@ private void ErrorIfDML()
474474
}
475475
}
476476

477-
internal bool TryGetNamedParameter(string name, out NamedParameter namedParameter)
477+
public bool TryGetNamedParameterType(string name, out IType type, out bool isGuessedType)
478478
{
479-
if (_namedParameters == null)
479+
if (_namedParameters == null || !_namedParameters.TryGetValue(name, out var p))
480480
{
481-
namedParameter = null;
481+
type = null;
482+
isGuessedType = false;
482483
return false;
483484
}
484485

485-
return _namedParameters.TryGetValue(name, out namedParameter);
486+
type = p.Item1;
487+
isGuessedType = p.Item2;
488+
return true;
486489
}
487490
}
488491

src/NHibernate/Linq/NhLinqExpression.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,18 @@ public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter
113113
return DuplicateTree(ExpressionToHqlTranslationResults.Statement.AstNode);
114114
}
115115

116-
internal void CopyExpressionTranslation(NhLinqExpression other)
116+
internal void CopyExpressionTranslation(NhLinqExpressionCache cache)
117117
{
118-
ExpressionToHqlTranslationResults = other.ExpressionToHqlTranslationResults;
119-
ParameterDescriptors = other.ParameterDescriptors;
118+
ExpressionToHqlTranslationResults = cache.ExpressionToHqlTranslationResults;
119+
ParameterDescriptors = cache.ParameterDescriptors;
120120
// Type could have been overridden by translation.
121-
Type = other.Type;
121+
Type = cache.Type;
122+
}
123+
124+
internal IDictionary<string, Tuple<IType, bool>> GetNamedParameterTypes()
125+
{
126+
return _constantToParameterMap.Values.Distinct()
127+
.ToDictionary(p => p.Name, p => System.Tuple.Create(p.Type, p.IsGuessedType));
122128
}
123129

124130
private static IASTNode DuplicateTree(IASTNode ast)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using NHibernate.Engine;
4+
using NHibernate.Engine.Query;
5+
using NHibernate.Hql.Ast.ANTLR.Tree;
6+
7+
namespace NHibernate.Linq
8+
{
9+
internal class NhLinqExpressionCache : IQueryExpression
10+
{
11+
internal NhLinqExpressionCache(NhLinqExpression expression)
12+
{
13+
ExpressionToHqlTranslationResults = expression.ExpressionToHqlTranslationResults ?? throw new ArgumentException("NhLinqExpression is not translated");
14+
Key = expression.Key;
15+
Type = expression.Type;
16+
ParameterDescriptors = expression.ParameterDescriptors;
17+
}
18+
19+
public ExpressionToHqlTranslationResults ExpressionToHqlTranslationResults { get; }
20+
public string Key { get; }
21+
public System.Type Type { get; }
22+
public IList<NamedParameterDescriptor> ParameterDescriptors { get; }
23+
24+
public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter)
25+
{
26+
return ExpressionToHqlTranslationResults.Statement.AstNode;
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)