Skip to content

Commit d2b6363

Browse files
committed
wrote integration test for inner hits
1 parent 81da982 commit d2b6363

File tree

13 files changed

+322
-7
lines changed

13 files changed

+322
-7
lines changed

src/Nest/DSL/Search/IGlobalInnerHit.cs

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace Nest
77
{
8-
public interface IGlobalInnerHit
8+
public interface IGlobalInnerHit : IInnerHits
99
{
1010
[JsonProperty(PropertyName = "query")]
1111
IQueryContainer Query { get; set; }
@@ -15,7 +15,7 @@ public interface IGlobalInnerHit
1515
IDictionary<string, IInnerHitsContainer> InnerHits { get; set; }
1616
}
1717

18-
public class GlobalInnerHit : IGlobalInnerHit
18+
public class GlobalInnerHit : InnerHits, IGlobalInnerHit
1919
{
2020
public IQueryContainer Query { get; set; }
2121
public IDictionary<string, IInnerHitsContainer> InnerHits { get; set; }
@@ -27,6 +27,16 @@ public class GlobalInnerHitDescriptor<T> : IGlobalInnerHit where T : class
2727

2828
IQueryContainer IGlobalInnerHit.Query { get; set; }
2929
IDictionary<string, IInnerHitsContainer> IGlobalInnerHit.InnerHits { get; set; }
30+
string IInnerHits.Name { get; set; }
31+
int? IInnerHits.From { get; set; }
32+
int? IInnerHits.Size { get; set; }
33+
IList<KeyValuePair<PropertyPathMarker, ISort>> IInnerHits.Sort { get; set; }
34+
IHighlightRequest IInnerHits.Highlight { get; set; }
35+
bool? IInnerHits.Explain { get; set; }
36+
ISourceFilter IInnerHits.Source { get; set; }
37+
bool? IInnerHits.Version { get; set; }
38+
IEnumerable<string> IInnerHits.FielddataFields { get; set; }
39+
IDictionary<string, IScriptFilter> IInnerHits.ScriptFields { get; set; }
3040

3141
public GlobalInnerHitDescriptor<T> Query(Func<QueryDescriptor<T>, IQueryContainer> querySelector)
3242
{
@@ -58,5 +68,112 @@ public GlobalInnerHitDescriptor<T> InnerHits(
5868
Self.InnerHits = containers;
5969
return this;
6070
}
71+
72+
public GlobalInnerHitDescriptor<T> From(int? from)
73+
{
74+
Self.From = from;
75+
return this;
76+
}
77+
78+
public GlobalInnerHitDescriptor<T> Size(int? size)
79+
{
80+
Self.Size = size;
81+
return this;
82+
}
83+
84+
public GlobalInnerHitDescriptor<T> Name(string name)
85+
{
86+
Self.Name = name;
87+
return this;
88+
}
89+
90+
public GlobalInnerHitDescriptor<T> FielddataFields(params string[] fielddataFields)
91+
{
92+
Self.FielddataFields = fielddataFields;
93+
return this;
94+
}
95+
96+
public GlobalInnerHitDescriptor<T> FielddataFields(IEnumerable<string> fielddataFields)
97+
{
98+
Self.FielddataFields = fielddataFields;
99+
return this;
100+
}
101+
102+
public GlobalInnerHitDescriptor<T> Explain(bool explain = true)
103+
{
104+
Self.Explain = explain;
105+
return this;
106+
}
107+
108+
public GlobalInnerHitDescriptor<T> Version(bool version = true)
109+
{
110+
Self.Version = version;
111+
return this;
112+
}
113+
114+
public GlobalInnerHitDescriptor<T> Sort(Func<SortDescriptor<T>, SortDescriptor<T>> sortSelector)
115+
{
116+
if (sortSelector == null) return this;
117+
118+
var descriptor = sortSelector(new SortDescriptor<T>());
119+
120+
Self.Sort = descriptor.InternalSortState.Count == 0 ? null : descriptor.InternalSortState;
121+
return this;
122+
}
123+
124+
/// <summary>
125+
/// Allow to highlight search results on one or more fields. The implementation uses the either lucene fast-vector-highlighter or highlighter.
126+
/// </summary>
127+
public GlobalInnerHitDescriptor<T> Highlight(Action<HighlightDescriptor<T>> highlightDescriptor)
128+
{
129+
highlightDescriptor.ThrowIfNull("highlightDescriptor");
130+
var d = new HighlightDescriptor<T>();
131+
highlightDescriptor(d);
132+
Self.Highlight = d;
133+
return this;
134+
}
135+
136+
public GlobalInnerHitDescriptor<T> Source(bool include = true)
137+
{
138+
if (!include)
139+
{
140+
Self.Source = new SourceFilter
141+
{
142+
Exclude = new PropertyPathMarker[] {"*"}
143+
};
144+
}
145+
else Self.Source = null;
146+
return this;
147+
}
148+
149+
public GlobalInnerHitDescriptor<T> Source(Func<SearchSourceDescriptor<T>, SearchSourceDescriptor<T>> sourceSelector)
150+
{
151+
if (sourceSelector == null) return this;
152+
Self.Source = sourceSelector(new SearchSourceDescriptor<T>());
153+
return this;
154+
}
155+
156+
public GlobalInnerHitDescriptor<T> ScriptFields(
157+
Func<FluentDictionary<string, Func<ScriptFilterDescriptor, ScriptFilterDescriptor>>,
158+
FluentDictionary<string, Func<ScriptFilterDescriptor, ScriptFilterDescriptor>>> scriptFields)
159+
{
160+
if (scriptFields == null) return null;
161+
162+
var scriptFieldDescriptors = scriptFields(new FluentDictionary<string, Func<ScriptFilterDescriptor, ScriptFilterDescriptor>>());
163+
if (scriptFieldDescriptors == null || scriptFieldDescriptors.All(d => d.Value == null))
164+
{
165+
Self.ScriptFields = null;
166+
return this;
167+
}
168+
Self.ScriptFields = new FluentDictionary<string, IScriptFilter>();
169+
foreach (var d in scriptFieldDescriptors)
170+
{
171+
if (d.Value == null)
172+
continue;
173+
Self.ScriptFields.Add(d.Key, d.Value(new ScriptFilterDescriptor()));
174+
}
175+
return this;
176+
}
177+
61178
}
62179
}

src/Nest/DSL/SearchDescriptor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ public SearchDescriptor<T> InnerHits(
401401
Self.InnerHits = containers;
402402
return this;
403403
}
404+
404405
public SearchDescriptor<T> Source(bool include = true)
405406
{
406407
if (!include)

src/Nest/Domain/DSL/IDocument.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public interface IDocument
1212
/// </summary>
1313
/// <typeparam name="T"></typeparam>
1414
/// <returns></returns>
15-
T OfType<T>() where T : class;
15+
T As<T>() where T : class;
1616
}
1717

1818
public class DocumentConverter : JsonConverter
@@ -45,7 +45,7 @@ public Document()
4545
//_value = new Lazy<object>(deserializer);
4646
}
4747

