pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / test / org / apache / lucene / index / TestIndexWriterDelete.java
1 package org.apache.lucene.index;
2
3 /**
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
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  */
19
20 import java.io.IOException;
21 import java.io.Reader;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Random;
26 import java.util.concurrent.atomic.AtomicInteger;
27 import java.util.concurrent.atomic.AtomicBoolean;
28
29 import org.apache.lucene.analysis.Analyzer;
30 import org.apache.lucene.analysis.MockAnalyzer;
31 import org.apache.lucene.analysis.MockTokenizer;
32 import org.apache.lucene.analysis.TokenStream;
33 import org.apache.lucene.document.Document;
34 import org.apache.lucene.document.Field;
35 import org.apache.lucene.search.IndexSearcher;
36 import org.apache.lucene.search.ScoreDoc;
37 import org.apache.lucene.search.TermQuery;
38 import org.apache.lucene.store.Directory;
39 import org.apache.lucene.store.MockDirectoryWrapper;
40 import org.apache.lucene.store.RAMDirectory;
41 import org.apache.lucene.util.LuceneTestCase;
42 import org.apache.lucene.util._TestUtil;
43
44 public class TestIndexWriterDelete extends LuceneTestCase {
45   
46   // test the simple case
47   public void testSimpleCase() throws IOException {
48     String[] keywords = { "1", "2" };
49     String[] unindexed = { "Netherlands", "Italy" };
50     String[] unstored = { "Amsterdam has lots of bridges",
51         "Venice has lots of canals" };
52     String[] text = { "Amsterdam", "Venice" };
53
54     Directory dir = newDirectory();
55     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
56         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDeleteTerms(1));
57
58     for (int i = 0; i < keywords.length; i++) {
59       Document doc = new Document();
60       doc.add(newField("id", keywords[i], Field.Store.YES,
61                         Field.Index.NOT_ANALYZED));
62       doc.add(newField("country", unindexed[i], Field.Store.YES,
63                         Field.Index.NO));
64       doc.add(newField("contents", unstored[i], Field.Store.NO,
65                         Field.Index.ANALYZED));
66       doc
67         .add(newField("city", text[i], Field.Store.YES,
68                        Field.Index.ANALYZED));
69       modifier.addDocument(doc);
70     }
71     modifier.forceMerge(1);
72     modifier.commit();
73
74     Term term = new Term("city", "Amsterdam");
75     int hitCount = getHitCount(dir, term);
76     assertEquals(1, hitCount);
77     modifier.deleteDocuments(term);
78     modifier.commit();
79     hitCount = getHitCount(dir, term);
80     assertEquals(0, hitCount);
81
82     modifier.close();
83     dir.close();
84   }
85
86   // test when delete terms only apply to disk segments
87   public void testNonRAMDelete() throws IOException {
88
89     Directory dir = newDirectory();
90     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
91         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
92         .setMaxBufferedDeleteTerms(2));
93     modifier.setInfoStream(VERBOSE ? System.out : null);
94     int id = 0;
95     int value = 100;
96
97     for (int i = 0; i < 7; i++) {
98       addDoc(modifier, ++id, value);
99     }
100     modifier.commit();
101
102     assertEquals(0, modifier.getNumBufferedDocuments());
103     assertTrue(0 < modifier.getSegmentCount());
104
105     modifier.commit();
106
107     IndexReader reader = IndexReader.open(dir, true);
108     assertEquals(7, reader.numDocs());
109     reader.close();
110
111     modifier.deleteDocuments(new Term("value", String.valueOf(value)));
112
113     modifier.commit();
114
115     reader = IndexReader.open(dir, true);
116     assertEquals(0, reader.numDocs());
117     reader.close();
118     modifier.close();
119     dir.close();
120   }
121
122   public void testMaxBufferedDeletes() throws IOException {
123     Directory dir = newDirectory();
124     IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(
125         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDeleteTerms(1));
126
127     writer.setInfoStream(VERBOSE ? System.out : null);
128     writer.addDocument(new Document());
129     writer.deleteDocuments(new Term("foobar", "1"));
130     writer.deleteDocuments(new Term("foobar", "1"));
131     writer.deleteDocuments(new Term("foobar", "1"));
132     assertEquals(3, writer.getFlushDeletesCount());
133     writer.close();
134     dir.close();
135   }
136
137   // test when delete terms only apply to ram segments
138   public void testRAMDeletes() throws IOException {
139     for(int t=0;t<2;t++) {
140       if (VERBOSE) {
141         System.out.println("TEST: t=" + t);
142       }
143       Directory dir = newDirectory();
144       IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
145           TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(4)
146           .setMaxBufferedDeleteTerms(4));
147       modifier.setInfoStream(VERBOSE ? System.out : null);
148       int id = 0;
149       int value = 100;
150
151       addDoc(modifier, ++id, value);
152       if (0 == t)
153         modifier.deleteDocuments(new Term("value", String.valueOf(value)));
154       else
155         modifier.deleteDocuments(new TermQuery(new Term("value", String.valueOf(value))));
156       addDoc(modifier, ++id, value);
157       if (0 == t) {
158         modifier.deleteDocuments(new Term("value", String.valueOf(value)));
159         assertEquals(2, modifier.getNumBufferedDeleteTerms());
160         assertEquals(1, modifier.getBufferedDeleteTermsSize());
161       }
162       else
163         modifier.deleteDocuments(new TermQuery(new Term("value", String.valueOf(value))));
164
165       addDoc(modifier, ++id, value);
166       assertEquals(0, modifier.getSegmentCount());
167       modifier.commit();
168
169       IndexReader reader = IndexReader.open(dir, true);
170       assertEquals(1, reader.numDocs());
171
172       int hitCount = getHitCount(dir, new Term("id", String.valueOf(id)));
173       assertEquals(1, hitCount);
174       reader.close();
175       modifier.close();
176       dir.close();
177     }
178   }
179
180   // test when delete terms apply to both disk and ram segments
181   public void testBothDeletes() throws IOException {
182     Directory dir = newDirectory();
183     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
184         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(100)
185         .setMaxBufferedDeleteTerms(100));
186
187     int id = 0;
188     int value = 100;
189
190     for (int i = 0; i < 5; i++) {
191       addDoc(modifier, ++id, value);
192     }
193
194     value = 200;
195     for (int i = 0; i < 5; i++) {
196       addDoc(modifier, ++id, value);
197     }
198     modifier.commit();
199
200     for (int i = 0; i < 5; i++) {
201       addDoc(modifier, ++id, value);
202     }
203     modifier.deleteDocuments(new Term("value", String.valueOf(value)));
204
205     modifier.commit();
206
207     IndexReader reader = IndexReader.open(dir, true);
208     assertEquals(5, reader.numDocs());
209     modifier.close();
210     reader.close();
211     dir.close();
212   }
213
214   // test that batched delete terms are flushed together
215   public void testBatchDeletes() throws IOException {
216     Directory dir = newDirectory();
217     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
218         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
219         .setMaxBufferedDeleteTerms(2));
220
221     int id = 0;
222     int value = 100;
223
224     for (int i = 0; i < 7; i++) {
225       addDoc(modifier, ++id, value);
226     }
227     modifier.commit();
228
229     IndexReader reader = IndexReader.open(dir, true);
230     assertEquals(7, reader.numDocs());
231     reader.close();
232       
233     id = 0;
234     modifier.deleteDocuments(new Term("id", String.valueOf(++id)));
235     modifier.deleteDocuments(new Term("id", String.valueOf(++id)));
236
237     modifier.commit();
238
239     reader = IndexReader.open(dir, true);
240     assertEquals(5, reader.numDocs());
241     reader.close();
242
243     Term[] terms = new Term[3];
244     for (int i = 0; i < terms.length; i++) {
245       terms[i] = new Term("id", String.valueOf(++id));
246     }
247     modifier.deleteDocuments(terms);
248     modifier.commit();
249     reader = IndexReader.open(dir, true);
250     assertEquals(2, reader.numDocs());
251     reader.close();
252
253     modifier.close();
254     dir.close();
255   }
256
257   // test deleteAll()
258   public void testDeleteAll() throws IOException {
259     Directory dir = newDirectory();
260     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
261         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
262         .setMaxBufferedDeleteTerms(2));
263
264     int id = 0;
265     int value = 100;
266
267     for (int i = 0; i < 7; i++) {
268       addDoc(modifier, ++id, value);
269     }
270     modifier.commit();
271
272     IndexReader reader = IndexReader.open(dir, true);
273     assertEquals(7, reader.numDocs());
274     reader.close();
275
276     // Add 1 doc (so we will have something buffered)
277     addDoc(modifier, 99, value);
278
279     // Delete all
280     modifier.deleteAll();
281
282     // Delete all shouldn't be on disk yet
283     reader = IndexReader.open(dir, true);
284     assertEquals(7, reader.numDocs());
285     reader.close();
286
287     // Add a doc and update a doc (after the deleteAll, before the commit)
288     addDoc(modifier, 101, value);
289     updateDoc(modifier, 102, value);
290
291     // commit the delete all
292     modifier.commit();
293
294     // Validate there are no docs left
295     reader = IndexReader.open(dir, true);
296     assertEquals(2, reader.numDocs());
297     reader.close();
298
299     modifier.close();
300     dir.close();
301   }
302
303   // test rollback of deleteAll()
304   public void testDeleteAllRollback() throws IOException {
305     Directory dir = newDirectory();
306     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
307         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
308         .setMaxBufferedDeleteTerms(2));
309     
310     int id = 0;
311     int value = 100;
312     
313     for (int i = 0; i < 7; i++) {
314       addDoc(modifier, ++id, value);
315     }
316     modifier.commit();
317     
318     addDoc(modifier, ++id, value);
319
320     IndexReader reader = IndexReader.open(dir, true);
321     assertEquals(7, reader.numDocs());
322     reader.close();
323     
324     // Delete all
325     modifier.deleteAll(); 
326
327     // Roll it back
328     modifier.rollback();
329     modifier.close();
330     
331     // Validate that the docs are still there
332     reader = IndexReader.open(dir, true);
333     assertEquals(7, reader.numDocs());
334     reader.close();
335     
336     dir.close();
337   }
338
339
340   // test deleteAll() w/ near real-time reader
341   public void testDeleteAllNRT() throws IOException {
342     Directory dir = newDirectory();
343     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
344         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
345         .setMaxBufferedDeleteTerms(2));
346     
347     int id = 0;
348     int value = 100;
349     
350     for (int i = 0; i < 7; i++) {
351       addDoc(modifier, ++id, value);
352     }
353     modifier.commit();
354
355     IndexReader reader = modifier.getReader();
356     assertEquals(7, reader.numDocs());
357     reader.close();
358
359     addDoc(modifier, ++id, value);
360     addDoc(modifier, ++id, value);
361     
362     // Delete all
363     modifier.deleteAll(); 
364
365     reader = modifier.getReader();
366     assertEquals(0, reader.numDocs());
367     reader.close();
368     
369
370     // Roll it back
371     modifier.rollback();
372     modifier.close();
373     
374     // Validate that the docs are still there
375     reader = IndexReader.open(dir, true);
376     assertEquals(7, reader.numDocs());
377     reader.close();
378     
379     dir.close();
380   }
381
382
383   private void updateDoc(IndexWriter modifier, int id, int value)
384       throws IOException {
385     Document doc = new Document();
386     doc.add(newField("content", "aaa", Field.Store.NO, Field.Index.ANALYZED));
387     doc.add(newField("id", String.valueOf(id), Field.Store.YES,
388         Field.Index.NOT_ANALYZED));
389     doc.add(newField("value", String.valueOf(value), Field.Store.NO,
390         Field.Index.NOT_ANALYZED));
391     modifier.updateDocument(new Term("id", String.valueOf(id)), doc);
392   }
393
394
395   private void addDoc(IndexWriter modifier, int id, int value)
396       throws IOException {
397     Document doc = new Document();
398     doc.add(newField("content", "aaa", Field.Store.NO, Field.Index.ANALYZED));
399     doc.add(newField("id", String.valueOf(id), Field.Store.YES,
400         Field.Index.NOT_ANALYZED));
401     doc.add(newField("value", String.valueOf(value), Field.Store.NO,
402         Field.Index.NOT_ANALYZED));
403     modifier.addDocument(doc);
404   }
405
406   private int getHitCount(Directory dir, Term term) throws IOException {
407     IndexReader reader = IndexReader.open(dir);
408     IndexSearcher searcher = new IndexSearcher(reader);
409     int hitCount = searcher.search(new TermQuery(term), null, 1000).totalHits;
410     searcher.close();
411     reader.close();
412     return hitCount;
413   }
414
415   public void testDeletesOnDiskFull() throws IOException {
416     doTestOperationsOnDiskFull(false);
417   }
418
419   public void testUpdatesOnDiskFull() throws IOException {
420     doTestOperationsOnDiskFull(true);
421   }
422
423   /**
424    * Make sure if modifier tries to commit but hits disk full that modifier
425    * remains consistent and usable. Similar to TestIndexReader.testDiskFull().
426    */
427   private void doTestOperationsOnDiskFull(boolean updates) throws IOException {
428
429     Term searchTerm = new Term("content", "aaa");
430     int START_COUNT = 157;
431     int END_COUNT = 144;
432
433     // First build up a starting index:
434     MockDirectoryWrapper startDir = newDirectory();
435     // TODO: find the resource leak that only occurs sometimes here.
436     startDir.setNoDeleteOpenFile(false);
437     IndexWriter writer = new IndexWriter(startDir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)));
438     for (int i = 0; i < 157; i++) {
439       Document d = new Document();
440       d.add(newField("id", Integer.toString(i), Field.Store.YES,
441                       Field.Index.NOT_ANALYZED));
442       d.add(newField("content", "aaa " + i, Field.Store.NO,
443                       Field.Index.ANALYZED));
444       writer.addDocument(d);
445     }
446     writer.close();
447
448     long diskUsage = startDir.sizeInBytes();
449     long diskFree = diskUsage + 10;
450
451     IOException err = null;
452
453     boolean done = false;
454
455     // Iterate w/ ever increasing free disk space:
456     while (!done) {
457       if (VERBOSE) {
458         System.out.println("TEST: cycle");
459       }
460       MockDirectoryWrapper dir = new MockDirectoryWrapper(random, new RAMDirectory(startDir));
461       dir.setPreventDoubleWrite(false);
462       IndexWriter modifier = new IndexWriter(dir,
463                                              newIndexWriterConfig(
464                                                                   TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false))
465                                              .setMaxBufferedDocs(1000)
466                                              .setMaxBufferedDeleteTerms(1000)
467                                              .setMergeScheduler(new ConcurrentMergeScheduler()));
468       ((ConcurrentMergeScheduler) modifier.getConfig().getMergeScheduler()).setSuppressExceptions();
469       modifier.setInfoStream(VERBOSE ? System.out : null);
470
471       // For each disk size, first try to commit against
472       // dir that will hit random IOExceptions & disk
473       // full; after, give it infinite disk space & turn
474       // off random IOExceptions & retry w/ same reader:
475       boolean success = false;
476
477       for (int x = 0; x < 2; x++) {
478         if (VERBOSE) {
479           System.out.println("TEST: x=" + x);
480         }
481
482         double rate = 0.1;
483         double diskRatio = ((double)diskFree) / diskUsage;
484         long thisDiskFree;
485         String testName;
486
487         if (0 == x) {
488           thisDiskFree = diskFree;
489           if (diskRatio >= 2.0) {
490             rate /= 2;
491           }
492           if (diskRatio >= 4.0) {
493             rate /= 2;
494           }
495           if (diskRatio >= 6.0) {
496             rate = 0.0;
497           }
498           if (VERBOSE) {
499             System.out.println("\ncycle: " + diskFree + " bytes");
500           }
501           testName = "disk full during reader.close() @ " + thisDiskFree
502             + " bytes";
503         } else {
504           thisDiskFree = 0;
505           rate = 0.0;
506           if (VERBOSE) {
507             System.out.println("\ncycle: same writer: unlimited disk space");
508           }
509           testName = "reader re-use after disk full";
510         }
511
512         dir.setMaxSizeInBytes(thisDiskFree);
513         dir.setRandomIOExceptionRate(rate);
514
515         try {
516           if (0 == x) {
517             int docId = 12;
518             for (int i = 0; i < 13; i++) {
519               if (updates) {
520                 Document d = new Document();
521                 d.add(newField("id", Integer.toString(i), Field.Store.YES,
522                                 Field.Index.NOT_ANALYZED));
523                 d.add(newField("content", "bbb " + i, Field.Store.NO,
524                                 Field.Index.ANALYZED));
525                 modifier.updateDocument(new Term("id", Integer.toString(docId)), d);
526               } else { // deletes
527                 modifier.deleteDocuments(new Term("id", Integer.toString(docId)));
528                 // modifier.setNorm(docId, "contents", (float)2.0);
529               }
530               docId += 12;
531             }
532           }
533           modifier.close();
534           success = true;
535           if (0 == x) {
536             done = true;
537           }
538         }
539         catch (IOException e) {
540           if (VERBOSE) {
541             System.out.println("  hit IOException: " + e);
542             e.printStackTrace(System.out);
543           }
544           err = e;
545           if (1 == x) {
546             e.printStackTrace();
547             fail(testName + " hit IOException after disk space was freed up");
548           }
549         }
550
551         if (!success) {
552           // Must force the close else the writer can have
553           // open files which cause exc in MockRAMDir.close
554           modifier.rollback();
555         }
556
557         // If the close() succeeded, make sure there are
558         // no unreferenced files.
559         if (success) {
560           _TestUtil.checkIndex(dir);
561           TestIndexWriter.assertNoUnreferencedFiles(dir, "after writer.close");
562         }
563
564         // Finally, verify index is not corrupt, and, if
565         // we succeeded, we see all docs changed, and if
566         // we failed, we see either all docs or no docs
567         // changed (transactional semantics):
568         IndexReader newReader = null;
569         try {
570           newReader = IndexReader.open(dir, true);
571         }
572         catch (IOException e) {
573           e.printStackTrace();
574           fail(testName
575                + ":exception when creating IndexReader after disk full during close: "
576                + e);
577         }
578
579         IndexSearcher searcher = newSearcher(newReader);
580         ScoreDoc[] hits = null;
581         try {
582           hits = searcher.search(new TermQuery(searchTerm), null, 1000).scoreDocs;
583         }
584         catch (IOException e) {
585           e.printStackTrace();
586           fail(testName + ": exception when searching: " + e);
587         }
588         int result2 = hits.length;
589         if (success) {
590           if (x == 0 && result2 != END_COUNT) {
591             fail(testName
592                  + ": method did not throw exception but hits.length for search on term 'aaa' is "
593                  + result2 + " instead of expected " + END_COUNT);
594           } else if (x == 1 && result2 != START_COUNT && result2 != END_COUNT) {
595             // It's possible that the first exception was
596             // "recoverable" wrt pending deletes, in which
597             // case the pending deletes are retained and
598             // then re-flushing (with plenty of disk
599             // space) will succeed in flushing the
600             // deletes:
601             fail(testName
602                  + ": method did not throw exception but hits.length for search on term 'aaa' is "
603                  + result2 + " instead of expected " + START_COUNT + " or " + END_COUNT);
604           }
605         } else {
606           // On hitting exception we still may have added
607           // all docs:
608           if (result2 != START_COUNT && result2 != END_COUNT) {
609             err.printStackTrace();
610             fail(testName
611                  + ": method did throw exception but hits.length for search on term 'aaa' is "
612                  + result2 + " instead of expected " + START_COUNT + " or " + END_COUNT);
613           }
614         }
615
616         searcher.close();
617         newReader.close();
618       }
619
620       modifier.close();
621       dir.close();
622
623       // Try again with 10 more bytes of free space:
624       diskFree += 10;
625     }
626     startDir.close();
627   }
628
629   // This test tests that buffered deletes are cleared when
630   // an Exception is hit during flush.
631   public void testErrorAfterApplyDeletes() throws IOException {
632     
633     MockDirectoryWrapper.Failure failure = new MockDirectoryWrapper.Failure() {
634         boolean sawMaybe = false;
635         boolean failed = false;
636         Thread thread;
637         @Override
638         public MockDirectoryWrapper.Failure reset() {
639           thread = Thread.currentThread();
640           sawMaybe = false;
641           failed = false;
642           return this;
643         }
644         @Override
645         public void eval(MockDirectoryWrapper dir)  throws IOException {
646           if (Thread.currentThread() != thread) {
647             // don't fail during merging
648             return;
649           }
650           if (sawMaybe && !failed) {
651             boolean seen = false;
652             StackTraceElement[] trace = new Exception().getStackTrace();
653             for (int i = 0; i < trace.length; i++) {
654               if ("applyDeletes".equals(trace[i].getMethodName())) {
655                 seen = true;
656                 break;
657               }
658             }
659             if (!seen) {
660               // Only fail once we are no longer in applyDeletes
661               failed = true;
662               if (VERBOSE) {
663                 System.out.println("TEST: mock failure: now fail");
664                 new Throwable().printStackTrace(System.out);
665               }
666               throw new IOException("fail after applyDeletes");
667             }
668           }
669           if (!failed) {
670             StackTraceElement[] trace = new Exception().getStackTrace();
671             for (int i = 0; i < trace.length; i++) {
672               if ("applyDeletes".equals(trace[i].getMethodName())) {
673                 if (VERBOSE) {
674                   System.out.println("TEST: mock failure: saw applyDeletes");
675                   new Throwable().printStackTrace(System.out);
676                 }
677                 sawMaybe = true;
678                 break;
679               }
680             }
681           }
682         }
683       };
684
685     // create a couple of files
686
687     String[] keywords = { "1", "2" };
688     String[] unindexed = { "Netherlands", "Italy" };
689     String[] unstored = { "Amsterdam has lots of bridges",
690         "Venice has lots of canals" };
691     String[] text = { "Amsterdam", "Venice" };
692
693     MockDirectoryWrapper dir = newDirectory();
694     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
695                                                                      TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDeleteTerms(2).setReaderPooling(false).setMergePolicy(newLogMergePolicy()));
696     modifier.setInfoStream(VERBOSE ? System.out : null);
697
698     LogMergePolicy lmp = (LogMergePolicy) modifier.getConfig().getMergePolicy();
699     lmp.setUseCompoundFile(true);
700
701     dir.failOn(failure.reset());
702
703     for (int i = 0; i < keywords.length; i++) {
704       Document doc = new Document();
705       doc.add(newField("id", keywords[i], Field.Store.YES,
706                         Field.Index.NOT_ANALYZED));
707       doc.add(newField("country", unindexed[i], Field.Store.YES,
708                         Field.Index.NO));
709       doc.add(newField("contents", unstored[i], Field.Store.NO,
710                         Field.Index.ANALYZED));
711       doc.add(newField("city", text[i], Field.Store.YES,
712                         Field.Index.ANALYZED));
713       modifier.addDocument(doc);
714     }
715     // flush (and commit if ac)
716
717     if (VERBOSE) {
718       System.out.println("TEST: now full merge");
719     }
720
721     modifier.forceMerge(1);
722     if (VERBOSE) {
723       System.out.println("TEST: now commit");
724     }
725     modifier.commit();
726
727     // one of the two files hits
728
729     Term term = new Term("city", "Amsterdam");
730     int hitCount = getHitCount(dir, term);
731     assertEquals(1, hitCount);
732
733     // open the writer again (closed above)
734
735     // delete the doc
736     // max buf del terms is two, so this is buffered
737
738     if (VERBOSE) {
739       System.out.println("TEST: delete term=" + term);
740     }
741
742     modifier.deleteDocuments(term);
743
744     // add a doc (needed for the !ac case; see below)
745     // doc remains buffered
746
747     if (VERBOSE) {
748       System.out.println("TEST: add empty doc");
749     }
750     Document doc = new Document();
751     modifier.addDocument(doc);
752
753     // commit the changes, the buffered deletes, and the new doc
754
755     // The failure object will fail on the first write after the del
756     // file gets created when processing the buffered delete
757
758     // in the ac case, this will be when writing the new segments
759     // files so we really don't need the new doc, but it's harmless
760
761     // a new segments file won't be created but in this
762     // case, creation of the cfs file happens next so we
763     // need the doc (to test that it's okay that we don't
764     // lose deletes if failing while creating the cfs file)
765     boolean failed = false;
766     try {
767       if (VERBOSE) {
768         System.out.println("TEST: now commit for failure");
769       }
770       modifier.commit();
771     } catch (IOException ioe) {
772       // expected
773       failed = true;
774     }
775
776     assertTrue(failed);
777
778     // The commit above failed, so we need to retry it (which will
779     // succeed, because the failure is a one-shot)
780
781     modifier.commit();
782
783     hitCount = getHitCount(dir, term);
784
785     // Make sure the delete was successfully flushed:
786     assertEquals(0, hitCount);
787
788     modifier.close();
789     dir.close();
790   }
791
792   // This test tests that the files created by the docs writer before
793   // a segment is written are cleaned up if there's an i/o error
794
795   public void testErrorInDocsWriterAdd() throws IOException {
796     
797     MockDirectoryWrapper.Failure failure = new MockDirectoryWrapper.Failure() {
798         boolean failed = false;
799         @Override
800         public MockDirectoryWrapper.Failure reset() {
801           failed = false;
802           return this;
803         }
804         @Override
805         public void eval(MockDirectoryWrapper dir)  throws IOException {
806           if (!failed) {
807             failed = true;
808             throw new IOException("fail in add doc");
809           }
810         }
811       };
812
813     // create a couple of files
814
815     String[] keywords = { "1", "2" };
816     String[] unindexed = { "Netherlands", "Italy" };
817     String[] unstored = { "Amsterdam has lots of bridges",
818         "Venice has lots of canals" };
819     String[] text = { "Amsterdam", "Venice" };
820
821     MockDirectoryWrapper dir = newDirectory();
822     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)));
823     modifier.commit();
824     dir.failOn(failure.reset());
825
826     for (int i = 0; i < keywords.length; i++) {
827       Document doc = new Document();
828       doc.add(newField("id", keywords[i], Field.Store.YES,
829                         Field.Index.NOT_ANALYZED));
830       doc.add(newField("country", unindexed[i], Field.Store.YES,
831                         Field.Index.NO));
832       doc.add(newField("contents", unstored[i], Field.Store.NO,
833                         Field.Index.ANALYZED));
834       doc.add(newField("city", text[i], Field.Store.YES,
835                         Field.Index.ANALYZED));
836       try {
837         modifier.addDocument(doc);
838       } catch (IOException io) {
839         if (VERBOSE) {
840           System.out.println("TEST: got expected exc:");
841           io.printStackTrace(System.out);
842         }
843         break;
844       }
845     }
846
847     modifier.close();
848     TestIndexWriter.assertNoUnreferencedFiles(dir, "docswriter abort() failed to delete unreferenced files");    
849     dir.close();
850   }
851
852   private String arrayToString(String[] l) {
853     String s = "";
854     for (int i = 0; i < l.length; i++) {
855       if (i > 0) {
856         s += "\n    ";
857       }
858       s += l[i];
859     }
860     return s;
861   }
862   
863   public void testDeleteAllSlowly() throws Exception {
864     final Directory dir = newDirectory();
865     RandomIndexWriter w = new RandomIndexWriter(random, dir);
866     final int NUM_DOCS = atLeast(1000);
867     final List<Integer> ids = new ArrayList<Integer>(NUM_DOCS);
868     for(int id=0;id<NUM_DOCS;id++) {
869       ids.add(id);
870     }
871     Collections.shuffle(ids, random);
872     for(int id : ids) {
873       Document doc = new Document();
874       doc.add(newField("id", ""+id, Field.Index.NOT_ANALYZED));
875       w.addDocument(doc);
876     }
877     Collections.shuffle(ids, random);
878     int upto = 0;
879     while(upto < ids.size()) {
880       final int left = ids.size() - upto;
881       final int inc = Math.min(left, _TestUtil.nextInt(random, 1, 20));
882       final int limit = upto + inc;
883       while(upto < limit) {
884         w.deleteDocuments(new Term("id", ""+ids.get(upto++)));
885       }
886       final IndexReader r = w.getReader();
887       assertEquals(NUM_DOCS - upto, r.numDocs());
888       r.close();
889     }
890
891     w.close();
892     dir.close();
893   }
894   
895   public void testIndexingThenDeleting() throws Exception {
896     final Random r = random;
897     Directory dir = newDirectory();
898     // note this test explicitly disables payloads
899     final Analyzer analyzer = new Analyzer() {
900       @Override
901       public TokenStream tokenStream(String fieldName, Reader reader) {
902         return new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
903       }
904     };
905     IndexWriter w = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, analyzer).setRAMBufferSizeMB(1.0).setMaxBufferedDocs(IndexWriterConfig.DISABLE_AUTO_FLUSH).setMaxBufferedDeleteTerms(IndexWriterConfig.DISABLE_AUTO_FLUSH));
906     w.setInfoStream(VERBOSE ? System.out : null);
907     Document doc = new Document();
908     doc.add(newField("field", "go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", Field.Store.NO, Field.Index.ANALYZED));
909     int num = atLeast(3);
910     for (int iter = 0; iter < num; iter++) {
911       int count = 0;
912
913       final boolean doIndexing = r.nextBoolean();
914       if (VERBOSE) {
915         System.out.println("TEST: iter doIndexing=" + doIndexing);
916       }
917       if (doIndexing) {
918         // Add docs until a flush is triggered
919         final int startFlushCount = w.getFlushCount();
920         while(w.getFlushCount() == startFlushCount) {
921           w.addDocument(doc);
922           count++;
923         }
924       } else {
925         // Delete docs until a flush is triggered
926         final int startFlushCount = w.getFlushCount();
927         while(w.getFlushCount() == startFlushCount) {
928           w.deleteDocuments(new Term("foo", ""+count));
929           count++;
930         }
931       }
932       assertTrue("flush happened too quickly during " + (doIndexing ? "indexing" : "deleting") + " count=" + count, count > 3000);
933     }
934     w.close();
935     dir.close();
936   }
937
938   // LUCENE-3340: make sure deletes that we don't apply
939   // during flush (ie are just pushed into the stream) are
940   // in fact later flushed due to their RAM usage:
941   public void testFlushPushedDeletesByRAM() throws Exception {
942     Directory dir = newDirectory();
943     // Cannot use RandomIndexWriter because we don't want to
944     // ever call commit() for this test:
945     // note: tiny rambuffer used, as with a 1MB buffer the test is too slow (flush @ 128,999)
946     IndexWriter w = new IndexWriter(dir,
947                                     newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random))
948                                     .setRAMBufferSizeMB(0.2f).setMaxBufferedDocs(1000).setMergePolicy(NoMergePolicy.NO_COMPOUND_FILES).setReaderPooling(false));
949     w.setInfoStream(VERBOSE ? System.out : null);
950     int count = 0;
951     while(true) {
952       Document doc = new Document();
953       doc.add(new Field("id", count+"", Field.Store.NO, Field.Index.NOT_ANALYZED));
954       final Term delTerm;
955       if (count == 1010) {
956         // This is the only delete that applies
957         delTerm = new Term("id", ""+0);
958       } else {
959         // These get buffered, taking up RAM, but delete
960         // nothing when applied:
961         delTerm = new Term("id", "x" + count);
962       }
963       w.updateDocument(delTerm, doc);
964       // Eventually segment 0 should get a del docs:
965       if (dir.fileExists("_0_1.del")) {
966         if (VERBOSE) {
967           System.out.println("TEST: deletes created @ count=" + count);
968         }
969         break;
970       }
971       count++;
972
973       // Today we applyDeletes @ count=21553; even if we make
974       // sizable improvements to RAM efficiency of buffered
975       // del term we're unlikely to go over 100K:
976       if (count > 100000) {
977         fail("delete's were not applied");
978       }
979     }
980     w.close();
981     dir.close();
982   }
983
984   // LUCENE-3340: make sure deletes that we don't apply
985   // during flush (ie are just pushed into the stream) are
986   // in fact later flushed due to their RAM usage:
987   public void testFlushPushedDeletesByCount() throws Exception {
988     Directory dir = newDirectory();
989     // Cannot use RandomIndexWriter because we don't want to
990     // ever call commit() for this test:
991     final int flushAtDelCount = atLeast(1020);
992     IndexWriter w = new IndexWriter(dir,
993                                     newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).
994                                     setMaxBufferedDeleteTerms(flushAtDelCount).setMaxBufferedDocs(1000).setRAMBufferSizeMB(IndexWriterConfig.DISABLE_AUTO_FLUSH).setMergePolicy(NoMergePolicy.NO_COMPOUND_FILES).setReaderPooling(false));
995     w.setInfoStream(VERBOSE ? System.out : null);
996     if (VERBOSE) {
997       System.out.println("TEST: flush @ " + flushAtDelCount + " buffered delete terms");
998     }
999     int count = 0;
1000     while(true) {
1001       Document doc = new Document();
1002       doc.add(new Field("id", count+"", Field.Store.NO, Field.Index.NOT_ANALYZED));
1003       final Term delTerm;
1004       if (count == 1010) {
1005         // This is the only delete that applies
1006         delTerm = new Term("id", ""+0);
1007       } else {
1008         // These get buffered, taking up RAM, but delete
1009         // nothing when applied:
1010         delTerm = new Term("id", "x" + count);
1011       }
1012       w.updateDocument(delTerm, doc);
1013       // Eventually segment 0 should get a del docs:
1014       if (dir.fileExists("_0_1.del")) {
1015         break;
1016       }
1017       count++;
1018       if (count > flushAtDelCount) {
1019         fail("delete's were not applied at count=" + flushAtDelCount);
1020       }
1021     }
1022     w.close();
1023     dir.close();
1024   }
1025
1026   // Make sure buffered (pushed) deletes don't use up so
1027   // much RAM that it forces long tail of tiny segments:
1028   public void testApplyDeletesOnFlush() throws Exception {
1029     Directory dir = newDirectory();
1030     // Cannot use RandomIndexWriter because we don't want to
1031     // ever call commit() for this test:
1032     final AtomicInteger docsInSegment = new AtomicInteger();
1033     final AtomicBoolean closing = new AtomicBoolean();
1034     final AtomicBoolean sawAfterFlush = new AtomicBoolean();
1035     IndexWriter w = new IndexWriter(dir,
1036                                     newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).
1037                                     setRAMBufferSizeMB(0.5).setMaxBufferedDocs(-1).setMergePolicy(NoMergePolicy.NO_COMPOUND_FILES).setReaderPooling(false)) {
1038         @Override
1039         public void doAfterFlush() {
1040           assertTrue("only " + docsInSegment.get() + " in segment", closing.get() || docsInSegment.get() >= 7);
1041           docsInSegment.set(0);
1042           sawAfterFlush.set(true);
1043         }
1044       };
1045     w.setInfoStream(VERBOSE ? System.out : null);
1046     int id = 0;
1047     while(true) {
1048       StringBuilder sb = new StringBuilder();
1049       for(int termIDX=0;termIDX<100;termIDX++) {
1050         sb.append(' ').append(_TestUtil.randomRealisticUnicodeString(random));
1051       }
1052       if (id == 500) {
1053         w.deleteDocuments(new Term("id", "0"));
1054       }
1055       Document doc = new Document();
1056       doc.add(newField("id", ""+id, Field.Index.NOT_ANALYZED));
1057       doc.add(newField("body", sb.toString(), Field.Index.ANALYZED));
1058       w.updateDocument(new Term("id", ""+id), doc);
1059       docsInSegment.incrementAndGet();
1060       if (dir.fileExists("_0_1.del")) {
1061         if (VERBOSE) {
1062           System.out.println("TEST: deletes created @ id=" + id);
1063         }
1064         break;
1065       }
1066       id++;
1067     }
1068     closing.set(true);
1069     assertTrue(sawAfterFlush.get());
1070     w.close();
1071     dir.close();
1072   }
1073 }