Skip to content

Commit 1a060f0

Browse files
author
Christoph Büscher
committed
SimpleQ.S.B and QueryStringQ.S.B tests should avoid now in query (#43199)
Currently the randomization of the q.b. in these tests can create query strings that can cause caching to be disabled for this query if we query all fields and there is a date field present. This is pretty much an anomaly that we shouldn't generally test for in the "testToQuery" tests where cache policies are checked. This change makes sure we don't create offending query strings so the cache checks never hit these cases and adds a special test method to check this edge case. Closes #43112
1 parent 1bc3782 commit 1a060f0

File tree

6 files changed

+125
-89
lines changed

6 files changed

+125
-89
lines changed

server/src/test/java/org/elasticsearch/index/query/FullTextQueryTestCase.java

Lines changed: 0 additions & 60 deletions
This file was deleted.

server/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.elasticsearch.index.search.MatchQuery.Type;
4747
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
4848
import org.elasticsearch.search.internal.SearchContext;
49+
import org.elasticsearch.test.AbstractQueryTestCase;
4950
import org.hamcrest.Matcher;
5051

5152
import java.io.IOException;
@@ -55,19 +56,13 @@
5556
import java.util.Locale;
5657
import java.util.Map;
5758

58-
import static java.util.Collections.singletonList;
5959
import static org.hamcrest.CoreMatchers.either;
6060
import static org.hamcrest.CoreMatchers.instanceOf;
6161
import static org.hamcrest.Matchers.containsString;
6262
import static org.hamcrest.Matchers.equalTo;
6363
import static org.hamcrest.Matchers.notNullValue;
6464

65-
public class MatchQueryBuilderTests extends FullTextQueryTestCase<MatchQueryBuilder> {
66-
@Override
67-
protected boolean isCacheable(MatchQueryBuilder queryBuilder) {
68-
return queryBuilder.fuzziness() != null
69-
|| isCacheable(singletonList(queryBuilder.fieldName()), queryBuilder.value().toString());
70-
}
65+
public class MatchQueryBuilderTests extends AbstractQueryTestCase<MatchQueryBuilder> {
7166

7267
@Override
7368
protected MatchQueryBuilder doCreateTestQueryBuilder() {
@@ -530,4 +525,22 @@ private static CannedBinaryTokenStream.BinaryToken[] createGiantGraphMultiTerms(
530525
}
531526
return tokens.toArray(new CannedBinaryTokenStream.BinaryToken[0]);
532527
}
528+
529+
/**
530+
* "now" on date fields should make the query non-cachable.
531+
*/
532+
public void testCachingStrategiesWithNow() throws IOException {
533+
// if we hit a date field with "now", this should diable cachability
534+
MatchQueryBuilder queryBuilder = new MatchQueryBuilder(DATE_FIELD_NAME, "now");
535+
QueryShardContext context = createShardContext();
536+
assert context.isCacheable();
537+
/*
538+
* We use a private rewrite context here since we want the most realistic way of asserting that we are cacheable or not. We do it
539+
* this way in SearchService where we first rewrite the query with a private context, then reset the context and then build the
540+
* actual lucene query
541+
*/
542+
QueryBuilder rewritten = rewriteQuery(queryBuilder, new QueryShardContext(context));
543+
assertNotNull(rewritten.toQuery(context));
544+
assertFalse("query should not be cacheable: " + queryBuilder.toString(), context.isCacheable());
545+
}
533546
}

server/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.elasticsearch.index.query.MultiMatchQueryBuilder.Type;
4343
import org.elasticsearch.index.search.MatchQuery;
4444
import org.elasticsearch.search.internal.SearchContext;
45+
import org.elasticsearch.test.AbstractQueryTestCase;
4546

4647
import java.io.IOException;
4748
import java.util.Arrays;
@@ -58,16 +59,10 @@
5859
import static org.hamcrest.CoreMatchers.instanceOf;
5960
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
6061

61-
public class MultiMatchQueryBuilderTests extends FullTextQueryTestCase<MultiMatchQueryBuilder> {
62+
public class MultiMatchQueryBuilderTests extends AbstractQueryTestCase<MultiMatchQueryBuilder> {
6263
private static final String MISSING_WILDCARD_FIELD_NAME = "missing_*";
6364
private static final String MISSING_FIELD_NAME = "missing";
6465

65-
@Override
66-
protected boolean isCacheable(MultiMatchQueryBuilder queryBuilder) {
67-
return queryBuilder.fuzziness() != null
68-
|| isCacheable(queryBuilder.fields().keySet(), queryBuilder.value().toString());
69-
}
70-
7166
@Override
7267
protected MultiMatchQueryBuilder doCreateTestQueryBuilder() {
7368
String fieldName = randomFrom(STRING_FIELD_NAME, INT_FIELD_NAME, DOUBLE_FIELD_NAME, BOOLEAN_FIELD_NAME, DATE_FIELD_NAME,
@@ -536,4 +531,22 @@ private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings
536531
.build();
537532
return IndexMetaData.builder(name).settings(build).build();
538533
}
534+
535+
/**
536+
* "now" on date fields should make the query non-cachable.
537+
*/
538+
public void testCachingStrategiesWithNow() throws IOException {
539+
// if we hit a date field with "now", this should diable cachability
540+
MultiMatchQueryBuilder queryBuilder = new MultiMatchQueryBuilder("now", DATE_FIELD_NAME);
541+
QueryShardContext context = createShardContext();
542+
assert context.isCacheable();
543+
/*
544+
* We use a private rewrite context here since we want the most realistic way of asserting that we are cacheable or not. We do it
545+
* this way in SearchService where we first rewrite the query with a private context, then reset the context and then build the
546+
* actual lucene query
547+
*/
548+
QueryBuilder rewritten = rewriteQuery(queryBuilder, new QueryShardContext(context));
549+
assertNotNull(rewritten.toQuery(context));
550+
assertFalse("query should not be cacheable: " + queryBuilder.toString(), context.isCacheable());
551+
}
539552
}

server/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import org.elasticsearch.index.mapper.MapperService;
6363
import org.elasticsearch.index.search.QueryStringQueryParser;
6464
import org.elasticsearch.search.internal.SearchContext;
65+
import org.elasticsearch.test.AbstractQueryTestCase;
6566
import org.hamcrest.CoreMatchers;
6667
import org.hamcrest.Matchers;
6768

@@ -72,6 +73,7 @@
7273
import java.util.Arrays;
7374
import java.util.HashMap;
7475
import java.util.List;
76+
import java.util.Locale;
7577
import java.util.Map;
7678

7779
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
@@ -83,12 +85,7 @@
8385
import static org.hamcrest.Matchers.containsString;
8486
import static org.hamcrest.Matchers.instanceOf;
8587

86-
public class QueryStringQueryBuilderTests extends FullTextQueryTestCase<QueryStringQueryBuilder> {
87-
@Override
88-
protected boolean isCacheable(QueryStringQueryBuilder queryBuilder) {
89-
return queryBuilder.fuzziness() != null
90-
|| isCacheable(queryBuilder.fields().keySet(), queryBuilder.queryString());
91-
}
88+
public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStringQueryBuilder> {
9289

9390
@Override
9491
protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
@@ -108,8 +105,11 @@ protected QueryStringQueryBuilder doCreateTestQueryBuilder() {
108105
int numTerms = randomIntBetween(0, 5);
109106
String query = "";
110107
for (int i = 0; i < numTerms; i++) {
111-
//min length 4 makes sure that the text is not an operator (AND/OR) so toQuery won't break
112-
query += (randomBoolean() ? STRING_FIELD_NAME + ":" : "") + randomAlphaOfLengthBetween(4, 10) + " ";
108+
// min length 4 makes sure that the text is not an operator (AND/OR) so toQuery won't break
109+
// also avoid "now" since we might hit dqte fields later and this complicates caching checks
110+
String term = randomValueOtherThanMany(s -> s.toLowerCase(Locale.ROOT).contains("now"),
111+
() -> randomAlphaOfLengthBetween(4, 10));
112+
query += (randomBoolean() ? STRING_FIELD_NAME + ":" : "") + term + " ";
113113
}
114114
QueryStringQueryBuilder queryStringQueryBuilder = new QueryStringQueryBuilder(query);
115115
if (randomBoolean()) {
@@ -1516,4 +1516,39 @@ private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings
15161516
.build();
15171517
return IndexMetaData.builder(name).settings(build).build();
15181518
}
1519+
1520+
/**
1521+
* Query terms that contain "now" can trigger a query to not be cacheable.
1522+
* This test checks the search context cacheable flag is updated accordingly.
1523+
*/
1524+
public void testCachingStrategiesWithNow() throws IOException {
1525+
// if we hit all fields, this should contain a date field and should diable cachability
1526+
String query = "now " + randomAlphaOfLengthBetween(4, 10);
1527+
QueryStringQueryBuilder queryStringQueryBuilder = new QueryStringQueryBuilder(query);
1528+
assertQueryCachability(queryStringQueryBuilder, false);
1529+
1530+
// if we hit a date field with "now", this should diable cachability
1531+
queryStringQueryBuilder = new QueryStringQueryBuilder("now");
1532+
queryStringQueryBuilder.field(DATE_FIELD_NAME);
1533+
assertQueryCachability(queryStringQueryBuilder, false);
1534+
1535+
// everything else is fine on all fields
1536+
query = randomFrom("NoW", "nOw", "NOW") + " " + randomAlphaOfLengthBetween(4, 10);
1537+
queryStringQueryBuilder = new QueryStringQueryBuilder(query);
1538+
assertQueryCachability(queryStringQueryBuilder, true);
1539+
}
1540+
1541+
private void assertQueryCachability(QueryStringQueryBuilder qb, boolean cachingExpected) throws IOException {
1542+
QueryShardContext context = createShardContext();
1543+
assert context.isCacheable();
1544+
/*
1545+
* We use a private rewrite context here since we want the most realistic way of asserting that we are cacheable or not. We do it
1546+
* this way in SearchService where we first rewrite the query with a private context, then reset the context and then build the
1547+
* actual lucene query
1548+
*/
1549+
QueryBuilder rewritten = rewriteQuery(qb, new QueryShardContext(context));
1550+
assertNotNull(rewritten.toQuery(context));
1551+
assertEquals("query should " + (cachingExpected ? "" : "not") + " be cacheable: " + qb.toString(), cachingExpected,
1552+
context.isCacheable());
1553+
}
15191554
}

server/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.elasticsearch.common.settings.Settings;
4545
import org.elasticsearch.index.search.SimpleQueryStringQueryParser;
4646
import org.elasticsearch.search.internal.SearchContext;
47+
import org.elasticsearch.test.AbstractQueryTestCase;
4748

4849
import java.io.IOException;
4950
import java.util.ArrayList;
@@ -64,15 +65,13 @@
6465
import static org.hamcrest.Matchers.is;
6566
import static org.hamcrest.Matchers.notNullValue;
6667

67-
public class SimpleQueryStringBuilderTests extends FullTextQueryTestCase<SimpleQueryStringBuilder> {
68-
@Override
69-
protected boolean isCacheable(SimpleQueryStringBuilder queryBuilder) {
70-
return isCacheable(queryBuilder.fields().keySet(), queryBuilder.value());
71-
}
68+
public class SimpleQueryStringBuilderTests extends AbstractQueryTestCase<SimpleQueryStringBuilder> {
7269

7370
@Override
7471
protected SimpleQueryStringBuilder doCreateTestQueryBuilder() {
75-
String queryText = randomAlphaOfLengthBetween(1, 10);
72+
// we avoid strings with "now" since those can have different caching policies that are checked elsewhere
73+
String queryText = randomValueOtherThanMany(s -> s.toLowerCase(Locale.ROOT).contains("now"),
74+
() -> randomAlphaOfLengthBetween(1, 10));
7675
SimpleQueryStringBuilder result = new SimpleQueryStringBuilder(queryText);
7776
if (randomBoolean()) {
7877
result.analyzeWildcard(randomBoolean());
@@ -743,4 +742,39 @@ private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings
743742
.build();
744743
return IndexMetaData.builder(name).settings(build).build();
745744
}
745+
746+
/**
747+
* Query terms that contain "now" can trigger a query to not be cacheable.
748+
* This test checks the search context cacheable flag is updated accordingly.
749+
*/
750+
public void testCachingStrategiesWithNow() throws IOException {
751+
// if we hit all fields, this should contain a date field and should diable cachability
752+
String query = "now " + randomAlphaOfLengthBetween(4, 10);
753+
SimpleQueryStringBuilder queryBuilder = new SimpleQueryStringBuilder(query);
754+
assertQueryCachability(queryBuilder, false);
755+
756+
// if we hit a date field with "now", this should diable cachability
757+
queryBuilder = new SimpleQueryStringBuilder("now");
758+
queryBuilder.field(DATE_FIELD_NAME);
759+
assertQueryCachability(queryBuilder, false);
760+
761+
// everything else is fine on all fields
762+
query = randomFrom("NoW", "nOw", "NOW") + " " + randomAlphaOfLengthBetween(4, 10);
763+
queryBuilder = new SimpleQueryStringBuilder(query);
764+
assertQueryCachability(queryBuilder, true);
765+
}
766+
767+
private void assertQueryCachability(SimpleQueryStringBuilder qb, boolean cachingExpected) throws IOException {
768+
QueryShardContext context = createShardContext();
769+
assert context.isCacheable();
770+
/*
771+
* We use a private rewrite context here since we want the most realistic way of asserting that we are cacheable or not. We do it
772+
* this way in SearchService where we first rewrite the query with a private context, then reset the context and then build the
773+
* actual lucene query
774+
*/
775+
QueryBuilder rewritten = rewriteQuery(qb, new QueryShardContext(context));
776+
assertNotNull(rewritten.toQuery(context));
777+
assertEquals("query should " + (cachingExpected ? "" : "not") + " be cacheable: " + qb.toString(), cachingExpected,
778+
context.isCacheable());
779+
}
746780
}

test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.test;
2121

2222
import com.fasterxml.jackson.core.io.JsonStringEncoder;
23+
2324
import org.apache.lucene.search.BoostQuery;
2425
import org.apache.lucene.search.Query;
2526
import org.apache.lucene.search.TermQuery;
@@ -470,7 +471,7 @@ public void testToQuery() throws IOException {
470471
}
471472
}
472473

473-
private QueryBuilder rewriteQuery(QB queryBuilder, QueryRewriteContext rewriteContext) throws IOException {
474+
protected QueryBuilder rewriteQuery(QB queryBuilder, QueryRewriteContext rewriteContext) throws IOException {
474475
QueryBuilder rewritten = rewriteAndFetch(queryBuilder, rewriteContext);
475476
// extra safety to fail fast - serialize the rewritten version to ensure it's serializable.
476477
assertSerialization(rewritten);

0 commit comments

Comments
 (0)