diff --git a/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs b/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs
index 9c93207d60b..4d3c329e390 100644
--- a/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs
+++ b/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs
@@ -171,6 +171,11 @@ public interface IQueryContainer
[DataMember(Name = "distance_feature")]
IDistanceFeatureQuery DistanceFeature { get; set; }
+ ///
+ [DataMember(Name = "pinned")]
+ IPinnedQuery Pinned { get; set; }
+
+
void Accept(IQueryVisitor visitor);
}
}
diff --git a/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs b/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs
index 6037a140510..92bcb150715 100644
--- a/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs
+++ b/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs
@@ -57,6 +57,7 @@ public partial class QueryContainer : IQueryContainer, IDescriptor
private ITermsSetQuery _termsSet;
private IWildcardQuery _wildcard;
private IRankFeatureQuery _rankFeature;
+ private IPinnedQuery _pinned;
[IgnoreDataMember]
private IQueryContainer Self => this;
@@ -360,6 +361,12 @@ IRankFeatureQuery IQueryContainer.RankFeature
get => _rankFeature;
set => _rankFeature = Set(value);
}
+ IPinnedQuery IQueryContainer.Pinned
+ {
+ get => _pinned;
+ set => _pinned = Set(value);
+ }
+
private T Set(T value) where T : IQuery
{
diff --git a/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs b/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs
index 90360748570..75527d61459 100644
--- a/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs
+++ b/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs
@@ -475,5 +475,8 @@ public QueryContainer ParentId(Func, IParentIdQuery>
///
public QueryContainer TermsSet(Func, ITermsSetQuery> selector) =>
WrapInContainer(selector, (query, container) => container.TermsSet = query);
+
+ public QueryContainer Pinned(Func, IPinnedQuery> selector) =>
+ WrapInContainer(selector, (query, container) => container.Pinned = query);
}
}
diff --git a/src/Nest/QueryDsl/Query.cs b/src/Nest/QueryDsl/Query.cs
index a83fa748fdf..ddca4ecff2e 100644
--- a/src/Nest/QueryDsl/Query.cs
+++ b/src/Nest/QueryDsl/Query.cs
@@ -190,5 +190,9 @@ public static QueryContainer Wildcard(Field field, string value, double? boost =
public static QueryContainer Wildcard(Func, IWildcardQuery> selector) =>
new QueryContainerDescriptor().Wildcard(selector);
+
+ public static QueryContainer Pinned(Func, IPinnedQuery> selector) =>
+ new QueryContainerDescriptor().Pinned(selector);
+
}
}
diff --git a/src/Nest/QueryDsl/Specialized/Pinned/PinnedQuery.cs b/src/Nest/QueryDsl/Specialized/Pinned/PinnedQuery.cs
new file mode 100644
index 00000000000..1e92d1077e9
--- /dev/null
+++ b/src/Nest/QueryDsl/Specialized/Pinned/PinnedQuery.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using Elasticsearch.Net.Utf8Json;
+
+namespace Nest
+{
+ [InterfaceDataContract]
+ [ReadAs(typeof(PinnedQuery))]
+ public interface IPinnedQuery : IQuery
+ {
+ [DataMember(Name = "ids")]
+ IEnumerable Ids { get; set; }
+
+ [DataMember(Name = "organic")]
+ QueryContainer Organic { get; set; }
+ }
+
+ public class PinnedQuery : QueryBase, IPinnedQuery
+ {
+ public IEnumerable Ids { get; set; }
+
+ public QueryContainer Organic { get; set; }
+
+ protected override bool Conditionless => IsConditionless(this);
+
+ internal override void InternalWrapInContainer(IQueryContainer c) => c.Pinned = this;
+
+ internal static bool IsConditionless(IPinnedQuery q) => !q.Ids.HasAny() && q.Organic.IsConditionless();
+ }
+
+ public class PinnedQueryDescriptor
+ : QueryDescriptorBase, IPinnedQuery>
+ , IPinnedQuery
+ where T : class
+ {
+ protected override bool Conditionless => PinnedQuery.IsConditionless(this);
+ IEnumerable IPinnedQuery.Ids { get; set; }
+ QueryContainer IPinnedQuery.Organic { get; set; }
+
+ public PinnedQueryDescriptor Ids(params Id[] ids) => Assign(ids, (a, v) => a.Ids = v);
+
+ public PinnedQueryDescriptor Ids(IEnumerable ids) => Ids(ids?.ToArray());
+
+ public PinnedQueryDescriptor Ids(IEnumerable ids) => Ids(ids.ToArray());
+
+ public PinnedQueryDescriptor Ids(IEnumerable ids) => Ids(ids.ToArray());
+
+ public PinnedQueryDescriptor Ids(IEnumerable ids) => Ids(ids.ToArray());
+
+ public PinnedQueryDescriptor Organic(Func, QueryContainer> selector) =>
+ Assign(selector, (a, v) => a.Organic = v?.Invoke(new QueryContainerDescriptor()));
+ }
+}
diff --git a/src/Nest/QueryDsl/TermLevel/Ids/IdsQuery.cs b/src/Nest/QueryDsl/TermLevel/Ids/IdsQuery.cs
index e0725632ffb..648227a70a5 100644
--- a/src/Nest/QueryDsl/TermLevel/Ids/IdsQuery.cs
+++ b/src/Nest/QueryDsl/TermLevel/Ids/IdsQuery.cs
@@ -35,6 +35,7 @@ public class IdsQueryDescriptor
public IdsQueryDescriptor Values(IEnumerable values) => Values(values?.ToArray());
+ // TODO 8.x remove params on Values already implicitly converting to Id
public IdsQueryDescriptor Values(params string[] values) => Assign(values?.Select(v => (Id)v), (a, v) => a.Values = v);
public IdsQueryDescriptor Values(IEnumerable values) => Values(values.ToArray());
diff --git a/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs b/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs
index 16b113f21db..89178348149 100644
--- a/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs
+++ b/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs
@@ -203,6 +203,8 @@ public virtual void Visit(IGeoShapeQuery query)
public virtual void Visit(ITermsSetQuery query) => Write("terms_set");
+ public virtual void Visit(IPinnedQuery query) => Write("pinned");
+
private void Write(string queryType, Dictionary properties)
{
properties = properties ?? new Dictionary();
diff --git a/src/Nest/QueryDsl/Visitor/QueryVisitor.cs b/src/Nest/QueryDsl/Visitor/QueryVisitor.cs
index 673efcd7c3d..daa99edad09 100644
--- a/src/Nest/QueryDsl/Visitor/QueryVisitor.cs
+++ b/src/Nest/QueryDsl/Visitor/QueryVisitor.cs
@@ -141,6 +141,8 @@ public interface IQueryVisitor
void Visit(IParentIdQuery query);
void Visit(ITermsSetQuery query);
+
+ void Visit(IPinnedQuery query);
}
public class QueryVisitor : IQueryVisitor
@@ -271,6 +273,8 @@ public virtual void Visit(IParentIdQuery query) { }
public virtual void Visit(ITermsSetQuery query) { }
+ public virtual void Visit(IPinnedQuery query) { }
+
public virtual void Visit(IQueryVisitor visitor) { }
}
}
diff --git a/src/Nest/QueryDsl/Visitor/QueryWalker.cs b/src/Nest/QueryDsl/Visitor/QueryWalker.cs
index 4632b245531..86eb5d67e96 100644
--- a/src/Nest/QueryDsl/Visitor/QueryWalker.cs
+++ b/src/Nest/QueryDsl/Visitor/QueryWalker.cs
@@ -54,6 +54,7 @@ public void Walk(IQueryContainer qd, IQueryVisitor visitor)
VisitQuery(qd.Percolate, visitor, (v, d) => v.Visit(d));
VisitQuery(qd.ParentId, visitor, (v, d) => v.Visit(d));
VisitQuery(qd.TermsSet, visitor, (v, d) => v.Visit(d));
+ VisitQuery(qd.Pinned, visitor, (v, d) => v.Visit(d));
VisitQuery(qd.Bool, visitor, (v, d) =>
{
diff --git a/src/Tests/Tests/QueryDsl/Specialized/Pinned/PinnedQueryUsageTests.cs b/src/Tests/Tests/QueryDsl/Specialized/Pinned/PinnedQueryUsageTests.cs
new file mode 100644
index 00000000000..ae35e22e9b2
--- /dev/null
+++ b/src/Tests/Tests/QueryDsl/Specialized/Pinned/PinnedQueryUsageTests.cs
@@ -0,0 +1,70 @@
+using System;
+using Nest;
+using Tests.Core.ManagedElasticsearch.Clusters;
+using Tests.Domain;
+using Tests.Framework.EndpointTests.TestState;
+
+#pragma warning disable 618 //Testing an obsolete method
+
+namespace Tests.QueryDsl.Specialized.Pinned
+{
+ /**
+ * Promotes selected documents to rank higher than those matching a given query. This feature is typically used to
+ * guide searchers to curated documents that are promoted over and above any "organic" matches for a search. The promoted or "pinned"
+ * documents are identified using the document IDs stored in the _id field.
+ * See the Elasticsearch documentation on {ref_current}/query-dsl-pinned-query.html[pinned query] for more details.
+ */
+ public class PinnedQueryUsageTests : QueryDslUsageTestsBase
+ {
+ public PinnedQueryUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }
+
+ protected override ConditionlessWhen ConditionlessWhen => new ConditionlessWhen(a => a.Pinned)
+ {
+ q =>
+ {
+ q.Ids = null;
+ q.Organic = null;
+ },
+ q =>
+ {
+ q.Ids = Array.Empty();
+ q.Organic = ConditionlessQuery;
+ },
+ };
+
+ protected override NotConditionlessWhen NotConditionlessWhen => new NotConditionlessWhen(a => a.Pinned)
+ {
+ q => q.Organic = VerbatimQuery,
+ };
+
+ protected override QueryContainer QueryInitializer => new PinnedQuery()
+ {
+ Name = "named_query",
+ Boost = 1.1,
+ Organic = new MatchAllQuery { Name = "organic_query" },
+ Ids = new Id[] { 1,11,22 },
+ };
+
+ protected override object QueryJson => new
+ {
+ pinned = new
+ {
+ _name = "named_query",
+ boost = 1.1,
+ organic = new
+ {
+ match_all = new { _name = "organic_query" }
+ },
+ ids = new [] { 1, 11, 22},
+ }
+ };
+
+ protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q
+ .Pinned(c => c
+ .Name("named_query")
+ .Boost(1.1)
+ .Organic(qq => qq.MatchAll(m => m.Name("organic_query")))
+ .Ids(1, 11, 22)
+ );
+ }
+}