Skip to content

Commit 897b2c6

Browse files
authored
Structural equals for infer and IUrlParameter types (#3126)
* All IUrlParameter and infer types now implement a structural Equals() and not a referential Equals() move equality test folder one level down into Inferrence Trim() is bad on singular types, removed triming from them as it only hides bugs. Moved the trim to plural type parsing of multi valued strings made sure implicit conversion from null/empty string/empty comma separated string all behave the same (return null) for IUrlParameters and Infer types Made sure null flows through all the Equals implementations and fixed existing tests that failed due to the updated implementation * boost should not be part of field equality afterall, (current behaviour)
1 parent 5727f7d commit 897b2c6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1751
-313
lines changed

src/Nest/CommonAbstractions/Extensions/Extensions.cs

+25
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ internal static bool HasAny<T>(this IEnumerable<T> list)
183183
return list != null && list.Any();
184184
}
185185

186+
internal static bool IsEmpty<T>(this IEnumerable<T> list)
187+
{
188+
if (list == null) return true;
189+
var enumerable = list as T[] ?? list.ToArray();
190+
return !enumerable.Any() || enumerable.All(t => t == null);
191+
}
192+
186193
internal static void ThrowIfNull<T>(this T value, string name, string message = null)
187194
{
188195
if (value == null && message.IsNullOrEmpty()) throw new ArgumentNullException(name);
@@ -193,6 +200,16 @@ internal static bool IsNullOrEmpty(this string value)
193200
{
194201
return string.IsNullOrWhiteSpace(value);
195202
}
203+
internal static bool IsNullOrEmptyCommaSeparatedList(this string value, out string[] split)
204+
{
205+
split = null;
206+
if (string.IsNullOrWhiteSpace(value)) return true;
207+
split = value.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)
208+
.Where(t=>!t.IsNullOrEmpty())
209+
.Select(t=>t.Trim())
210+
.ToArray();
211+
return split.Length == 0;
212+
}
196213

197214
internal static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> handler)
198215
{
@@ -282,5 +299,13 @@ private static async Task ProcessAsync<TSource, TResult>(
282299
additionalRateLimiter?.Release();
283300
}
284301
}
302+
303+
internal static bool NullOrEquals<T>(this T o, T other)
304+
{
305+
if (o == null && other == null) return true;
306+
if (o == null || other == null) return false;
307+
return o.Equals(other);
308+
}
309+
285310
}
286311
}

src/Nest/CommonAbstractions/Infer/ActionIds/ActionIds.cs

+19-5
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Linq;
5-
using System.Text;
6-
using System.Threading.Tasks;
75
using Elasticsearch.Net;
86

97
namespace Nest
108
{
119
[DebuggerDisplay("{DebugDisplay,nq}")]
12-
public class ActionIds : IUrlParameter
10+
public class ActionIds : IUrlParameter, IEquatable<ActionIds>
1311
{
1412
private readonly List<string> _actionIds;
13+
internal IReadOnlyList<string> Ids => _actionIds;
1514

1615
public ActionIds(IEnumerable<string> actionIds)
1716
{
@@ -31,8 +30,23 @@ public ActionIds(string actionIds)
3130

3231
string IUrlParameter.GetString(IConnectionConfigurationValues settings) => string.Join(",", this._actionIds);
3332

34-
public static implicit operator ActionIds(string actionIds) => new ActionIds(actionIds);
33+
public static implicit operator ActionIds(string actionIds) => actionIds.IsNullOrEmptyCommaSeparatedList(out var list) ? null : new ActionIds(list);
3534

36-
public static implicit operator ActionIds(string[] actionIds) => new ActionIds(actionIds);
35+
public static implicit operator ActionIds(string[] actionIds) => actionIds.IsEmpty() ? null : new ActionIds(actionIds);
36+
37+
public bool Equals(ActionIds other)
38+
{
39+
if (this.Ids == null && other.Ids == null) return true;
40+
if (this.Ids == null || other.Ids == null) return false;
41+
return this.Ids.Count == other.Ids.Count && !this.Ids.Except(other.Ids).Any();
42+
}
43+
44+
public override bool Equals(object obj) => obj is ActionIds other && Equals(other);
45+
46+
public override int GetHashCode() => this._actionIds.GetHashCode();
47+
48+
public static bool operator ==(ActionIds left, ActionIds right) => Equals(left, right);
49+
50+
public static bool operator !=(ActionIds left, ActionIds right) => !Equals(left, right);
3751
}
3852
}
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
1-
using System.Globalization;
1+
using System;
2+
using System.Globalization;
23
using Elasticsearch.Net;
34

45
namespace Nest
56
{
6-
public class CategoryId : IUrlParameter
7+
public class CategoryId : IUrlParameter, IEquatable<CategoryId>
78
{
8-
private readonly long _categoryId;
9+
internal readonly long Value;
910

10-
public CategoryId(long categoryId)
11+
public CategoryId(long value) => Value = value;
12+
13+
public static implicit operator CategoryId(long categoryId) => new CategoryId(categoryId);
14+
public static implicit operator long(CategoryId categoryId) => categoryId.Value;
15+
16+
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
17+
public string GetString(IConnectionConfigurationValues settings) => Value.ToString(CultureInfo.InvariantCulture);
18+
19+
public bool Equals(CategoryId other) => this.Value == other.Value;
20+
21+
public override bool Equals(object obj)
1122
{
12-
_categoryId = categoryId;
23+
switch (obj)
24+
{
25+
case int l: return this.Value == l;
26+
case long l: return this.Value == l;
27+
case CategoryId i: return this.Value == i.Value;
28+
default: return false;
29+
}
1330
}
1431

15-
public static implicit operator CategoryId(long categoryId) => new CategoryId(categoryId);
32+
public override int GetHashCode() => this.Value.GetHashCode();
33+
34+
public static bool operator ==(CategoryId left, CategoryId right) => Equals(left, right);
1635

17-
public string GetString(IConnectionConfigurationValues settings) => _categoryId.ToString(CultureInfo.InvariantCulture);
36+
public static bool operator !=(CategoryId left, CategoryId right) => !Equals(left, right);
1837
}
1938
}

src/Nest/CommonAbstractions/Infer/DocumentPath/DocumentPath.cs

+35-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public interface IDocumentPath
99
TypeName Type { get; set; }
1010
}
1111

