Skip to content

Commit f787b8d

Browse files
committed
Merge pull request #1823 from elastic/fix/bool-null-querycontainer
Handle collection of QueryContainer where the first item is null
2 parents 29f40c8 + 2ba5271 commit f787b8d

File tree

3 files changed

+163
-22
lines changed

3 files changed

+163
-22
lines changed

src/Nest/QueryDsl/Compound/Bool/BoolQuery.cs

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,43 @@ namespace Nest
99
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
1010
public interface IBoolQuery : IQuery
1111
{
12-
[JsonProperty("must")]
12+
/// <summary>
13+
/// The clause(s) that must appear in matching documents
14+
/// </summary>
15+
[JsonProperty("must", DefaultValueHandling = DefaultValueHandling.Ignore)]
1316
IEnumerable<QueryContainer> Must { get; set; }
1417

15-
[JsonProperty("must_not")]
18+
/// <summary>
19+
/// The clause (query) must not appear in the matching documents. Note that it is not possible to search on documents that only consists of a must_not clauses.
20+
/// </summary>
21+
[JsonProperty("must_not", DefaultValueHandling = DefaultValueHandling.Ignore)]
1622
IEnumerable<QueryContainer> MustNot { get; set; }
1723

18-
[JsonProperty("should")]
24+
/// <summary>
25+
/// The clause (query) should appear in the matching document. A boolean query with no must clauses, one or more should clauses must match a document.
26+
/// The minimum number of should clauses to match can be set using minimum_should_match parameter.
27+
/// </summary>
28+
[JsonProperty("should", DefaultValueHandling = DefaultValueHandling.Ignore)]
1929
IEnumerable<QueryContainer> Should { get; set; }
2030

21-
[JsonProperty("filter")]
31+
/// <summary>
32+
/// The clause (query) which is to be used as a filter (in filter context).
33+
/// </summary>
34+
[JsonProperty("filter", DefaultValueHandling = DefaultValueHandling.Ignore)]
2235
IEnumerable<QueryContainer> Filter { get; set; }
2336

37+
/// <summary>
38+
/// Specifies a minimum number of the optional BooleanClauses which must be satisfied.
39+
/// </summary>
2440
[JsonProperty("minimum_should_match")]
2541
MinimumShouldMatch MinimumShouldMatch { get; set; }
2642

43+
/// <summary>
44+
/// Specifies if the coordination factor for the query should be disabled.
45+
/// The coordination factor is used to reward documents that contain a higher
46+
/// percentage of the query terms. The more query terms that appear in the document,
47+
/// the greater the chances that the document is a good match for the query.
48+
/// </summary>
2749
[JsonProperty("disable_coord")]
2850
bool? DisableCoord { get; set; }
2951

@@ -35,11 +57,38 @@ public class BoolQuery : QueryBase, IBoolQuery
3557
internal static bool Locked(IBoolQuery q) => !q.Name.IsNullOrEmpty() || q.Boost.HasValue || q.DisableCoord.HasValue || q.MinimumShouldMatch != null;
3658
bool IBoolQuery.Locked => BoolQuery.Locked(this);
3759

60+
/// <summary>
61+
/// The clause(s) that must appear in matching documents
62+
/// </summary>
3863
public IEnumerable<QueryContainer> Must { get; set; }
64+
65+
/// <summary>
66+
/// The clause (query) must not appear in the matching documents. Note that it is not possible to search on documents that only consists of a must_not clauses.
67+
/// </summary>
3968
public IEnumerable<QueryContainer> MustNot { get; set; }
69+
70+
/// <summary>
71+
/// The clause (query) should appear in the matching document. A boolean query with no must clauses, one or more should clauses must match a document.
72+
/// The minimum number of should clauses to match can be set using minimum_should_match parameter.
73+
/// </summary>
4074
public IEnumerable<QueryContainer> Should { get; set; }
75+
76+
/// <summary>
77+
/// The clause (query) which is to be used as a filter (in filter context).
78+
/// </summary>
4179
public IEnumerable<QueryContainer> Filter { get; set; }
80+
81+
/// <summary>
82+
/// Specifies a minimum number of the optional BooleanClauses which must be satisfied.
83+
/// </summary>
4284
public MinimumShouldMatch MinimumShouldMatch { get; set; }
85+
86+
/// <summary>
87+
/// Specifies if the coordination factor for the query should be disabled.
88+
/// The coordination factor is used to reward documents that contain a higher
89+
/// percentage of the query terms. The more query terms that appear in the document,
90+
/// the greater the chances that the document is a good match for the query.
91+
/// </summary>
4392
public bool? DisableCoord { get; set; }
4493

4594
internal override void WrapInContainer(IQueryContainer c) => c.Bool = this;
@@ -50,10 +99,10 @@ internal static bool IsConditionless(IBoolQuery q)
5099
if (!q.Must.HasAny() && !q.Should.HasAny() && !q.MustNot.HasAny() && !q.Filter.HasAny())
51100
return true;
52101

53-
var mustNots = q.MustNot.HasAny() && q.MustNot.All(qq => qq.IsConditionless);
54-
var shoulds = q.Should.HasAny() && q.Should.All(qq => qq.IsConditionless);
55-
var musts = q.Must.HasAny() && q.Must.All(qq => qq.IsConditionless);
56-
var filters = q.Filter.HasAny() && q.Filter.All(qq => qq.IsConditionless);
102+
var mustNots = q.MustNot.HasAny() && q.MustNot.All(qq => qq.IsConditionless());
103+
var shoulds = q.Should.HasAny() && q.Should.All(qq => qq.IsConditionless());
104+
var musts = q.Must.HasAny() && q.Must.All(qq => qq.IsConditionless());
105+
var filters = q.Filter.HasAny() && q.Filter.All(qq => qq.IsConditionless());
57106

58107
return mustNots && shoulds && musts && filters;
59108
}
@@ -64,7 +113,7 @@ public class BoolQueryDescriptor<T>
64113
, IBoolQuery where T : class
65114
{
66115
bool IBoolQuery.Locked => BoolQuery.Locked(this);
67-
116+
68117
protected override bool Conditionless => BoolQuery.IsConditionless(this);
69118
IEnumerable<QueryContainer> IBoolQuery.Must { get; set; }
70119
IEnumerable<QueryContainer> IBoolQuery.MustNot { get; set; }
@@ -73,6 +122,13 @@ public class BoolQueryDescriptor<T>
73122
MinimumShouldMatch IBoolQuery.MinimumShouldMatch { get; set; }
74123
bool? IBoolQuery.DisableCoord { get; set; }
75124

125+
/// <summary>
126+
/// Specifies if the coordination factor for the query should be disabled.
127+
/// The coordination factor is used to reward documents that contain a higher
128+
/// percentage of the query terms. The more query terms that appear in the document,
129+
/// the greater the chances that the document is a good match for the query.
130+
/// </summary>
131+
/// <returns></returns>
76132
public BoolQueryDescriptor<T> DisableCoord() => Assign(a => a.DisableCoord = true);
77133

78134
/// <summary>
@@ -97,7 +153,8 @@ public BoolQueryDescriptor<T> Must(IEnumerable<Func<QueryContainerDescriptor<T>,
97153
/// <summary>
98154
/// The clause(s) that must appear in matching documents
99155
/// </summary>
100-
public BoolQueryDescriptor<T> Must(params QueryContainer[] queries) => Assign(a => a.Must = queries.Where(q => !q.IsNullOrConditionless()).ToListOrNullIfEmpty());
156+
public BoolQueryDescriptor<T> Must(params QueryContainer[] queries) =>
157+
Assign(a => a.Must = queries.Where(q => !q.IsNullOrConditionless()).ToListOrNullIfEmpty());
101158

102159
/// <summary>
103160
/// The clause (query) must not appear in the matching documents. Note that it is not possible to search on documents that only consists of a must_not clauses.
@@ -120,7 +177,8 @@ public BoolQueryDescriptor<T> MustNot(IEnumerable<Func<QueryContainerDescriptor<
120177
/// </summary>
121178
/// <param name="queries"></param>
122179
/// <returns></returns>
123-
public BoolQueryDescriptor<T> MustNot(params QueryContainer[] queries) => Assign(a => a.MustNot = queries.Where(q => !q.IsNullOrConditionless()).ToListOrNullIfEmpty());
180+
public BoolQueryDescriptor<T> MustNot(params QueryContainer[] queries) =>
181+
Assign(a => a.MustNot = queries.Where(q => !q.IsNullOrConditionless()).ToListOrNullIfEmpty());
124182

125183
/// <summary>
126184
/// The clause (query) should appear in the matching document. A boolean query with no must clauses, one or more should clauses must match a document.
@@ -146,7 +204,8 @@ public BoolQueryDescriptor<T> Should(IEnumerable<Func<QueryContainerDescriptor<T
146204
/// </summary>
147205
/// <param name="queries"></param>
148206
/// <returns></returns>
149-
public BoolQueryDescriptor<T> Should(params QueryContainer[] queries) => Assign(a => a.Should = queries.Where(q => !q.IsNullOrConditionless()).ToListOrNullIfEmpty());
207+
public BoolQueryDescriptor<T> Should(params QueryContainer[] queries) =>
208+
Assign(a => a.Should = queries.Where(q => !q.IsNullOrConditionless()).ToListOrNullIfEmpty());
150209

151210
/// <summary>
152211
/// The clause (query) which is to be used as a filter (in filter context).
@@ -169,6 +228,7 @@ public BoolQueryDescriptor<T> Filter(IEnumerable<Func<QueryContainerDescriptor<T
169228
/// </summary>
170229
/// <param name="queries"></param>
171230
/// <returns></returns>
172-
public BoolQueryDescriptor<T> Filter(params QueryContainer[] queries) => Assign(a => a.Filter = queries.Where(q => !q.IsNullOrConditionless()).ToListOrNullIfEmpty());
231+
public BoolQueryDescriptor<T> Filter(params QueryContainer[] queries) =>
232+
Assign(a => a.Filter = queries.Where(q => !q.IsNullOrConditionless()).ToListOrNullIfEmpty());
173233
}
174234
}

src/Tests/Aggregations/Bucket/Filter/FilterAggregationUsageTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public EmptyFilterAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage)
9595
{
9696
empty_filter = new
9797
{
98-
filter = new object()
98+
filter = new {}
9999
}
100100
}
101101
};

src/Tests/Search/Search/SearchApiTests.cs

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,9 @@ protected override void ExpectResponse(ISearchResponse<Project> response)
188188
}
189189