48-
public T OfType<T>() where T : class
48+
public T As<T>() where T : class
4949
{
5050
var jToken = this._Value;
5151
return jToken != null ? jToken.ToObject<T>() : null;

src/Nest/Domain/Hit/Hit.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public interface IHit<out T> where T : class
2020
HighlightFieldDictionary Highlights { get; }
2121
Explanation Explanation { get; }
2222
IEnumerable<string> MatchedQueries { get; }
23+
IDictionary<string, InnerHitsResult> InnerHits { get; }
2324
}
2425

2526
[JsonObject]
@@ -35,6 +36,10 @@ public class Hit<T> : IHit<T>
3536
[JsonProperty(PropertyName = "_index")]
3637
public string Index { get; internal set; }
3738

39+
[JsonProperty(PropertyName = "inner_hits")]
40+
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
41+
public IDictionary<string, InnerHitsResult> InnerHits { get; internal set; }
42+
3843
//TODO in NEST 2.0 make the property itself double?
3944
[JsonProperty(PropertyName = "_score")]
4045
internal double? _score { get; set; }

src/Nest/Domain/Hit/HitsMetaData.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Linq;
23
using Newtonsoft.Json;
34

45
namespace Nest
@@ -14,5 +15,39 @@ public class HitsMetaData<T> where T : class
1415

