Skip to content

Commit 99dac2a

Browse files
committed
Handle caching expressions with different expression parameters
When calling .ToString() on expression, strip the expression parameter, arrow and parameter from the expression, e.g. Expression<Func<Project, object>> expression = p => p.Name; would be p => p.Name in string form, but strip p => p. to leave just Name. We can generate the same hashcode for two expressions for the same type and expression that use different expression parameters. Implement GetHashCode similarly in PropertyName as in Field Implement _simple_ Equality in Field and PropertyName Removed the superfluous cache buster tests and moved others onto FieldResolverCacheTests
1 parent a5d3398 commit 99dac2a

File tree

13 files changed

+325
-174
lines changed

13 files changed

+325
-174
lines changed

docs/asciidoc/query-dsl/specialized/script/script-query-usage.asciidoc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ See the Elasticsearch documentation on {ref_current}/query-dsl-script-query.html
1818
q
1919
.Script(sn => sn
2020
.Name("named_query")
21-
.Boost(1.1)
2221
.Inline(_templateString)
2322
.Params(p => p.Add("param1", 50))
2423
)
@@ -31,7 +30,6 @@ q
3130
new ScriptQuery
3231
{
3332
Name = "named_query",
34-
Boost = 1.1,
3533
Inline = _templateString,
3634
Params = new Dictionary<string, object>
3735
{
@@ -46,7 +44,6 @@ new ScriptQuery
4644
{
4745
"script": {
4846
"_name": "named_query",
49-
"boost": 1.1,
5047
"script": {
5148
"inline": "doc['numberOfCommits'].value > param1",
5249
"params": {

src/Nest/CommonAbstractions/Extensions/ExpressionExtensions.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
2+
using System.Linq;
23
using System.Linq.Expressions;
4+
using System.Text.RegularExpressions;
35

46
namespace Nest
57
{
@@ -44,5 +46,26 @@ protected override Expression VisitUnary(UnaryExpression node)
4446
return node;
4547
}
4648
}
49+
50+
private static readonly Regex ExpressionRegex = new Regex(@"^\s*(.*)\s*\=\>\s*\1\.");
51+
private static readonly Regex MemberExpressionRegex = new Regex(@"^[^\.]*\.");
52+
53+
internal static object ComparisonValueFromExpression(this Expression expression, out Type type)
54+
{
55+
type = null;
56+
57+
if (expression == null) return null;
58+
59+
var lambda = expression as LambdaExpression;
60+
if (lambda == null)
61+
return ExpressionRegex.Replace(expression.ToString(), string.Empty);
62+
63+
type = lambda.Parameters.FirstOrDefault()?.Type;
64+
65+
var memberExpression = lambda.Body as MemberExpression;
66+
return memberExpression != null
67+
? MemberExpressionRegex.Replace(memberExpression.ToString(), string.Empty)
68+
: ExpressionRegex.Replace(expression.ToString(), string.Empty);
69+
}
4770
}
48-
}
71+
}

src/Nest/CommonAbstractions/Infer/Field/Field.cs

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ public class Field : IEquatable<Field>, IUrlParameter
1313
private string _name;
1414
private Expression _expression;
1515
private PropertyInfo _property;
16-
17-
private Type Type { get; set; }
16+
private Type _type;
17+
private object _comparisonValue;
1818

1919
public string Name
2020
{
@@ -35,8 +35,8 @@ public Expression Expression
3535
{
3636
_expression = value;
3737
Type type;
38-
var comparisonValue = ComparisonValueFromExpression(value, out type);
39-
Type = type;
38+
var comparisonValue = value.ComparisonValueFromExpression(out type);
39+
_type = type;
4040
SetComparisonValue(comparisonValue);
4141
CacheableExpression = !new HasConstantExpressionVisitor(value).Found;
4242
}
@@ -49,14 +49,12 @@ public PropertyInfo Property
4949
{
5050
_property = value;
5151
SetComparisonValue(value);
52-
Type = value.DeclaringType;
52+
_type = value.DeclaringType;
5353
}
5454
}
5555

5656
public double? Boost { get; set; }
5757

58-
private object ComparisonValue { get; set; }
59-
6058
public bool CacheableExpression { get; private set; }
6159

6260
public Fields And<T>(Expression<Func<T, object>> field) where T : class =>
@@ -77,6 +75,8 @@ public static Field Create(string name, double? boost = null)
7775

7876
public static Field Create(Expression expression, double? boost = null)
7977
{
78+
if (expression == null) return null;
79+
8080
Field field = expression;
8181
field.Boost = boost;
8282
return field;
@@ -96,22 +96,6 @@ private static string ParseFieldName(string name, out double? boost)
9696
return name;
9797
}
9898

99-
private static object ComparisonValueFromExpression(Expression expression, out Type type)
100-
{
101-
type = null;
102-
103-
if (expression == null) return null;
104-
105-
var lambda = expression as LambdaExpression;
106-
if (lambda == null)
107-
return expression.ToString();
108-
109-
type = lambda.Parameters.FirstOrDefault()?.Type;
110-
111-
var memberExpression = lambda.Body as MemberExpression;
112-
return memberExpression?.ToString() ?? expression.ToString();
113-
}
114-
11599
public static implicit operator Field(string name)
116100
{
117101
return name.IsNullOrEmpty() ? null : new Field
@@ -138,19 +122,37 @@ public static implicit operator Field(PropertyInfo property)
138122

139123
public override int GetHashCode()
140124
{
141-
var hashCode = ComparisonValue?.GetHashCode() ?? 0;
142-
hashCode = (hashCode * 397) ^ (Type?.GetHashCode() ?? 0);
143-
return hashCode;
125+
unchecked
126+
{
127+
var hashCode = _comparisonValue?.GetHashCode() ?? 0;
128+
hashCode = (hashCode * 397) ^ (_type?.GetHashCode() ?? 0);
129+
return hashCode;
130+
}
144131
}
145132

146-
bool IEquatable<Field>.Equals(Field other) => Equals(other);
133+
bool IEquatable<Field>.Equals(Field other)
134+
{
135+
return _type != null
136+
? other != null && _type == other._type && _comparisonValue.Equals(other._comparisonValue)
137+
: other != null && _comparisonValue.Equals(other._comparisonValue);
138+
}
147139

148140
public override bool Equals(object obj)
149141
{
150-
var other = obj as Field;
151-
if (other == null)
152-
return false;
153-
return ComparisonValue.Equals(other.ComparisonValue);
142+
if (ReferenceEquals(null, obj)) return false;
143+
if (ReferenceEquals(this, obj)) return true;
144+
if (obj.GetType() != GetType()) return false;
145+
return ((IEquatable<Field>)this).Equals(obj as Field);
146+
}
147+
148+
public static bool operator ==(Field x, Field y)
149+
{
150+
return Equals(x, y);
151+
}
152+
153+
public static bool operator !=(Field x, Field y)
154+
{
155+
return !(x == y);
154156
}
155157

156158
string IUrlParameter.GetString(IConnectionConfigurationValues settings)
@@ -164,10 +166,10 @@ string IUrlParameter.GetString(IConnectionConfigurationValues settings)
164166

165167
private void SetComparisonValue(object value)
166168
{
167-
if (ComparisonValue != null && value != null)
168-
throw new InvalidOperationException($"{nameof(ComparisonValue)} already has a value");
169+
if (_comparisonValue != null && value != null)
170+
throw new InvalidOperationException($"{nameof(_comparisonValue)} already has a value");
169171

170-
ComparisonValue = value;
172+
_comparisonValue = value;
171173
}
172174
}
173175
}

src/Nest/CommonAbstractions/Infer/Field/FieldExpressionVisitor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ protected override Expression VisitConstant(ConstantExpression node)
6464

6565
internal class FieldExpressionVisitor : ExpressionVisitor
6666
{
67-
private Stack<string> _stack = new Stack<string>();
67+
private readonly Stack<string> _stack = new Stack<string>();
6868

69-
private IConnectionSettingsValues _settings;
69+
private readonly IConnectionSettingsValues _settings;
7070

7171
public FieldExpressionVisitor(IConnectionSettingsValues settings)
7272
{

src/Nest/CommonAbstractions/Infer/Field/FieldResolver.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,29 @@ private string ResolveFieldName(Field field)
4242
return this.Resolve(field.Expression, field.Property);
4343
}
4444

45-
string f;
46-
if (this.Fields.TryGetValue(field, out f))
47-
return f;
45+
string fieldName;
46+
if (this.Fields.TryGetValue(field, out fieldName))
47+
return fieldName;
4848

49-
f = this.Resolve(field.Expression, field.Property);
50-
this.Fields.TryAdd(field, f);
51-
return f;
49+
fieldName = this.Resolve(field.Expression, field.Property);
50+
this.Fields.TryAdd(field, fieldName);
51+
return fieldName;
5252
}
5353

5454
public string Resolve(PropertyName property)
5555
{
5656
if (property.IsConditionless()) return null;
57-
if (!property.Name.IsNullOrEmpty())
58-
return property.Name;
57+
if (!property.Name.IsNullOrEmpty()) return property.Name;
58+
59+
if (property.Expression != null && !property.CacheableExpression)
60+
{
61+
return this.Resolve(property.Expression, property.Property);
62+
}
5963

6064
string propertyName;
6165
if (this.Properties.TryGetValue(property, out propertyName))
6266
return propertyName;
67+
6368
propertyName = this.Resolve(property.Expression, property.Property, true);
6469
this.Properties.TryAdd(property, propertyName);
6570
return propertyName;

src/Nest/CommonAbstractions/Infer/PropertyName/PropertyName.cs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,43 @@
11
using System;
2+
using System.Linq;
23
using System.Linq.Expressions;
34
using System.Reflection;
5+
using System.Text.RegularExpressions;
46
using Elasticsearch.Net;
57

68
namespace Nest
79
{
810
[ContractJsonConverter(typeof(PropertyNameJsonConverter))]
911
public class PropertyName : IEquatable<PropertyName>, IUrlParameter
1012
{
13+
private object _comparisonValue;
14+
private Type _type;
15+
1116
public string Name { get; set; }
1217
public Expression Expression { get; set; }
1318
public PropertyInfo Property { get; set; }
14-
15-
private object ComparisonValue;
19+
public bool CacheableExpression { get; private set; }
1620

1721
public static implicit operator PropertyName(string name)
1822
{
1923
return name == null ? null : new PropertyName
2024
{
2125
Name = name,
22-
ComparisonValue = name
26+
_comparisonValue = name
2327
};
2428
}
2529

2630
public static implicit operator PropertyName(Expression expression)
2731
{
28-
return expression == null ? null : new PropertyName
32+
if (expression == null) return null;
33+
34+
Type type;
35+
return new PropertyName
2936
{
3037
Expression = expression,
31-
ComparisonValue = ((expression as LambdaExpression)?.Body as MemberExpression)?.Member.Name ?? expression.ToString()
38+
CacheableExpression = !new HasConstantExpressionVisitor(expression).Found,
39+
_comparisonValue = expression.ComparisonValueFromExpression(out type),
40+
_type = type
3241
};
3342
}
3443

@@ -37,34 +46,50 @@ public static implicit operator PropertyName(PropertyInfo property)
3746
return property == null ? null : new PropertyName
3847
{
3948
Property = property,
40-
ComparisonValue = property
49+
_comparisonValue = property
4150
};
4251
}
4352

4453
public override int GetHashCode()
4554
{
46-
return ComparisonValue?.GetHashCode() ?? 0;
55+
unchecked
56+
{
57+
var hashCode = _comparisonValue?.GetHashCode() ?? 0;
58+
hashCode = (hashCode * 397) ^ (_type?.GetHashCode() ?? 0);
59+
return hashCode;
60+
}
4761
}
4862

4963
bool IEquatable<PropertyName>.Equals(PropertyName other)
5064
{
51-
return Equals(other);
65+
return _type != null
66+
? other != null && _type == other._type && _comparisonValue.Equals(other._comparisonValue)
67+
: other != null && _comparisonValue.Equals(other._comparisonValue);
5268
}
5369

5470
public override bool Equals(object obj)
5571
{
56-
var other = obj as PropertyName;
57-
if (other == null)
58-
return false;
72+
if (ReferenceEquals(null, obj)) return false;
73+
if (ReferenceEquals(this, obj)) return true;
74+
if (obj.GetType() != GetType()) return false;
75+
return ((IEquatable<PropertyName>)this).Equals(obj as PropertyName);
76+
}
5977

60-
return ComparisonValue.Equals(other.ComparisonValue);
78+
public static bool operator ==(PropertyName x, PropertyName y)
79+
{
80+
return Equals(x, y);
81+
}
82+
83+
public static bool operator !=(PropertyName x, PropertyName y)
84+
{
85+
return !(x == y);
6186
}
6287

6388
string IUrlParameter.GetString(IConnectionConfigurationValues settings)
6489
{
6590
var nestSettings = settings as IConnectionSettingsValues;
6691
if (nestSettings == null)
67-
throw new Exception("Tried to pass field name on querysting but it could not be resolved because no nest settings are available");
92+
throw new Exception("Tried to pass field name on querystring but it could not be resolved because no nest settings are available");
6893
var infer = new Inferrer(nestSettings);
6994
return infer.PropertyName(this);
7095
}

src/Nest/QueryDsl/Query.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,11 @@ public static QueryContainer SpanTerm(Func<SpanTermQueryDescriptor<T>, ISpanTerm
161161
public static QueryContainer SpanWithin(Func<SpanWithinQueryDescriptor<T>, ISpanWithinQuery> selector) =>
162162
new QueryContainerDescriptor<T>().SpanWithin(selector);
163163

164+
#pragma warning disable 618
165+
[Obsolete("Scheduled to be removed in 5.0. Setting Strict() at the container level does is a noop and must be set on each individual query.")]
164166
public static QueryContainerDescriptor<T> Strict(bool strict = true) =>
165167
new QueryContainerDescriptor<T>().Strict(strict);
168+
#pragma warning restore 618
166169

167170
public static QueryContainer Template(Func<TemplateQueryDescriptor<T>, ITemplateQuery> selector) =>
168171
new QueryContainerDescriptor<T>().Template(selector);

0 commit comments

Comments
 (0)