ContextIndexSearcherTests.java 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. /*
  2. * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
  3. * or more contributor license agreements. Licensed under the "Elastic License
  4. * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
  5. * Public License v 1"; you may not use this file except in compliance with, at
  6. * your election, the "Elastic License 2.0", the "GNU Affero General Public
  7. * License v3.0 only", or the "Server Side Public License, v 1".
  8. */
  9. package org.elasticsearch.search.internal;
  10. import org.apache.lucene.analysis.standard.StandardAnalyzer;
  11. import org.apache.lucene.codecs.StoredFieldsReader;
  12. import org.apache.lucene.document.Document;
  13. import org.apache.lucene.document.Field;
  14. import org.apache.lucene.document.IntPoint;
  15. import org.apache.lucene.document.StringField;
  16. import org.apache.lucene.index.DirectoryReader;
  17. import org.apache.lucene.index.FilterDirectoryReader;
  18. import org.apache.lucene.index.FilterLeafReader;
  19. import org.apache.lucene.index.IndexWriter;
  20. import org.apache.lucene.index.IndexWriterConfig;
  21. import org.apache.lucene.index.LeafReader;
  22. import org.apache.lucene.index.LeafReaderContext;
  23. import org.apache.lucene.index.NoMergePolicy;
  24. import org.apache.lucene.index.PostingsEnum;
  25. import org.apache.lucene.index.Term;
  26. import org.apache.lucene.index.Terms;
  27. import org.apache.lucene.index.TermsEnum;
  28. import org.apache.lucene.search.BoostQuery;
  29. import org.apache.lucene.search.Collector;
  30. import org.apache.lucene.search.CollectorManager;
  31. import org.apache.lucene.search.ConstantScoreQuery;
  32. import org.apache.lucene.search.ConstantScoreScorer;
  33. import org.apache.lucene.search.ConstantScoreWeight;
  34. import org.apache.lucene.search.DocIdSetIterator;
  35. import org.apache.lucene.search.Explanation;
  36. import org.apache.lucene.search.IndexSearcher;
  37. import org.apache.lucene.search.IndexSearcher.LeafSlice;
  38. import org.apache.lucene.search.KnnFloatVectorQuery;
  39. import org.apache.lucene.search.LeafCollector;
  40. import org.apache.lucene.search.MatchAllDocsQuery;
  41. import org.apache.lucene.search.MatchNoDocsQuery;
  42. import org.apache.lucene.search.Query;
  43. import org.apache.lucene.search.QueryVisitor;
  44. import org.apache.lucene.search.Scorable;
  45. import org.apache.lucene.search.ScoreMode;
  46. import org.apache.lucene.search.Scorer;
  47. import org.apache.lucene.search.ScorerSupplier;
  48. import org.apache.lucene.search.TermQuery;
  49. import org.apache.lucene.search.TopDocs;
  50. import org.apache.lucene.search.TotalHitCountCollectorManager;
  51. import org.apache.lucene.search.Weight;
  52. import org.apache.lucene.store.Directory;
  53. import org.apache.lucene.tests.index.RandomIndexWriter;
  54. import org.apache.lucene.util.BitSet;
  55. import org.apache.lucene.util.BitSetIterator;
  56. import org.apache.lucene.util.Bits;
  57. import org.apache.lucene.util.FixedBitSet;
  58. import org.apache.lucene.util.SparseFixedBitSet;
  59. import org.elasticsearch.ExceptionsHelper;
  60. import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
  61. import org.elasticsearch.common.lucene.index.SequentialStoredFieldsLeafReader;
  62. import org.elasticsearch.common.settings.Settings;
  63. import org.elasticsearch.core.IOUtils;
  64. import org.elasticsearch.index.IndexSettings;
  65. import org.elasticsearch.index.cache.bitset.BitsetFilterCache;
  66. import org.elasticsearch.index.shard.ShardId;
  67. import org.elasticsearch.lucene.util.CombinedBits;
  68. import org.elasticsearch.lucene.util.MatchAllBitSet;
  69. import org.elasticsearch.search.aggregations.BucketCollector;
  70. import org.elasticsearch.search.aggregations.LeafBucketCollector;
  71. import org.elasticsearch.test.ESTestCase;
  72. import org.elasticsearch.test.IndexSettingsModule;
  73. import java.io.IOException;
  74. import java.io.UncheckedIOException;
  75. import java.util.Collection;
  76. import java.util.Collections;
  77. import java.util.IdentityHashMap;
  78. import java.util.List;
  79. import java.util.Set;
  80. import java.util.concurrent.Executors;
  81. import java.util.concurrent.ThreadPoolExecutor;
  82. import static org.elasticsearch.search.internal.ContextIndexSearcher.intersectScorerAndBitSet;
  83. import static org.elasticsearch.search.internal.ExitableDirectoryReader.ExitableLeafReader;
  84. import static org.elasticsearch.search.internal.ExitableDirectoryReader.ExitablePointValues;
  85. import static org.elasticsearch.search.internal.ExitableDirectoryReader.ExitableTerms;
  86. import static org.hamcrest.Matchers.anyOf;
  87. import static org.hamcrest.Matchers.equalTo;
  88. import static org.hamcrest.Matchers.greaterThanOrEqualTo;
  89. import static org.hamcrest.Matchers.instanceOf;
  90. import static org.hamcrest.Matchers.lessThanOrEqualTo;
  91. public class ContextIndexSearcherTests extends ESTestCase {
  92. public void testIntersectScorerAndRoleBits() throws Exception {
  93. final Directory directory = newDirectory();
  94. IndexWriter iw = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer()).setMergePolicy(NoMergePolicy.INSTANCE));
  95. Document document = new Document();
  96. document.add(new StringField("field1", "value1", Field.Store.NO));
  97. document.add(new StringField("field2", "value1", Field.Store.NO));
  98. iw.addDocument(document);
  99. document = new Document();
  100. document.add(new StringField("field1", "value2", Field.Store.NO));
  101. document.add(new StringField("field2", "value1", Field.Store.NO));
  102. iw.addDocument(document);
  103. document = new Document();
  104. document.add(new StringField("field1", "value3", Field.Store.NO));
  105. document.add(new StringField("field2", "value1", Field.Store.NO));
  106. iw.addDocument(document);
  107. document = new Document();
  108. document.add(new StringField("field1", "value4", Field.Store.NO));
  109. document.add(new StringField("field2", "value1", Field.Store.NO));
  110. iw.addDocument(document);
  111. iw.commit();
  112. iw.deleteDocuments(new Term("field1", "value3"));
  113. iw.close();
  114. DirectoryReader directoryReader = DirectoryReader.open(directory);
  115. IndexSearcher searcher = newSearcher(directoryReader);
  116. Weight weight = searcher.createWeight(
  117. new BoostQuery(new ConstantScoreQuery(new TermQuery(new Term("field2", "value1"))), 3f),
  118. ScoreMode.COMPLETE,
  119. 1f
  120. );
  121. LeafReaderContext leaf = searcher.getIndexReader().leaves().get(0);
  122. CombinedBits bitSet = new CombinedBits(query(leaf, "field1", "value1"), leaf.reader().getLiveDocs());
  123. LeafCollector leafCollector = new LeafBucketCollector() {
  124. Scorable scorer;
  125. @Override
  126. public void setScorer(Scorable scorer) throws IOException {
  127. this.scorer = scorer;
  128. }
  129. @Override
  130. public void collect(int doc, long bucket) throws IOException {
  131. assertThat(doc, equalTo(0));
  132. assertThat(scorer.score(), equalTo(3f));
  133. }
  134. };
  135. intersectScorerAndBitSet(weight.scorer(leaf), bitSet, leafCollector, () -> {});
  136. bitSet = new CombinedBits(query(leaf, "field1", "value2"), leaf.reader().getLiveDocs());
  137. leafCollector = new LeafBucketCollector() {
  138. @Override
  139. public void collect(int doc, long bucket) throws IOException {
  140. assertThat(doc, equalTo(1));
  141. }
  142. };
  143. intersectScorerAndBitSet(weight.scorer(leaf), bitSet, leafCollector, () -> {});
  144. bitSet = new CombinedBits(query(leaf, "field1", "value3"), leaf.reader().getLiveDocs());
  145. leafCollector = new LeafBucketCollector() {
  146. @Override
  147. public void collect(int doc, long bucket) throws IOException {
  148. fail("docId [" + doc + "] should have been deleted");
  149. }
  150. };
  151. intersectScorerAndBitSet(weight.scorer(leaf), bitSet, leafCollector, () -> {});
  152. bitSet = new CombinedBits(query(leaf, "field1", "value4"), leaf.reader().getLiveDocs());
  153. leafCollector = new LeafBucketCollector() {
  154. @Override
  155. public void collect(int doc, long bucket) throws IOException {
  156. assertThat(doc, equalTo(3));
  157. }
  158. };
  159. intersectScorerAndBitSet(weight.scorer(leaf), bitSet, leafCollector, () -> {});
  160. directoryReader.close();
  161. directory.close();
  162. }
  163. private int indexDocs(Directory directory) throws IOException {
  164. try (
  165. RandomIndexWriter iw = new RandomIndexWriter(
  166. random(),
  167. directory,
  168. new IndexWriterConfig().setMergePolicy(NoMergePolicy.INSTANCE)
  169. )
  170. ) {
  171. final int numDocs = randomIntBetween(500, 1000);
  172. for (int i = 0; i < numDocs; i++) {
  173. Document document = new Document();
  174. document.add(new StringField("field", "value", Field.Store.NO));
  175. iw.addDocument(document);
  176. if (rarely()) {
  177. iw.flush();
  178. }
  179. }
  180. return numDocs;
  181. }
  182. }
  183. /**
  184. * Check that knn queries rewrite parallelizes on the number of segments
  185. */
  186. public void testConcurrentRewrite() throws Exception {
  187. ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(randomIntBetween(2, 5));
  188. try (Directory directory = newDirectory()) {
  189. indexDocs(directory);
  190. try (DirectoryReader directoryReader = DirectoryReader.open(directory)) {
  191. ContextIndexSearcher searcher = new ContextIndexSearcher(
  192. directoryReader,
  193. IndexSearcher.getDefaultSimilarity(),
  194. IndexSearcher.getDefaultQueryCache(),
  195. IndexSearcher.getDefaultQueryCachingPolicy(),
  196. randomBoolean(),
  197. executor,
  198. // create as many slices as possible
  199. Integer.MAX_VALUE,
  200. 1
  201. );
  202. int numSegments = directoryReader.getContext().leaves().size();
  203. KnnFloatVectorQuery vectorQuery = new KnnFloatVectorQuery("float_vector", new float[] { 0, 0, 0 }, 10, null);
  204. vectorQuery.rewrite(searcher);
  205. // 1 task gets executed on the caller thread
  206. assertBusy(() -> assertEquals(numSegments - 1, executor.getCompletedTaskCount()));
  207. }
  208. } finally {
  209. terminate(executor);
  210. }
  211. }
  212. /**
  213. * Test that collection starts one task per slice, all offloaded to the separate executor, none executed in the caller thread
  214. */
  215. public void testConcurrentCollection() throws Exception {
  216. ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(randomIntBetween(2, 5));
  217. try (Directory directory = newDirectory()) {
  218. int numDocs = indexDocs(directory);
  219. try (DirectoryReader directoryReader = DirectoryReader.open(directory)) {
  220. ContextIndexSearcher searcher = new ContextIndexSearcher(
  221. directoryReader,
  222. IndexSearcher.getDefaultSimilarity(),
  223. IndexSearcher.getDefaultQueryCache(),
  224. IndexSearcher.getDefaultQueryCachingPolicy(),
  225. randomBoolean(),
  226. executor,
  227. // create as many slices as possible
  228. Integer.MAX_VALUE,
  229. 1
  230. );
  231. Integer totalHits = searcher.search(new MatchAllDocsQuery(), new TotalHitCountCollectorManager(searcher.getSlices()));
  232. assertEquals(numDocs, totalHits.intValue());
  233. int numExpectedTasks = ContextIndexSearcher.computeSlices(searcher.getIndexReader().leaves(), Integer.MAX_VALUE, 1).length;
  234. // check that each slice except for one that executes on the calling thread goes to the executor, no matter the queue size
  235. // or the number of slices
  236. assertBusy(() -> assertEquals(numExpectedTasks - 1, executor.getCompletedTaskCount()));
  237. }
  238. } finally {
  239. terminate(executor);
  240. }
  241. }
  242. public void testContextIndexSearcherSparseNoDeletions() throws IOException {
  243. doTestContextIndexSearcher(true, false);
  244. }
  245. public void testContextIndexSearcherDenseNoDeletions() throws IOException {
  246. doTestContextIndexSearcher(false, false);
  247. }
  248. public void testContextIndexSearcherSparseWithDeletions() throws IOException {
  249. doTestContextIndexSearcher(true, true);
  250. }
  251. public void testContextIndexSearcherDenseWithDeletions() throws IOException {
  252. doTestContextIndexSearcher(false, true);
  253. }
  254. public void doTestContextIndexSearcher(boolean sparse, boolean deletions) throws IOException {
  255. Directory dir = newDirectory();
  256. IndexWriter w = new IndexWriter(dir, newIndexWriterConfig(null));
  257. Document doc = new Document();
  258. StringField allowedField = new StringField("allowed", "yes", Field.Store.NO);
  259. doc.add(allowedField);
  260. StringField fooField = new StringField("foo", "bar", Field.Store.NO);
  261. doc.add(fooField);
  262. StringField deleteField = new StringField("delete", "no", Field.Store.NO);
  263. doc.add(deleteField);
  264. IntPoint pointField = new IntPoint("point", 1, 2);
  265. doc.add(pointField);
  266. w.addDocument(doc);
  267. if (deletions) {
  268. // add a document that matches foo:bar but will be deleted
  269. deleteField.setStringValue("yes");
  270. w.addDocument(doc);
  271. deleteField.setStringValue("no");
  272. }
  273. allowedField.setStringValue("no");
  274. w.addDocument(doc);
  275. if (sparse) {
  276. for (int i = 0; i < 1000; ++i) {
  277. w.addDocument(doc);
  278. }
  279. w.forceMerge(1);
  280. }
  281. w.deleteDocuments(new Term("delete", "yes"));
  282. IndexSettings settings = IndexSettingsModule.newIndexSettings("_index", Settings.EMPTY);
  283. DirectoryReader reader = ElasticsearchDirectoryReader.wrap(DirectoryReader.open(w), new ShardId(settings.getIndex(), 0));
  284. BitsetFilterCache cache = new BitsetFilterCache(settings, BitsetFilterCache.Listener.NOOP);
  285. Query roleQuery = new TermQuery(new Term("allowed", "yes"));
  286. BitSet bitSet = cache.getBitSetProducer(roleQuery).getBitSet(reader.leaves().get(0));
  287. if (sparse) {
  288. assertThat(bitSet, instanceOf(SparseFixedBitSet.class));
  289. } else {
  290. assertThat(bitSet, anyOf(instanceOf(FixedBitSet.class), instanceOf(MatchAllBitSet.class)));
  291. }
  292. DocumentSubsetDirectoryReader filteredReader = new DocumentSubsetDirectoryReader(reader, cache, roleQuery);
  293. ContextIndexSearcher searcher = new ContextIndexSearcher(
  294. filteredReader,
  295. IndexSearcher.getDefaultSimilarity(),
  296. IndexSearcher.getDefaultQueryCache(),
  297. IndexSearcher.getDefaultQueryCachingPolicy(),
  298. true
  299. );
  300. for (LeafReaderContext context : searcher.getIndexReader().leaves()) {
  301. assertThat(context.reader(), instanceOf(SequentialStoredFieldsLeafReader.class));
  302. SequentialStoredFieldsLeafReader lf = (SequentialStoredFieldsLeafReader) context.reader();
  303. assertNotNull(lf.getSequentialStoredFieldsReader());
  304. }
  305. // Assert wrapping
  306. assertEquals(ExitableDirectoryReader.class, searcher.getIndexReader().getClass());
  307. for (LeafReaderContext lrc : searcher.getIndexReader().leaves()) {
  308. assertEquals(ExitableLeafReader.class, lrc.reader().getClass());
  309. assertNotEquals(ExitableTerms.class, lrc.reader().terms("foo").getClass());
  310. assertNotEquals(ExitablePointValues.class, lrc.reader().getPointValues("point").getClass());
  311. }
  312. searcher.addQueryCancellation(() -> {});
  313. for (LeafReaderContext lrc : searcher.getIndexReader().leaves()) {
  314. assertEquals(ExitableTerms.class, lrc.reader().terms("foo").getClass());
  315. assertEquals(ExitablePointValues.class, lrc.reader().getPointValues("point").getClass());
  316. }
  317. // Searching a non-existing term will trigger a null scorer
  318. assertEquals(0, searcher.count(new TermQuery(new Term("non_existing_field", "non_existing_value"))));
  319. assertEquals(1, searcher.count(new TermQuery(new Term("foo", "bar"))));
  320. // make sure scorers are created only once, see #1725
  321. assertEquals(1, searcher.count(new CreateScorerOnceQuery(new MatchAllDocsQuery())));
  322. TopDocs topDocs = searcher.search(new BoostQuery(new ConstantScoreQuery(new TermQuery(new Term("foo", "bar"))), 3f), 1);
  323. assertEquals(1, topDocs.totalHits.value());
  324. assertEquals(1, topDocs.scoreDocs.length);
  325. assertEquals(3f, topDocs.scoreDocs[0].score, 0);
  326. IOUtils.close(reader, w, dir);
  327. }
  328. public void testComputeSlices() throws IOException {
  329. Directory dir = newDirectory();
  330. RandomIndexWriter w = new RandomIndexWriter(random(), dir);
  331. int numDocs = rarely() ? randomIntBetween(0, 1000) : randomIntBetween(1000, 25000);
  332. Document doc = new Document();
  333. for (int i = 0; i < numDocs; i++) {
  334. w.addDocument(doc);
  335. }
  336. DirectoryReader reader = w.getReader();
  337. List<LeafReaderContext> contexts = reader.leaves();
  338. int iter = randomIntBetween(16, 64);
  339. for (int i = 0; i < iter; i++) {
  340. int numThreads = randomIntBetween(1, 16);
  341. LeafSlice[] slices = ContextIndexSearcher.computeSlices(contexts, numThreads, 1);
  342. assertSlices(slices, numDocs, numThreads);
  343. }
  344. // expect exception for numThreads < 1
  345. int numThreads = randomIntBetween(-16, 0);
  346. IllegalArgumentException ex = expectThrows(
  347. IllegalArgumentException.class,
  348. () -> ContextIndexSearcher.computeSlices(contexts, numThreads, 1)
  349. );
  350. assertThat(ex.getMessage(), equalTo("maxSliceNum must be >= 1 (got " + numThreads + ")"));
  351. IOUtils.close(reader, w, dir);
  352. }
  353. private static void assertSlices(LeafSlice[] slices, int numDocs, int numThreads) {
  354. // checks that the number of slices is not bigger than the number of available threads
  355. // and each slice contains at least 10% of the data (which means the max number of slices is 10)
  356. int sumDocs = 0;
  357. assertThat(slices.length, lessThanOrEqualTo(numThreads));
  358. for (LeafSlice slice : slices) {
  359. int sliceDocs = slice.getMaxDocs();
  360. assertThat(sliceDocs, greaterThanOrEqualTo((int) (0.1 * numDocs)));
  361. sumDocs += sliceDocs;
  362. }
  363. assertThat(sumDocs, equalTo(numDocs));
  364. }
  365. public void testClearQueryCancellations() throws IOException {
  366. Directory dir = newDirectory();
  367. RandomIndexWriter w = new RandomIndexWriter(random(), dir);
  368. w.addDocument(new Document());
  369. DirectoryReader reader = w.getReader();
  370. ContextIndexSearcher searcher = new ContextIndexSearcher(
  371. reader,
  372. IndexSearcher.getDefaultSimilarity(),
  373. IndexSearcher.getDefaultQueryCache(),
  374. IndexSearcher.getDefaultQueryCachingPolicy(),
  375. true
  376. );
  377. assertFalse(searcher.hasCancellations());
  378. searcher.addQueryCancellation(() -> {});
  379. assertTrue(searcher.hasCancellations());
  380. searcher.clearQueryCancellations();
  381. assertFalse(searcher.hasCancellations());
  382. IOUtils.close(reader, w, dir);
  383. }
  384. public void testExitableTermsMinAndMax() throws IOException {
  385. Directory dir = newDirectory();
  386. IndexWriter w = new IndexWriter(dir, newIndexWriterConfig(null));
  387. Document doc = new Document();
  388. StringField fooField = new StringField("foo", "bar", Field.Store.NO);
  389. doc.add(fooField);
  390. w.addDocument(doc);
  391. w.flush();
  392. DirectoryReader directoryReader = DirectoryReader.open(w);
  393. for (LeafReaderContext lfc : directoryReader.leaves()) {
  394. Terms terms = lfc.reader().terms("foo");
  395. FilterLeafReader.FilterTerms filterTerms = new ExitableTerms(terms, new ExitableDirectoryReader.QueryCancellation() {
  396. @Override
  397. public boolean isEnabled() {
  398. return false;
  399. }
  400. @Override
  401. public void checkCancelled() {
  402. }
  403. }) {
  404. @Override
  405. public TermsEnum iterator() {
  406. fail("Retrieving min and max should retrieve values from block tree instead of iterating");
  407. return null;
  408. }
  409. };
  410. assertEquals("bar", filterTerms.getMin().utf8ToString());
  411. assertEquals("bar", filterTerms.getMax().utf8ToString());
  412. }
  413. w.close();
  414. directoryReader.close();
  415. dir.close();
  416. }
  417. public void testReduceIsCalledOnTimeout() throws IOException {
  418. try (Directory dir = newDirectory()) {
  419. indexDocs(dir);
  420. ThreadPoolExecutor executor = null;
  421. try (DirectoryReader directoryReader = DirectoryReader.open(dir)) {
  422. if (randomBoolean()) {
  423. executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(randomIntBetween(2, 5));
  424. }
  425. ContextIndexSearcher contextIndexSearcher = new ContextIndexSearcher(
  426. directoryReader,
  427. IndexSearcher.getDefaultSimilarity(),
  428. IndexSearcher.getDefaultQueryCache(),
  429. IndexSearcher.getDefaultQueryCachingPolicy(),
  430. true,
  431. executor,
  432. executor == null ? -1 : executor.getMaximumPoolSize(),
  433. 1
  434. );
  435. boolean[] called = new boolean[1];
  436. CollectorManager<Collector, Void> manager = new CollectorManager<>() {
  437. @Override
  438. public Collector newCollector() {
  439. return BucketCollector.NO_OP_COLLECTOR;
  440. }
  441. @Override
  442. public Void reduce(Collection<Collector> collectors) {
  443. called[0] = true;
  444. return null;
  445. }
  446. };
  447. contextIndexSearcher.search(new TestQuery() {
  448. @Override
  449. public Query rewrite(IndexSearcher indexSearcher) throws IOException {
  450. if (randomBoolean()) {
  451. contextIndexSearcher.throwTimeExceededException();
  452. }
  453. return super.rewrite(indexSearcher);
  454. }
  455. @Override
  456. public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) {
  457. if (randomBoolean()) {
  458. contextIndexSearcher.throwTimeExceededException();
  459. }
  460. return new ConstantScoreWeight(this, boost) {
  461. @Override
  462. public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
  463. contextIndexSearcher.throwTimeExceededException();
  464. Scorer scorer = new ConstantScoreScorer(
  465. score(),
  466. scoreMode,
  467. DocIdSetIterator.all(context.reader().maxDoc())
  468. );
  469. return new DefaultScorerSupplier(scorer);
  470. }
  471. @Override
  472. public boolean isCacheable(LeafReaderContext ctx) {
  473. return false;
  474. }
  475. };
  476. }
  477. }, manager);
  478. assertTrue(contextIndexSearcher.timeExceeded());
  479. assertThat(called[0], equalTo(true));
  480. } finally {
  481. if (executor != null) {
  482. terminate(executor);
  483. }
  484. }
  485. }
  486. }
  487. public void testTimeoutOnRewriteStandalone() throws IOException {
  488. try (Directory dir = newDirectory()) {
  489. indexDocs(dir);
  490. ThreadPoolExecutor executor = null;
  491. try (DirectoryReader directoryReader = DirectoryReader.open(dir)) {
  492. if (randomBoolean()) {
  493. executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(randomIntBetween(2, 5));
  494. }
  495. ContextIndexSearcher contextIndexSearcher = new ContextIndexSearcher(
  496. directoryReader,
  497. IndexSearcher.getDefaultSimilarity(),
  498. IndexSearcher.getDefaultQueryCache(),
  499. IndexSearcher.getDefaultQueryCachingPolicy(),
  500. true,
  501. executor,
  502. executor == null ? -1 : executor.getMaximumPoolSize(),
  503. 1
  504. );
  505. TestQuery testQuery = new TestQuery() {
  506. @Override
  507. public Query rewrite(IndexSearcher indexSearcher) {
  508. contextIndexSearcher.throwTimeExceededException();
  509. assert false;
  510. return null;
  511. }
  512. };
  513. Query rewrite = contextIndexSearcher.rewrite(testQuery);
  514. assertThat(rewrite, instanceOf(MatchNoDocsQuery.class));
  515. assertEquals("MatchNoDocsQuery(\"rewrite timed out\")", rewrite.toString());
  516. assertTrue(contextIndexSearcher.timeExceeded());
  517. } finally {
  518. if (executor != null) {
  519. terminate(executor);
  520. }
  521. }
  522. }
  523. }
  524. public void testTimeoutOnRewriteDuringSearch() throws IOException {
  525. try (Directory dir = newDirectory()) {
  526. indexDocs(dir);
  527. ThreadPoolExecutor executor = null;
  528. try (DirectoryReader directoryReader = DirectoryReader.open(dir)) {
  529. if (randomBoolean()) {
  530. executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(randomIntBetween(2, 5));
  531. }
  532. ContextIndexSearcher contextIndexSearcher = new ContextIndexSearcher(
  533. directoryReader,
  534. IndexSearcher.getDefaultSimilarity(),
  535. IndexSearcher.getDefaultQueryCache(),
  536. IndexSearcher.getDefaultQueryCachingPolicy(),
  537. true,
  538. executor,
  539. executor == null ? -1 : executor.getMaximumPoolSize(),
  540. 1
  541. );
  542. TestQuery testQuery = new TestQuery() {
  543. @Override
  544. public Query rewrite(IndexSearcher indexSearcher) {
  545. contextIndexSearcher.throwTimeExceededException();
  546. assert false;
  547. return null;
  548. }
  549. };
  550. Integer hitCount = contextIndexSearcher.search(
  551. testQuery,
  552. new TotalHitCountCollectorManager(contextIndexSearcher.getSlices())
  553. );
  554. assertEquals(0, hitCount.intValue());
  555. assertTrue(contextIndexSearcher.timeExceeded());
  556. } finally {
  557. if (executor != null) {
  558. terminate(executor);
  559. }
  560. }
  561. }
  562. }
  563. private static class TestQuery extends Query {
  564. @Override
  565. public String toString(String field) {
  566. return "query";
  567. }
  568. @Override
  569. public void visit(QueryVisitor visitor) {
  570. visitor.visitLeaf(this);
  571. }
  572. @Override
  573. public boolean equals(Object o) {
  574. return sameClassAs(o);
  575. }
  576. @Override
  577. public int hashCode() {
  578. return classHash();
  579. }
  580. }
  581. private SparseFixedBitSet query(LeafReaderContext leaf, String field, String value) throws IOException {
  582. SparseFixedBitSet sparseFixedBitSet = new SparseFixedBitSet(leaf.reader().maxDoc());
  583. TermsEnum tenum = leaf.reader().terms(field).iterator();
  584. while (tenum.next().utf8ToString().equals(value) == false) {
  585. }
  586. PostingsEnum penum = tenum.postings(null);
  587. sparseFixedBitSet.or(penum);
  588. return sparseFixedBitSet;
  589. }
  590. public static class DocumentSubsetDirectoryReader extends FilterDirectoryReader {
  591. private final BitsetFilterCache bitsetFilterCache;
  592. private final Query roleQuery;
  593. public DocumentSubsetDirectoryReader(DirectoryReader in, BitsetFilterCache bitsetFilterCache, Query roleQuery) throws IOException {
  594. super(in, new SubReaderWrapper() {
  595. @Override
  596. public LeafReader wrap(LeafReader reader) {
  597. try {
  598. return new DocumentSubsetReader(reader, bitsetFilterCache, roleQuery);
  599. } catch (Exception e) {
  600. throw ExceptionsHelper.convertToElastic(e);
  601. }
  602. }
  603. });
  604. this.bitsetFilterCache = bitsetFilterCache;
  605. this.roleQuery = roleQuery;
  606. }
  607. @Override
  608. protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException {
  609. return new DocumentSubsetDirectoryReader(in, bitsetFilterCache, roleQuery);
  610. }
  611. @Override
  612. public CacheHelper getReaderCacheHelper() {
  613. return in.getReaderCacheHelper();
  614. }
  615. }
  616. private static class DocumentSubsetReader extends SequentialStoredFieldsLeafReader {
  617. private final BitSet roleQueryBits;
  618. private final int numDocs;
  619. /**
  620. * <p>Construct a FilterLeafReader based on the specified base reader.
  621. * <p>Note that base reader is closed if this FilterLeafReader is closed.</p>
  622. *
  623. * @param in specified base reader.
  624. */
  625. DocumentSubsetReader(LeafReader in, BitsetFilterCache bitsetFilterCache, Query roleQuery) throws IOException {
  626. super(in);
  627. this.roleQueryBits = bitsetFilterCache.getBitSetProducer(roleQuery).getBitSet(in.getContext());
  628. this.numDocs = computeNumDocs(in, roleQueryBits);
  629. }
  630. @Override
  631. public CacheHelper getCoreCacheHelper() {
  632. return in.getCoreCacheHelper();
  633. }
  634. @Override
  635. public CacheHelper getReaderCacheHelper() {
  636. // Not delegated since we change the live docs
  637. return null;
  638. }
  639. @Override
  640. public int numDocs() {
  641. return numDocs;
  642. }
  643. @Override
  644. public Bits getLiveDocs() {
  645. final Bits actualLiveDocs = in.getLiveDocs();
  646. if (roleQueryBits == null) {
  647. return new Bits.MatchNoBits(in.maxDoc());
  648. } else if (actualLiveDocs == null) {
  649. return roleQueryBits;
  650. } else {
  651. // apply deletes when needed:
  652. return new CombinedBits(roleQueryBits, actualLiveDocs);
  653. }
  654. }
  655. @Override
  656. protected StoredFieldsReader doGetSequentialStoredFieldsReader(StoredFieldsReader reader) {
  657. return reader;
  658. }
  659. private static int computeNumDocs(LeafReader reader, BitSet roleQueryBits) {
  660. final Bits liveDocs = reader.getLiveDocs();
  661. if (roleQueryBits == null) {
  662. return 0;
  663. } else if (liveDocs == null) {
  664. // slow
  665. return roleQueryBits.cardinality();
  666. } else {
  667. // very slow, but necessary in order to be correct
  668. int numDocs = 0;
  669. DocIdSetIterator it = new BitSetIterator(roleQueryBits, 0L); // we don't use the cost
  670. try {
  671. for (int doc = it.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = it.nextDoc()) {
  672. if (liveDocs.get(doc)) {
  673. numDocs++;
  674. }
  675. }
  676. return numDocs;
  677. } catch (IOException e) {
  678. throw new UncheckedIOException(e);
  679. }
  680. }
  681. }
  682. }
  683. private static class CreateScorerOnceWeight extends Weight {
  684. private final Weight weight;
  685. private final Set<Object> seenLeaves = Collections.newSetFromMap(new IdentityHashMap<>());
  686. CreateScorerOnceWeight(Weight weight) {
  687. super(weight.getQuery());
  688. this.weight = weight;
  689. }
  690. @Override
  691. public Explanation explain(LeafReaderContext context, int doc) throws IOException {
  692. return weight.explain(context, doc);
  693. }
  694. @Override
  695. public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
  696. assertTrue(seenLeaves.add(context.reader().getCoreCacheHelper().getKey()));
  697. return weight.scorerSupplier(context);
  698. }
  699. @Override
  700. public boolean isCacheable(LeafReaderContext ctx) {
  701. return true;
  702. }
  703. }
  704. private static class CreateScorerOnceQuery extends Query {
  705. private final Query query;
  706. CreateScorerOnceQuery(Query query) {
  707. this.query = query;
  708. }
  709. @Override
  710. public String toString(String field) {
  711. return query.toString(field);
  712. }
  713. @Override
  714. public Query rewrite(IndexSearcher searcher) throws IOException {
  715. Query queryRewritten = query.rewrite(searcher);
  716. if (query != queryRewritten) {
  717. return new CreateScorerOnceQuery(queryRewritten);
  718. }
  719. return super.rewrite(searcher);
  720. }
  721. @Override
  722. public Weight createWeight(IndexSearcher searcher, org.apache.lucene.search.ScoreMode scoreMode, float boost) throws IOException {
  723. return new CreateScorerOnceWeight(query.createWeight(searcher, scoreMode, boost));
  724. }
  725. @Override
  726. public boolean equals(Object obj) {
  727. return sameClassAs(obj) && query.equals(((CreateScorerOnceQuery) obj).query);
  728. }
  729. @Override
  730. public int hashCode() {
  731. return 31 * classHash() + query.hashCode();
  732. }
  733. @Override
  734. public void visit(QueryVisitor visitor) {
  735. visitor.visitLeaf(this);
  736. }
  737. }
  738. }