Skip to content

Commit c6652ac

Browse files
committed
Include Format property on Field for Doc Values fields (#3491)
This commit adds a format property on the Field type to use with Doc Values fields, and updates serialization components to handle the presence of a Format property value. (cherry picked from commit e7daead)
1 parent b8cf545 commit c6652ac

File tree

7 files changed

+241
-73
lines changed

7 files changed

+241
-73
lines changed

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

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,82 @@
44
using System.Linq.Expressions;
55
using System.Reflection;
66
using Elasticsearch.Net;
7+
using Newtonsoft.Json;
78

89
namespace Nest
910
{
10-
[ContractJsonConverter(typeof(FieldJsonConverter))]
11+
/// <summary>
12+
/// A field within Elasticsearch
13+
/// </summary>
14+
[JsonConverter(typeof(FieldJsonConverter))]
1115
[DebuggerDisplay("{DebugDisplay,nq}")]
1216
public class Field : IEquatable<Field>, IUrlParameter
1317
{
1418
private readonly object _comparisonValue;
1519
private readonly Type _type;
1620

17-
public Field(string name, double? boost = null)
21+
public Field(string name, double? boost = null, string format = null)
1822
{
1923
name.ThrowIfNullOrEmpty(nameof(name));
20-
double? b;
21-
Name = ParseFieldName(name, out b);
24+
Name = ParseFieldName(name, out var b);
2225
Boost = b ?? boost;
26+
Format = format;
2327
_comparisonValue = Name;
2428
}
2529

26-
public Field(Expression expression, double? boost = null)
30+
public Field(Expression expression, double? boost = null, string format = null)
2731
{
2832
Expression = expression ?? throw new ArgumentNullException(nameof(expression));
2933
Boost = boost;
34+
Format = format;
3035
_comparisonValue = expression.ComparisonValueFromExpression(out var type);
3136
_type = type;
3237
CachableExpression = !new HasVariableExpressionVisitor(expression).Found;
3338
}
3439

35-
public Field(PropertyInfo property, double? boost = null)
40+
public Field(PropertyInfo property, double? boost = null, string format = null)
3641
{
3742
Property = property ?? throw new ArgumentNullException(nameof(property));
3843
Boost = boost;
44+
Format = format;
3945
_comparisonValue = property;
4046
_type = property.DeclaringType;
4147
}
4248

49+
/// <summary>
50+
/// A boost to apply to the field
51+
/// </summary>
4352
public double? Boost { get; set; }
4453

54+
/// <summary>
55+
/// A format to apply to the field.
56+
/// </summary>
57+
/// <remarks>
58+
/// Can be used only for Doc Value Fields Elasticsearch 6.4.0+
59+
/// </remarks>
60+
public string Format { get; set; }
61+
4562
public bool CachableExpression { get; }
4663

64+
/// <summary>
65+
/// An expression from which the name of the field can be inferred
66+
/// </summary>
4767
public Expression Expression { get; }
4868

69+
/// <summary>
70+
/// The name of the field
71+
/// </summary>
4972
public string Name { get; }
5073

74+
/// <summary>
75+
/// A property from which the name of the field can be inferred
76+
/// </summary>
5177
public PropertyInfo Property { get; }
5278

5379
internal string DebugDisplay =>
54-
$"{Expression?.ToString() ?? PropertyDebug ?? Name}{(Boost.HasValue ? "^" + Boost.Value : "")}{(_type == null ? "" : " typeof: " + _type.Name)}";
80+
$"{Expression?.ToString() ?? PropertyDebug ?? Name}{(Boost.HasValue ? "^" + Boost.Value : string.Empty)}"
81+
+ $"{(!string.IsNullOrEmpty(Format) ? " format: " + Format : string.Empty)}"
82+
+ $"{(_type == null ? string.Empty : " typeof: " + _type.Name)}";
5583

5684
private string PropertyDebug => Property == null ? null : $"PropertyInfo: {Property.Name}";
5785

@@ -70,21 +98,25 @@ string IUrlParameter.GetString(IConnectionConfigurationValues settings)
7098

7199
public Fields And(Field field) => new Fields(new[] { this, field });
72100

73-
public Fields And<T>(Expression<Func<T, object>> field, double? boost = null) where T : class =>
74-
new Fields(new[] { this, new Field(field, boost) });
101+
public Fields And<T>(Expression<Func<T, object>> field, double? boost = null, string format = null) where T : class =>
102+
new Fields(new[] { this, new Field(field, boost, format) });
75103

76-
public Fields And(string field, double? boost = null) => new Fields(new[] { this, new Field(field, boost) });
104+
public Fields And(string field, double? boost = null, string format = null) =>
105+
new Fields(new[] { this, new Field(field, boost, format) });
77106

78-
public Fields And(PropertyInfo property, double? boost = null) => new Fields(new[] { this, new Field(property, boost) });
107+
public Fields And(PropertyInfo property, double? boost = null, string format = null) =>
108+
new Fields(new[] { this, new Field(property, boost, format) });
79109

80110
private static string ParseFieldName(string name, out double? boost)
81111
{
82112
boost = null;
83113
if (name == null) return null;
84114

85-
var parts = name.Split(new[] { '^' }, StringSplitOptions.RemoveEmptyEntries);
86-
if (parts.Length <= 1) return name;
115+
var caretIndex = name.IndexOf('^');
116+
if (caretIndex == -1)
117+
return name;
87118

119+
var parts = name.Split(new[] { '^' }, 2, StringSplitOptions.RemoveEmptyEntries);
88120
name = parts[0];
89121
boost = double.Parse(parts[1], CultureInfo.InvariantCulture);
90122
return name;

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

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,73 @@ internal class FieldJsonConverter : JsonConverter
1313

1414
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
1515
{
16-
var field = value as Field;
16+
var field = (Field)value;
1717
if (field == null)
1818
{
1919
writer.WriteNull();
2020
return;
2121
}
2222
var settings = serializer.GetConnectionSettings();
23-
writer.WriteValue(settings.Inferrer.Field(field));
23+
var fieldName = settings.Inferrer.Field(field);
24+
25+
if (!string.IsNullOrEmpty(field.Format))
26+
{
27+
writer.WriteStartObject();
28+
writer.WritePropertyName("field");
29+
writer.WriteValue(fieldName);
30+
writer.WritePropertyName("format");
31+
writer.WriteValue(field.Format);
32+
writer.WriteEndObject();
33+
}
34+
else
35+
{
36+
writer.WriteValue(fieldName);
37+
}
2438
}
2539

2640
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
2741
{
28-
if (reader.TokenType != JsonToken.String) return null;
42+
switch (reader.TokenType)
43+
{
44+
case JsonToken.String:
45+
return new Field((string)reader.Value);
46+
case JsonToken.StartObject:
47+
string fieldName = null;
48+
double? boost = null;
49+
string format = null;
50+
reader.Read();
51+
52+
while (reader.TokenType != JsonToken.EndObject)
53+
{
54+
if (reader.TokenType == JsonToken.PropertyName)
55+
{
56+
switch ((string)reader.Value)
57+
{
58+
case "field":
59+
fieldName = reader.ReadAsString();
60+
break;
61+
case "boost":
62+
boost = reader.ReadAsDouble();
63+
break;
64+
case "format":
65+
format = reader.ReadAsString();
66+
break;
67+
default:
68+
reader.Read();
69+
break;
70+
}
71+
72+
reader.Read();
73+
continue;
74+
}
2975

30-
var field = reader.Value.ToString();
31-
return (Field)field;
76+
break;
77+
}
78+
79+
return new Field(fieldName, boost, format);
80+
default:
81+
return null;
82+
}
3283
}
3384
}
3485
}

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

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
using System.Linq.Expressions;
77
using System.Reflection;
88
using Elasticsearch.Net;
9+
using Newtonsoft.Json;
910

1011
namespace Nest
1112
{
12-
[ContractJsonConverter(typeof(FieldsJsonConverter))]
13+
[JsonConverter(typeof(FieldsJsonConverter))]
1314
[DebuggerDisplay("{DebugDisplay,nq}")]
1415
public class Fields : IUrlParameter, IEnumerable<Field>, IEquatable<Fields>
1516
{
@@ -37,33 +38,33 @@ string IUrlParameter.GetString(IConnectionConfigurationValues settings)
3738
return string.Join(",", ListOfFields.Where(f => f != null).Select(f => ((IUrlParameter)f).GetString(nestSettings)));
3839
}
3940

40-
public static implicit operator Fields(string[] fields) => fields.IsEmpty() ? null : new Fields(fields.Select(f => (Field)f));
41+
public static implicit operator Fields(string[] fields) => fields.IsEmpty() ? null : new Fields(fields.Select(f => new Field(f)));
4142

4243
public static implicit operator Fields(string field) => field.IsNullOrEmptyCommaSeparatedList(out var split)
4344
? null
44-
: new Fields(split.Select(f => (Field)f));
45+
: new Fields(split.Select(f => new Field(f)));
4546

46-
public static implicit operator Fields(Expression[] fields) => fields.IsEmpty() ? null : new Fields(fields.Select(f => (Field)f));
47+
public static implicit operator Fields(Expression[] fields) => fields.IsEmpty() ? null : new Fields(fields.Select(f => new Field(f)));
4748

48-
public static implicit operator Fields(Expression field) => field == null ? null : new Fields(new[] { (Field)field });
49+
public static implicit operator Fields(Expression field) => field == null ? null : new Fields(new[] { new Field(field) });
4950

5051
public static implicit operator Fields(Field field) => field == null ? null : new Fields(new[] { field });
5152

5253
public static implicit operator Fields(PropertyInfo field) => field == null ? null : new Fields(new Field[] { field });
5354

54-
public static implicit operator Fields(PropertyInfo[] fields) => fields.IsEmpty() ? null : new Fields(fields.Select(f => (Field)f));
55+
public static implicit operator Fields(PropertyInfo[] fields) => fields.IsEmpty() ? null : new Fields(fields.Select(f => new Field(f)));
5556

5657
public static implicit operator Fields(Field[] fields) => fields.IsEmpty() ? null : new Fields(fields);
5758

58-
public Fields And<T>(Expression<Func<T, object>> field, double? boost = null) where T : class
59+
public Fields And<T>(Expression<Func<T, object>> field, double? boost = null, string format = null) where T : class
5960
{
60-
ListOfFields.Add(new Field(field, boost));
61+
ListOfFields.Add(new Field(field, boost, format));
6162
return this;
6263
}
6364

64-
public Fields And(string field, double? boost = null)
65+
public Fields And(string field, double? boost = null, string format = null)
6566
{
66-
ListOfFields.Add(new Field(field, boost));
67+
ListOfFields.Add(new Field(field, boost, format));
6768
return this;
6869
}
6970

@@ -75,19 +76,19 @@ public Fields And(PropertyInfo property, double? boost = null)
7576

7677
public Fields And<T>(params Expression<Func<T, object>>[] fields) where T : class
7778
{
78-
ListOfFields.AddRange(fields.Select(f => (Field)f));
79+
ListOfFields.AddRange(fields.Select(f => new Field(f)));
7980
return this;
8081
}
8182

8283
public Fields And(params string[] fields)
8384
{
84-
ListOfFields.AddRange(fields.Select(f => (Field)f));
85+
ListOfFields.AddRange(fields.Select(f => new Field(f)));
8586
return this;
8687
}
8788

8889
public Fields And(params PropertyInfo[] properties)
8990
{
90-
ListOfFields.AddRange(properties.Select(f => (Field)f));
91+
ListOfFields.AddRange(properties.Select(f => new Field(f)));
9192
return this;
9293
}
9394

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ public FieldsDescriptor() : base(new Fields()) { }
1515

1616
public FieldsDescriptor<T> Fields(IEnumerable<Field> fields) => Assign(f => f.ListOfFields.AddRange(fields));
1717

18-
public FieldsDescriptor<T> Field(Expression<Func<T, object>> field, double? boost = null) => Assign(f => f.And(field, boost));
18+
public FieldsDescriptor<T> Field(Expression<Func<T, object>> field, double? boost = null, string format = null) =>
19+
Assign(f => f.And(field, boost, format));
1920

20-
public FieldsDescriptor<T> Field(string field, double? boost = null) => Assign(f => f.And(field, boost));
21+
public FieldsDescriptor<T> Field(string field, double? boost = null, string format = null) =>
22+
Assign(f => f.And(field, boost, format));
2123

2224
public FieldsDescriptor<T> Field(Field field) => Assign(f => f.And(field));
2325
}
Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using Newtonsoft.Json;
34

45
namespace Nest
@@ -17,37 +18,22 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
1718
writer.WriteStartArray();
1819
if (fields != null)
1920
{
20-
var infer = serializer.GetConnectionSettings().Inferrer;
21-
foreach (var f in fields.ListOfFields) writer.WriteValue(infer.Field(f));
22-
}
23-
writer.WriteEndArray();
24-
}
21+
// overridden Equals() method means a Fields with only one Field
22+
// results in Equality, which triggers Json.NET's Reference loop detection
23+
var referenceLoopHandling = serializer.ReferenceLoopHandling;
24+
serializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
2525

26-
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
27-
{
28-
if (reader.TokenType != JsonToken.StartArray) return null;
26+
foreach (var field in fields.ListOfFields)
27+
serializer.Serialize(writer, field);
2928

30-
var fields = new Fields();
31-
while (reader.TokenType != JsonToken.EndArray)
32-
{
33-
// as per https://github.com/elastic/elasticsearch/pull/29639 this can now be an array of objects
34-
reader.Read();
35-
switch (reader.TokenType)
36-
{
37-
case JsonToken.String:
38-
fields.And((string)reader.Value);
39-
break;
40-
case JsonToken.StartObject:
41-
/// TODO 6.4 this is temporary until we add proper support for doc_values format
42-
reader.Read(); // "field";
43-
var field = reader.ReadAsString();
44-
fields.And(field);
45-
while (reader.TokenType != JsonToken.EndObject) reader.Read();
46-
reader.Read(); // "}";
47-
break;
48-
}
29+
serializer.ReferenceLoopHandling = referenceLoopHandling;
4930
}
50-
return fields;
31+
writer.WriteEndArray();
5132
}
33+
34+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
35+
reader.TokenType != JsonToken.StartArray
36+
? null
37+
: new Fields(serializer.Deserialize<IEnumerable<Field>>(reader));
5238
}
5339
}

