Skip to content

Commit b8d5176

Browse files
russcamMpdreamz
authored andcommitted
fix #2461 &= in the bool DSL is viral. (#2487)
The performance and allocations fixes in #2235 also introduced virality on the QueryContainer when using &= assignment. The composed result was correct but the QueryContainer composed of `&=` would be left marked as `CreatedByTheBoolDsl` which meant it was marked for reuse. If you then later combined it with && (andAssignedQuery && newQuery) the resulting query would be correct but if you rely on `andAssignedQuery` not being mutated you'd be in for a nasty suprise. A QueryContainer composed with `&&` did not expose the same virality problems. Removed the notion of trying to reuse all together since the big performance gain from #2235 is that we can flatten many boolean should queries using `&=` or `&&` or visa versa many boolean must queries with `|=` or `||`. (cherry-picked from commit 10ee84a)
1 parent 6219522 commit b8d5176

File tree

8 files changed

+60
-76
lines changed

8 files changed

+60
-76
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ private static bool IfEitherIsEmptyReturnTheOtherOrEmpty(QueryContainer leftCont
6868
if (!leftWritable && !rightWritable) return true;
6969

7070
queryContainer = leftWritable ? leftContainer : rightContainer;
71-
return !leftWritable || !rightWritable;
71+
return true;
7272
}
7373

7474
public static QueryContainer operator !(QueryContainer queryContainer) => queryContainer == null || (!queryContainer.IsWritable)

src/Nest/QueryDsl/Abstractions/Query/BoolQueryAndExtensions.cs

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ internal static QueryContainer CombineAsMust(this QueryContainer leftContainer,
2626
var filterClauses = OrphanFilters(leftContainer).EagerConcat(OrphanFilters(rightContainer));
2727
var mustClauses = OrphanMusts(leftContainer).EagerConcat(OrphanMusts(rightContainer));
2828

29-
var reuseContainer = ContainerContainingBool(leftContainer, rightContainer);
30-
var container = CreateMustContainer(mustClauses, mustNotClauses, filterClauses, reuseContainer);
29+
var container = CreateMustContainer(mustClauses, mustNotClauses, filterClauses);
3130
return container;
3231
}
3332

@@ -79,66 +78,41 @@ private static bool TryHandleBoolsWithOnlyShouldClauses(
7978
c = null;
8079
var leftHasOnlyShoulds = leftBool.HasOnlyShouldClauses();
8180
var rightHasOnlyShoulds = rightBool.HasOnlyShouldClauses();
82-
if (leftHasOnlyShoulds || rightHasOnlyShoulds)
81+
if (!leftHasOnlyShoulds && !rightHasOnlyShoulds) return false;
82+
if (leftContainer.HoldsOnlyShouldMusts && rightHasOnlyShoulds)
8383
{
84-
if (leftContainer.HoldsOnlyShouldMusts && rightHasOnlyShoulds)
85-
{
86-
leftBool.Must = leftBool.Must.AddIfNotNull(rightContainer);
87-
c = leftContainer;
88-
}
89-
else if (rightContainer.HoldsOnlyShouldMusts && leftHasOnlyShoulds)
90-
{
91-
rightBool.Must = rightBool.Must.AddIfNotNull(leftContainer);
92-
c = rightContainer;
93-
}
94-
else
95-
{
96-
//c = CreateMustContainer(leftContainer, rightContainer);
97-
c = CreateMustContainer(new Containers { leftContainer, rightContainer }, null);
98-
c.HoldsOnlyShouldMusts = rightHasOnlyShoulds && leftHasOnlyShoulds;
99-
}
84+
leftBool.Must = leftBool.Must.AddIfNotNull(rightContainer);
85+
c = leftContainer;
10086
}
101-
return c != null;
87+
else if (rightContainer.HoldsOnlyShouldMusts && leftHasOnlyShoulds)
88+
{
89+
rightBool.Must = rightBool.Must.AddIfNotNull(leftContainer);
90+
c = rightContainer;
91+
}
92+
else
93+
{
94+
c = CreateMustContainer(new Containers { leftContainer, rightContainer }, null);
95+
c.HoldsOnlyShouldMusts = rightHasOnlyShoulds && leftHasOnlyShoulds;
96+
}
97+
return true;
10298
}
10399

