diff --git a/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs b/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs
index 7613319e3f1..5b85cebb6e0 100644
--- a/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs
+++ b/src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs
@@ -185,6 +185,9 @@ public interface IQueryContainer
[DataMember(Name = "pinned")]
IPinnedQuery Pinned { get; set; }
+ ///
+ [DataMember(Name = "combined_fields")]
+ ICombinedFieldsQuery CombinedFields { 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 29fda74500a..96ddec955bb 100644
--- a/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs
+++ b/src/Nest/QueryDsl/Abstractions/Container/QueryContainer-Assignments.cs
@@ -1,7 +1,7 @@
-// Licensed to Elasticsearch B.V under one or more agreements.
-// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
-// See the LICENSE file in the project root for more information
-
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
using System;
using System.Runtime.Serialization;
@@ -64,6 +64,7 @@ public partial class QueryContainer : IQueryContainer, IDescriptor
private IWildcardQuery _wildcard;
private IRankFeatureQuery _rankFeature;
private IPinnedQuery _pinned;
+ private ICombinedFieldsQuery _combinedFieldsQuery;
[IgnoreDataMember]
private IQueryContainer Self => this;
@@ -385,6 +386,11 @@ IPinnedQuery IQueryContainer.Pinned
set => _pinned = Set(value);
}
+ ICombinedFieldsQuery IQueryContainer.CombinedFields
+ {
+ get => _combinedFieldsQuery;
+ set => _combinedFieldsQuery = 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 c638a07de9e..4b635833b13 100644
--- a/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs
+++ b/src/Nest/QueryDsl/Abstractions/Container/QueryContainerDescriptor.cs
@@ -492,5 +492,8 @@ public QueryContainer TermsSet(Func, ITermsSetQuery>
public QueryContainer Pinned(Func, IPinnedQuery> selector) =>
WrapInContainer(selector, (query, container) => container.Pinned = query);
+
+ public QueryContainer CombinedFields(Func, ICombinedFieldsQuery> selector) =>
+ WrapInContainer(selector, (query, container) => container.CombinedFields = query);
}
}
diff --git a/src/Nest/QueryDsl/FullText/CombinedFields/CombinedFieldsQuery.cs b/src/Nest/QueryDsl/FullText/CombinedFields/CombinedFieldsQuery.cs
new file mode 100644
index 00000000000..5bcbea9980d
--- /dev/null
+++ b/src/Nest/QueryDsl/FullText/CombinedFields/CombinedFieldsQuery.cs
@@ -0,0 +1,123 @@
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
+using System;
+using System.Runtime.Serialization;
+using Nest.Utf8Json;
+
+namespace Nest
+{
+ [InterfaceDataContract]
+ [ReadAs(typeof(CombinedFieldsQuery))]
+ public interface ICombinedFieldsQuery : IQuery
+ {
+ ///
+ /// The query to execute
+ ///
+ [DataMember(Name = "query")]
+ string Query { get; set; }
+
+ ///
+ /// The fields to perform the query against.
+ ///
+ [DataMember(Name = "fields")]
+ Fields Fields { get; set; }
+
+ ///
+ /// A value controlling how many "should" clauses in the resulting boolean query should match.
+ /// It can be an absolute value, a percentage or a combination of both.
+ ///
+ [DataMember(Name = "minimum_should_match")]
+ MinimumShouldMatch MinimumShouldMatch { get; set; }
+
+ ///
+ /// If `true`, match phrase queries are automatically created for multi-term synonyms.
+ ///
+ [DataMember(Name = "auto_generate_synonyms_phrase_query")]
+ bool? AutoGenerateSynonymsPhraseQuery { get; set; }
+
+ ///
+ /// The operator used if no explicit operator is specified.
+ /// The default operator is
+ ///
+ ///
+ /// and types are field-centric?;
+ /// they generate a match query per field. This means that and
+ /// are applied to each field individually, which is probably not what you want.
+ /// Consider using .
+ ///
+ [DataMember(Name = "operator")]
+ Operator? Operator { get; set; }
+
+ ///
+ /// If the analyzer used removes all tokens in a query like a stop filter does, the default behavior is
+ /// to match no documents at all. In order to change that, can be used,
+ /// which accepts (default) and
+ /// which corresponds to a match_all query.
+ ///
+ [DataMember(Name = "zero_terms_query")]
+ ZeroTermsQuery? ZeroTermsQuery { get; set; }
+ }
+
+ ///
+ [DataContract]
+ public class CombinedFieldsQuery : QueryBase, ICombinedFieldsQuery
+ {
+ ///
+ public string Query { get; set; }
+ ///
+ public Fields Fields { get; set; }
+ ///
+ public MinimumShouldMatch MinimumShouldMatch { get; set; }
+ ///
+ public bool? AutoGenerateSynonymsPhraseQuery { get; set; }
+ ///
+ public Operator? Operator { get; set; }
+ ///
+ public ZeroTermsQuery? ZeroTermsQuery { get; set; }
+
+ protected override bool Conditionless => IsConditionless(this);
+
+ internal override void InternalWrapInContainer(IQueryContainer c) => c.CombinedFields = this;
+
+ internal static bool IsConditionless(ICombinedFieldsQuery q) => q.Fields.IsConditionless() || q.Query.IsNullOrEmpty();
+ }
+
+ public class CombinedFieldsQueryDescriptor
+ : QueryDescriptorBase, ICombinedFieldsQuery>, ICombinedFieldsQuery where T : class
+ {
+ protected override bool Conditionless => CombinedFieldsQuery.IsConditionless(this);
+
+ string ICombinedFieldsQuery.Query { get; set; }
+ Fields ICombinedFieldsQuery.Fields { get; set; }
+ MinimumShouldMatch ICombinedFieldsQuery.MinimumShouldMatch { get; set; }
+ bool? ICombinedFieldsQuery.AutoGenerateSynonymsPhraseQuery { get; set; }
+ Operator? ICombinedFieldsQuery.Operator { get; set; }
+ ZeroTermsQuery? ICombinedFieldsQuery.ZeroTermsQuery { get; set; }
+
+ ///
+ public CombinedFieldsQueryDescriptor Query(string query) => Assign(query, (a, v) => a.Query = v);
+
+ ///
+ public CombinedFieldsQueryDescriptor Fields(Func, IPromise> fields) =>
+ Assign(fields, (a, v) => a.Fields = v?.Invoke(new FieldsDescriptor())?.Value);
+
+ ///
+ public CombinedFieldsQueryDescriptor Fields(Fields fields) => Assign(fields, (a, v) => a.Fields = v);
+
+ ///
+ public CombinedFieldsQueryDescriptor MinimumShouldMatch(MinimumShouldMatch minimumShouldMatch)
+ => Assign(minimumShouldMatch, (a, v) => a.MinimumShouldMatch = v);
+
+ ///
+ public CombinedFieldsQueryDescriptor Operator(Operator? op) => Assign(op, (a, v) => a.Operator = v);
+
+ ///
+ public CombinedFieldsQueryDescriptor ZeroTermsQuery(ZeroTermsQuery? zeroTermsQuery) => Assign(zeroTermsQuery, (a, v) => a.ZeroTermsQuery = v);
+
+ ///
+ public CombinedFieldsQueryDescriptor AutoGenerateSynonymsPhraseQuery(bool? autoGenerateSynonymsPhraseQuery = true) =>
+ Assign(autoGenerateSynonymsPhraseQuery, (a, v) => a.AutoGenerateSynonymsPhraseQuery = v);
+ }
+}
diff --git a/src/Nest/QueryDsl/Query.cs b/src/Nest/QueryDsl/Query.cs
index aae59ad000b..77c4d054c02 100644
--- a/src/Nest/QueryDsl/Query.cs
+++ b/src/Nest/QueryDsl/Query.cs
@@ -1,7 +1,7 @@
-// Licensed to Elasticsearch B.V under one or more agreements.
-// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
-// See the LICENSE file in the project root for more information
-
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
using System;
using System.Linq.Expressions;
@@ -205,5 +205,8 @@ public static QueryContainer Wildcard(Func, IWildcard
public static QueryContainer Pinned(Func, IPinnedQuery> selector) =>
new QueryContainerDescriptor().Pinned(selector);
+ public static QueryContainer CombinedFields(Func, ICombinedFieldsQuery> selector) =>
+ new QueryContainerDescriptor().CombinedFields(selector);
+
}
}
diff --git a/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs b/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs
index a5c832498e1..59fc0120981 100644
--- a/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs
+++ b/src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs
@@ -215,6 +215,8 @@ private void WriteShape(IGeoShape shape, IFieldLookup indexedField, Field field,
public virtual void Visit(IPinnedQuery query) => Write("pinned");
+ public virtual void Visit(ICombinedFieldsQuery query) => Write("combined_fields");
+
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 041c9c02509..51092a477da 100644
--- a/src/Nest/QueryDsl/Visitor/QueryVisitor.cs
+++ b/src/Nest/QueryDsl/Visitor/QueryVisitor.cs
@@ -151,6 +151,8 @@ public interface IQueryVisitor
void Visit(ITermsSetQuery query);
void Visit(IPinnedQuery query);
+
+ void Visit(ICombinedFieldsQuery query);
}
public class QueryVisitor : IQueryVisitor
@@ -287,6 +289,8 @@ public virtual void Visit(ITermsSetQuery query) { }
public virtual void Visit(IPinnedQuery query) { }
+ public virtual void Visit(ICombinedFieldsQuery query) { }
+
public virtual void Visit(IQueryVisitor visitor) { }
}
}
diff --git a/src/Nest/QueryDsl/Visitor/QueryWalker.cs b/src/Nest/QueryDsl/Visitor/QueryWalker.cs
index 43066faebb3..e4160632b6a 100644
--- a/src/Nest/QueryDsl/Visitor/QueryWalker.cs
+++ b/src/Nest/QueryDsl/Visitor/QueryWalker.cs
@@ -1,7 +1,7 @@
-// Licensed to Elasticsearch B.V under one or more agreements.
-// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
-// See the LICENSE file in the project root for more information
-
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -61,6 +61,7 @@ public void Walk(IQueryContainer qd, IQueryVisitor visitor)
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.CombinedFields, visitor, (v, d) => v.Visit(d));
VisitQuery(qd.Bool, visitor, (v, d) =>
{
diff --git a/tests/Tests/QueryDsl/FullText/CombinedFields/CombinedFieldsUsageTests.cs b/tests/Tests/QueryDsl/FullText/CombinedFields/CombinedFieldsUsageTests.cs
new file mode 100644
index 00000000000..80b98c44c60
--- /dev/null
+++ b/tests/Tests/QueryDsl/FullText/CombinedFields/CombinedFieldsUsageTests.cs
@@ -0,0 +1,108 @@
+// Licensed to Elasticsearch B.V under one or more agreements.
+// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information
+
+using Elastic.Elasticsearch.Xunit.XunitPlumbing;
+using Nest;
+using Tests.Core.ManagedElasticsearch.Clusters;
+using Tests.Domain;
+using Tests.Framework.EndpointTests.TestState;
+using static Nest.Infer;
+
+namespace Tests.QueryDsl.FullText.CombinedFields
+{
+ /**
+ * The `combined_fields` query supports searching multiple text fields as if their contents had been indexed into one combined field. It takes a
+ * term-centric view of the query: first it analyzes the query string into individual terms, then looks for each term in any of the fields.
+ *
+ * See the Elasticsearch documentation on {ref_current}/query-dsl-combined-fields-query.html[combined fields query] for more details.
+ */
+ [SkipVersion("<7.13.0", "Implemented in version 7.13.0")]
+ public class CombinedFieldsUsageTests : QueryDslUsageTestsBase
+ {
+ public CombinedFieldsUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }
+
+ protected override ConditionlessWhen ConditionlessWhen => new ConditionlessWhen(a => a.CombinedFields)
+ {
+ q => q.Query = null,
+ q => q.Query = string.Empty
+ };
+
+ protected override QueryContainer QueryInitializer => new CombinedFieldsQuery
+ {
+ Fields = Field(p => p.Description).And("myOtherField"),
+ Query = "hello world",
+ Boost = 1.1,
+ Operator = Operator.Or,
+ MinimumShouldMatch = "2",
+ ZeroTermsQuery = ZeroTermsQuery.All,
+ Name = "combined_fields",
+ AutoGenerateSynonymsPhraseQuery = false
+ };
+
+ protected override object QueryJson => new
+ {
+ combined_fields = new
+ {
+ _name = "combined_fields",
+ boost = 1.1,
+ query = "hello world",
+ minimum_should_match = "2",
+ @operator = "or",
+ fields = new[]
+ {
+ "description",
+ "myOtherField"
+ },
+ zero_terms_query = "all",
+ auto_generate_synonyms_phrase_query = false
+ }
+ };
+
+ protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q
+ .CombinedFields(c => c
+ .Fields(f => f.Field(p => p.Description).Field("myOtherField"))
+ .Query("hello world")
+ .Boost(1.1)
+ .Operator(Operator.Or)
+ .MinimumShouldMatch("2")
+ .ZeroTermsQuery(ZeroTermsQuery.All)
+ .Name("combined_fields")
+ .AutoGenerateSynonymsPhraseQuery(false)
+ );
+ }
+
+ /**[float]
+ * === Combined fields with boost usage
+ */
+ [SkipVersion("<7.13.0", "Implemented in version 7.13.0")]
+ public class CombinedFieldsWithBoostUsageTests : QueryDslUsageTestsBase
+ {
+ public CombinedFieldsWithBoostUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }
+
+ protected override QueryContainer QueryInitializer => new CombinedFieldsQuery
+ {
+ Fields = Field(p => p.Description, 2.2).And("myOtherField^1.2"),
+ Query = "hello world",
+ };
+
+ protected override object QueryJson => new
+ {
+ combined_fields = new
+ {
+ query = "hello world",
+ fields = new[]
+ {
+ "description^2.2",
+ "myOtherField^1.2"
+ }
+ }
+ };
+
+ protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q
+ .CombinedFields(c => c
+ .Fields(Field(p => p.Description, 2.2).And("myOtherField^1.2"))
+ .Query("hello world")
+ );
+ }
+}