1 package org.apache.lucene.index;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import org.apache.lucene.search.Similarity;
21 import org.apache.lucene.analysis.MockAnalyzer;
22 import org.apache.lucene.document.Document;
23 import org.apache.lucene.document.Field;
24 import org.apache.lucene.store.Directory;
25 import org.apache.lucene.store.LockObtainFailedException;
26 import org.apache.lucene.util.LuceneTestCase;
29 * Tests cloning multiple types of readers, modifying the deletedDocs and norms
30 * and verifies copy on write semantics of the deletedDocs and norms is
31 * implemented properly
33 public class TestIndexReaderClone extends LuceneTestCase {
35 public void testCloneReadOnlySegmentReader() throws Exception {
36 final Directory dir1 = newDirectory();
38 TestIndexReaderReopen.createIndex(random, dir1, false);
39 IndexReader reader = IndexReader.open(dir1, false);
40 IndexReader readOnlyReader = reader.clone(true);
41 if (!isReadOnly(readOnlyReader)) {
42 fail("reader isn't read only");
44 if (deleteWorked(1, readOnlyReader)) {
45 fail("deleting from the original should not have worked");
48 readOnlyReader.close();
52 // open non-readOnly reader1, clone to non-readOnly
53 // reader2, make sure we can change reader2
54 public void testCloneNoChangesStillReadOnly() throws Exception {
55 final Directory dir1 = newDirectory();
57 TestIndexReaderReopen.createIndex(random, dir1, true);
58 IndexReader r1 = IndexReader.open(dir1, false);
59 IndexReader r2 = r1.clone(false);
60 if (!deleteWorked(1, r2)) {
61 fail("deleting from the cloned should have worked");
68 // open non-readOnly reader1, clone to non-readOnly
69 // reader2, make sure we can change reader1
70 public void testCloneWriteToOrig() throws Exception {
71 final Directory dir1 = newDirectory();
73 TestIndexReaderReopen.createIndex(random, dir1, true);
74 IndexReader r1 = IndexReader.open(dir1, false);
75 IndexReader r2 = r1.clone(false);
76 if (!deleteWorked(1, r1)) {
77 fail("deleting from the original should have worked");
84 // open non-readOnly reader1, clone to non-readOnly
85 // reader2, make sure we can change reader2
86 public void testCloneWriteToClone() throws Exception {
87 final Directory dir1 = newDirectory();
89 TestIndexReaderReopen.createIndex(random, dir1, true);
90 IndexReader r1 = IndexReader.open(dir1, false);
91 IndexReader r2 = r1.clone(false);
92 if (!deleteWorked(1, r2)) {
93 fail("deleting from the original should have worked");
95 // should fail because reader1 holds the write lock
96 assertTrue("first reader should not be able to delete", !deleteWorked(1, r1));
98 // should fail because we are now stale (reader1
100 assertTrue("first reader should not be able to delete", !deleteWorked(1, r1));
106 // create single-segment index, open non-readOnly
107 // SegmentReader, add docs, reopen to multireader, then do
109 public void testReopenSegmentReaderToMultiReader() throws Exception {
110 final Directory dir1 = newDirectory();
112 TestIndexReaderReopen.createIndex(random, dir1, false);
113 IndexReader reader1 = IndexReader.open(dir1, false);
115 TestIndexReaderReopen.modifyIndex(5, dir1);
117 IndexReader reader2 = IndexReader.openIfChanged(reader1);
118 assertNotNull(reader2);
119 assertTrue(reader1 != reader2);
121 assertTrue(deleteWorked(1, reader2));
127 // open non-readOnly reader1, clone to readOnly reader2
128 public void testCloneWriteableToReadOnly() throws Exception {
129 final Directory dir1 = newDirectory();
131 TestIndexReaderReopen.createIndex(random, dir1, true);
132 IndexReader reader = IndexReader.open(dir1, false);
133 IndexReader readOnlyReader = reader.clone(true);
134 if (!isReadOnly(readOnlyReader)) {
135 fail("reader isn't read only");
137 if (deleteWorked(1, readOnlyReader)) {
138 fail("deleting from the original should not have worked");
140 // this readonly reader shouldn't have a write lock
141 if (readOnlyReader.hasChanges) {
142 fail("readOnlyReader has a write lock");
145 readOnlyReader.close();
149 // open non-readOnly reader1, reopen to readOnly reader2
150 public void testReopenWriteableToReadOnly() throws Exception {
151 final Directory dir1 = newDirectory();
153 TestIndexReaderReopen.createIndex(random, dir1, true);
154 IndexReader reader = IndexReader.open(dir1, false);
155 final int docCount = reader.numDocs();
156 assertTrue(deleteWorked(1, reader));
157 assertEquals(docCount-1, reader.numDocs());
159 IndexReader readOnlyReader = IndexReader.openIfChanged(reader, true);
160 assertNotNull(readOnlyReader);
161 if (!isReadOnly(readOnlyReader)) {
162 fail("reader isn't read only");
164 assertFalse(deleteWorked(1, readOnlyReader));
165 assertEquals(docCount-1, readOnlyReader.numDocs());
167 readOnlyReader.close();
171 // open readOnly reader1, clone to non-readOnly reader2
172 public void testCloneReadOnlyToWriteable() throws Exception {
173 final Directory dir1 = newDirectory();
175 TestIndexReaderReopen.createIndex(random, dir1, true);
176 IndexReader reader1 = IndexReader.open(dir1, true);
178 IndexReader reader2 = reader1.clone(false);
179 if (isReadOnly(reader2)) {
180 fail("reader should not be read only");
182 assertFalse("deleting from the original reader should not have worked", deleteWorked(1, reader1));
183 // this readonly reader shouldn't yet have a write lock
184 if (reader2.hasChanges) {
185 fail("cloned reader should not have write lock");
187 assertTrue("deleting from the cloned reader should have worked", deleteWorked(1, reader2));
193 // open non-readOnly reader1 on multi-segment index, then
194 // fully merge the index, then clone to readOnly reader2
195 public void testReadOnlyCloneAfterFullMerge() throws Exception {
196 final Directory dir1 = newDirectory();
198 TestIndexReaderReopen.createIndex(random, dir1, true);
199 IndexReader reader1 = IndexReader.open(dir1, false);
200 IndexWriter w = new IndexWriter(dir1, newIndexWriterConfig(
201 TEST_VERSION_CURRENT, new MockAnalyzer(random)));
204 IndexReader reader2 = reader1.clone(true);
205 assertTrue(isReadOnly(reader2));
211 private static boolean deleteWorked(int doc, IndexReader r) {
212 boolean exception = false;
214 // trying to delete from the original reader should throw an exception
215 r.deleteDocument(doc);
216 } catch (Exception ex) {
222 public void testCloneReadOnlyDirectoryReader() throws Exception {
223 final Directory dir1 = newDirectory();
225 TestIndexReaderReopen.createIndex(random, dir1, true);
226 IndexReader reader = IndexReader.open(dir1, false);
227 IndexReader readOnlyReader = reader.clone(true);
228 if (!isReadOnly(readOnlyReader)) {
229 fail("reader isn't read only");
232 readOnlyReader.close();
236 public static boolean isReadOnly(IndexReader r) {
237 if (r instanceof ReadOnlySegmentReader
238 || r instanceof ReadOnlyDirectoryReader)
243 public void testParallelReader() throws Exception {
244 final Directory dir1 = newDirectory();
245 TestIndexReaderReopen.createIndex(random, dir1, true);
246 final Directory dir2 = newDirectory();
247 TestIndexReaderReopen.createIndex(random, dir2, true);
248 IndexReader r1 = IndexReader.open(dir1, false);
249 IndexReader r2 = IndexReader.open(dir2, false);
251 ParallelReader pr1 = new ParallelReader();
255 performDefaultTests(pr1);
262 * 1. Get a norm from the original reader 2. Clone the original reader 3.
263 * Delete a document and set the norm of the cloned reader 4. Verify the norms
264 * are not the same on each reader 5. Verify the doc deleted is only in the
265 * cloned reader 6. Try to delete a document in the original reader, an
266 * exception should be thrown
268 * @param r1 IndexReader to perform tests on
271 private void performDefaultTests(IndexReader r1) throws Exception {
272 float norm1 = Similarity.getDefault().decodeNormValue(r1.norms("field1")[4]);
274 IndexReader pr1Clone = (IndexReader) r1.clone();
275 pr1Clone.deleteDocument(10);
276 pr1Clone.setNorm(4, "field1", 0.5f);
277 assertTrue(Similarity.getDefault().decodeNormValue(r1.norms("field1")[4]) == norm1);
278 assertTrue(Similarity.getDefault().decodeNormValue(pr1Clone.norms("field1")[4]) != norm1);
280 assertTrue(!r1.isDeleted(10));
281 assertTrue(pr1Clone.isDeleted(10));
283 // try to update the original reader, which should throw an exception
285 r1.deleteDocument(11);
286 fail("Tried to delete doc 11 and an exception should have been thrown");
287 } catch (Exception exception) {
293 public void testMixedReaders() throws Exception {
294 final Directory dir1 = newDirectory();
295 TestIndexReaderReopen.createIndex(random, dir1, true);
296 final Directory dir2 = newDirectory();
297 TestIndexReaderReopen.createIndex(random, dir2, true);
298 IndexReader r1 = IndexReader.open(dir1, false);
299 IndexReader r2 = IndexReader.open(dir2, false);
301 MultiReader multiReader = new MultiReader(new IndexReader[] { r1, r2 });
302 performDefaultTests(multiReader);
308 public void testSegmentReaderUndeleteall() throws Exception {
309 final Directory dir1 = newDirectory();
310 TestIndexReaderReopen.createIndex(random, dir1, false);
311 SegmentReader origSegmentReader = SegmentReader.getOnlySegmentReader(dir1);
312 origSegmentReader.deleteDocument(10);
313 assertDelDocsRefCountEquals(1, origSegmentReader);
314 origSegmentReader.undeleteAll();
315 assertNull(origSegmentReader.deletedDocsRef);
316 origSegmentReader.close();
317 // need to test norms?
321 public void testSegmentReaderCloseReferencing() throws Exception {
322 final Directory dir1 = newDirectory();
323 TestIndexReaderReopen.createIndex(random, dir1, false);
324 SegmentReader origSegmentReader = SegmentReader.getOnlySegmentReader(dir1);
325 origSegmentReader.deleteDocument(1);
326 origSegmentReader.setNorm(4, "field1", 0.5f);
328 SegmentReader clonedSegmentReader = (SegmentReader) origSegmentReader
330 assertDelDocsRefCountEquals(2, origSegmentReader);
331 origSegmentReader.close();
332 assertDelDocsRefCountEquals(1, origSegmentReader);
333 // check the norm refs
334 SegmentNorms norm = clonedSegmentReader.norms.get("field1");
335 assertEquals(1, norm.bytesRef().get());
336 clonedSegmentReader.close();
340 public void testSegmentReaderDelDocsReferenceCounting() throws Exception {
341 final Directory dir1 = newDirectory();
342 TestIndexReaderReopen.createIndex(random, dir1, false);
344 IndexReader origReader = IndexReader.open(dir1, false);
345 SegmentReader origSegmentReader = SegmentReader.getOnlySegmentReader(origReader);
346 // deletedDocsRef should be null because nothing has updated yet
347 assertNull(origSegmentReader.deletedDocsRef);
349 // we deleted a document, so there is now a deletedDocs bitvector and a
351 origReader.deleteDocument(1);
352 assertDelDocsRefCountEquals(1, origSegmentReader);
354 // the cloned segmentreader should have 2 references, 1 to itself, and 1 to
355 // the original segmentreader
356 IndexReader clonedReader = (IndexReader) origReader.clone();
357 SegmentReader clonedSegmentReader = SegmentReader.getOnlySegmentReader(clonedReader);
358 assertDelDocsRefCountEquals(2, origSegmentReader);
359 // deleting a document creates a new deletedDocs bitvector, the refs goes to
361 clonedReader.deleteDocument(2);
362 assertDelDocsRefCountEquals(1, origSegmentReader);
363 assertDelDocsRefCountEquals(1, clonedSegmentReader);
365 // make sure the deletedocs objects are different (copy
367 assertTrue(origSegmentReader.deletedDocs != clonedSegmentReader.deletedDocs);
369 assertDocDeleted(origSegmentReader, clonedSegmentReader, 1);
370 assertTrue(!origSegmentReader.isDeleted(2)); // doc 2 should not be deleted
371 // in original segmentreader
372 assertTrue(clonedSegmentReader.isDeleted(2)); // doc 2 should be deleted in
373 // cloned segmentreader
375 // deleting a doc from the original segmentreader should throw an exception
377 origReader.deleteDocument(4);
378 fail("expected exception");
379 } catch (LockObtainFailedException lbfe) {
384 // try closing the original segment reader to see if it affects the
385 // clonedSegmentReader
386 clonedReader.deleteDocument(3);
387 clonedReader.flush();
388 assertDelDocsRefCountEquals(1, clonedSegmentReader);
390 // test a reopened reader
391 IndexReader reopenedReader = IndexReader.openIfChanged(clonedReader);
392 if (reopenedReader == null) {
393 reopenedReader = clonedReader;
395 IndexReader cloneReader2 = (IndexReader) reopenedReader.clone();
396 SegmentReader cloneSegmentReader2 = SegmentReader.getOnlySegmentReader(cloneReader2);
397 assertDelDocsRefCountEquals(2, cloneSegmentReader2);
398 clonedReader.close();
399 reopenedReader.close();
400 cloneReader2.close();
406 public void testCloneWithDeletes() throws Throwable {
407 final Directory dir1 = newDirectory();
408 TestIndexReaderReopen.createIndex(random, dir1, false);
409 IndexReader origReader = IndexReader.open(dir1, false);
410 origReader.deleteDocument(1);
412 IndexReader clonedReader = (IndexReader) origReader.clone();
414 clonedReader.close();
416 IndexReader r = IndexReader.open(dir1, false);
417 assertTrue(r.isDeleted(1));
423 public void testCloneWithSetNorm() throws Throwable {
424 final Directory dir1 = newDirectory();
425 TestIndexReaderReopen.createIndex(random, dir1, false);
426 IndexReader orig = IndexReader.open(dir1, false);
427 orig.setNorm(1, "field1", 17.0f);
428 final byte encoded = Similarity.getDefault().encodeNormValue(17.0f);
429 assertEquals(encoded, orig.norms("field1")[1]);
431 // the cloned segmentreader should have 2 references, 1 to itself, and 1 to
432 // the original segmentreader
433 IndexReader clonedReader = (IndexReader) orig.clone();
435 clonedReader.close();
437 IndexReader r = IndexReader.open(dir1, false);
438 assertEquals(encoded, r.norms("field1")[1]);
443 private void assertDocDeleted(SegmentReader reader, SegmentReader reader2,
445 assertEquals(reader.isDeleted(doc), reader2.isDeleted(doc));
448 private void assertDelDocsRefCountEquals(int refCount, SegmentReader reader) {
449 assertEquals(refCount, reader.deletedDocsRef.get());
452 public void testCloneSubreaders() throws Exception {
453 final Directory dir1 = newDirectory();
455 TestIndexReaderReopen.createIndex(random, dir1, true);
456 IndexReader reader = IndexReader.open(dir1, false);
457 reader.deleteDocument(1); // acquire write lock
458 IndexReader[] subs = reader.getSequentialSubReaders();
459 assert subs.length > 1;
461 IndexReader[] clones = new IndexReader[subs.length];
462 for (int x=0; x < subs.length; x++) {
463 clones[x] = (IndexReader) subs[x].clone();
466 for (int x=0; x < subs.length; x++) {
472 public void testLucene1516Bug() throws Exception {
473 final Directory dir1 = newDirectory();
474 TestIndexReaderReopen.createIndex(random, dir1, false);
475 IndexReader r1 = IndexReader.open(dir1, false);
477 IndexReader r2 = r1.clone(false);
478 r1.deleteDocument(5);
489 public void testCloseStoredFields() throws Exception {
490 final Directory dir = newDirectory();
491 IndexWriter w = new IndexWriter(
493 newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).
494 setMergePolicy(newLogMergePolicy(false))
496 Document doc = new Document();
497 doc.add(newField("field", "yes it's stored", Field.Store.YES, Field.Index.ANALYZED));
500 IndexReader r1 = IndexReader.open(dir, false);
501 IndexReader r2 = r1.clone(false);