blob: ca37892e2312d4bb00ed6e5e7439cc40555e41a7 [file] [log] [blame]
/*
* 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.
*/
package org.apache.lucene.index;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.MockDirectoryWrapper;
import org.apache.lucene.store.MockDirectoryWrapper.FakeIOException;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TestUtil;
public class TestDirectoryReaderReopen extends LuceneTestCase {
public void testReopen() throws Exception {
final Directory dir1 = newDirectory();
createIndex(random(), dir1, false);
performDefaultTests(new TestReopen() {
@Override
protected void modifyIndex(int i) throws IOException {
TestDirectoryReaderReopen.modifyIndex(i, dir1);
}
@Override
protected DirectoryReader openReader() throws IOException {
return DirectoryReader.open(dir1);
}
});
dir1.close();
final Directory dir2 = newDirectory();
createIndex(random(), dir2, true);
performDefaultTests(new TestReopen() {
@Override
protected void modifyIndex(int i) throws IOException {
TestDirectoryReaderReopen.modifyIndex(i, dir2);
}
@Override
protected DirectoryReader openReader() throws IOException {
return DirectoryReader.open(dir2);
}
});
dir2.close();
}
// LUCENE-1228: IndexWriter.commit() does not update the index version
// populate an index in iterations.
// at the end of every iteration, commit the index and reopen/recreate the reader.
// in each iteration verify the work of previous iteration.
// try this once with reopen once recreate, on both RAMDir and FSDir.
public void testCommitReopen () throws IOException {
Directory dir = newDirectory();
doTestReopenWithCommit(random(), dir, true);
dir.close();
}
public void testCommitRecreate () throws IOException {
Directory dir = newDirectory();
doTestReopenWithCommit(random(), dir, false);
dir.close();
}
private void doTestReopenWithCommit (Random random, Directory dir, boolean withReopen) throws IOException {
IndexWriter iwriter = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random))
.setOpenMode(OpenMode.CREATE)
.setMergeScheduler(new SerialMergeScheduler())
.setMergePolicy(newLogMergePolicy()));
iwriter.commit();
DirectoryReader reader = DirectoryReader.open(dir);
try {
int M = 3;
FieldType customType = new FieldType(TextField.TYPE_STORED);
customType.setTokenized(false);
FieldType customType2 = new FieldType(TextField.TYPE_STORED);
customType2.setTokenized(false);
customType2.setOmitNorms(true);
FieldType customType3 = new FieldType();
customType3.setStored(true);
for (int i=0; i<4; i++) {
for (int j=0; j<M; j++) {
Document doc = new Document();
doc.add(newField("id", i+"_"+j, customType));
doc.add(newField("id2", i+"_"+j, customType2));
doc.add(newField("id3", i+"_"+j, customType3));
iwriter.addDocument(doc);
if (i>0) {
int k = i-1;
int n = j + k*M;
Document prevItereationDoc = reader.document(n);
assertNotNull(prevItereationDoc);
String id = prevItereationDoc.get("id");
assertEquals(k+"_"+j, id);
}
}
iwriter.commit();
if (withReopen) {
// reopen
DirectoryReader r2 = DirectoryReader.openIfChanged(reader);
if (r2 != null) {
reader.close();
reader = r2;
}
} else {
// recreate
reader.close();
reader = DirectoryReader.open(dir);
}
}
} finally {
iwriter.close();
reader.close();
}
}
private void performDefaultTests(TestReopen test) throws Exception {
DirectoryReader index1 = test.openReader();
DirectoryReader index2 = test.openReader();
TestDirectoryReader.assertIndexEquals(index1, index2);
// verify that reopen() does not return a new reader instance
// in case the index has no changes
ReaderCouple couple = refreshReader(index2, false);
assertTrue(couple.refreshedReader == index2);
couple = refreshReader(index2, test, 0, true);
index1.close();
index1 = couple.newReader;
DirectoryReader index2_refreshed = couple.refreshedReader;
index2.close();
// test if refreshed reader and newly opened reader return equal results
TestDirectoryReader.assertIndexEquals(index1, index2_refreshed);
index2_refreshed.close();
assertReaderClosed(index2, true);
assertReaderClosed(index2_refreshed, true);
index2 = test.openReader();
for (int i = 1; i < 4; i++) {
index1.close();
couple = refreshReader(index2, test, i, true);
// refresh DirectoryReader
index2.close();
index2 = couple.refreshedReader;
index1 = couple.newReader;
TestDirectoryReader.assertIndexEquals(index1, index2);
}
index1.close();
index2.close();
assertReaderClosed(index1, true);
assertReaderClosed(index2, true);
}
public void testThreadSafety() throws Exception {
final Directory dir = newDirectory();
// NOTE: this also controls the number of threads!
final int n = TestUtil.nextInt(random(), 20, 40);
IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())));
for (int i = 0; i < n; i++) {
writer.addDocument(createDocument(i, 3));
}
writer.forceMerge(1);
writer.close();
final TestReopen test = new TestReopen() {
@Override
protected void modifyIndex(int i) throws IOException {
IndexWriter modifier = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
modifier.addDocument(createDocument(n + i, 6));
modifier.close();
}
@Override
protected DirectoryReader openReader() throws IOException {
return DirectoryReader.open(dir);
}
};
final List<ReaderCouple> readers = Collections.synchronizedList(new ArrayList<ReaderCouple>());
DirectoryReader firstReader = DirectoryReader.open(dir);
DirectoryReader reader = firstReader;
ReaderThread[] threads = new ReaderThread[n];
final Set<DirectoryReader> readersToClose = Collections.synchronizedSet(new HashSet<DirectoryReader>());
for (int i = 0; i < n; i++) {
if (i % 2 == 0) {
DirectoryReader refreshed = DirectoryReader.openIfChanged(reader);
if (refreshed != null) {
readersToClose.add(reader);
reader = refreshed;
}
}
final DirectoryReader r = reader;
final int index = i;
ReaderThreadTask task;
if (i < 4 || (i >=10 && i < 14) || i > 18) {
task = new ReaderThreadTask() {
@Override
public void run() throws Exception {
Random rnd = LuceneTestCase.random();
while (!stopped) {
if (index % 2 == 0) {
// refresh reader synchronized
ReaderCouple c = (refreshReader(r, test, index, true));
readersToClose.add(c.newReader);
readersToClose.add(c.refreshedReader);
readers.add(c);
// prevent too many readers
break;
} else {
// not synchronized
DirectoryReader refreshed = DirectoryReader.openIfChanged(r);
if (refreshed == null) {
refreshed = r;
}
IndexSearcher searcher = newSearcher(refreshed);
ScoreDoc[] hits = searcher.search(
new TermQuery(new Term("field1", "a" + rnd.nextInt(refreshed.maxDoc()))),
1000).scoreDocs;
if (hits.length > 0) {
searcher.doc(hits[0].doc);
}
if (refreshed != r) {
refreshed.close();
}
}
synchronized(this) {
wait(TestUtil.nextInt(random(), 1, 100));
}
}
}
};
} else {
task = new ReaderThreadTask() {
@Override
public void run() throws Exception {
Random rnd = LuceneTestCase.random();
while (!stopped) {
int numReaders = readers.size();
if (numReaders > 0) {
ReaderCouple c = readers.get(rnd.nextInt(numReaders));
TestDirectoryReader.assertIndexEquals(c.newReader, c.refreshedReader);
}
synchronized(this) {
wait(TestUtil.nextInt(random(), 1, 100));
}
}
}
};
}
threads[i] = new ReaderThread(task);
threads[i].start();
}
synchronized(this) {
wait(1000);
}
for (int i = 0; i < n; i++) {
if (threads[i] != null) {
threads[i].stopThread();
}
}
for (int i = 0; i < n; i++) {
if (threads[i] != null) {
threads[i].join();
if (threads[i].error != null) {
String msg = "Error occurred in thread " + threads[i].getName() + ":\n" + threads[i].error.getMessage();
fail(msg);
}
}
}
for (final DirectoryReader readerToClose : readersToClose) {
readerToClose.close();
}
firstReader.close();
reader.close();
for (final DirectoryReader readerToClose : readersToClose) {
assertReaderClosed(readerToClose, true);
}
assertReaderClosed(reader, true);
assertReaderClosed(firstReader, true);
dir.close();
}
private static class ReaderCouple {
ReaderCouple(DirectoryReader r1, DirectoryReader r2) {
newReader = r1;
refreshedReader = r2;
}
DirectoryReader newReader;
DirectoryReader refreshedReader;
}
abstract static class ReaderThreadTask {
protected volatile boolean stopped;
public void stop() {
this.stopped = true;
}
public abstract void run() throws Exception;
}
private static class ReaderThread extends Thread {
ReaderThreadTask task;
Throwable error;
ReaderThread(ReaderThreadTask task) {
this.task = task;
}
public void stopThread() {
this.task.stop();
}
@Override
public void run() {
try {
this.task.run();
} catch (Throwable r) {
r.printStackTrace(System.out);
this.error = r;
}
}
}
private Object createReaderMutex = new Object();
private ReaderCouple refreshReader(DirectoryReader reader, boolean hasChanges) throws IOException {
return refreshReader(reader, null, -1, hasChanges);
}
ReaderCouple refreshReader(DirectoryReader reader, TestReopen test, int modify, boolean hasChanges) throws IOException {
synchronized (createReaderMutex) {
DirectoryReader r = null;
if (test != null) {
test.modifyIndex(modify);
r = test.openReader();
}
DirectoryReader refreshed = null;
try {
refreshed = DirectoryReader.openIfChanged(reader);
if (refreshed == null) {
refreshed = reader;
}
} finally {
if (refreshed == null && r != null) {
// Hit exception -- close opened reader
r.close();
}
}
if (hasChanges) {
if (refreshed == reader) {
fail("No new DirectoryReader instance created during refresh.");
}
} else {
if (refreshed != reader) {
fail("New DirectoryReader instance created during refresh even though index had no changes.");
}
}
return new ReaderCouple(r, refreshed);
}
}
public static void createIndex(Random random, Directory dir, boolean multiSegment) throws IOException {
IndexWriter w = new IndexWriter(dir, LuceneTestCase.newIndexWriterConfig(random, new MockAnalyzer(random))
.setMergePolicy(new LogDocMergePolicy()));
for (int i = 0; i < 100; i++) {
w.addDocument(createDocument(i, 4));
if (multiSegment && (i % 10) == 0) {
w.commit();
}
}
if (!multiSegment) {
w.forceMerge(1);
}
w.close();
DirectoryReader r = DirectoryReader.open(dir);
if (multiSegment) {
assertTrue(r.leaves().size() > 1);
} else {
assertTrue(r.leaves().size() == 1);
}
r.close();
}
public static Document createDocument(int n, int numFields) {
StringBuilder sb = new StringBuilder();
Document doc = new Document();
sb.append("a");
sb.append(n);
FieldType customType2 = new FieldType(TextField.TYPE_STORED);
customType2.setTokenized(false);
customType2.setOmitNorms(true);
FieldType customType3 = new FieldType();
customType3.setStored(true);
doc.add(new TextField("field1", sb.toString(), Field.Store.YES));
doc.add(new Field("fielda", sb.toString(), customType2));
doc.add(new Field("fieldb", sb.toString(), customType3));
sb.append(" b");
sb.append(n);
for (int i = 1; i < numFields; i++) {
doc.add(new TextField("field" + (i+1), sb.toString(), Field.Store.YES));
}
return doc;
}
static void modifyIndex(int i, Directory dir) throws IOException {
switch (i) {
case 0: {
if (VERBOSE) {
System.out.println("TEST: modify index");
}
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
w.deleteDocuments(new Term("field2", "a11"));
w.deleteDocuments(new Term("field2", "b30"));
w.close();
break;
}
case 1: {
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
w.forceMerge(1);
w.close();
break;
}
case 2: {
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
w.addDocument(createDocument(101, 4));
w.forceMerge(1);
w.addDocument(createDocument(102, 4));
w.addDocument(createDocument(103, 4));
w.close();
break;
}
case 3: {
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
w.addDocument(createDocument(101, 4));
w.close();
break;
}
}
}
static void assertReaderClosed(IndexReader reader, boolean checkSubReaders) {
assertEquals(0, reader.getRefCount());
if (checkSubReaders && reader instanceof CompositeReader) {
// we cannot use reader context here, as reader is
// already closed and calling getTopReaderContext() throws AlreadyClosed!
List<? extends IndexReader> subReaders = ((CompositeReader) reader).getSequentialSubReaders();
for (final IndexReader r : subReaders) {
assertReaderClosed(r, checkSubReaders);
}
}
}
abstract static class TestReopen {
protected abstract DirectoryReader openReader() throws IOException;
protected abstract void modifyIndex(int i) throws IOException;
}
static class KeepAllCommits extends IndexDeletionPolicy {
@Override
public void onInit(List<? extends IndexCommit> commits) {
}
@Override
public void onCommit(List<? extends IndexCommit> commits) {
}
}
public void testReopenOnCommit() throws Throwable {
Directory dir = newDirectory();
IndexWriter writer = new IndexWriter(
dir,
newIndexWriterConfig(new MockAnalyzer(random()))
.setIndexDeletionPolicy(new KeepAllCommits())
.setMaxBufferedDocs(-1)
.setMergePolicy(newLogMergePolicy(10))
);
for(int i=0;i<4;i++) {
Document doc = new Document();
doc.add(newStringField("id", ""+i, Field.Store.NO));
writer.addDocument(doc);
Map<String,String> data = new HashMap<>();
data.put("index", i+"");
writer.setLiveCommitData(data.entrySet());
writer.commit();
}
for(int i=0;i<4;i++) {
writer.deleteDocuments(new Term("id", ""+i));
Map<String,String> data = new HashMap<>();
data.put("index", (4+i)+"");
writer.setLiveCommitData(data.entrySet());
writer.commit();
}
writer.close();
DirectoryReader r = DirectoryReader.open(dir);
assertEquals(0, r.numDocs());
Collection<IndexCommit> commits = DirectoryReader.listCommits(dir);
for (final IndexCommit commit : commits) {
DirectoryReader r2 = DirectoryReader.openIfChanged(r, commit);
assertNotNull(r2);
assertTrue(r2 != r);
final Map<String,String> s = commit.getUserData();
final int v;
if (s.size() == 0) {
// First commit created by IW
v = -1;
} else {
v = Integer.parseInt(s.get("index"));
}
if (v < 4) {
assertEquals(1+v, r2.numDocs());
} else {
assertEquals(7-v, r2.numDocs());
}
r.close();
r = r2;
}
r.close();
dir.close();
}
public void testOpenIfChangedNRTToCommit() throws Exception {
Directory dir = newDirectory();
// Can't use RIW because it randomly commits:
IndexWriter w = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())));
Document doc = new Document();
doc.add(newStringField("field", "value", Field.Store.NO));
w.addDocument(doc);
w.commit();
List<IndexCommit> commits = DirectoryReader.listCommits(dir);
assertEquals(1, commits.size());
w.addDocument(doc);
DirectoryReader r = DirectoryReader.open(w);
assertEquals(2, r.numDocs());
IndexReader r2 = DirectoryReader.openIfChanged(r, commits.get(0));
assertNotNull(r2);
r.close();
assertEquals(1, r2.numDocs());
w.close();
r2.close();
dir.close();
}
public void testOverDecRefDuringReopen() throws Exception {
MockDirectoryWrapper dir = newMockDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
iwc.setCodec(TestUtil.getDefaultCodec());
IndexWriter w = new IndexWriter(dir, iwc);
Document doc = new Document();
doc.add(newStringField("id", "id", Field.Store.NO));
w.addDocument(doc);
doc = new Document();
doc.add(newStringField("id", "id2", Field.Store.NO));
w.addDocument(doc);
w.commit();
// Open reader w/ one segment w/ 2 docs:
DirectoryReader r = DirectoryReader.open(dir);
// Delete 1 doc from the segment:
//System.out.println("TEST: now delete");
w.deleteDocuments(new Term("id", "id"));
//System.out.println("TEST: now commit");
w.commit();
// Fail when reopen tries to open the live docs file:
dir.failOn(new MockDirectoryWrapper.Failure() {
boolean failed;
@Override
public void eval(MockDirectoryWrapper dir) throws IOException {
if (failed) {
return;
}
//System.out.println("failOn: ");
//new Throwable().printStackTrace(System.out);
if (callStackContainsAnyOf("readLiveDocs")) {
if (VERBOSE) {
System.out.println("TEST: now fail; exc:");
new Throwable().printStackTrace(System.out);
}
failed = true;
throw new FakeIOException();
}
}
});
// Now reopen:
//System.out.println("TEST: now reopen");
expectThrows(FakeIOException.class, () -> {
DirectoryReader.openIfChanged(r);
});
IndexSearcher s = newSearcher(r);
assertEquals(1, s.count(new TermQuery(new Term("id", "id"))));
r.close();
w.close();
dir.close();
}
public void testNPEAfterInvalidReindex1() throws Exception {
Directory dir = new RAMDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(NoMergePolicy.INSTANCE));
Document doc = new Document();
doc.add(newStringField("id", "id", Field.Store.NO));
w.addDocument(doc);
doc = new Document();
doc.add(newStringField("id", "id2", Field.Store.NO));
w.addDocument(doc);
w.deleteDocuments(new Term("id", "id"));
w.commit();
w.close();
// Open reader w/ one segment w/ 2 docs, 1 deleted:
DirectoryReader r = DirectoryReader.open(dir);
// Blow away the index:
for(String fileName : dir.listAll()) {
dir.deleteFile(fileName);
}
w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
doc = new Document();
doc.add(newStringField("id", "id", Field.Store.NO));
doc.add(new NumericDocValuesField("ndv", 13));
w.addDocument(doc);
doc = new Document();
doc.add(newStringField("id", "id2", Field.Store.NO));
w.addDocument(doc);
w.commit();
doc = new Document();
doc.add(newStringField("id", "id2", Field.Store.NO));
w.addDocument(doc);
w.updateNumericDocValue(new Term("id", "id"), "ndv", 17L);
w.commit();
w.close();
expectThrows(IllegalStateException.class, () -> {
DirectoryReader.openIfChanged(r);
});
r.close();
w.close();
dir.close();
}
public void testNPEAfterInvalidReindex2() throws Exception {
Directory dir = new RAMDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(NoMergePolicy.INSTANCE));
Document doc = new Document();
doc.add(newStringField("id", "id", Field.Store.NO));
w.addDocument(doc);
doc = new Document();
doc.add(newStringField("id", "id2", Field.Store.NO));
w.addDocument(doc);
w.deleteDocuments(new Term("id", "id"));
w.commit();
w.close();
// Open reader w/ one segment w/ 2 docs, 1 deleted:
DirectoryReader r = DirectoryReader.open(dir);
// Blow away the index:
for(String name : dir.listAll()) {
dir.deleteFile(name);
}
w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
doc = new Document();
doc.add(newStringField("id", "id", Field.Store.NO));
doc.add(new NumericDocValuesField("ndv", 13));
w.addDocument(doc);
w.commit();
doc = new Document();
doc.add(newStringField("id", "id2", Field.Store.NO));
w.addDocument(doc);
w.commit();
w.close();
expectThrows(IllegalStateException.class, () -> {
DirectoryReader.openIfChanged(r);
});
r.close();
dir.close();
}
/** test reopening backwards from a non-NRT reader (with document deletes) */
public void testNRTMdeletes() throws Exception {
Directory dir = newDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy());
iwc.setIndexDeletionPolicy(snapshotter);
IndexWriter writer = new IndexWriter(dir, iwc);
writer.commit(); // make sure all index metadata is written out
Document doc = new Document();
doc.add(new StringField("key", "value1", Field.Store.YES));
writer.addDocument(doc);
doc = new Document();
doc.add(new StringField("key", "value2", Field.Store.YES));
writer.addDocument(doc);
writer.commit();
IndexCommit ic1 = snapshotter.snapshot();
doc = new Document();
doc.add(new StringField("key", "value3", Field.Store.YES));
writer.updateDocument(new Term("key", "value1"), doc);
writer.commit();
IndexCommit ic2 = snapshotter.snapshot();
DirectoryReader latest = DirectoryReader.open(ic2);
assertEquals(2, latest.leaves().size());
// This reader will be used for searching against commit point 1
DirectoryReader oldest = DirectoryReader.openIfChanged(latest, ic1);
assertEquals(1, oldest.leaves().size());
// sharing same core
assertSame(latest.leaves().get(0).reader().getCoreCacheHelper().getKey(),
oldest.leaves().get(0).reader().getCoreCacheHelper().getKey());
latest.close();
oldest.close();
snapshotter.release(ic1);
snapshotter.release(ic2);
writer.close();
dir.close();
}
/** test reopening backwards from an NRT reader (with document deletes) */
public void testNRTMdeletes2() throws Exception {
Directory dir = newDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy());
iwc.setIndexDeletionPolicy(snapshotter);
IndexWriter writer = new IndexWriter(dir, iwc);
writer.commit(); // make sure all index metadata is written out
Document doc = new Document();
doc.add(new StringField("key", "value1", Field.Store.YES));
writer.addDocument(doc);
doc = new Document();
doc.add(new StringField("key", "value2", Field.Store.YES));
writer.addDocument(doc);
writer.commit();
IndexCommit ic1 = snapshotter.snapshot();
doc = new Document();
doc.add(new StringField("key", "value3", Field.Store.YES));
writer.updateDocument(new Term("key", "value1"), doc);
DirectoryReader latest = DirectoryReader.open(writer);
assertEquals(2, latest.leaves().size());
// This reader will be used for searching against commit point 1
DirectoryReader oldest = DirectoryReader.openIfChanged(latest, ic1);
// This reader should not see the deletion:
assertEquals(2, oldest.numDocs());
assertFalse(oldest.hasDeletions());
snapshotter.release(ic1);
assertEquals(1, oldest.leaves().size());
// sharing same core
assertSame(latest.leaves().get(0).reader().getCoreCacheHelper().getKey(),
oldest.leaves().get(0).reader().getCoreCacheHelper().getKey());
latest.close();
oldest.close();
writer.close();
dir.close();
}
/** test reopening backwards from a non-NRT reader with DV updates */
public void testNRTMupdates() throws Exception {
Directory dir = newDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy());
iwc.setIndexDeletionPolicy(snapshotter);
IndexWriter writer = new IndexWriter(dir, iwc);
writer.commit(); // make sure all index metadata is written out
Document doc = new Document();
doc.add(new StringField("key", "value1", Field.Store.YES));
doc.add(new NumericDocValuesField("dv", 1));
writer.addDocument(doc);
writer.commit();
IndexCommit ic1 = snapshotter.snapshot();
writer.updateNumericDocValue(new Term("key", "value1"), "dv", 2);
writer.commit();
IndexCommit ic2 = snapshotter.snapshot();
DirectoryReader latest = DirectoryReader.open(ic2);
assertEquals(1, latest.leaves().size());
// This reader will be used for searching against commit point 1
DirectoryReader oldest = DirectoryReader.openIfChanged(latest, ic1);
assertEquals(1, oldest.leaves().size());
// sharing same core
assertSame(latest.leaves().get(0).reader().getCoreCacheHelper().getKey(),
oldest.leaves().get(0).reader().getCoreCacheHelper().getKey());
NumericDocValues values = getOnlyLeafReader(oldest).getNumericDocValues("dv");
assertEquals(0, values.nextDoc());
assertEquals(1, values.longValue());
values = getOnlyLeafReader(latest).getNumericDocValues("dv");
assertEquals(0, values.nextDoc());
assertEquals(2, values.longValue());
latest.close();
oldest.close();
snapshotter.release(ic1);
snapshotter.release(ic2);
writer.close();
dir.close();
}
/** test reopening backwards from an NRT reader with DV updates */
public void testNRTMupdates2() throws Exception {
Directory dir = newDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy());
iwc.setIndexDeletionPolicy(snapshotter);
IndexWriter writer = new IndexWriter(dir, iwc);
writer.commit(); // make sure all index metadata is written out
Document doc = new Document();
doc.add(new StringField("key", "value1", Field.Store.YES));
doc.add(new NumericDocValuesField("dv", 1));
writer.addDocument(doc);
writer.commit();
IndexCommit ic1 = snapshotter.snapshot();
writer.updateNumericDocValue(new Term("key", "value1"), "dv", 2);
DirectoryReader latest = DirectoryReader.open(writer);
assertEquals(1, latest.leaves().size());
// This reader will be used for searching against commit point 1
DirectoryReader oldest = DirectoryReader.openIfChanged(latest, ic1);
assertEquals(1, oldest.leaves().size());
// sharing same core
assertSame(latest.leaves().get(0).reader().getCoreCacheHelper().getKey(),
oldest.leaves().get(0).reader().getCoreCacheHelper().getKey());
NumericDocValues values = getOnlyLeafReader(oldest).getNumericDocValues("dv");
assertEquals(0, values.nextDoc());
assertEquals(1, values.longValue());
values = getOnlyLeafReader(latest).getNumericDocValues("dv");
assertEquals(0, values.nextDoc());
assertEquals(2, values.longValue());
latest.close();
oldest.close();
snapshotter.release(ic1);
writer.close();
dir.close();
}
// LUCENE-5931: we make a "best effort" to catch this abuse and throw a clear(er)
// exception than what would otherwise look like hard to explain index corruption during searching
public void testDeleteIndexFilesWhileReaderStillOpen() throws Exception {
RAMDirectory dir = new RAMDirectory();
IndexWriter w = new IndexWriter(dir,
new IndexWriterConfig(new MockAnalyzer(random())));
Document doc = new Document();
doc.add(newStringField("field", "value", Field.Store.NO));
w.addDocument(doc);
// Creates single segment index:
w.close();
DirectoryReader r = DirectoryReader.open(dir);
// Abuse: remove all files while reader is open; one is supposed to use IW.deleteAll, or open a new IW with OpenMode.CREATE instead:
for(String file : dir.listAll()) {
dir.deleteFile(file);
}
w = new IndexWriter(dir,
new IndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(NoMergePolicy.INSTANCE));
doc = new Document();
doc.add(newStringField("field", "value", Field.Store.NO));
w.addDocument(doc);
doc = new Document();
doc.add(newStringField("field", "value2", Field.Store.NO));
w.addDocument(doc);
// Writes same segment, this time with two documents:
w.commit();
w.deleteDocuments(new Term("field", "value2"));
w.addDocument(doc);
// Writes another segments file, so openIfChanged sees that the index has in fact changed:
w.close();
expectThrows(IllegalStateException.class, () -> {
DirectoryReader.openIfChanged(r);
});
}
public void testReuseUnchangedLeafReaderOnDVUpdate() throws IOException {
Directory dir = newDirectory();
IndexWriterConfig indexWriterConfig = newIndexWriterConfig();
indexWriterConfig.setMergePolicy(NoMergePolicy.INSTANCE);
IndexWriter writer = new IndexWriter(dir, indexWriterConfig);
Document doc = new Document();
doc.add(new StringField("id", "1", Field.Store.YES));
doc.add(new StringField("version", "1", Field.Store.YES));
doc.add(new NumericDocValuesField("some_docvalue", 2));
writer.addDocument(doc);
doc = new Document();
doc.add(new StringField("id", "2", Field.Store.YES));
doc.add(new StringField("version", "1", Field.Store.YES));
writer.addDocument(doc);
writer.commit();
DirectoryReader reader = DirectoryReader.open(dir);
assertEquals(2, reader.numDocs());
assertEquals(2, reader.maxDoc());
assertEquals(0, reader.numDeletedDocs());
doc = new Document();
doc.add(new StringField("id", "1", Field.Store.YES));
doc.add(new StringField("version", "2", Field.Store.YES));
writer.updateDocValues(new Term("id", "1"), new NumericDocValuesField("some_docvalue", 1));
writer.commit();
DirectoryReader newReader = DirectoryReader.openIfChanged(reader);
assertNotSame(newReader, reader);
reader.close();
reader = newReader;
assertEquals(2, reader.numDocs());
assertEquals(2, reader.maxDoc());
assertEquals(0, reader.numDeletedDocs());
doc = new Document();
doc.add(new StringField("id", "3", Field.Store.YES));
doc.add(new StringField("version", "3", Field.Store.YES));
writer.updateDocument(new Term("id", "3"), doc);
writer.commit();
newReader = DirectoryReader.openIfChanged(reader);
assertNotSame(newReader, reader);
assertEquals(2, newReader.getSequentialSubReaders().size());
assertEquals(1, reader.getSequentialSubReaders().size());
assertSame(reader.getSequentialSubReaders().get(0), newReader.getSequentialSubReaders().get(0));
reader.close();
reader = newReader;
assertEquals(3, reader.numDocs());
assertEquals(3, reader.maxDoc());
assertEquals(0, reader.numDeletedDocs());
IOUtils.close(reader, writer, dir);
}
}