1516
[JsonProperty("hits")]
1617
public List<IHit<T>> Hits { get; internal set; }
18+
19+
1720
}
21+
22+
public class InnerHitsResult
23+
{
24+
25+
[JsonProperty("hits")]
26+
public InnerHitsMetaData Hits { get; internal set; }
27+
28+
public IEnumerable<T> Documents<T>() where T : class
29+
{
30+
return this.Hits == null ? Enumerable.Empty<T>() : this.Hits.Documents<T>();
31+
}
32+
}
33+
34+
public class InnerHitsMetaData
35+
{
36+
[JsonProperty("total")]
37+
public long Total { get; internal set; }
38+
39+
[JsonProperty("max_score")]
40+
public double? MaxScore { get; internal set; }
41+
42+
[JsonProperty("hits")]
43+
public List<Hit<IDocument>> Hits { get; internal set; }
44+
45+
public IEnumerable<T> Documents<T>() where T : class
46+
{
47+
if (this.Hits == null || !this.Hits.HasAny())
48+
return Enumerable.Empty<T>();
49+
return this.Hits.Select(hit => hit.Source.As<T>()).ToList();
50+
}
51+
}
52+
1853
}

src/Nest/Resolvers/ElasticContractResolver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ protected override IList<JsonProperty> CreateProperties(Type type, MemberSeriali
119119
defaultProperties = PropertiesOf<IMultiGetOperation>(type, memberSerialization, defaultProperties, lookup);
120120
defaultProperties = PropertiesOf<IRepository>(type, memberSerialization, defaultProperties, lookup);
121121
defaultProperties = PropertiesOf<ICreateAliasOperation>(type, memberSerialization, defaultProperties, lookup);
122-
defaultProperties = PropertiesOf<IInnerHits>(type, memberSerialization, defaultProperties, lookup);
123122
defaultProperties = PropertiesOf<IInnerHitsContainer>(type, memberSerialization, defaultProperties, lookup);
124-
defaultProperties = PropertiesOf<IGlobalInnerHit>(type, memberSerialization, defaultProperties, lookup);
123+
//defaultProperties = PropertiesOf<IGlobalInnerHit>(type, memberSerialization, defaultProperties, lookup);
124+
defaultProperties = PropertiesOf<IInnerHits>(type, memberSerialization, defaultProperties, lookup);
125125
return defaultProperties;
126126
}
127127

src/Tests/Nest.Tests.Integration/Nest.Tests.Integration.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@
183183
<Compile Include="Reproduce\Reproduce986Tests.cs" />
184184
<Compile Include="Reproduce\Reproduce960Tests.cs" />
185185
<Compile Include="Reproduce\Reproduce1279Tests.cs" />
186+
<Compile Include="Search\InnerHitsTests.cs" />
186187
<Compile Include="Search\IDocumentTests.cs" />
187188
<Compile Include="Search\Filter\AndOrNotFilterTests.cs" />
188189
<Compile Include="Search\Filter\ScriptFilterTests.cs" />