12-
public class DocumentPath<T> : IDocumentPath where T : class
12+
public class DocumentPath<T> : IEquatable<DocumentPath<T>>, IDocumentPath where T : class
1313
{
1414
internal IDocumentPath Self => this;
1515
internal T Document { get; set; }
@@ -28,10 +28,10 @@ public DocumentPath(Id id)
2828
public static DocumentPath<T> Id(Id id) => new DocumentPath<T>(id);
2929
public static DocumentPath<T> Id(T @object) => new DocumentPath<T>(@object);
3030

31-
public static implicit operator DocumentPath<T>(T @object) => new DocumentPath<T>(@object);
32-
public static implicit operator DocumentPath<T>(Id id) => new DocumentPath<T>(id);
31+
public static implicit operator DocumentPath<T>(T @object) => @object == null ? null : new DocumentPath<T>(@object);
32+
public static implicit operator DocumentPath<T>(Id id) => id == null ? null : new DocumentPath<T>(id);
3333
public static implicit operator DocumentPath<T>(long id) => new DocumentPath<T>(id);
34-
public static implicit operator DocumentPath<T>(string id) => new DocumentPath<T>(id);
34+
public static implicit operator DocumentPath<T>(string id) => id.IsNullOrEmpty() ? null : new DocumentPath<T>(id);
3535
public static implicit operator DocumentPath<T>(Guid id) => new DocumentPath<T>(id);
3636

3737
public DocumentPath<T> Index(IndexName index)
@@ -46,5 +46,36 @@ public DocumentPath<T> Type(TypeName type)
4646
Self.Type = type;
4747
return this;
4848
}
49+
50+
public override int GetHashCode()
51+
{
52+
unchecked
53+
{
54+
var hashCode = Self.Type?.GetHashCode() ?? 0;
55+
hashCode = (hashCode * 397) ^ (Self.Index?.GetHashCode() ?? 0);
56+
hashCode = (hashCode * 397) ^ (Self.Id?.GetHashCode() ?? 0);
57+
return hashCode;
58+
}
59+
}
60+
61+
public bool Equals(DocumentPath<T> other)
62+
{
63+
IDocumentPath o = other, s = Self;
64+
return s.Index.NullOrEquals(o.Index) && s.Type.NullOrEquals(o.Type) && s.Id.NullOrEquals(o.Id)
65+
&& (this.Document?.Equals(other.Document) ?? true);
66+
}
67+
68+
public override bool Equals(object obj)
69+
{
70+
switch (obj)
71+
{
72+
case DocumentPath<T> d: return this.Equals(d);
73+
default: return false;
74+
}
75+
}
76+
77+
public static bool operator ==(DocumentPath<T> x, DocumentPath<T> y) => Equals(x, y);
78+
79+
public static bool operator !=(DocumentPath<T> x, DocumentPath<T> y)=> !Equals(x, y);
4980
}
5081
}

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

+19-35
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,16 @@ public Field(string name, double? boost = null)
5050