src/Nest/CommonAbstractions/Static/Infer.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,24 @@ public static class Infer
4646
public static Id Id<T>(T document) where T : class => Nest.Id.From(document);
4747

4848
public static Fields Fields<T>(params Expression<Func<T, object>>[] fields) where T : class =>
49-
new Fields(fields.Select(f => (Field)f));
49+
new Fields(fields.Select(f => new Field(f)));
5050

51-
public static Fields Fields(params string[] fields) => new Fields(fields.Select(f => (Field)f));
51+
public static Fields Fields(params string[] fields) => new Fields(fields.Select(f => new Field(f)));
5252

53-
public static Fields Fields(params PropertyInfo[] properties) => new Fields(properties.Select(f => (Field)f));
53+
public static Fields Fields(params PropertyInfo[] properties) => new Fields(properties.Select(f => new Field(f)));
5454

5555
/// <summary>
5656
/// Create a strongly typed string field name representation of the path to a property
5757
/// <para>e.g. p => p.Array.First().SubProperty.Field will return 'array.subProperty.field'</para>
5858
/// </summary>
59-
/// <typeparam name="T">The type of the object</typeparam>
60-
/// <param name="path">The path we want to specify</param>
61-
/// <param name="boost">An optional ^boost postfix, only make sense with certain queries</param>
62-
public static Field Field<T>(Expression<Func<T, object>> path, double? boost = null)
63-
where T : class => new Field(path, boost);
59+
public static Field Field<T>(Expression<Func<T, object>> path, double? boost = null, string format = null)
60+
where T : class => new Field(path, boost, format);
6461

65-
public static Field Field(string field, double? boost = null) => new Field(field, boost);
62+
public static Field Field(string field, double? boost = null, string format = null) =>
63+
new Field(field, boost, format);
6664

67-
public static Field Field(PropertyInfo property, double? boost = null) => new Field(property, boost);
65+
public static Field Field(PropertyInfo property, double? boost = null, string format = null) =>
66+
new Field(property, boost, format);
6867

6968
public static PropertyName Property(string property) => property;
7069

0 commit comments

Comments
 (0)