Skip to content

Commit

Permalink
Support 2nd level field collapsing (#3444)
Browse files Browse the repository at this point in the history
as per elastic/elasticsearch#31808

(cherry picked from commit 7d78767)
  • Loading branch information
Mpdreamz authored and russcam committed Oct 26, 2018
1 parent 4cc8fa2 commit 92fbf2f
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 8 deletions.
22 changes: 18 additions & 4 deletions src/Nest/Search/Search/Collapsing/FieldCollapse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,38 +27,52 @@ public interface IFieldCollapse
[JsonProperty("inner_hits")]
IInnerHits InnerHits { get; set; }

/// <summary>
/// The expansion of the group is done by sending an additional query for each inner_hit request for each collapsed hit returned
/// in the response. This can significantly slow things down if you have too many groups and/or inner_hit requests.
/// The max_concurrent_group_searches request parameter can be used to control the maximum number of
/// concurrent searches allowed in this phase. The default is based on the number of data nodes and the
/// default search thread pool size.
/// </summary>
[JsonProperty("max_concurrent_group_searches")]
int? MaxConcurrentGroupSearches { get; set; }
}

/// <inheritdoc/>
/// <inheritdoc cref="IFieldCollapse"/>
public class FieldCollapse : IFieldCollapse
{
/// <inheritdoc/>
/// <inheritdoc cref="IFieldCollapse.Field"/>
public Field Field { get; set; }

/// <inheritdoc cref="IFieldCollapse.InnerHits"/>
public IInnerHits InnerHits { get; set; }

/// <inheritdoc cref="IFieldCollapse.MaxConcurrentGroupSearches"/>
public int? MaxConcurrentGroupSearches { get; set; }

}

/// <inheritdoc/>
/// <inheritdoc cref="IFieldCollapse"/>
public class FieldCollapseDescriptor<T> : DescriptorBase<FieldCollapseDescriptor<T>, IFieldCollapse>, IFieldCollapse
where T : class
{
/// <inheritdoc/>
Field IFieldCollapse.Field { get; set; }
IInnerHits IFieldCollapse.InnerHits { get; set; }
int? IFieldCollapse.MaxConcurrentGroupSearches { get; set; }

/// <inheritdoc cref="IFieldCollapse.MaxConcurrentGroupSearches"/>
public FieldCollapseDescriptor<T> MaxConcurrentGroupSearches(int? maxConcurrentGroupSearches) =>
Assign(a => a.MaxConcurrentGroupSearches = maxConcurrentGroupSearches);

/// <inheritdoc cref="IFieldCollapse.Field"/>
public FieldCollapseDescriptor<T> Field(Field field) => Assign(a => a.Field = field);

/// <inheritdoc cref="IFieldCollapse.Field"/>
public FieldCollapseDescriptor<T> Field(Expression<Func<T, object>> objectPath) => Assign(a => a.Field = objectPath);

/// <inheritdoc cref="IFieldCollapse.InnherHits"/>
public FieldCollapseDescriptor<T> InnerHits(Func<InnerHitsDescriptor<T>, IInnerHits> selector = null) =>
Assign(a => a.InnerHits = selector.InvokeOrDefault(new InnerHitsDescriptor<T>()));

}
}
14 changes: 14 additions & 0 deletions src/Nest/Search/Search/InnerHits/InnerHits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ public interface IInnerHits

[JsonProperty("ignore_unmapped")]
bool? IgnoreUnmapped { get; set; }

/// <summary>
/// Provides a second level of collapsing, NOTE: Elasticsearch only supports collapsing up to two levels.
/// </summary>
[JsonProperty("collapse")]
IFieldCollapse Collapse { get; set; }
}

public class InnerHits : IInnerHits
Expand All @@ -66,6 +72,9 @@ public class InnerHits : IInnerHits
public Fields DocValueFields { get; set; }

public bool? IgnoreUnmapped { get; set; }

/// <inheritdoc cref="IInnerHits.Collapse"/>
public IFieldCollapse Collapse { get; set; }
}

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
Expand All @@ -82,6 +91,7 @@ public class InnerHitsDescriptor<T> : DescriptorBase<InnerHitsDescriptor<T>, IIn
IScriptFields IInnerHits.ScriptFields { get; set; }
Fields IInnerHits.DocValueFields { get; set; }
bool? IInnerHits.IgnoreUnmapped { get; set; }
IFieldCollapse IInnerHits.Collapse { get; set; }

public InnerHitsDescriptor<T> From(int? from) => Assign(a => a.From = from);