src/Tests/Nest.Tests.Integration/Search/IDocumentTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void Search()
1717
.AllTypes()
1818
);
1919
searchResults.Total.Should().BeGreaterThan(0);
20-
var project = searchResults.Documents.First().OfType<ElasticsearchProject>();
20+
var project = searchResults.Documents.First().As<ElasticsearchProject>();
2121
project.Should().NotBeNull();
2222
project.Name.Should().NotBeNullOrEmpty();
2323
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Elasticsearch.Net;
5+
using Nest.Tests.MockData;
6+
using Nest.Tests.MockData.Domain;
7+
using NUnit.Framework;
8+
using FluentAssertions;
9+
10+
namespace Nest.Tests.Integration.Search
11+
{
12+
[TestFixture]
13+
public class InnerHitsTests : IntegrationTests
14+
{
15+
private readonly string _indexName;
16+
17+
public InnerHitsTests()
18+
{
19+
this._indexName = ElasticsearchConfiguration.NewUniqueIndexName();
20+
var create = this.Client.CreateIndex(this._indexName, c => c
21+
.NumberOfReplicas(0)
22+
.NumberOfShards(1)
23+
.AddMapping<King>(m => m.MapFromAttributes())
24+
.AddMapping<Prince>(m => m.MapFromAttributes().SetParent<King>())
25+
.AddMapping<Duke>(m => m.MapFromAttributes().SetParent<Prince>())
26+
.AddMapping<Earl>(m => m.MapFromAttributes().SetParent<Duke>())
27+
.AddMapping<Baron>(m => m.MapFromAttributes().SetParent<Earl>())
28+
);
29+
30+
var bulk = new BulkDescriptor();
31+
IndexAll(bulk, ()=> NestTestData.Session.List<King>(2).Get(), indexChildren: king =>
32+
IndexAll(bulk, ()=> NestTestData.Session.List<Prince>(2).Get(), king.Name, prince =>
33+
IndexAll(bulk, ()=> NestTestData.Session.List<Duke>(3).Get(), prince.Name, duke =>
34+
IndexAll(bulk, ()=> NestTestData.Session.List<Earl>(5).Get(), duke.Name, earl =>
35+
IndexAll(bulk, ()=> NestTestData.Session.List<Baron>(1).Get(), earl.Name)
36+
)
37+
)
38+
)
39+
);
40+
var bulkResult = this.Client.Bulk(b => bulk);
41+
this.Client.Refresh(r => r.Index(this._indexName));
42+
}
43+
44+
45+
private void IndexAll<TRoyal>(BulkDescriptor bulk, Func<IList<TRoyal>> create, string parent = null, Action<TRoyal> indexChildren = null)
46+
where TRoyal : class, IRoyal
47+
{
48+
var current = create();
49+
//looping twice horrible but easy to debug :)
50+
foreach (var royal in current)
51+
{
52+
var royal1 = royal;
53+
bulk.Index<TRoyal>(i => i.Document(royal1).Index(this._indexName).Parent(parent));
54+
}
55+
if (indexChildren == null) return;
56+
foreach (var royal in current)
57+
indexChildren(royal);
58+
}
59+
60+
[Test]
61+
public void Search()
62+
{
63+
var results = this.Client.Search<Duke>(s => s
64+
.Index(this._indexName)
65+
.InnerHits(innerHits => innerHits
66+
.Add("earls", i => i
67+
.Type<Earl>(ii=>ii
68+
.Size(5)
69+
.InnerHits(innerInnerHits => innerInnerHits
70+
.Add("barons", iii=>iii.Type<Baron>())
71+
)
72+
)
73+
)
74+
.Add("princes", i=>i.Type<Prince>())
75+
)
76+
);
77+
results.IsValid.Should().BeTrue();
78+
results.Hits.Should().NotBeEmpty();
79+
foreach (var hit in results.Hits)
80+
{
81+
hit.InnerHits.Should().NotBeEmpty();
82+
hit.InnerHits.Should().ContainKey("earls");
83+
var earlHits = hit.InnerHits["earls"].Hits;
84+
earlHits.Total.Should().BeGreaterThan(0);
85+
earlHits.Hits.Should().NotBeEmpty().And.HaveCount(5);
86+
var earls = earlHits.Documents<Earl>();
87+
earls.Should().NotBeEmpty().And.OnlyContain(earl => !earl.Name.IsNullOrEmpty());
88+
foreach (var earlHit in earlHits.Hits)
89+
{
90+
var baronHits = earlHit.InnerHits["barons"];
91+
baronHits.Should().NotBeNull();
92+
var baron = baronHits.Documents<Baron>().FirstOrDefault();
93+
baron.Should().NotBeNull();
94+
baron.Name.Should().StartWith("baron");
95+
}
96+
}
97+
98+
}
99+
100+
}
101+
}

0 commit comments

Comments
 (0)