Skip to content

Commit 36d9210

Browse files
committed
Search enhancement: pinned queries (#4143)
* Search enhancement: pinned queries Addresses elastic/elasticsearch#44345 * update docs in test * remove params overloads on Ids that already convert to Ids * add note to IdsQuery
1 parent c7276c7 commit 36d9210

File tree

10 files changed

+152
-0
lines changed

10 files changed

+152
-0
lines changed

src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs

+5
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ public interface IQueryContainer
171171
[DataMember(Name = "distance_feature")]
172172
IDistanceFeatureQuery DistanceFeature { get; set; }
173173

174+
/// <inheritdoc cref="IPinnedQuery"/>
175+
[DataMember(Name = "pinned")]
176+
IPinnedQuery Pinned { get; set; }
177+
178+
174179
void Accept(IQueryVisitor visitor);
175180
}
176181
}

src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs

+7
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public partial class QueryContainer : IQueryContainer, IDescriptor
5757
private ITermsSetQuery _termsSet;
5858
private IWildcardQuery _wildcard;
5959
private IRankFeatureQuery _rankFeature;
60+
private IPinnedQuery _pinned;
6061

6162
[IgnoreDataMember]
6263
private IQueryContainer Self => this;
@@ -360,6 +361,12 @@ IRankFeatureQuery IQueryContainer.RankFeature
360361
get => _rankFeature;
361362
set => _rankFeature = Set(value);
362363
}
364+
IPinnedQuery IQueryContainer.Pinned
365+
{
366+
get => _pinned;
367+
set => _pinned = Set(value);
368+
}
369+
363370

364371
private T Set<T>(T value) where T : IQuery
365372
{

src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs

+3
Original file line numberDiff line numberDiff line change
@@ -475,5 +475,8 @@ public QueryContainer ParentId(Func<ParentIdQueryDescriptor<T>, IParentIdQuery>
475475
/// </summary>
476476
public QueryContainer TermsSet(Func<TermsSetQueryDescriptor<T>, ITermsSetQuery> selector) =>
477477
WrapInContainer(selector, (query, container) => container.TermsSet = query);
478+
479+
public QueryContainer Pinned(Func<PinnedQueryDescriptor<T>, IPinnedQuery> selector) =>
480+
WrapInContainer(selector, (query, container) => container.Pinned = query);
478481
}
479482
}

src/Nest/QueryDsl/Query.cs

+4
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,9 @@ public static QueryContainer Wildcard(Field field, string value, double? boost =
190190

191191
public static QueryContainer Wildcard(Func<WildcardQueryDescriptor<T>, IWildcardQuery> selector) =>
192192
new QueryContainerDescriptor<T>().Wildcard(selector);
193+
194+
public static QueryContainer Pinned(Func<PinnedQueryDescriptor<T>, IPinnedQuery> selector) =>
195+
new QueryContainerDescriptor<T>().Pinned(selector);
196+
193197
}
194198
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Runtime.Serialization;
5+
using Elasticsearch.Net.Utf8Json;
6+
7+
namespace Nest
8+
{
9+
[InterfaceDataContract]
10+
[ReadAs(typeof(PinnedQuery))]
11+
public interface IPinnedQuery : IQuery
12+
{
13+
[DataMember(Name = "ids")]
14+
IEnumerable<Id> Ids { get; set; }
15+
16+
[DataMember(Name = "organic")]
17+
QueryContainer Organic { get; set; }
18+
}
19+
20+
public class PinnedQuery : QueryBase, IPinnedQuery
21+
{
22+
public IEnumerable<Id> Ids { get; set; }
23+
24+
public QueryContainer Organic { get; set; }
25+
26+
protected override bool Conditionless => IsConditionless(this);
27+
28+
internal override void InternalWrapInContainer(IQueryContainer c) => c.Pinned = this;
29+
30+
internal static bool IsConditionless(IPinnedQuery q) => !q.Ids.HasAny() && q.Organic.IsConditionless();
31+
}
32+
33+
public class PinnedQueryDescriptor<T>
34+
: QueryDescriptorBase<PinnedQueryDescriptor<T>, IPinnedQuery>
35+
, IPinnedQuery
36+
where T : class
37+
{
38+
protected override bool Conditionless => PinnedQuery.IsConditionless(this);
39+
IEnumerable<Id> IPinnedQuery.Ids { get; set; }
40+
QueryContainer IPinnedQuery.Organic { get; set; }
41+
42+
public PinnedQueryDescriptor<T> Ids(params Id[] ids) => Assign(ids, (a, v) => a.Ids = v);
43+
44+
public PinnedQueryDescriptor<T> Ids(IEnumerable<Id> ids) => Ids(ids?.ToArray());
45+
46+
public PinnedQueryDescriptor<T> Ids(IEnumerable<string> ids) => Ids(ids.ToArray());
47+
48+
public PinnedQueryDescriptor<T> Ids(IEnumerable<long> ids) => Ids(ids.ToArray());
49+
50+
public PinnedQueryDescriptor<T> Ids(IEnumerable<Guid> ids) => Ids(ids.ToArray());
51+
52+
public PinnedQueryDescriptor<T> Organic(Func<QueryContainerDescriptor<T>, QueryContainer> selector) =>
53+
Assign(selector, (a, v) => a.Organic = v?.Invoke(new QueryContainerDescriptor<T>()));
54+
}
55+
}