Expand Down Expand Up @@ -115,5 +125,9 @@ public InnerHitsDescriptor<T> DocValueFields(Func<FieldsDescriptor<T>, IPromise<
public InnerHitsDescriptor<T> DocValueFields(Fields fields) => Assign(a => a.DocValueFields = fields);

public InnerHitsDescriptor<T> IgnoreUnmapped(bool? ignoreUnmapped = true) => Assign(a => a.IgnoreUnmapped = ignoreUnmapped);

/// <inheritdoc cref="IInnerHits.Collapse"/>
public InnerHitsDescriptor<T> Collapse(Func<FieldCollapseDescriptor<T>, IFieldCollapse> collapseSelector) =>
Assign(a => a.Collapse = collapseSelector?.Invoke(new FieldCollapseDescriptor<T>()));
}
}
1 change: 1 addition & 0 deletions src/Nest/Search/Search/SearchRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ public SearchDescriptor<T> Highlight(Func<HighlightDescriptor<T>, IHighlight> hi
/// For instance the query below retrieves the best tweet for each user and sorts them by number of likes.
/// <para>
/// NOTE: The collapsing is applied to the top hits only and does not affect aggregations.
/// You can only collapse to a depth of 2.
/// </para>
/// </summary>
public SearchDescriptor<T> Collapse(Func<FieldCollapseDescriptor<T>, IFieldCollapse> collapseSelector) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
using System;
using Elastic.Xunit.XunitPlumbing;
using FluentAssertions;
using Nest;
using Tests.Core.Extensions;
using Tests.Core.ManagedElasticsearch.Clusters;
using Tests.Core.ManagedElasticsearch.NodeSeeders;
using Tests.Domain;
using Tests.Framework.Integration;
using Tests.Framework.ManagedElasticsearch.Clusters;
using Tests.Framework.ManagedElasticsearch.NodeSeeders;
using static Nest.Infer;

namespace Tests.Search.Search.Collapsing
{
/**
*/
public class FieldCollapseUsageTests : SearchUsageTestBase
{
protected override string UrlPath => $"/{DefaultSeeder.ProjectsAliasFilter}/doc/_search";
Expand Down Expand Up @@ -77,4 +74,93 @@ protected override void ExpectResponse(ISearchResponse<Project> response)
}
}
}

[SkipVersion("<6.4.0", "2nd level collapsing is a new feature in 6.4.0")]
public class FieldCollapseSecondLevelUsageTests : SearchUsageTestBase
{
protected override string UrlPath => $"/{DefaultSeeder.ProjectsAliasFilter}/doc/_search";

public FieldCollapseSecondLevelUsageTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }

protected override object ExpectJson => new
{
_source = new { excludes = new [] { "*" } },
collapse = new {
field = "state",
inner_hits = new {
_source = new {
excludes = new [] { "*" }
},
collapse = new {
field = "name"
},
from = 1,
name = "stateofbeing",
size = 5
},
max_concurrent_group_searches = 1000
}
};

protected override Func<SearchDescriptor<Project>, ISearchRequest> Fluent => s => s
.Source(source=>source.ExcludeAll())
.Index(DefaultSeeder.ProjectsAliasFilter)
.Collapse(c => c
.Field(f => f.State)
.MaxConcurrentGroupSearches(1000)
.InnerHits(i => i
.Source(source=>source.ExcludeAll())
.Name(nameof(StateOfBeing).ToLowerInvariant())
.Size(5)
.From(1)
.Collapse(c2 => c2
.Field(p=>p.Name)
)
)
);

protected override SearchRequest<Project> Initializer => new SearchRequest<Project>(DefaultSeeder.ProjectsAliasFilter)
{
Source = SourceFilter.ExcludeAll,
Collapse = new FieldCollapse
{
Field = Field<Project>(p => p.State),
MaxConcurrentGroupSearches = 1000,
InnerHits = new InnerHits
{
Source = SourceFilter.ExcludeAll,
Name = nameof(StateOfBeing).ToLowerInvariant(),
Size = 5,
From = 1,
Collapse = new FieldCollapse
{
Field = Field<Project>(p=>p.Name)
}
}
}
};

protected override void ExpectResponse(ISearchResponse<Project> response)
{
var numberOfStates = Enum.GetValues(typeof(StateOfBeing)).Length;
response.HitsMetadata.Total.Should().BeGreaterThan(numberOfStates);
response.Hits.Count.Should().Be(numberOfStates);
foreach (var hit in response.Hits)
{
var name = nameof(StateOfBeing).ToLowerInvariant();
hit.InnerHits.Should().NotBeNull().And.ContainKey(name);
var innerHits = hit.InnerHits[name];
innerHits.Hits.Total.Should().BeGreaterThan(0);
var i = 0;
foreach (var innerHit in innerHits.Hits.Hits)
{
i++;
innerHit.Fields.Should().NotBeEmpty()
.And.ContainKey("name");
}

i.Should().NotBe(0, "we expect to inspect 2nd level collapsed fields");
}
}
}
}

0 comments on commit 92fbf2f

Please sign in to comment.