104100
private static QueryContainer CreateMustContainer(QueryContainer left, QueryContainer right)
105101
{
106-
return CreateMustContainer(new Containers { left, right }, ContainerContainingBool(left, right));
107-
}
108-
109-
private static QueryContainer ContainerContainingBool(QueryContainer left, QueryContainer right)
110-
{
111-
var l = (left?.Self().Bool?.CreatedByBoolDsl).GetValueOrDefault();
112-
var r = (right?.Self().Bool?.CreatedByBoolDsl).GetValueOrDefault();
113-
return l ? left : r ? right : null;
102+
return CreateMustContainer(new Containers { left, right }, null);
114103
}
115104

116105
private static QueryContainer CreateMustContainer(List<QueryContainer> mustClauses, QueryContainer reuse)
117106
{
118-
var existingBool = reuse?.Self().Bool;
119-
if (existingBool != null && existingBool.CreatedByBoolDsl)
120-
{
121-
existingBool.Must = mustClauses.ToListOrNullIfEmpty();
122-
return reuse;
123-
}
124-
return new QueryContainer(new BoolQuery(createdByBoolDsl: true) { Must = mustClauses.ToListOrNullIfEmpty() });
107+
return new QueryContainer(new BoolQuery() { Must = mustClauses.ToListOrNullIfEmpty() });
125108
}
126109

