3
3
// See the LICENSE file in the project root for more information.
4
4
5
5
using System . Collections . Generic ;
6
+ using System . Diagnostics . CodeAnalysis ;
6
7
using System . Linq ;
7
8
8
9
namespace Elastic . Clients . Elasticsearch . QueryDsl ;
@@ -14,17 +15,23 @@ internal static Query CombineAsMust(this Query leftContainer, Query rightContain
14
15
var hasLeftBool = leftContainer . TryGet < BoolQuery > ( out var leftBool ) ;
15
16
var hasRightBool = rightContainer . TryGet < BoolQuery > ( out var rightBool ) ;
16
17
17
- //neither side is a bool, no special handling needed wrap in a bool must
18
+ // neither side is a bool, no special handling needed wrap in a bool must
18
19
if ( ! hasLeftBool && ! hasRightBool )
20
+ {
19
21
return CreateMustContainer ( new List < Query > { leftContainer , rightContainer } ) ;
22
+ }
20
23
21
24
else if ( TryHandleBoolsWithOnlyShouldClauses ( leftContainer , rightContainer , leftBool , rightBool , out var query ) )
25
+ {
22
26
return query ;
27
+ }
23
28
24
29
else if ( TryHandleUnmergableBools ( leftContainer , rightContainer , leftBool , rightBool , out query ) )
30
+ {
25
31
return query ;
32
+ }
26
33
27
- //neither side is unmergable so neither is a bool with should clauses
34
+ // neither side is unmergable so neither is a bool with should clauses
28
35
29
36
var mustNotClauses = OrphanMustNots ( leftContainer ) . EagerConcat ( OrphanMustNots ( rightContainer ) ) ;
30
37
var filterClauses = OrphanFilters ( leftContainer ) . EagerConcat ( OrphanFilters ( rightContainer ) ) ;
@@ -36,45 +43,92 @@ internal static Query CombineAsMust(this Query leftContainer, Query rightContain
36
43
}
37
44
38
45
/// <summary>
39
- /// Handles cases where either side is a bool which indicates it can't be merged yet the other side is mergable.
46
+ /// Handles cases where either side is a bool which indicates it can't be merged, yet the other side is mergable.
40
47
/// A side is considered unmergable if its locked (has important metadata) or has should clauses.
41
- /// Instead of always wrapping these cases in another bool we merge to unmergable side into to others must clause therefor flattening the
42
- /// generated graph
48
+ /// Instead of always wrapping these cases in another bool we merge the unmergable side into to others must clause therefore flattening the
49
+ /// generated graph. In such cases, we copy the existing BoolQuery so as not to cause a potentially surprising side-effect. (see https://github.com/elastic/elasticsearch-net/issues/5076).
43
50
/// </summary>
44
- private static bool TryHandleUnmergableBools ( Query leftContainer , Query rightContainer , BoolQuery leftBool , BoolQuery rightBool , out Query query )
51
+ private static bool TryHandleUnmergableBools ( Query leftContainer , Query rightContainer , BoolQuery ? leftBool , BoolQuery ? rightBool , [ NotNullWhen ( true ) ] out Query ? query )
45
52
{
46
53
query = null ;
47
- var leftCantMergeAnd = leftBool != null && ! leftBool . CanMergeAnd ( ) ;
48
- var rightCantMergeAnd = rightBool != null && ! rightBool . CanMergeAnd ( ) ;
54
+
55
+ var leftCantMergeAnd = leftBool is not null && ! leftBool . CanMergeAnd ( ) ;
56
+ var rightCantMergeAnd = rightBool is not null && ! rightBool . CanMergeAnd ( ) ;
57
+
49
58
if ( ! leftCantMergeAnd && ! rightCantMergeAnd )
59
+ {
50
60
return false ;
61
+ }
51
62
52
63
if ( leftCantMergeAnd && rightCantMergeAnd )
64
+ {
53
65
query = CreateMustContainer ( leftContainer , rightContainer ) ;
66
+ }
54
67
55
- //right can't merge but left can and is a bool so we add left to the must clause of right
68
+ // right can't merge but left can and is a bool so we add left to the must clause of right
56
69
else if ( ! leftCantMergeAnd && leftBool != null && rightCantMergeAnd )
57
70
{
58
- leftBool . Must = leftBool . Must . AddIfNotNull ( rightContainer ) . ToArray ( ) ;
59
- query = leftContainer ;
71
+ if ( rightContainer is null )
72
+ {
73
+ query = leftContainer ;
74
+ }
75
+ else
76
+ {
77
+ // We create a copy here to avoid side-effects on a user provided bool query such as
78
+ // adding a must clause where one did not previously exist.
79
+
80
+ var leftBoolCopy = new BoolQuery
81
+ {
82
+ Boost = leftBool . Boost ,
83
+ MinimumShouldMatch = leftBool . MinimumShouldMatch ,
84
+ QueryName = leftBool . QueryName ,
85
+ Should = leftBool . Should ,
86
+ Must = leftBool . Must . AddIfNotNull ( rightContainer ) . ToArray ( ) ,
87
+ MustNot = leftBool . MustNot ,
88
+ Filter = leftBool . Filter
89
+ } ;
90
+
91
+ query = leftBoolCopy ;
92
+ }
60
93
}
61
94
62
- //right can't merge and left is not a bool, we forcefully create a wrapped must container
63
- else if ( ! leftCantMergeAnd && leftBool == null && rightCantMergeAnd )
95
+ // right can't merge and left is not a bool, we forcefully create a wrapped must container
96
+ else if ( ! leftCantMergeAnd && leftBool is null && rightCantMergeAnd )
97
+ {
64
98
query = CreateMustContainer ( leftContainer , rightContainer ) ;
99
+ }
65
100
66
- //left can't merge but right can and is a bool so we add left to the must clause of right
67
- else if ( leftCantMergeAnd && ! rightCantMergeAnd && rightBool != null )
101
+ // left can't merge but right can and is a bool so we add left to the must clause of right
102
+ else if ( leftCantMergeAnd && ! rightCantMergeAnd && rightBool is not null )
68
103
{
69
- rightBool . Must = rightBool . Must . AddIfNotNull ( leftContainer ) . ToArray ( ) ;
70
- query = rightContainer ;
104
+ if ( leftContainer is null )
105
+ {
106
+ query = rightContainer ;
107
+ }
108
+ else
109
+ {
110
+ var rightBoolCopy = new BoolQuery
111
+ {
112
+ Boost = rightBool . Boost ,
113
+ MinimumShouldMatch = rightBool . MinimumShouldMatch ,
114
+ QueryName = rightBool . QueryName ,
115
+ Should = rightBool . Should ,
116
+ Must = rightBool . Must . AddIfNotNull ( leftContainer ) . ToArray ( ) ,
117
+ MustNot = rightBool . MustNot ,
118
+ Filter = rightBool . Filter
119
+ } ;
120
+
121
+ query = rightBoolCopy ;
122
+ }
71
123
}
72
124
73
125
//left can't merge and right is not a bool, we forcefully create a wrapped must container
74
126
else if ( leftCantMergeAnd && ! rightCantMergeAnd && rightBool == null )
127
+ {
75
128
query = CreateMustContainer ( new List < Query > { leftContainer , rightContainer } ) ;
129
+ }
76
130
77
- return query != null ;
131
+ return query is not null ;
78
132
}
79
133
80
134
/// <summary>
@@ -86,10 +140,14 @@ private static bool TryHandleUnmergableBools(Query leftContainer, Query rightCon
86
140
private static bool TryHandleBoolsWithOnlyShouldClauses ( Query leftContainer , Query rightContainer , BoolQuery leftBool , BoolQuery rightBool , out Query query )
87
141
{
88
142
query = null ;
143
+
89
144
var leftHasOnlyShoulds = leftBool . HasOnlyShouldClauses ( ) ;
90
145
var rightHasOnlyShoulds = rightBool . HasOnlyShouldClauses ( ) ;
146
+
91
147
if ( ! leftHasOnlyShoulds && ! rightHasOnlyShoulds )
148
+ {
92
149
return false ;
150
+ }
93
151
94
152
if ( leftContainer . HoldsOnlyShouldMusts && rightHasOnlyShoulds )
95
153
{
@@ -106,25 +164,26 @@ private static bool TryHandleBoolsWithOnlyShouldClauses(Query leftContainer, Que
106
164
query = CreateMustContainer ( new List < Query > { leftContainer , rightContainer } ) ;
107
165
query . HoldsOnlyShouldMusts = rightHasOnlyShoulds && leftHasOnlyShoulds ;
108
166
}
167
+
109
168
return true ;
110
169
}
111
170
112
171
private static Query CreateMustContainer ( Query left , Query right ) =>
113
172
CreateMustContainer ( new List < Query > { left , right } ) ;
114
173
115
174
private static Query CreateMustContainer ( List < Query > mustClauses ) =>
116
- new Query ( new BoolQuery ( ) { Must = mustClauses . ToListOrNullIfEmpty ( ) } ) ;
175
+ new ( new BoolQuery ( ) { Must = mustClauses . ToListOrNullIfEmpty ( ) } ) ;
117
176
118
177
private static Query CreateMustContainer (
119
178
List < Query > mustClauses ,
120
179
List < Query > mustNotClauses ,
121
180
List < Query > filters
122
- ) => new Query ( new BoolQuery
123
- {
124
- Must = mustClauses . ToListOrNullIfEmpty ( ) ,
125
- MustNot = mustNotClauses . ToListOrNullIfEmpty ( ) ,
126
- Filter = filters . ToListOrNullIfEmpty ( )
127
- } ) ;
181
+ ) => new ( new BoolQuery
182
+ {
183
+ Must = mustClauses . ToListOrNullIfEmpty ( ) ,
184
+ MustNot = mustNotClauses . ToListOrNullIfEmpty ( ) ,
185
+ Filter = filters . ToListOrNullIfEmpty ( )
186
+ } ) ;
128
187
129
188
private static bool CanMergeAnd ( this BoolQuery boolQuery ) =>
130
189
boolQuery != null && ! boolQuery . Locked && ! boolQuery . Should . HasAny ( ) ;
0 commit comments