Index: src/test/org/apache/lucene/search/TestSearchHitsWithDeletions.java
===================================================================
--- src/test/org/apache/lucene/search/TestSearchHitsWithDeletions.java (revision 0)
+++ src/test/org/apache/lucene/search/TestSearchHitsWithDeletions.java (revision 0)
@@ -0,0 +1,180 @@
+package org.apache.lucene.search;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ConcurrentModificationException;
+
+import junit.framework.TestCase;
+
+import org.apache.lucene.analysis.WhitespaceAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.Hits;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+
+/**
+ * Test Hits searches with interleaved deletions.
+ *
+ * See {@link http://issues.apache.org/jira/browse/LUCENE-1096}.
+ */
+public class TestSearchHitsWithDeletions extends TestCase {
+
+ private static boolean VERBOSE = false;
+ private static final String TEXT_FIELD = "text";
+ private static final int N = 16100;
+
+ private static Directory directory;
+
+ public void setUp() throws Exception {
+ // Create an index writer.
+ directory = new RAMDirectory();
+ IndexWriter writer = new IndexWriter(directory, new WhitespaceAnalyzer(), true);
+ for (int i=0; i
Note: Deleting matching documents concurrently with traversing
+ * the hits, might, when deleting hits that were not yet retrieved, decrease
+ * {@link #length()}. In such case,
+ * {@link java.util.ConcurrentModificationException ConcurrentModificationException}
+ * is thrown when accessing hit n ≥ current_{@link #length()}
+ * (but n < {@link #length()}_at_start).
*/
public final class Hits {
private Weight weight;
@@ -45,12 +52,20 @@
private HitDoc last; // tail of LRU cache
private int numDocs = 0; // number cached
private int maxDocs = 200; // max to cache
+
+ private int nDeletions; // # deleted docs in the index.
+ private int lengthAtStart; // this is the number apps usually count on (although deletions can bring it down).
+ private int nDeletedHits = 0; // # of already collected hits that were meanwhile deleted.
+ boolean debugCheckedForDeletions = false; // for test purposes.
+
Hits(Searcher s, Query q, Filter f) throws IOException {
weight = q.weight(s);
searcher = s;
filter = f;
+ nDeletions = countDeletions(s);
getMoreDocs(50); // retrieve 100 initially
+ lengthAtStart = length;
}
Hits(Searcher s, Query q, Filter f, Sort o) throws IOException {
@@ -58,9 +73,20 @@
searcher = s;
filter = f;
sort = o;
+ nDeletions = countDeletions(s);
getMoreDocs(50); // retrieve 100 initially
+ lengthAtStart = length;
}
+ // count # deletions, return -1 if unknown.
+ private int countDeletions(Searcher s) throws IOException {
+ int cnt = -1;
+ if (s instanceof IndexSearcher) {
+ cnt = s.maxDoc() - ((IndexSearcher) s).getIndexReader().numDocs();
+ }
+ return cnt;
+ }
+
/**
* Tries to add new documents to hitDocs.
* Ensures that the hit numbered min has been retrieved.
@@ -72,6 +98,7 @@
int n = min * 2; // double # retrieved
TopDocs topDocs = (sort == null) ? searcher.search(weight, filter, n) : searcher.search(weight, filter, n, sort);
+
length = topDocs.totalHits;
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
@@ -81,11 +108,36 @@
scoreNorm = 1.0f / topDocs.getMaxScore();
}
+ int start = hitDocs.size() - nDeletedHits;
+
+ // any new deletions?
+ int nDels2 = countDeletions(searcher);
+ debugCheckedForDeletions = false;
+ if (nDeletions < 0 || nDels2 > nDeletions) {
+ // either we cannot count deletions, or some "previously valid hits" might have been deleted, so find exact start point
+ nDeletedHits = 0;
+ debugCheckedForDeletions = true;
+ int i2 = 0;
+ for (int i1=0; i1