5151
public Field(Expression expression, double? boost = null)
5252
{
53-
if (expression == null) throw new ArgumentNullException(nameof(expression));
54-
Expression = expression;
53+
Expression = expression ?? throw new ArgumentNullException(nameof(expression));
5554
Boost = boost;
56-
Type type;
57-
_comparisonValue = expression.ComparisonValueFromExpression(out type);
55+
_comparisonValue = expression.ComparisonValueFromExpression(out var type);
5856
_type = type;
5957
CachableExpression = !new HasVariableExpressionVisitor(expression).Found;
6058
}
6159

6260
public Field(PropertyInfo property, double? boost = null)
6361
{
64-
if (property == null) throw new ArgumentNullException(nameof(property));
65-
Property = property;
62+
Property = property ?? throw new ArgumentNullException(nameof(property));
6663
Boost = boost;
6764
_comparisonValue = property;
6865
_type = property.DeclaringType;
@@ -74,26 +71,17 @@ private static string ParseFieldName(string name, out double? boost)
7471
if (name == null) return null;
7572

7673
var parts = name.Split(new [] { '^' }, StringSplitOptions.RemoveEmptyEntries);
77-
if (parts.Length <= 1) return name.Trim();
74+
if (parts.Length <= 1) return name;
7875
name = parts[0];
7976
boost = double.Parse(parts[1], CultureInfo.InvariantCulture);
80-
return name.Trim();
77+
return name;
8178
}
8279

83-
public static implicit operator Field(string name)
84-
{
85-
return name.IsNullOrEmpty() ? null : new Field(name);
86-
}
80+
public static implicit operator Field(string name) => name.IsNullOrEmpty() ? null : new Field(name);
8781

88-
public static implicit operator Field(Expression expression)
89-
{
90-
return expression == null ? null : new Field(expression);
91-
}
82+
public static implicit operator Field(Expression expression) => expression == null ? null : new Field(expression);
9283

93-
public static implicit operator Field(PropertyInfo property)
94-
{
95-
return property == null ? null : new Field(property);
96-
}
84+
public static implicit operator Field(PropertyInfo property) => property == null ? null : new Field(property);
9785

