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