190190
[Collection(IntegrationContext.ReadOnly)]
191-
public class SearchApiConditionlessQueryContainerTests : SearchApiTests
191+
public class SearchApiContainingConditionlessQueryContainerTests : SearchApiTests
192192
{
193-
public SearchApiConditionlessQueryContainerTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
193+
public SearchApiContainingConditionlessQueryContainerTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
194194

195195
protected override object ExpectJson => new
196196
{
@@ -211,19 +211,22 @@ public SearchApiConditionlessQueryContainerTests(ReadOnlyCluster cluster, Endpoi
211211
m => m.QueryString(qs => qs.Query("query")),
212212
m => m.QueryString(qs => qs.Query(string.Empty)),
213213
m => m.QueryString(qs => qs.Query(null)),
214-
m => new QueryContainer()
214+
m => new QueryContainer(),
215+
null
215216
)
216217
.Should(
217218
m => m.QueryString(qs => qs.Query("query")),
218219
m => m.QueryString(qs => qs.Query(string.Empty)),
219220
m => m.QueryString(qs => qs.Query(null)),
220-
m => new QueryContainer()
221+
m => new QueryContainer(),
222+
null
221223
)
222224
.MustNot(
223225
m => m.QueryString(qs => qs.Query("query")),
224226
m => m.QueryString(qs => qs.Query(string.Empty)),
225227
m => m.QueryString(qs => qs.Query(null)),
226-
m => new QueryContainer()
228+
m => new QueryContainer(),
229+
null
227230
)
228231
)
229232
);
@@ -237,21 +240,24 @@ public SearchApiConditionlessQueryContainerTests(ReadOnlyCluster cluster, Endpoi
237240
new QueryStringQuery{ Query = "query" },
238241
new QueryStringQuery{ Query = string.Empty },
239242
new QueryStringQuery{ Query = null },
240-
new QueryContainer()
243+
new QueryContainer(),
244+
null
241245
},
242246
Should = new List<QueryContainer>
243247
{
244248
new QueryStringQuery{ Query = "query" },
245249
new QueryStringQuery{ Query = string.Empty },
246250
new QueryStringQuery{ Query = null },
247-
new QueryContainer()
251+
new QueryContainer(),
252+
null
248253
},
249254
MustNot = new List<QueryContainer>
250255
{
251256
new QueryStringQuery{ Query = "query" },
252257
new QueryStringQuery{ Query = string.Empty },
253258
new QueryStringQuery{ Query = null },
254-
new QueryContainer()
259+
new QueryContainer(),
260+
null
255261
}
256262
}
257263
};
@@ -261,4 +267,79 @@ protected override void ExpectResponse(ISearchResponse<Project> response)
261267
response.IsValid.Should().BeTrue();
262268
}
263269
}
270+
271+
[Collection(IntegrationContext.ReadOnly)]
272+
public class SearchApiNullQueryContainerTests : SearchApiTests
273+
{
274+
public SearchApiNullQueryContainerTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
275+
276+
protected override object ExpectJson => new {};
277+
278+
protected override Func<SearchDescriptor<Project>, ISearchRequest> Fluent => s => s
279+
.Query(q => q
280+
.Bool(b => b
281+
.Must((Func<QueryContainerDescriptor<Project>,QueryContainer>)null)
282+
.Should((Func<QueryContainerDescriptor<Project>, QueryContainer>)null)
283+
.MustNot((Func<QueryContainerDescriptor<Project>, QueryContainer>)null)
284+
)
285+
);
286+
287+
protected override SearchRequest<Project> Initializer => new SearchRequest<Project>()
288+
{
289+
Query = new BoolQuery
290+
{
291+
Must = null,
292+
Should = null,
293+
MustNot = null
294+
}
295+
};
296+
297+
protected override void ExpectResponse(ISearchResponse<Project> response)
298+
{
299+
response.IsValid.Should().BeTrue();
300+
}
301+
}
302+
303+
[Collection(IntegrationContext.ReadOnly)]
304+
public class SearchApiNullQueriesInQueryContainerTests : SearchApiTests
305+
{
306+
public SearchApiNullQueriesInQueryContainerTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
307+
308+
protected override object ExpectJson => new
309+
{
310+
query = new
311+
{
312+
@bool = new { }
313+
}
314+
};
315+
316+
// There is no *direct equivalent* to a query container collection only with a null querycontainer
317+
// since the fluent methods filter them out
318+
protected override Func<SearchDescriptor<Project>, ISearchRequest> Fluent => s => s
319+
.Query(q => q
320+
.Bool(b =>
321+
{
322+
IBoolQuery bq = b;
323+
bq.Must = new QueryContainer[] {null};
324+
bq.Should = new QueryContainer[] {null};
325+
bq.MustNot = new QueryContainer[] {null};
326+
return bq;
327+
})
328+
);
329+
330+
protected override SearchRequest<Project> Initializer => new SearchRequest<Project>()
331+
{
332+
Query = new BoolQuery
333+
{
334+
Must = new QueryContainer[] { null },
335+
Should = new QueryContainer[] { null },
336+
MustNot = new QueryContainer[] { null }
337+
}
338+
};
339+
340+
protected override void ExpectResponse(ISearchResponse<Project> response)
341+
{
342+
response.IsValid.Should().BeTrue();
343+
}
344+
}
264345
}

0 commit comments

Comments
 (0)