9886
public override int GetHashCode()
9987
{
@@ -114,27 +102,23 @@ public bool Equals(Field other)
114102

115103
public override bool Equals(object obj)
116104
{
117-
if (ReferenceEquals(null, obj)) return false;
118-
if (ReferenceEquals(this, obj)) return true;
119-
if (obj.GetType() != GetType()) return false;
120-
return this.Equals(obj as Field);
105+
switch (obj)
106+
{
107+
case string s: return this.Equals(s);
108+
case PropertyInfo p: return this.Equals(p);
109+
case Field f: return this.Equals(f);
110+
default: return false;
111+
}
121112
}
122113

123-
public static bool operator ==(Field x, Field y)
124-
{
125-
return Equals(x, y);
126-
}
114+
public static bool operator ==(Field x, Field y) => Equals(x, y);
127115

128-
public static bool operator !=(Field x, Field y)
129-
{
130-
return !(x == y);
131-
}
116+
public static bool operator !=(Field x, Field y)=> !Equals(x, y);
132117

133118
string IUrlParameter.GetString(IConnectionConfigurationValues settings)
134119
{
135-
var nestSettings = settings as IConnectionSettingsValues;
136-
if (nestSettings == null)
137-
throw new Exception("Tried to pass field name on querystring but it could not be resolved because no nest settings are available");
120+
if (!(settings is IConnectionSettingsValues nestSettings))
121+
throw new ArgumentNullException(nameof(settings), $"Can not resolve {nameof(Field)} if no {nameof(IConnectionSettingsValues)} is provided");
138122

139123
return nestSettings.Inferrer.Field(this);
140124
}

src/Nest/CommonAbstractions/Infer/Fields/Fields.cs

+48-16
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,40 @@ namespace Nest
1111
{
1212
[ContractJsonConverter(typeof(FieldsJsonConverter))]
1313
[DebuggerDisplay("{DebugDisplay,nq}")]
14-
public class Fields : IUrlParameter, IEnumerable<Field>
14+
public class Fields : IUrlParameter, IEnumerable<Field>, IEquatable<Fields>
1515
{
1616
internal readonly List<Field> ListOfFields;
1717

18-
string IUrlParameter.GetString(IConnectionConfigurationValues settings) =>
19-
string.Join(",", ListOfFields.Select(f => ((IUrlParameter)f).GetString(settings)));
18+
string IUrlParameter.GetString(IConnectionConfigurationValues settings)
19+
{
20+
if (!(settings is IConnectionSettingsValues nestSettings))
21+
throw new ArgumentNullException(nameof(settings), $"Can not resolve {nameof(Fields)} if no {nameof(IConnectionSettingsValues)} is provided");
2022

21-
private string DebugDisplay =>
22-
$"Count: {ListOfFields.Count} [" + string.Join(",", ListOfFields.Select((t, i) => $"({i + 1}: {t.DebugDisplay})")) + "]";
23+
return string.Join(",", ListOfFields.Where(f => f != null).Select(f => ((IUrlParameter)f).GetString(nestSettings)));
24+
}
2325

26+
private string DebugDisplay =>
27+
$"Count: {ListOfFields.Count} [" + string.Join(",", ListOfFields.Select((t, i) => $"({i + 1}: {t?.DebugDisplay ?? "NULL"})")) + "]";
2428

2529
internal Fields() { this.ListOfFields = new List<Field>(); }
2630
internal Fields(IEnumerable<Field> fieldNames) { this.ListOfFields = fieldNames.ToList(); }
2731

28-
public static implicit operator Fields(string[] fields) => new Fields(fields.Select(f => (Field)f));
32+
public static implicit operator Fields(string[] fields) => fields.IsEmpty() ? null : new Fields(fields.Select(f => (Field)f));
33+
34+
public static implicit operator Fields(string field) => field.IsNullOrEmptyCommaSeparatedList(out var split)
35+
? null : new Fields(split.Select(f=>(Field)f));
36+
37+
public static implicit operator Fields(Expression[] fields) => fields.IsEmpty() ? null : new Fields(fields.Select(f => (Field)f));
2938

30-
public static implicit operator Fields(string field) =>
31-
new Fields(field.Split(new [] {','}, StringSplitOptions.RemoveEmptyEntries).Select(f=>(Field)f));
39+
public static implicit operator Fields(Expression field) => field == null ? null : new Fields(new [] { (Field)field });
3240

33-
public static implicit operator Fields(Expression[] fields) => new Fields(fields.Select(f => (Field)f));
41+
public static implicit operator Fields(Field field) => field == null ? null : new Fields(new[] { field });
3442

35-
public static implicit operator Fields(Expression field) => new Fields(new [] { (Field)field });
43+
public static implicit operator Fields(PropertyInfo field) => field == null ? null : new Fields(new Field[] { field });
3644

37-
public static implicit operator Fields(Field field) => new Fields(new[] { field });
45+
public static implicit operator Fields(PropertyInfo[] fields) => fields.IsEmpty() ? null : new Fields(fields.Select(f=>(Field)f));
3846

39-
public static implicit operator Fields(Field[] fields) => new Fields(fields);
47+
public static implicit operator Fields(Field[] fields) => fields.IsEmpty() ? null : new Fields(fields);
4048

4149
public Fields And<T>(Expression<Func<T, object>> field, double? boost = null) where T : class
4250
{
@@ -80,14 +88,38 @@ public Fields And(params Field[] fields)
8088
return this;
8189
}
8290

83-
public IEnumerator<Field> GetEnumerator()
91+
public IEnumerator<Field> GetEnumerator() => this.ListOfFields.GetEnumerator();
92+
93+
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
94+
95+
public static bool operator ==(Fields left, Fields right) => Equals(left, right);
96+
97+
public static bool operator !=(Fields left, Fields right) => !Equals(left, right);
98+
99+
public bool Equals(Fields other) => EqualsAllFields(this.ListOfFields, other.ListOfFields);
100+
101+
public override bool Equals(object obj)
84102
{
85-
return this.ListOfFields.GetEnumerator();
103+
switch (obj)
104+
{
105+
case Fields f: return Equals(f);
106+
case string s: return Equals(s);
107+
case Field fn: return Equals(fn);
108+
case Field[] fns: return Equals(fns);
109+
case Expression e: return Equals(e);
110+
case Expression[] es: return Equals(es);
111+
default: return false;
112+
}
86113
}
87114

88-
IEnumerator IEnumerable.GetEnumerator()
115+
private static bool EqualsAllFields(IReadOnlyList<Field> thisTypes, IReadOnlyList<Field> otherTypes)
89116
{
90-
return this.GetEnumerator();
117+
if (thisTypes == null && otherTypes == null) return true;
118+
if (thisTypes == null || otherTypes == null) return false;
119+
if (thisTypes.Count != otherTypes.Count) return false;
120+
return thisTypes.Count == otherTypes.Count && !thisTypes.Except(otherTypes).Any();
91121
}
122+
123+
public override int GetHashCode() => this.ListOfFields.GetHashCode();
92124
}
93125
}

0 commit comments

Comments
 (0)