LUCENE-5843: IndexWriter now enforces max docs in one index

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1614388 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Michael McCandless 2014-07-29 15:34:44 +00:00
parent ad8aa3826c
commit 858e309c7b
9 changed files with 510 additions and 100 deletions

View File

@ -133,6 +133,12 @@ New Features
checksum against all the bytes), retrieve checksum to validate structure
of footer, this can detect some forms of corruption such as truncation.
(Robert Muir)
* LUCENE-5843: Added IndexWriter.MAX_DOCS which is the maximum number
of documents allowed in a single index, and any operations that add
documents will now throw IllegalStateException if the max count
would be exceeded, instead of silently creating an unusable
index. (Mike McCandless)
API Changes

View File

@ -73,8 +73,8 @@ public abstract class BaseCompositeReader<R extends IndexReader> extends Composi
starts[i] = maxDoc;
final IndexReader r = subReaders[i];
maxDoc += r.maxDoc(); // compute maxDocs
if (maxDoc < 0 /* overflow */) {
throw new IllegalArgumentException("Too many documents, composite IndexReaders cannot exceed " + Integer.MAX_VALUE);
if (maxDoc < 0 /* overflow */ || maxDoc > IndexWriter.getActualMaxDocs()) {
throw new IllegalArgumentException("Too many documents, composite IndexReaders cannot exceed " + IndexWriter.getActualMaxDocs());
}
numDocs += r.numDocs(); // compute numDocs
r.registerParentReader(this);

View File

@ -388,7 +388,8 @@ final class DocumentsWriter implements Closeable, Accountable {
final FieldInfos.Builder infos = new FieldInfos.Builder(
writer.globalFieldNumberMap);
state.dwpt = new DocumentsWriterPerThread(writer.newSegmentName(),
directory, config, infoStream, deleteQueue, infos);
directory, config, infoStream, deleteQueue, infos,
writer.pendingNumDocs);
}
}

View File

