|
9 | 9 |
|
10 | 10 | package org.elasticsearch.test; |
11 | 11 |
|
| 12 | +import org.apache.lucene.index.IndexReader; |
12 | 13 | import org.apache.lucene.search.BoostQuery; |
| 14 | +import org.apache.lucene.search.IndexSearcher; |
13 | 15 | import org.apache.lucene.search.MatchNoDocsQuery; |
14 | 16 | import org.apache.lucene.search.Query; |
15 | 17 | import org.apache.lucene.search.TermQuery; |
| 18 | +import org.apache.lucene.store.Directory; |
| 19 | +import org.apache.lucene.tests.index.RandomIndexWriter; |
16 | 20 | import org.elasticsearch.ElasticsearchParseException; |
17 | 21 | import org.elasticsearch.TransportVersion; |
18 | 22 | import org.elasticsearch.action.support.PlainActionFuture; |
|
45 | 49 | import org.elasticsearch.xcontent.json.JsonStringEncoder; |
46 | 50 | import org.elasticsearch.xcontent.json.JsonXContent; |
47 | 51 |
|
| 52 | +import java.io.Closeable; |
48 | 53 | import java.io.IOException; |
49 | 54 | import java.time.Instant; |
50 | 55 | import java.time.ZoneOffset; |
@@ -453,82 +458,87 @@ protected boolean builderGeneratesCacheableQueries() { |
453 | 458 | return true; |
454 | 459 | } |
455 | 460 |
|
| 461 | + protected IndexReaderManager getIndexReaderManager() { |
| 462 | + return NullIndexReaderManager.INSTANCE; |
| 463 | + } |
| 464 | + |
456 | 465 | /** |
457 | 466 | * Test creates the {@link Query} from the {@link QueryBuilder} under test and delegates the |
458 | 467 | * assertions being made on the result to the implementing subclass. |
459 | 468 | */ |
460 | 469 | public void testToQuery() throws IOException { |
461 | 470 | for (int runs = 0; runs < NUMBER_OF_TESTQUERIES; runs++) { |
462 | | - SearchExecutionContext context = createSearchExecutionContext(); |
463 | | - assert context.isCacheable(); |
464 | | - context.setAllowUnmappedFields(true); |
465 | | - QB firstQuery = createTestQueryBuilder(); |
466 | | - QB controlQuery = copyQuery(firstQuery); |
467 | | - /* we use a private rewrite context here since we want the most realistic way of asserting that we are cacheable or not. |
468 | | - * We do it this way in SearchService where |
469 | | - * we first rewrite the query with a private context, then reset the context and then build the actual lucene query*/ |
470 | | - QueryBuilder rewritten = rewriteQuery(firstQuery, createQueryRewriteContext(), new SearchExecutionContext(context)); |
471 | | - Query firstLuceneQuery = rewritten.toQuery(context); |
472 | | - assertNotNull("toQuery should not return null", firstLuceneQuery); |
473 | | - assertLuceneQuery(firstQuery, firstLuceneQuery, context); |
474 | | - // remove after assertLuceneQuery since the assertLuceneQuery impl might access the context as well |
475 | | - assertEquals( |
476 | | - "query is not equal to its copy after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, |
477 | | - firstQuery, |
478 | | - controlQuery |
479 | | - ); |
480 | | - assertEquals( |
481 | | - "equals is not symmetric after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, |
482 | | - controlQuery, |
483 | | - firstQuery |
484 | | - ); |
485 | | - assertThat( |
486 | | - "query copy's hashcode is different from original hashcode after calling toQuery, firstQuery: " |
487 | | - + firstQuery |
488 | | - + ", secondQuery: " |
489 | | - + controlQuery, |
490 | | - controlQuery.hashCode(), |
491 | | - equalTo(firstQuery.hashCode()) |
492 | | - ); |
493 | | - |
494 | | - QB secondQuery = copyQuery(firstQuery); |
495 | | - // query _name never should affect the result of toQuery, we randomly set it to make sure |
496 | | - if (randomBoolean()) { |
497 | | - secondQuery.queryName( |
498 | | - secondQuery.queryName() == null |
499 | | - ? randomAlphaOfLengthBetween(1, 30) |
500 | | - : secondQuery.queryName() + randomAlphaOfLengthBetween(1, 10) |
501 | | - ); |
502 | | - } |
503 | | - context = new SearchExecutionContext(context); |
504 | | - Query secondLuceneQuery = rewriteQuery(secondQuery, createQueryRewriteContext(), new SearchExecutionContext(context)).toQuery( |
505 | | - context |
506 | | - ); |
507 | | - assertNotNull("toQuery should not return null", secondLuceneQuery); |
508 | | - assertLuceneQuery(secondQuery, secondLuceneQuery, context); |
509 | | - |
510 | | - if (builderGeneratesCacheableQueries()) { |
| 471 | + try (IndexReaderManager irm = getIndexReaderManager()) { |
| 472 | + SearchExecutionContext context = createSearchExecutionContext(irm.getIndexSearcher()); |
| 473 | + assert context.isCacheable(); |
| 474 | + context.setAllowUnmappedFields(true); |
| 475 | + QB firstQuery = createTestQueryBuilder(); |
| 476 | + QB controlQuery = copyQuery(firstQuery); |
| 477 | + /* we use a private rewrite context here since we want the most realistic way of asserting that we are cacheable or not. |
| 478 | + * We do it this way in SearchService where |
| 479 | + * we first rewrite the query with a private context, then reset the context and then build the actual lucene query*/ |
| 480 | + QueryBuilder rewritten = rewriteQuery(firstQuery, createQueryRewriteContext(), new SearchExecutionContext(context)); |
| 481 | + Query firstLuceneQuery = rewritten.toQuery(context); |
| 482 | + assertNotNull("toQuery should not return null", firstLuceneQuery); |
| 483 | + assertLuceneQuery(firstQuery, firstLuceneQuery, context); |
| 484 | + // remove after assertLuceneQuery since the assertLuceneQuery impl might access the context as well |
511 | 485 | assertEquals( |
512 | | - "two equivalent query builders lead to different lucene queries hashcode", |
513 | | - secondLuceneQuery.hashCode(), |
514 | | - firstLuceneQuery.hashCode() |
| 486 | + "query is not equal to its copy after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, |
| 487 | + firstQuery, |
| 488 | + controlQuery |
515 | 489 | ); |
516 | 490 | assertEquals( |
517 | | - "two equivalent query builders lead to different lucene queries", |
518 | | - rewrite(secondLuceneQuery), |
519 | | - rewrite(firstLuceneQuery) |
| 491 | + "equals is not symmetric after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, |
| 492 | + controlQuery, |
| 493 | + firstQuery |
| 494 | + ); |
| 495 | + assertThat( |
| 496 | + "query copy's hashcode is different from original hashcode after calling toQuery, firstQuery: " |
| 497 | + + firstQuery |
| 498 | + + ", secondQuery: " |
| 499 | + + controlQuery, |
| 500 | + controlQuery.hashCode(), |
| 501 | + equalTo(firstQuery.hashCode()) |
520 | 502 | ); |
521 | | - } |
522 | 503 |
|
523 | | - if (supportsBoost() && firstLuceneQuery instanceof MatchNoDocsQuery == false) { |
524 | | - secondQuery.boost(firstQuery.boost() + 1f + randomFloat()); |
525 | | - Query thirdLuceneQuery = rewriteQuery(secondQuery, createQueryRewriteContext(), new SearchExecutionContext(context)) |
| 504 | + QB secondQuery = copyQuery(firstQuery); |
| 505 | + // query _name never should affect the result of toQuery, we randomly set it to make sure |
| 506 | + if (randomBoolean()) { |
| 507 | + secondQuery.queryName( |
| 508 | + secondQuery.queryName() == null |
| 509 | + ? randomAlphaOfLengthBetween(1, 30) |
| 510 | + : secondQuery.queryName() + randomAlphaOfLengthBetween(1, 10) |
| 511 | + ); |
| 512 | + } |
| 513 | + context = new SearchExecutionContext(context); |
| 514 | + Query secondLuceneQuery = rewriteQuery(secondQuery, createQueryRewriteContext(), new SearchExecutionContext(context)) |
526 | 515 | .toQuery(context); |
527 | | - assertNotEquals( |
528 | | - "modifying the boost doesn't affect the corresponding lucene query", |
529 | | - rewrite(firstLuceneQuery), |
530 | | - rewrite(thirdLuceneQuery) |
531 | | - ); |
| 516 | + assertNotNull("toQuery should not return null", secondLuceneQuery); |
| 517 | + assertLuceneQuery(secondQuery, secondLuceneQuery, context); |
| 518 | + |
| 519 | + if (builderGeneratesCacheableQueries()) { |
| 520 | + assertEquals( |
| 521 | + "two equivalent query builders lead to different lucene queries hashcode", |
| 522 | + secondLuceneQuery.hashCode(), |
| 523 | + firstLuceneQuery.hashCode() |
| 524 | + ); |
| 525 | + assertEquals( |
| 526 | + "two equivalent query builders lead to different lucene queries", |
| 527 | + rewrite(secondLuceneQuery), |
| 528 | + rewrite(firstLuceneQuery) |
| 529 | + ); |
| 530 | + } |
| 531 | + |
| 532 | + if (supportsBoost() && firstLuceneQuery instanceof MatchNoDocsQuery == false) { |
| 533 | + secondQuery.boost(firstQuery.boost() + 1f + randomFloat()); |
| 534 | + Query thirdLuceneQuery = rewriteQuery(secondQuery, createQueryRewriteContext(), new SearchExecutionContext(context)) |
| 535 | + .toQuery(context); |
| 536 | + assertNotEquals( |
| 537 | + "modifying the boost doesn't affect the corresponding lucene query", |
| 538 | + rewrite(firstLuceneQuery), |
| 539 | + rewrite(thirdLuceneQuery) |
| 540 | + ); |
| 541 | + } |
532 | 542 | } |
533 | 543 | } |
534 | 544 | } |
@@ -938,9 +948,75 @@ public boolean isTextField(String fieldName) { |
938 | 948 | */ |
939 | 949 | public void testCacheability() throws IOException { |
940 | 950 | QB queryBuilder = createTestQueryBuilder(); |
941 | | - SearchExecutionContext context = createSearchExecutionContext(); |
942 | | - QueryBuilder rewriteQuery = rewriteQuery(queryBuilder, createQueryRewriteContext(), new SearchExecutionContext(context)); |
943 | | - assertNotNull(rewriteQuery.toQuery(context)); |
944 | | - assertTrue("query should be cacheable: " + queryBuilder.toString(), context.isCacheable()); |
| 951 | + try (IndexReaderManager irm = getIndexReaderManager()) { |
| 952 | + SearchExecutionContext context = createSearchExecutionContext(irm.getIndexSearcher()); |
| 953 | + QueryBuilder rewriteQuery = rewriteQuery(queryBuilder, createQueryRewriteContext(), new SearchExecutionContext(context)); |
| 954 | + assertNotNull(rewriteQuery.toQuery(context)); |
| 955 | + assertTrue("query should be cacheable: " + queryBuilder.toString(), context.isCacheable()); |
| 956 | + } |
| 957 | + } |
| 958 | + |
| 959 | + public static class IndexReaderManager implements Closeable { |
| 960 | + private final Directory directory; |
| 961 | + private RandomIndexWriter indexWriter; |
| 962 | + private IndexReader indexReader; |
| 963 | + private IndexSearcher indexSearcher; |
| 964 | + |
| 965 | + public IndexReaderManager() { |
| 966 | + this.directory = newDirectory(); |
| 967 | + } |
| 968 | + |
| 969 | + private IndexReaderManager(Directory directory) { |
| 970 | + this.directory = directory; |
| 971 | + } |
| 972 | + |
| 973 | + public IndexReader getIndexReader() throws IOException { |
| 974 | + if (indexReader == null) { |
| 975 | + indexWriter = new RandomIndexWriter(random(), directory); |
| 976 | + initIndexWriter(indexWriter); |
| 977 | + indexReader = indexWriter.getReader(); |
| 978 | + } |
| 979 | + return indexReader; |
| 980 | + } |
| 981 | + |
| 982 | + public IndexSearcher getIndexSearcher() throws IOException { |
| 983 | + if (indexSearcher == null) { |
| 984 | + indexSearcher = newSearcher(getIndexReader()); |
| 985 | + } |
| 986 | + return indexSearcher; |
| 987 | + } |
| 988 | + |
| 989 | + @Override |
| 990 | + public void close() throws IOException { |
| 991 | + if (indexReader != null) { |
| 992 | + indexReader.close(); |
| 993 | + } |
| 994 | + if (indexWriter != null) { |
| 995 | + indexWriter.close(); |
| 996 | + } |
| 997 | + if (directory != null) { |
| 998 | + directory.close(); |
| 999 | + } |
| 1000 | + } |
| 1001 | + |
| 1002 | + protected void initIndexWriter(RandomIndexWriter indexWriter) {} |
| 1003 | + } |
| 1004 | + |
| 1005 | + public static class NullIndexReaderManager extends IndexReaderManager { |
| 1006 | + public static final NullIndexReaderManager INSTANCE = new NullIndexReaderManager(); |
| 1007 | + |
| 1008 | + public NullIndexReaderManager() { |
| 1009 | + super(null); |
| 1010 | + } |
| 1011 | + |
| 1012 | + @Override |
| 1013 | + public IndexReader getIndexReader() { |
| 1014 | + return null; |
| 1015 | + } |
| 1016 | + |
| 1017 | + @Override |
| 1018 | + public IndexSearcher getIndexSearcher() { |
| 1019 | + return null; |
| 1020 | + } |
945 | 1021 | } |
946 | 1022 | } |
0 commit comments