127110
private static QueryContainer CreateMustContainer(
128111
List<QueryContainer> mustClauses,
129112
List<QueryContainer> mustNotClauses,
130-
List<QueryContainer> filters,
131-
QueryContainer reuse
113+
List<QueryContainer> filters
132114
)
133115
{
134-
var existingBool = reuse?.Self().Bool;
135-
if (existingBool != null && existingBool.CreatedByBoolDsl)
136-
{
137-
existingBool.Must = mustClauses.ToListOrNullIfEmpty();
138-
existingBool.MustNot = mustNotClauses.ToListOrNullIfEmpty();
139-
existingBool.Filter = filters.ToListOrNullIfEmpty();
140-
return reuse;
141-
}
142116
return new QueryContainer(new BoolQuery
143117
{
144118
Must = mustClauses.ToListOrNullIfEmpty(),
@@ -148,11 +122,8 @@ QueryContainer reuse
148122
}
149123

150124
private static bool CanMergeAnd(this IBoolQuery boolQuery) =>
151-
//boolQuery != null && boolQuery.IsWritable && !boolQuery.Locked && !boolQuery.Should.HasAny();
152125
boolQuery != null && !boolQuery.Locked && !boolQuery.Should.HasAny();
153126

154-
private static bool CanMergeAnd(this IQueryContainer container) => container.Bool.CanMergeAnd();
155-
156127
private static IEnumerable<QueryContainer> OrphanMusts(QueryContainer container)
157128
{
158129
var lBoolQuery = container.Self().Bool;

src/Nest/QueryDsl/Abstractions/Query/BoolQueryOrExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ private static bool CanMergeShould(this IBoolQuery boolQuery) =>
5656
boolQuery != null && boolQuery.IsWritable && !boolQuery.Locked && boolQuery.HasOnlyShouldClauses();
5757

5858
private static QueryContainer CreateShouldContainer(List<QueryContainer> shouldClauses) =>
59-
new BoolQuery(createdByBoolDsl: true)
59+
//new BoolQuery(createdByBoolDsl: true)
60+
new BoolQuery()
6061
{
6162
Should = shouldClauses.ToListOrNullIfEmpty()
6263
};

src/Nest/QueryDsl/Abstractions/Query/QueryBase.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,8 @@ private static bool IfEitherIsEmptyReturnTheOtherOrEmpty(QueryBase leftQuery, Qu
8282
return anyEmpty;
8383
}
8484

85-
public static implicit operator QueryContainer(QueryBase query)
86-
{
87-
if (query == null)
88-
return null;
89-
return new QueryContainer(query);
90-
91-
92-
}
85+
public static implicit operator QueryContainer(QueryBase query) =>
86+
query == null ? null : new QueryContainer(query);
9387

9488
internal void WrapInContainer(IQueryContainer container)
9589
{

src/Nest/QueryDsl/Abstractions/Query/QueryDescriptorBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ public abstract class QueryDescriptorBase<TDescriptor, TInterface>
2323

2424
public TDescriptor Strict(bool strict = true) => Assign(a => a.IsStrict = strict);
2525

26-
bool IQuery.IsWritable { get { return Self.IsVerbatim || !Self.Conditionless; } }
26+
bool IQuery.IsWritable => Self.IsVerbatim || !Self.Conditionless;
2727
}
2828
}

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,12 @@ public interface IBoolQuery : IQuery
5151
bool? DisableCoord { get; set; }
5252

5353
bool Locked { get; }
54-
bool CreatedByBoolDsl { get; }
5554
}
5655

5756
public class BoolQuery : QueryBase, IBoolQuery
5857
{
5958
internal static bool Locked(IBoolQuery q) => !q.Name.IsNullOrEmpty() || q.Boost.HasValue || q.DisableCoord.HasValue || q.MinimumShouldMatch != null;
6059
bool IBoolQuery.Locked => BoolQuery.Locked(this);
61-
private readonly bool _createdByBoolDsl;
62-
bool IBoolQuery.CreatedByBoolDsl => _createdByBoolDsl;
6360

6461
private IList<QueryContainer> _must;
6562
private IList<QueryContainer> _mustNot;
@@ -68,16 +65,6 @@ public class BoolQuery : QueryBase, IBoolQuery
6865

6966
public BoolQuery() { }
7067

71-
/// <summary>
72-
/// Internal constructor which we use internally in the bool dsl so we know its safe to reuse a boolean query instance
73-
/// </summary>
74-
/// <param name="createdByBoolDsl">ignored</param>
75-
internal BoolQuery(bool createdByBoolDsl)
76-
{
77-
this._createdByBoolDsl = true;
78-
}
79-
80-
8168
/// <summary>
8269
/// The clause(s) that must appear in matching documents
8370
/// </summary>
@@ -136,7 +123,6 @@ public class BoolQueryDescriptor<T>
136123
, IBoolQuery where T : class
137124
{
138125
bool IBoolQuery.Locked => BoolQuery.Locked(this);
139-
bool IBoolQuery.CreatedByBoolDsl { get; } = false;
140126

141127
protected override bool Conditionless => BoolQuery.IsConditionless(this);
142128
private IList<QueryContainer> _must;

src/Tests/QueryDsl/BoolDsl/BoolDsl.doc.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,33 @@ private static void AssertDoesNotJoinOntoLockedBool(IQueryContainer c, string fi
336336
nestedBool.Bool.Name.Should().Be(firstName);
337337
}
338338

339+
//hide
340+
[U] public void AndShouldNotBeViralAnyWayYouComposeIt()
341+
{
342+
QueryContainer andAssign = new TermQuery { Field = "a", Value = "a" };
343+
andAssign &= new TermQuery { Field = "b", Value = "b" };
344+
345+
AssertAndIsNotViral(andAssign, "&=");
346+
347+
QueryContainer andOperator =
348+
new TermQuery { Field = "a", Value = "a" } && new TermQuery { Field = "b", Value = "b" };
349+
350+
AssertAndIsNotViral(andOperator, "&&");
351+
}
352+
353+
//hide
354+
private static void AssertAndIsNotViral(QueryContainer query, string origin)
355+
{
356+
IQueryContainer original = query;
357+
original.Bool.Must.Should().HaveCount(2, $"query composed using {origin} should have 2 must clauses before");
358+
IQueryContainer result = query && new TermQuery { Field = "c", Value = "c" };
359+
result.Bool.Must.Should().HaveCount(3, $"query composed using {origin} combined with && should have 3 must clauses");
360+
original.Bool.Must.Should().HaveCount(2, $"query composed using {origin} should still have 2 must clauses after composition");
361+
}
362+
363+
364+
365+
339366
/** === Perfomance considerations
340367
*
341368
* If you have a requirement of combining many many queries using the bool dsl please take the following into account.
@@ -349,8 +376,10 @@ private static void SlowCombine()
349376
var c = new QueryContainer();
350377
var q = new TermQuery { Field = "x", Value = "x" };
351378

352-
for (var i=0;i<1000;i++)
379+
for (var i = 0; i < 1000; i++)
380+
{
353381
c &= q;
382+
}
354383
}
355384
/**
356385
*....

src/Tests/tests.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ mode: m
44
elasticsearch_version: 2.4.2
55
# cluster filter allows you to only run the integration tests of a particular cluster (cluster suffix not needed)
66
cluster_filter:
7+
# test filter allows you to run a particular test
8+
test_filter:
79
# whether we want to forcefully reseed on the node, if you are starting the tests with a node already running
810
force_reseed: true
9-
# do not spawn nodes as part of the test setup but rely on a manually started es node being up
10-
do_not_spawn: false
11+
# do not spawn nodes as part of the test setup if we find a node is already running
12+
# this is opt in during development in CI we never want to see our tests running against an already running node
13+
test_against_already_running_elasticsearch: true

0 commit comments

Comments
 (0)