src/Nest/QueryDsl/TermLevel/Ids/IdsQuery.cs

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class IdsQueryDescriptor
3535

3636
public IdsQueryDescriptor Values(IEnumerable<Id> values) => Values(values?.ToArray());
3737

38+
// TODO 8.x remove params on Values already implicitly converting to Id
3839
public IdsQueryDescriptor Values(params string[] values) => Assign(values?.Select(v => (Id)v), (a, v) => a.Values = v);
3940

4041
public IdsQueryDescriptor Values(IEnumerable<string> values) => Values(values.ToArray());

src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs

+2
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ public virtual void Visit(IGeoShapeQuery query)
203203

204204
public virtual void Visit(ITermsSetQuery query) => Write("terms_set");
205205

206+
public virtual void Visit(IPinnedQuery query) => Write("pinned");
207+
206208
private void Write(string queryType, Dictionary<string, string> properties)
207209
{
208210
properties = properties ?? new Dictionary<string, string>();

src/Nest/QueryDsl/Visitor/QueryVisitor.cs

+4
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ public interface IQueryVisitor
141141
void Visit(IParentIdQuery query);
142142

143143
void Visit(ITermsSetQuery query);
144+
145+
void Visit(IPinnedQuery query);
144146
}
145147

146148
public class QueryVisitor : IQueryVisitor
@@ -271,6 +273,8 @@ public virtual void Visit(IParentIdQuery query) { }
271273

272274
public virtual void Visit(ITermsSetQuery query) { }
273275

276+
public virtual void Visit(IPinnedQuery query) { }
277+
274278
public virtual void Visit(IQueryVisitor visitor) { }
275279
}
276280
}

src/Nest/QueryDsl/Visitor/QueryWalker.cs

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public void Walk(IQueryContainer qd, IQueryVisitor visitor)
5454
VisitQuery(qd.Percolate, visitor, (v, d) => v.Visit(d));
5555
VisitQuery(qd.ParentId, visitor, (v, d) => v.Visit(d));
5656
VisitQuery(qd.TermsSet, visitor, (v, d) => v.Visit(d));
57+
VisitQuery(qd.Pinned, visitor, (v, d) => v.Visit(d));
5758

5859
VisitQuery(qd.Bool, visitor, (v, d) =>
5960
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using Nest;
3+
using Tests.Core.ManagedElasticsearch.Clusters;
4+
using Tests.Domain;
5+
using Tests.Framework.EndpointTests.TestState;
6+
7+
#pragma warning disable 618 //Testing an obsolete method
8+
9+
namespace Tests.QueryDsl.Specialized.Pinned
10+
{
11+
/**
12+
* Promotes selected documents to rank higher than those matching a given query. This feature is typically used to
13+
* guide searchers to curated documents that are promoted over and above any "organic" matches for a search. The promoted or "pinned"
14+
* documents are identified using the document IDs stored in the _id field.
15+
* See the Elasticsearch documentation on {ref_current}/query-dsl-pinned-query.html[pinned query] for more details.
16+
*/
17+
public class PinnedQueryUsageTests : QueryDslUsageTestsBase
18+
{
19+
public PinnedQueryUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }
20+
21+
protected override ConditionlessWhen ConditionlessWhen => new ConditionlessWhen<IPinnedQuery>(a => a.Pinned)
22+
{
23+
q =>
24+
{
25+
q.Ids = null;
26+
q.Organic = null;
27+
},
28+
q =>
29+
{
30+
q.Ids = Array.Empty<Id>();
31+
q.Organic = ConditionlessQuery;
32+
},
33+
};
34+
35+
protected override NotConditionlessWhen NotConditionlessWhen => new NotConditionlessWhen<IPinnedQuery>(a => a.Pinned)
36+
{
37+
q => q.Organic = VerbatimQuery,
38+
};
39+
40+
protected override QueryContainer QueryInitializer => new PinnedQuery()
41+
{
42+
Name = "named_query",
43+
Boost = 1.1,
44+
Organic = new MatchAllQuery { Name = "organic_query" },
45+
Ids = new Id[] { 1,11,22 },
46+
};
47+
48+
protected override object QueryJson => new
49+
{
50+
pinned = new
51+
{
52+
_name = "named_query",
53+
boost = 1.1,
54+
organic = new
55+
{
56+
match_all = new { _name = "organic_query" }
57+
},
58+
ids = new [] { 1, 11, 22},
59+
}
60+
};
61+
62+
protected override QueryContainer QueryFluent(QueryContainerDescriptor<Project> q) => q
63+
.Pinned(c => c
64+
.Name("named_query")
65+
.Boost(1.1)
66+
.Organic(qq => qq.MatchAll(m => m.Name("organic_query")))
67+
.Ids(1, 11, 22)
68+
);
69+
}
70+
}

0 commit comments

Comments
 (0)