@ -17,14 +17,12 @@ package org.apache.lucene.index;
* limitations under the License.
*/
import static org.apache.lucene.util.ByteBlockPool.BYTE_BLOCK_MASK;
import static org.apache.lucene.util.ByteBlockPool.BYTE_BLOCK_SIZE;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.codecs.Codec;
@ -43,6 +41,9 @@ import org.apache.lucene.util.IntBlockPool;
import org.apache.lucene.util.MutableBits;
import org.apache.lucene.util.RamUsageEstimator;
import static org.apache.lucene.util.ByteBlockPool.BYTE_BLOCK_MASK;
import static org.apache.lucene.util.ByteBlockPool.BYTE_BLOCK_SIZE;
class DocumentsWriterPerThread {
/**
@ -154,11 +155,11 @@ class DocumentsWriterPerThread {
private final NumberFormat nf = NumberFormat.getInstance(Locale.ROOT);
final Allocator byteBlockAllocator;
final IntBlockPool.Allocator intBlockAllocator;
private final AtomicLong pendingNumDocs;
private final LiveIndexWriterConfig indexWriterConfig;
public DocumentsWriterPerThread(String segmentName, Directory directory, LiveIndexWriterConfig indexWriterConfig, InfoStream infoStream, DocumentsWriterDeleteQueue deleteQueue,
FieldInfos.Builder fieldInfos) throws IOException {
FieldInfos.Builder fieldInfos, AtomicLong pendingNumDocs) throws IOException {
this.directoryOrig = directory;
this.directory = new TrackingDirectoryWrapper(directory);
this.fieldInfos = fieldInfos;
@ -167,6 +168,7 @@ class DocumentsWriterPerThread {
this.codec = indexWriterConfig.getCodec();
this.docState = new DocState(this, infoStream);
this.docState.similarity = indexWriterConfig.getSimilarity();
this.pendingNumDocs = pendingNumDocs;
bytesUsed = Counter.newCounter();
byteBlockAllocator = new DirectTrackingAllocator(bytesUsed);
pendingUpdates = new BufferedUpdates();
@ -207,6 +209,16 @@ class DocumentsWriterPerThread {
return true;
}
/** Anything that will add N docs to the index should reserve first to
* make sure it's allowed. */
private void reserveDoc() {
if (pendingNumDocs.incrementAndGet() > IndexWriter.getActualMaxDocs()) {
// Reserve failed
pendingNumDocs.decrementAndGet();
throw new IllegalStateException("number of documents in the index cannot exceed " + IndexWriter.getActualMaxDocs());
}
}
public void updateDocument(IndexDocument doc, Analyzer analyzer, Term delTerm) throws IOException {
assert testPoint("DocumentsWriterPerThread addDocument start");
assert deleteQueue != null;
@ -216,6 +228,13 @@ class DocumentsWriterPerThread {
if (INFO_VERBOSE && infoStream.isEnabled("DWPT")) {
infoStream.message("DWPT", Thread.currentThread().getName() + " update delTerm=" + delTerm + " docID=" + docState.docID + " seg=" + segmentInfo.name);
}
// Even on exception, the document is still added (but marked
// deleted), so we don't need to un-reserve at that point.
// Aborting exceptions will actually "lose" more than one
// document, so the counter will be "wrong" in that case, but
// it's very hard to fix (we can't easily distinguish aborting
// vs non-aborting exceptions):
reserveDoc();
boolean success = false;
try {
try {
@ -250,6 +269,13 @@ class DocumentsWriterPerThread {
try {
for(IndexDocument doc : docs) {
// Even on exception, the document is still added (but marked
// deleted), so we don't need to un-reserve at that point.
// Aborting exceptions will actually "lose" more than one
// document, so the counter will be "wrong" in that case, but
// it's very hard to fix (we can't easily distinguish aborting
// vs non-aborting exceptions):
reserveDoc();
docState.doc = doc;
docState.docID = numDocsInRAM;
docCount++;

View File

@ -32,11 +32,12 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.codecs.Codec;
@ -194,7 +195,31 @@ import org.apache.lucene.util.Version;
* keeps track of the last non commit checkpoint.
*/
public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
/** Hard limit on maximum number of documents that may be added to the
* index. If you try to add more than this you'll hit {@code IllegalStateException}. */
// We defensively subtract 128 to be well below the lowest
// ArrayUtil.MAX_ARRAY_LENGTH on "typical" JVMs. We don't just use
// ArrayUtil.MAX_ARRAY_LENGTH here because this can vary across JVMs:
public static final int MAX_DOCS = Integer.MAX_VALUE - 128;
// Use package-private instance var to enforce the limit so testing
// can use less electricity:
private static int actualMaxDocs = MAX_DOCS;
/** Used only for testing. */
static void setMaxDocs(int maxDocs) {
if (maxDocs > MAX_DOCS) {
// Cannot go higher than the hard max:
throw new IllegalArgumentException("maxDocs must be <= IndexWriter.MAX_DOCS=" + MAX_DOCS + "; got: " + maxDocs);
}
IndexWriter.actualMaxDocs = maxDocs;
}
static int getActualMaxDocs() {
return IndexWriter.actualMaxDocs;
}
private static final int UNBOUNDED_MAX_MERGE_SEGMENTS = -1;
/**
@ -289,6 +314,13 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
* an infoStream message about how long commit took. */
private long startCommitTime;
/** How many documents are in the index, or are in the process of being
* added (reserved). E.g., operations like addIndexes will first reserve
* the right to add N docs, before they actually change the index,
* much like how hotels place an "authorization hold" on your credit
* card to make sure they can later charge you when you check out. */
final AtomicLong pendingNumDocs = new AtomicLong();
DirectoryReader getReader() throws IOException {
return getReader(true);
}
@ -2437,7 +2469,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
flush(false, true);
List<SegmentCommitInfo> infos = new ArrayList<>();
int totalDocCount = 0;
boolean success = false;
try {
for (Directory dir : dirs) {
@ -2446,6 +2478,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
}
SegmentInfos sis = new SegmentInfos(); // read infos from dir
sis.read(dir);
totalDocCount += sis.totalDocCount();
for (SegmentCommitInfo info : sis) {
assert !infos.contains(info): "dup info dir=" + info.info.dir + " name=" + info.info.name;
@ -2482,6 +2515,9 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
success = false;
try {
ensureOpen();
// Make sure adding the new documents to this index won't
// exceed the limit:
reserveDocs(totalDocCount);
success = true;
} finally {
if (!success) {
@ -2566,6 +2602,10 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
mergeReaders.add(ctx.reader());
}
}
// Make sure adding the new documents to this index won't
// exceed the limit:
reserveDocs(numDocs);
final IOContext context = new IOContext(new MergeInfo(numDocs, -1, true, -1));
@ -3119,6 +3159,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
// it once it's done:
if (!mergingSegments.contains(info)) {
segmentInfos.remove(info);
pendingNumDocs.addAndGet(-info.info.getDocCount());
readerPool.drop(info);
}
}
@ -3491,6 +3532,12 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
// merge:
segmentInfos.applyMergeChanges(merge, dropSegment);
// Now deduct the deleted docs that we just reclaimed from this
// merge:
int delDocCount = merge.totalDocCount - merge.info.info.getDocCount();
assert delDocCount >= 0;
pendingNumDocs.addAndGet(-delDocCount);
if (dropSegment) {
assert !segmentInfos.contains(merge.info);
readerPool.drop(merge.info);
@ -3774,6 +3821,7 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
}
for(SegmentCommitInfo info : result.allDeleted) {
segmentInfos.remove(info);
pendingNumDocs.addAndGet(-info.info.getDocCount());
if (merge.segments.contains(info)) {
mergingSegments.remove(info);
merge.segments.remove(info);
@ -4644,4 +4692,15 @@ public class IndexWriter implements Closeable, TwoPhaseCommit, Accountable {
return false;
}
}
/** Anything that will add N docs to the index should reserve first to
* make sure it's allowed. This will throw {@code
* IllegalStateException} if it's not allowed. */
private void reserveDocs(int numDocs) {
if (pendingNumDocs.addAndGet(numDocs) > actualMaxDocs) {
// Reserve failed
pendingNumDocs.addAndGet(-numDocs);
throw new IllegalStateException("number of documents in the index cannot exceed " + actualMaxDocs);
}
}
}

View File

@ -20,8 +20,6 @@ package org.apache.lucene.index;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.MergeInfo;
import org.apache.lucene.util.FixedBitSet;
import org.apache.lucene.util.SetOnce;
import org.apache.lucene.util.SetOnce.AlreadySetException;
import java.io.IOException;
import java.util.ArrayList;
@ -92,7 +90,7 @@ public abstract class MergePolicy implements java.io.Closeable {
public static class OneMerge {
SegmentCommitInfo info; // used by IndexWriter
SegmentCommitInfo info; // used by IndexWriter
boolean registerDone; // used by IndexWriter
long mergeGen; // used by IndexWriter
boolean isExternal; // used by IndexWriter
@ -109,7 +107,7 @@ public abstract class MergePolicy implements java.io.Closeable {
/** Segments to be merged. */
public final List<SegmentCommitInfo> segments;
/** Number of documents in the merged segment. */
/** Total number of documents in segments to be merged, not accounting for deletions. */
public final int totalDocCount;
boolean aborted;
Throwable error;

View File

@ -1,83 +0,0 @@
package org.apache.lucene.index;
/*
* 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.Arrays;
import org.apache.lucene.document.Document;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TestUtil;
import org.junit.AfterClass;
import org.junit.BeforeClass;
public class Test2BDocs extends LuceneTestCase {
static Directory dir;
@BeforeClass
public static void beforeClass() throws Exception {
dir = newFSDirectory(createTempDir("2Bdocs"));
IndexWriter iw = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
Document doc = new Document();
for (int i = 0; i < 262144; i++) {
iw.addDocument(doc);
}
iw.forceMerge(1);
iw.shutdown();
}
@AfterClass
public static void afterClass() throws Exception {
dir.close();
dir = null;
}
public void testOverflow() throws Exception {
DirectoryReader ir = DirectoryReader.open(dir);
IndexReader subReaders[] = new IndexReader[8192];
Arrays.fill(subReaders, ir);
try {
new MultiReader(subReaders);
fail();
} catch (IllegalArgumentException expected) {
// expected
}
ir.close();
}
public void testExactlyAtLimit() throws Exception {
Directory dir2 = newFSDirectory(createTempDir("2BDocs2"));
IndexWriter iw = new IndexWriter(dir2, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
Document doc = new Document();
for (int i = 0; i < 262143; i++) {
iw.addDocument(doc);
}
iw.shutdown();
DirectoryReader ir = DirectoryReader.open(dir);
DirectoryReader ir2 = DirectoryReader.open(dir2);
IndexReader subReaders[] = new IndexReader[8192];
Arrays.fill(subReaders, ir);
subReaders[subReaders.length-1] = ir2;
MultiReader mr = new MultiReader(subReaders);
assertEquals(Integer.MAX_VALUE, mr.maxDoc());
assertEquals(Integer.MAX_VALUE, mr.numDocs());
ir.close();
ir2.close();
dir2.close();
}
}

View File

@ -0,0 +1,378 @@
package org.apache.lucene.index;
/*
* 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.Arrays;
import java.util.Collections;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.LuceneTestCase.Monster;
import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TimeUnits;
import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;
@SuppressCodecs({ "SimpleText", "Memory", "Direct" })
@TimeoutSuite(millis = 6 * TimeUnits.HOUR)
public class TestIndexWriterMaxDocs extends LuceneTestCase {
@Monster("takes a long time")
public void testExactlyAtTrueLimit() throws Exception {
Directory dir = newFSDirectory(createTempDir("2BDocs3"));
IndexWriter iw = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
Document doc = new Document();
doc.add(newStringField("field", "text", Field.Store.NO));
for (int i = 0; i < IndexWriter.MAX_DOCS; i++) {
iw.addDocument(doc);
/*
if (i%1000000 == 0) {
System.out.println((i/1000000) + " M docs...");
}
*/
}
iw.commit();
// First unoptimized, then optimized:
for(int i=0;i<2;i++) {
DirectoryReader ir = DirectoryReader.open(dir);
assertEquals(IndexWriter.MAX_DOCS, ir.maxDoc());
assertEquals(IndexWriter.MAX_DOCS, ir.numDocs());
IndexSearcher searcher = new IndexSearcher(ir);
TopDocs hits = searcher.search(new TermQuery(new Term("field", "text")), 10);
assertEquals(IndexWriter.MAX_DOCS, hits.totalHits);
// Sort by docID reversed:
hits = searcher.search(new TermQuery(new Term("field", "text")), null, 10, new Sort(new SortField(null, SortField.Type.DOC, true)));
assertEquals(IndexWriter.MAX_DOCS, hits.totalHits);
assertEquals(10, hits.scoreDocs.length);
assertEquals(IndexWriter.MAX_DOCS-1, hits.scoreDocs[0].doc);
ir.close();
iw.forceMerge(1);
}
iw.shutdown();
dir.close();
}
public void testAddDocument() throws Exception {
setIndexWriterMaxDocs(10);
try {
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for(int i=0;i<10;i++) {
w.addDocument(new Document());
}
// 11th document should fail:
try {
w.addDocument(new Document());
fail("didn't hit exception");
} catch (IllegalStateException ise) {
// expected
}
w.close();
dir.close();
} finally {
restoreIndexWriterMaxDocs();
}
}
public void testAddDocuments() throws Exception {
setIndexWriterMaxDocs(10);
try {
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for(int i=0;i<10;i++) {
w.addDocument(new Document());
}
// 11th document should fail:
try {
w.addDocuments(Collections.singletonList(new Document()));
fail("didn't hit exception");
} catch (IllegalStateException ise) {
// expected
}
w.close();
dir.close();
} finally {
restoreIndexWriterMaxDocs();
}
}
public void testUpdateDocument() throws Exception {
setIndexWriterMaxDocs(10);
try {
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for(int i=0;i<10;i++) {
w.addDocument(new Document());
}
// 11th document should fail:
try {
w.updateDocument(new Term("field", "foo"), new Document());
fail("didn't hit exception");
} catch (IllegalStateException ise) {
// expected
}
w.close();
dir.close();
} finally {
restoreIndexWriterMaxDocs();
}
}
public void testUpdateDocuments() throws Exception {
setIndexWriterMaxDocs(10);
try {
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for(int i=0;i<10;i++) {
w.addDocument(new Document());
}
// 11th document should fail:
try {
w.updateDocuments(new Term("field", "foo"), Collections.singletonList(new Document()));
fail("didn't hit exception");
} catch (IllegalStateException ise) {
// expected
}
w.close();
dir.close();
} finally {
restoreIndexWriterMaxDocs();
}
}
public void testReclaimedDeletes() throws Exception {
setIndexWriterMaxDocs(10);
try {
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for(int i=0;i<10;i++) {
Document doc = new Document();
doc.add(newStringField("id", ""+i, Field.Store.NO));
w.addDocument(doc);
}
// Delete 5 of them:
for(int i=0;i<5;i++) {
w.deleteDocuments(new Term("id", ""+i));
}
w.forceMerge(1);
assertEquals(5, w.maxDoc());
// Add 5 more docs
for(int i=0;i<5;i++) {
w.addDocument(new Document());
}
// 11th document should fail:
try {
w.addDocument(new Document());
fail("didn't hit exception");
} catch (IllegalStateException ise) {
// expected
}
w.close();
dir.close();
} finally {
restoreIndexWriterMaxDocs();
}
}
// Tests that 100% deleted segments (which IW "specializes" by dropping entirely) are not mis-counted
public void testReclaimedDeletesWholeSegments() throws Exception {
setIndexWriterMaxDocs(10);
try {
Directory dir = newDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
iwc.setMergePolicy(NoMergePolicy.INSTANCE);
IndexWriter w = new IndexWriter(dir, iwc);
for(int i=0;i<10;i++) {
Document doc = new Document();
doc.add(newStringField("id", ""+i, Field.Store.NO));
w.addDocument(doc);
if (i % 2 == 0) {
// Make a new segment every 2 docs:
w.commit();
}
}
// Delete 5 of them:
for(int i=0;i<5;i++) {
w.deleteDocuments(new Term("id", ""+i));
}
w.forceMerge(1);
assertEquals(5, w.maxDoc());
// Add 5 more docs
for(int i=0;i<5;i++) {
w.addDocument(new Document());
}
// 11th document should fail:
try {
w.addDocument(new Document());
fail("didn't hit exception");
} catch (IllegalStateException ise) {
// expected
}
w.close();
dir.close();
} finally {
restoreIndexWriterMaxDocs();
}
}
public void testAddIndexes() throws Exception {
setIndexWriterMaxDocs(10);
try {
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for(int i=0;i<10;i++) {
w.addDocument(new Document());
}
w.shutdown();
Directory dir2 = newDirectory();
IndexWriter w2 = new IndexWriter(dir2, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
w2.addDocument(new Document());
try {
w2.addIndexes(new Directory[] {dir});
fail("didn't hit exception");
} catch (IllegalStateException ise) {
// expected
}
assertEquals(1, w2.maxDoc());
IndexReader ir = DirectoryReader.open(dir);
try {
w2.addIndexes(new IndexReader[] {ir});
fail("didn't hit exception");
} catch (IllegalStateException ise) {
// expected
}
w2.close();
ir.close();
dir.close();
dir2.close();
} finally {
restoreIndexWriterMaxDocs();
}
}
// Make sure MultiReader lets you search exactly the limit number of docs:
public void testMultiReaderExactLimit() throws Exception {
Directory dir = newDirectory();
Document doc = new Document();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for (int i = 0; i < 100000; i++) {
w.addDocument(doc);
}
w.shutdown();
int remainder = IndexWriter.MAX_DOCS % 100000;
Directory dir2 = newDirectory();
w = new IndexWriter(dir2, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for (int i = 0; i < remainder; i++) {
w.addDocument(doc);
}
w.shutdown();
int copies = IndexWriter.MAX_DOCS / 100000;
DirectoryReader ir = DirectoryReader.open(dir);
DirectoryReader ir2 = DirectoryReader.open(dir2);
IndexReader subReaders[] = new IndexReader[copies+1];
Arrays.fill(subReaders, ir);
subReaders[subReaders.length-1] = ir2;
MultiReader mr = new MultiReader(subReaders);
assertEquals(IndexWriter.MAX_DOCS, mr.maxDoc());
assertEquals(IndexWriter.MAX_DOCS, mr.numDocs());
ir.close();
ir2.close();
dir.close();
dir2.close();
}
// Make sure MultiReader is upset if you exceed the limit
public void testMultiReaderBeyondLimit() throws Exception {
Directory dir = newDirectory();
Document doc = new Document();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for (int i = 0; i < 100000; i++) {
w.addDocument(doc);
}
w.shutdown();
int remainder = IndexWriter.MAX_DOCS % 100000;
// One too many:
remainder++;
Directory dir2 = newDirectory();
w = new IndexWriter(dir2, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
for (int i = 0; i < remainder; i++) {
w.addDocument(doc);
}
w.shutdown();
int copies = IndexWriter.MAX_DOCS / 100000;
DirectoryReader ir = DirectoryReader.open(dir);
DirectoryReader ir2 = DirectoryReader.open(dir2);
IndexReader subReaders[] = new IndexReader[copies+1];
Arrays.fill(subReaders, ir);
subReaders[subReaders.length-1] = ir2;
try {
new MultiReader(subReaders);
fail("didn't hit exception");
} catch (IllegalArgumentException iae) {
// expected
}
ir.close();
ir2.close();
dir.close();
dir2.close();
}
public void testTooLargeMaxDocs() throws Exception {
try {
IndexWriter.setMaxDocs(Integer.MAX_VALUE);
fail("didn't hit exception");
} catch (IllegalArgumentException iae) {
// expected
}
}
}

View File

@ -77,6 +77,7 @@ import org.apache.lucene.index.FieldInfos;
import org.apache.lucene.index.Fields;
import org.apache.lucene.index.IndexReader.ReaderClosedListener;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.LiveIndexWriterConfig;
import org.apache.lucene.index.LogByteSizeMergePolicy;
@ -663,8 +664,34 @@ public abstract class LuceneTestCase extends Assert {
public void tearDown() throws Exception {
parentChainCallRule.teardownCalled = true;
fieldToType.clear();
// Test is supposed to call this itself, but we do this defensively in case it forgot:
restoreIndexWriterMaxDocs();
}
/** Tells {@link IndexWriter} to enforce the specified limit as the maximum number of documents in one index; call
* {@link #restoreIndexWriterMaxDocs} once your test is done. */
public void setIndexWriterMaxDocs(int limit) {
Method m;
try {
m = IndexWriter.class.getDeclaredMethod("setMaxDocs", int.class);
} catch (NoSuchMethodException nsme) {
throw new RuntimeException(nsme);
}
m.setAccessible(true);
try {
m.invoke(IndexWriter.class, limit);
} catch (IllegalAccessException iae) {
throw new RuntimeException(iae);
} catch (InvocationTargetException ite) {
throw new RuntimeException(ite);
}
}
/** Returns the default {@link IndexWriter#MAX_DOCS} limit. */
public void restoreIndexWriterMaxDocs() {
setIndexWriterMaxDocs(IndexWriter.MAX_DOCS);
}
// -----------------------------------------------------------------
// Test facilities and facades for subclasses.
@ -2251,8 +2278,6 @@ public abstract class LuceneTestCase extends Assert {
// numOrds
assertEquals(info, leftValues.getValueCount(), rightValues.getValueCount());
// ords
BytesRef scratchLeft = new BytesRef();
BytesRef scratchRight = new BytesRef();
for (int i = 0; i < leftValues.getValueCount(); i++) {
final BytesRef left = BytesRef.deepCopyOf(leftValues.lookupOrd(i));
final BytesRef right = rightValues.lookupOrd(i);