View Javadoc
1   /*
2   Copyright (c) 2007 Health Market Science, Inc.
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.NoSuchElementException;
26  import java.util.TreeSet;
27  import java.util.stream.Collectors;
28  
29  import static com.healthmarketscience.jackcess.Database.*;
30  import com.healthmarketscience.jackcess.impl.ColumnImpl;
31  import com.healthmarketscience.jackcess.impl.JetFormatTest;
32  import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
33  import com.healthmarketscience.jackcess.impl.RowIdImpl;
34  import com.healthmarketscience.jackcess.impl.TableImpl;
35  import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
36  import com.healthmarketscience.jackcess.util.ColumnMatcher;
37  import com.healthmarketscience.jackcess.util.RowFilterTest;
38  import com.healthmarketscience.jackcess.util.SimpleColumnMatcher;
39  import junit.framework.TestCase;
40  import static com.healthmarketscience.jackcess.TestUtil.*;
41  import static com.healthmarketscience.jackcess.DatabaseBuilder.*;
42  
43  /**
44   * @author James Ahlborn
45   */
46  public class CursorTest extends TestCase {
47  
48    static final List<TestDB> INDEX_CURSOR_DBS =
49      TestDB.getSupportedForBasename(Basename.INDEX_CURSOR);
50  
51  
52    public CursorTest(String name) throws Exception {
53      super(name);
54    }
55  
56    @Override
57    protected void setUp() {
58      TestUtil.setTestAutoSync(false);
59    }
60  
61    @Override
62    protected void tearDown() {
63      TestUtil.clearTestAutoSync();
64    }
65  
66    private static List<Map<String,Object>> createTestTableData()
67      throws Exception
68    {
69      List<Map<String,Object>> expectedRows =
70        new ArrayList<Map<String,Object>>();
71      for(int i = 0; i < 10; ++i) {
72        expectedRows.add(createExpectedRow("id", i, "value", "data" + i));
73      }
74      return expectedRows;
75    }
76  
77    private static List<Map<String,Object>> createTestTableData(
78        int startIdx,
79        int endIdx)
80      throws Exception
81    {
82      List<Map<String,Object>> expectedRows = createTestTableData();
83      expectedRows.subList(endIdx, expectedRows.size()).clear();
84      expectedRows.subList(0, startIdx).clear();
85      return expectedRows;
86    }
87  
88    private static Database createTestTable(final FileFormat fileFormat)
89      throws Exception
90    {
91      Database db = createMem(fileFormat);
92  
93      Table table = newTable("test")
94        .addColumn(newColumn("id", DataType.LONG))
95        .addColumn(newColumn("value", DataType.TEXT))
96        .toTable(db);
97  
98      for(Map<String,Object> row : createTestTableData()) {
99        table.addRow(row.get("id"), row.get("value"));
100     }
101 
102     return db;
103   }
104 
105   private static List<Map<String,Object>> createUnorderedTestTableData()
106     throws Exception
107   {
108     List<Map<String,Object>> expectedRows =
109       new ArrayList<Map<String,Object>>();
110     int[] ids = new int[]{3, 7, 6, 1, 2, 9, 0, 5, 4, 8};
111     for(int i : ids) {
112       expectedRows.add(createExpectedRow("id", i, "value", "data" + i));
113     }
114     return expectedRows;
115   }
116 
117   static Database createTestIndexTable(final TestDB indexCursorDB)
118     throws Exception
119   {
120     Database db = openMem(indexCursorDB);
121 
122     Table table = db.getTable("test");
123 
124     for(Map<String,Object> row : createUnorderedTestTableData()) {
125       table.addRow(row.get("id"), row.get("value"));
126     }
127 
128     return db;
129   }
130 
131   private static List<Map<String,Object>> createDupeTestTableData()
132     throws Exception
133   {
134     List<Map<String,Object>> expectedRows =
135       new ArrayList<Map<String,Object>>();
136     int[] ids = new int[]{3, 7, 6, 1, 2, 9, 0, 5, 4, 8};
137     for(int i : ids) {
138       expectedRows.add(createExpectedRow("id", i, "value", "data" + (i % 3)));
139     }
140     for(int i : ids) {
141       expectedRows.add(createExpectedRow("id", i, "value", "data" + (i % 5)));
142     }
143     return expectedRows;
144   }
145 
146   private static Database createDupeTestTable(final FileFormat fileFormat)
147     throws Exception
148   {
149     Database db = createMem(fileFormat);
150 
151     Table table = newTable("test")
152       .addColumn(newColumn("id", DataType.LONG))
153       .addColumn(newColumn("value", DataType.TEXT))
154       .toTable(db);
155 
156     for(Map<String,Object> row : createDupeTestTableData()) {
157       table.addRow(row.get("id"), row.get("value"));
158     }
159 
160     return db;
161   }
162 
163   static Database createDupeTestTable(final TestDB indexCursorDB)
164     throws Exception
165   {
166     Database db = openMem(indexCursorDB);
167 
168     Table table = db.getTable("test");
169 
170     for(Map<String,Object> row : createDupeTestTableData()) {
171       table.addRow(row.get("id"), row.get("value"));
172     }
173 
174     return db;
175   }
176 
177   private static Cursor createIndexSubRangeCursor(Table table,
178                                                   Index idx,
179                                                   int type)
180     throws Exception
181   {
182     return table.newCursor()
183       .setIndex(idx)
184       .setStartEntry(3 - type)
185       .setStartRowInclusive(type == 0)
186       .setEndEntry(8 + type)
187       .setEndRowInclusive(type == 0)
188       .toCursor();
189   }
190 
191   public void testRowId() throws Exception {
192     // test special cases
193     RowIdImpl rowId1 = new RowIdImpl(1, 2);
194     RowIdImpl rowId2 = new RowIdImpl(1, 3);
195     RowIdImpl rowId3 = new RowIdImpl(2, 1);
196 
197     List<RowIdImpl> sortedRowIds =
198       new ArrayList<RowIdImpl>(new TreeSet<RowIdImpl>(
199         Arrays.asList(rowId1, rowId2, rowId3, RowIdImpl.FIRST_ROW_ID,
200                       RowIdImpl.LAST_ROW_ID)));
201 
202     assertEquals(Arrays.asList(RowIdImpl.FIRST_ROW_ID, rowId1, rowId2, rowId3,
203                                RowIdImpl.LAST_ROW_ID),
204                  sortedRowIds);
205   }
206 
207   public void testSimple() throws Exception {
208     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
209       Database db = createTestTable(fileFormat);
210 
211       Table table = db.getTable("test");
212       Cursor cursor = CursorBuilder.createCursor(table);
213       doTestSimple(cursor, null);
214       db.close();
215     }
216   }
217 
218   private static void doTestSimple(Cursor cursor,
219                                    List<Map<String, Object>> expectedRows)
220     throws Exception
221   {
222     if(expectedRows == null) {
223       expectedRows = createTestTableData();
224     }
225 
226     List<Map<String, Object>> foundRows =
227       new ArrayList<Map<String, Object>>();
228     for(Map<String, Object> row : cursor) {
229       foundRows.add(row);
230     }
231     assertEquals(expectedRows, foundRows);
232   }
233 
234   public void testMove() throws Exception {
235     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
236       Database db = createTestTable(fileFormat);
237 
238       Table table = db.getTable("test");
239       Cursor cursor = CursorBuilder.createCursor(table);
240       doTestMove(cursor, null);
241 
242       db.close();
243     }
244   }
245 
246   private static void doTestMove(Cursor cursor,
247                                  List<Map<String, Object>> expectedRows)
248     throws Exception
249   {
250     if(expectedRows == null) {
251       expectedRows = createTestTableData();
252     }
253     expectedRows.subList(1, 4).clear();
254 
255     List<Map<String, Object>> foundRows =
256       new ArrayList<Map<String, Object>>();
257     assertTrue(cursor.isBeforeFirst());
258     assertFalse(cursor.isAfterLast());
259     foundRows.add(cursor.getNextRow());
260     assertEquals(3, cursor.moveNextRows(3));
261     assertFalse(cursor.isBeforeFirst());
262     assertFalse(cursor.isAfterLast());
263 
264     Map<String,Object> expectedRow = cursor.getCurrentRow();
265     Cursor.Savepoint savepoint = cursor.getSavepoint();
266     assertEquals(2, cursor.movePreviousRows(2));
267     assertEquals(2, cursor.moveNextRows(2));
268     assertTrue(cursor.moveToNextRow());
269     assertTrue(cursor.moveToPreviousRow());
270     assertEquals(expectedRow, cursor.getCurrentRow());
271 
272     while(cursor.moveToNextRow()) {
273       foundRows.add(cursor.getCurrentRow());
274     }
275     assertEquals(expectedRows, foundRows);
276     assertFalse(cursor.isBeforeFirst());
277     assertTrue(cursor.isAfterLast());
278 
279     assertEquals(0, cursor.moveNextRows(3));
280 
281     cursor.beforeFirst();
282     assertTrue(cursor.isBeforeFirst());
283     assertFalse(cursor.isAfterLast());
284 
285     cursor.afterLast();
286     assertFalse(cursor.isBeforeFirst());
287     assertTrue(cursor.isAfterLast());
288 
289     cursor.restoreSavepoint(savepoint);
290     assertEquals(expectedRow, cursor.getCurrentRow());
291   }
292 
293   public void testMoveNoReset() throws Exception {
294     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
295       Database db = createTestTable(fileFormat);
296 
297       Table table = db.getTable("test");
298       Cursor cursor = CursorBuilder.createCursor(table);
299       doTestMoveNoReset(cursor);
300 
301       db.close();
302     }
303   }
304 
305   private static void doTestMoveNoReset(Cursor cursor)
306     throws Exception
307   {
308     List<Map<String, Object>> expectedRows = createTestTableData();
309     List<Map<String, Object>> foundRows = new ArrayList<Map<String, Object>>();
310 
311     Iterator<Row> iter = cursor.newIterable().iterator();
312 
313     for(int i = 0; i < 6; ++i) {
314       foundRows.add(iter.next());
315     }
316 
317     iter = cursor.newIterable().reset(false).reverse().iterator();
318     iter.next();
319     Map<String, Object> row = iter.next();
320     assertEquals(expectedRows.get(4), row);
321 
322     iter = cursor.newIterable().reset(false).iterator();
323     iter.next();
324     row = iter.next();
325     assertEquals(expectedRows.get(5), row);
326     iter.next();
327 
328     iter = cursor.newIterable().reset(false).iterator();
329     for(int i = 6; i < 10; ++i) {
330       foundRows.add(iter.next());
331     }
332 
333     assertEquals(expectedRows, foundRows);
334   }
335 
336   public void testSearch() throws Exception {
337     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
338       Database db = createTestTable(fileFormat);
339 
340       Table table = db.getTable("test");
341       Cursor cursor = CursorBuilder.createCursor(table);
342       doTestSearch(table, cursor, null, 42, -13);
343 
344       db.close();
345     }
346   }
347 
348   private static void doTestSearch(Table table, Cursor cursor, Index index,
349                                    Integer... outOfRangeValues)
350     throws Exception
351   {
352     assertTrue(cursor.findFirstRow(table.getColumn("id"), 3));
353     assertEquals(createExpectedRow("id", 3,
354                                    "value", "data" + 3),
355                  cursor.getCurrentRow());
356 
357     assertTrue(cursor.findFirstRow(createExpectedRow(
358                                     "id", 6,
359                                     "value", "data" + 6)));
360     assertEquals(createExpectedRow("id", 6,
361                                    "value", "data" + 6),
362                  cursor.getCurrentRow());
363 
364     assertFalse(cursor.findFirstRow(createExpectedRow(
365                                    "id", 8,
366                                    "value", "data" + 13)));
367     assertFalse(cursor.findFirstRow(table.getColumn("id"), 13));
368     assertEquals(createExpectedRow("id", 6,
369                                    "value", "data" + 6),
370                  cursor.getCurrentRow());
371 
372     assertTrue(cursor.findFirstRow(createExpectedRow(
373                                     "value", "data" + 7)));
374     assertEquals(createExpectedRow("id", 7,
375                                    "value", "data" + 7),
376                  cursor.getCurrentRow());
377 
378     assertTrue(cursor.findFirstRow(table.getColumn("value"), "data" + 4));
379     assertEquals(createExpectedRow("id", 4,
380                                    "value", "data" + 4),
381                  cursor.getCurrentRow());
382 
383     for(Integer outOfRangeValue : outOfRangeValues) {
384       assertFalse(cursor.findFirstRow(table.getColumn("id"),
385                                  outOfRangeValue));
386       assertFalse(cursor.findFirstRow(table.getColumn("value"),
387                                  "data" + outOfRangeValue));
388       assertFalse(cursor.findFirstRow(createExpectedRow(
389                                      "id", outOfRangeValue,
390                                      "value", "data" + outOfRangeValue)));
391     }
392 
393     assertEquals("data" + 5,
394                  CursorBuilder.findValue(table,
395                                   table.getColumn("value"),
396                                   table.getColumn("id"), 5));
397     assertEquals(createExpectedRow("id", 5,
398                                    "value", "data" + 5),
399                  CursorBuilder.findRow(table,
400                                 createExpectedRow("id", 5)));
401     if(index != null) {
402       assertEquals("data" + 5,
403                    CursorBuilder.findValue(index,
404                                     table.getColumn("value"),
405                                     table.getColumn("id"), 5));
406       assertEquals(createExpectedRow("id", 5,
407                                      "value", "data" + 5),
408                    CursorBuilder.findRow(index,
409                                   createExpectedRow("id", 5)));
410 
411       assertNull(CursorBuilder.findValue(index,
412                                   table.getColumn("value"),
413                                   table.getColumn("id"),
414                                   -17));
415       assertNull(CursorBuilder.findRow(index,
416                                 createExpectedRow("id", 13)));
417     }
418   }
419 
420   public void testReverse() throws Exception {
421     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
422       Database db = createTestTable(fileFormat);
423 
424       Table table = db.getTable("test");
425       Cursor cursor = CursorBuilder.createCursor(table);
426       doTestReverse(cursor, null);
427 
428       db.close();
429     }
430   }
431 
432   private static void doTestReverse(Cursor cursor,
433                                     List<Map<String, Object>> expectedRows)
434     throws Exception
435   {
436     if(expectedRows == null) {
437       expectedRows = createTestTableData();
438     }
439     Collections.reverse(expectedRows);
440 
441     List<Map<String, Object>> foundRows =
442       new ArrayList<Map<String, Object>>();
443     for(Map<String, Object> row : cursor.newIterable().reverse()) {
444       foundRows.add(row);
445     }
446     assertEquals(expectedRows, foundRows);
447   }
448 
449   public void testLiveAddition() throws Exception {
450     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
451       Database db = createTestTable(fileFormat);
452 
453       Table table = db.getTable("test");
454 
455       Cursor cursor1 = CursorBuilder.createCursor(table);
456       Cursor cursor2 = CursorBuilder.createCursor(table);
457       doTestLiveAddition(table, cursor1, cursor2, 11);
458 
459       db.close();
460     }
461   }
462 
463   private static void doTestLiveAddition(Table table,
464                                          Cursor cursor1,
465                                          Cursor cursor2,
466                                          Integer newRowNum) throws Exception
467   {
468     cursor1.moveNextRows(11);
469     cursor2.moveNextRows(11);
470 
471     assertTrue(cursor1.isAfterLast());
472     assertTrue(cursor2.isAfterLast());
473 
474     table.addRow(newRowNum, "data" + newRowNum);
475     Map<String,Object> expectedRow =
476       createExpectedRow("id", newRowNum, "value", "data" + newRowNum);
477 
478     assertFalse(cursor1.isAfterLast());
479     assertFalse(cursor2.isAfterLast());
480 
481     assertEquals(expectedRow, cursor1.getCurrentRow());
482     assertEquals(expectedRow, cursor2.getCurrentRow());
483     assertFalse(cursor1.moveToNextRow());
484     assertFalse(cursor2.moveToNextRow());
485     assertTrue(cursor1.isAfterLast());
486     assertTrue(cursor2.isAfterLast());
487   }
488 
489 
490   public void testLiveDeletion() throws Exception {
491     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
492       Database db = createTestTable(fileFormat);
493 
494       Table table = db.getTable("test");
495 
496       Cursor cursor1 = CursorBuilder.createCursor(table);
497       Cursor cursor2 = CursorBuilder.createCursor(table);
498       Cursor cursor3 = CursorBuilder.createCursor(table);
499       Cursor cursor4 = CursorBuilder.createCursor(table);
500       doTestLiveDeletion(cursor1, cursor2, cursor3, cursor4, 1);
501 
502       db.close();
503     }
504   }
505 
506   private static void doTestLiveDeletion(
507           Cursor cursor1,
508           Cursor cursor2,
509           Cursor cursor3,
510           Cursor cursor4,
511           int firstValue) throws Exception
512   {
513     assertEquals(2, cursor1.moveNextRows(2));
514     assertEquals(3, cursor2.moveNextRows(3));
515     assertEquals(3, cursor3.moveNextRows(3));
516     assertEquals(4, cursor4.moveNextRows(4));
517 
518     Map<String,Object> expectedPrevRow =
519       createExpectedRow("id", firstValue, "value", "data" + firstValue);
520     ++firstValue;
521     Map<String,Object> expectedDeletedRow =
522       createExpectedRow("id", firstValue, "value", "data" + firstValue);
523     ++firstValue;
524     Map<String,Object> expectedNextRow =
525       createExpectedRow("id", firstValue, "value", "data" + firstValue);
526 
527     assertEquals(expectedDeletedRow, cursor2.getCurrentRow());
528     assertEquals(expectedDeletedRow, cursor3.getCurrentRow());
529 
530     assertFalse(cursor2.isCurrentRowDeleted());
531     assertFalse(cursor3.isCurrentRowDeleted());
532 
533     cursor2.deleteCurrentRow();
534 
535     assertTrue(cursor2.isCurrentRowDeleted());
536     assertTrue(cursor3.isCurrentRowDeleted());
537 
538     assertEquals(expectedNextRow, cursor1.getNextRow());
539     assertEquals(expectedNextRow, cursor2.getNextRow());
540     assertEquals(expectedNextRow, cursor3.getNextRow());
541 
542     assertEquals(expectedPrevRow, cursor3.getPreviousRow());
543 
544     assertTrue(cursor3.moveToNextRow());
545     cursor3.deleteCurrentRow();
546     assertTrue(cursor3.isCurrentRowDeleted());
547 
548     firstValue += 2;
549     expectedNextRow =
550       createExpectedRow("id", firstValue, "value", "data" + firstValue);
551     assertTrue(cursor3.moveToNextRow());
552     assertEquals(expectedNextRow, cursor3.getNextRow());
553 
554     cursor1.beforeFirst();
555     assertTrue(cursor1.moveToNextRow());
556     cursor1.deleteCurrentRow();
557     assertFalse(cursor1.isBeforeFirst());
558     assertFalse(cursor1.isAfterLast());
559     assertFalse(cursor1.moveToPreviousRow());
560     assertTrue(cursor1.isBeforeFirst());
561     assertFalse(cursor1.isAfterLast());
562 
563     cursor1.afterLast();
564     assertTrue(cursor1.moveToPreviousRow());
565     cursor1.deleteCurrentRow();
566     assertFalse(cursor1.isBeforeFirst());
567     assertFalse(cursor1.isAfterLast());
568     assertFalse(cursor1.moveToNextRow());
569     assertFalse(cursor1.isBeforeFirst());
570     assertTrue(cursor1.isAfterLast());
571 
572     cursor1.beforeFirst();
573     while(cursor1.moveToNextRow()) {
574       cursor1.deleteCurrentRow();
575     }
576 
577     assertTrue(cursor1.isAfterLast());
578     assertTrue(cursor2.isCurrentRowDeleted());
579     assertTrue(cursor3.isCurrentRowDeleted());
580     assertTrue(cursor4.isCurrentRowDeleted());
581   }
582 
583   public void testSimpleIndex() throws Exception {
584     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
585       Database db = createTestIndexTable(indexCursorDB);
586 
587       Table table = db.getTable("test");
588       Index idx = table.getIndexes().get(0);
589 
590       assertTable(createUnorderedTestTableData(), table);
591 
592       Cursor cursor = CursorBuilder.createCursor(idx);
593       doTestSimple(cursor, null);
594 
595       db.close();
596     }
597   }
598 
599   public void testMoveIndex() throws Exception {
600     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
601       Database db = createTestIndexTable(indexCursorDB);
602 
603       Table table = db.getTable("test");
604       Index idx = table.getIndexes().get(0);
605       Cursor cursor = CursorBuilder.createCursor(idx);
606       doTestMove(cursor, null);
607 
608       db.close();
609     }
610   }
611 
612   public void testReverseIndex() throws Exception {
613     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
614       Database db = createTestIndexTable(indexCursorDB);
615 
616       Table table = db.getTable("test");
617       Index idx = table.getIndexes().get(0);
618       Cursor cursor = CursorBuilder.createCursor(idx);
619       doTestReverse(cursor, null);
620 
621       db.close();
622     }
623   }
624 
625   public void testSearchIndex() throws Exception {
626     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
627       Database db = createTestIndexTable(indexCursorDB);
628 
629       Table table = db.getTable("test");
630       Index idx = table.getIndexes().get(0);
631       Cursor cursor = CursorBuilder.createCursor(idx);
632       doTestSearch(table, cursor, idx, 42, -13);
633 
634       db.close();
635     }
636   }
637 
638   public void testLiveAdditionIndex() throws Exception {
639     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
640       Database db = createTestIndexTable(indexCursorDB);
641 
642       Table table = db.getTable("test");
643       Index idx = table.getIndexes().get(0);
644 
645       Cursor cursor1 = CursorBuilder.createCursor(idx);
646       Cursor cursor2 = CursorBuilder.createCursor(idx);
647       doTestLiveAddition(table, cursor1, cursor2, 11);
648 
649       db.close();
650     }
651   }
652 
653   public void testLiveDeletionIndex() throws Exception {
654     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
655       Database db = createTestIndexTable(indexCursorDB);
656 
657       Table table = db.getTable("test");
658       Index idx = table.getIndexes().get(0);
659 
660       Cursor cursor1 = CursorBuilder.createCursor(idx);
661       Cursor cursor2 = CursorBuilder.createCursor(idx);
662       Cursor cursor3 = CursorBuilder.createCursor(idx);
663       Cursor cursor4 = CursorBuilder.createCursor(idx);
664       doTestLiveDeletion(cursor1, cursor2, cursor3, cursor4, 1);
665 
666       db.close();
667     }
668   }
669 
670   public void testSimpleIndexSubRange() throws Exception {
671     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
672       for(int i = 0; i < 2; ++i) {
673         Database db = createTestIndexTable(indexCursorDB);
674 
675         Table table = db.getTable("test");
676         Index idx = table.getIndexes().get(0);
677 
678         Cursor cursor = createIndexSubRangeCursor(table, idx, i);
679 
680         List<Map<String,Object>> expectedRows =
681           createTestTableData(3, 9);
682 
683         doTestSimple(cursor, expectedRows);
684 
685         db.close();
686       }
687     }
688   }
689 
690   public void testMoveIndexSubRange() throws Exception {
691     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
692       for(int i = 0; i < 2; ++i) {
693         Database db = createTestIndexTable(indexCursorDB);
694 
695         Table table = db.getTable("test");
696         Index idx = table.getIndexes().get(0);
697 
698         Cursor cursor = createIndexSubRangeCursor(table, idx, i);
699 
700         List<Map<String,Object>> expectedRows =
701           createTestTableData(3, 9);
702 
703         doTestMove(cursor, expectedRows);
704 
705         db.close();
706       }
707     }
708   }
709 
710   public void testSearchIndexSubRange() throws Exception {
711     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
712       for(int i = 0; i < 2; ++i) {
713         Database db = createTestIndexTable(indexCursorDB);
714 
715         Table table = db.getTable("test");
716         Index idx = table.getIndexes().get(0);
717 
718         Cursor cursor = createIndexSubRangeCursor(table, idx, i);
719 
720         doTestSearch(table, cursor, idx, 2, 9);
721 
722         db.close();
723       }
724     }
725   }
726 
727   public void testReverseIndexSubRange() throws Exception {
728     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
729       for(int i = 0; i < 2; ++i) {
730         Database db = createTestIndexTable(indexCursorDB);
731 
732         Table table = db.getTable("test");
733         Index idx = table.getIndexes().get(0);
734 
735         Cursor cursor = createIndexSubRangeCursor(table, idx, i);
736 
737         List<Map<String,Object>> expectedRows =
738           createTestTableData(3, 9);
739 
740         doTestReverse(cursor, expectedRows);
741 
742         db.close();
743       }
744     }
745   }
746 
747   public void testLiveAdditionIndexSubRange() throws Exception {
748     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
749       for(int i = 0; i < 2; ++i) {
750         Database db = createTestIndexTable(indexCursorDB);
751 
752         Table table = db.getTable("test");
753         Index idx = table.getIndexes().get(0);
754 
755         Cursor cursor1 = createIndexSubRangeCursor(table, idx, i);
756         Cursor cursor2 = createIndexSubRangeCursor(table, idx, i);
757 
758         doTestLiveAddition(table, cursor1, cursor2, 8);
759 
760         db.close();
761       }
762     }
763   }
764 
765   public void testLiveDeletionIndexSubRange() throws Exception {
766     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
767       for(int i = 0; i < 2; ++i) {
768         Database db = createTestIndexTable(indexCursorDB);
769 
770         Table table = db.getTable("test");
771         Index idx = table.getIndexes().get(0);
772 
773         Cursor cursor1 = createIndexSubRangeCursor(table, idx, i);
774         Cursor cursor2 = createIndexSubRangeCursor(table, idx, i);
775         Cursor cursor3 = createIndexSubRangeCursor(table, idx, i);
776         Cursor cursor4 = createIndexSubRangeCursor(table, idx, i);
777 
778         doTestLiveDeletion(cursor1, cursor2, cursor3, cursor4, 4);
779 
780         db.close();
781       }
782     }
783   }
784 
785   public void testFindAllIndex() throws Exception {
786     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
787       Database db = createDupeTestTable(fileFormat);
788 
789       Table table = db.getTable("test");
790       Cursor cursor = CursorBuilder.createCursor(table);
791 
792       doTestFindAll(table, cursor, null);
793 
794       db.close();
795     }
796   }
797 
798   public void testFindAll() throws Exception {
799     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
800       Database db = createDupeTestTable(indexCursorDB);
801 
802       Table table = db.getTable("test");
803       Index idx = table.getIndexes().get(0);
804       Cursor cursor = CursorBuilder.createCursor(idx);
805 
806       doTestFindAll(table, cursor, idx);
807 
808       db.close();
809     }
810   }
811 
812   private static void doTestFindAll(Table table, Cursor cursor, Index index)
813     throws Exception
814   {
815     List<? extends Map<String,Object>> rows = RowFilterTest.toList(
816         cursor.newIterable().setMatchPattern("value", "data2"));
817 
818     List<? extends Map<String, Object>> expectedRows = null;
819 
820     if(index == null) {
821       expectedRows =
822         createExpectedTable(
823             createExpectedRow(
824                 "id", 2, "value", "data2"),
825             createExpectedRow(
826                 "id", 5, "value", "data2"),
827             createExpectedRow(
828                 "id", 8, "value", "data2"),
829             createExpectedRow(
830                 "id", 7, "value", "data2"),
831             createExpectedRow(
832                 "id", 2, "value", "data2"));
833     } else {
834       expectedRows =
835         createExpectedTable(
836             createExpectedRow(
837                 "id", 2, "value", "data2"),
838             createExpectedRow(
839                 "id", 2, "value", "data2"),
840             createExpectedRow(
841                 "id", 5, "value", "data2"),
842             createExpectedRow(
843                 "id", 7, "value", "data2"),
844             createExpectedRow(
845                 "id", 8, "value", "data2"));
846     }
847     assertEquals(expectedRows, rows);
848 
849     Column valCol = table.getColumn("value");
850     rows = RowFilterTest.toList(
851         cursor.newIterable().setMatchPattern(valCol, "data4"));
852 
853     if(index == null) {
854       expectedRows =
855         createExpectedTable(
856             createExpectedRow(
857                 "id", 9, "value", "data4"),
858             createExpectedRow(
859                 "id", 4, "value", "data4"));
860     } else {
861       expectedRows =
862         createExpectedTable(
863             createExpectedRow(
864                 "id", 4, "value", "data4"),
865             createExpectedRow(
866                 "id", 9, "value", "data4"));
867     }
868     assertEquals(expectedRows, rows);
869 
870     rows = RowFilterTest.toList(
871         cursor.newIterable().setMatchPattern(valCol, "data9"));
872 
873     assertTrue(rows.isEmpty());
874 
875     rows = RowFilterTest.toList(
876         cursor.newIterable().setMatchPattern(
877             Collections.singletonMap("id", 8)));
878 
879     expectedRows =
880       createExpectedTable(
881           createExpectedRow(
882               "id", 8, "value", "data2"),
883           createExpectedRow(
884               "id", 8, "value", "data3"));
885     assertEquals(expectedRows, rows);
886 
887     for(Map<String,Object> row : table) {
888 
889       List<Map<String,Object>> tmpRows = new ArrayList<Map<String,Object>>();
890       for(Map<String,Object> tmpRow : cursor) {
891         if(row.equals(tmpRow)) {
892           tmpRows.add(tmpRow);
893         }
894       }
895       expectedRows = tmpRows;
896       assertFalse(expectedRows.isEmpty());
897 
898       rows = RowFilterTest.toList(cursor.newIterable().setMatchPattern(row));
899 
900       assertEquals(expectedRows, rows);
901     }
902 
903     rows = RowFilterTest.toList(
904         cursor.newIterable().addMatchPattern("id", 8)
905         .addMatchPattern("value", "data13"));
906     assertTrue(rows.isEmpty());
907   }
908 
909   public void testId() throws Exception
910   {
911     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
912       Database db = createTestIndexTable(indexCursorDB);
913 
914       Table table = db.getTable("test");
915       Index idx = table.getIndexes().get(0);
916 
917       Cursor tCursor = CursorBuilder.createCursor(table);
918       Cursor iCursor = CursorBuilder.createCursor(idx);
919 
920       Cursor.Savepoint tSave = tCursor.getSavepoint();
921       Cursor.Savepoint iSave = iCursor.getSavepoint();
922 
923       tCursor.restoreSavepoint(tSave);
924       iCursor.restoreSavepoint(iSave);
925 
926       try {
927         tCursor.restoreSavepoint(iSave);
928         fail("IllegalArgumentException should have been thrown");
929       } catch(IllegalArgumentException e) {
930         // success
931       }
932 
933       try {
934         iCursor.restoreSavepoint(tSave);
935         fail("IllegalArgumentException should have been thrown");
936       } catch(IllegalArgumentException e) {
937         // success
938       }
939 
940       Cursor tCursor2 = CursorBuilder.createCursor(table);
941       Cursor iCursor2 = CursorBuilder.createCursor(idx);
942 
943       tCursor2.restoreSavepoint(tSave);
944       iCursor2.restoreSavepoint(iSave);
945 
946       db.close();
947     }
948   }
949 
950   public void testColumnMatcher() throws Exception {
951 
952 
953     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
954       Database db = createTestTable(fileFormat);
955 
956       Table table = db.getTable("test");
957 
958       doTestMatchers(table, SimpleColumnMatcher.INSTANCE, false);
959       doTestMatchers(table, CaseInsensitiveColumnMatcher.INSTANCE, true);
960 
961       Cursor cursor = CursorBuilder.createCursor(table);
962       doTestMatcher(table, cursor, SimpleColumnMatcher.INSTANCE, false);
963       doTestMatcher(table, cursor, CaseInsensitiveColumnMatcher.INSTANCE,
964                     true);
965       db.close();
966     }
967   }
968 
969   private static void doTestMatchers(Table table, ColumnMatcher columnMatcher,
970                                      boolean caseInsensitive)
971     throws Exception
972   {
973       assertTrue(columnMatcher.matches(table, "value", null, null));
974       assertFalse(columnMatcher.matches(table, "value", "foo", null));
975       assertFalse(columnMatcher.matches(table, "value", null, "foo"));
976       assertTrue(columnMatcher.matches(table, "value", "foo", "foo"));
977       assertTrue(columnMatcher.matches(table, "value", "foo", "Foo")
978                  == caseInsensitive);
979 
980       assertFalse(columnMatcher.matches(table, "value", 13, null));
981       assertFalse(columnMatcher.matches(table, "value", null, 13));
982       assertTrue(columnMatcher.matches(table, "value", 13, 13));
983   }
984 
985   private static void doTestMatcher(Table table, Cursor cursor,
986                                     ColumnMatcher columnMatcher,
987                                     boolean caseInsensitive)
988     throws Exception
989   {
990     cursor.setColumnMatcher(columnMatcher);
991 
992     assertTrue(cursor.findFirstRow(table.getColumn("id"), 3));
993     assertEquals(createExpectedRow("id", 3,
994                                    "value", "data" + 3),
995                  cursor.getCurrentRow());
996 
997     assertTrue(cursor.findFirstRow(createExpectedRow(
998                                     "id", 6,
999                                     "value", "data" + 6)));
1000     assertEquals(createExpectedRow("id", 6,
1001                                    "value", "data" + 6),
1002                  cursor.getCurrentRow());
1003 
1004     assertTrue(cursor.findFirstRow(createExpectedRow(
1005                                     "id", 6,
1006                                     "value", "Data" + 6)) == caseInsensitive);
1007     if(caseInsensitive) {
1008       assertEquals(createExpectedRow("id", 6,
1009                                      "value", "data" + 6),
1010                    cursor.getCurrentRow());
1011     }
1012 
1013     assertFalse(cursor.findFirstRow(createExpectedRow(
1014                                    "id", 8,
1015                                    "value", "data" + 13)));
1016     assertFalse(cursor.findFirstRow(table.getColumn("id"), 13));
1017     assertEquals(createExpectedRow("id", 6,
1018                                    "value", "data" + 6),
1019                  cursor.getCurrentRow());
1020 
1021     assertTrue(cursor.findFirstRow(createExpectedRow(
1022                                     "value", "data" + 7)));
1023     assertEquals(createExpectedRow("id", 7,
1024                                    "value", "data" + 7),
1025                  cursor.getCurrentRow());
1026 
1027     assertTrue(cursor.findFirstRow(createExpectedRow(
1028                                     "value", "Data" + 7)) == caseInsensitive);
1029     if(caseInsensitive) {
1030       assertEquals(createExpectedRow("id", 7,
1031                                      "value", "data" + 7),
1032                    cursor.getCurrentRow());
1033     }
1034 
1035     assertTrue(cursor.findFirstRow(table.getColumn("value"), "data" + 4));
1036     assertEquals(createExpectedRow("id", 4,
1037                                    "value", "data" + 4),
1038                  cursor.getCurrentRow());
1039 
1040     assertTrue(cursor.findFirstRow(table.getColumn("value"), "Data" + 4)
1041                == caseInsensitive);
1042     if(caseInsensitive) {
1043       assertEquals(createExpectedRow("id", 4,
1044                                      "value", "data" + 4),
1045                    cursor.getCurrentRow());
1046     }
1047 
1048     assertEquals(Arrays.asList(createExpectedRow("id", 4,
1049                                                  "value", "data" + 4)),
1050                  RowFilterTest.toList(
1051                      cursor.newIterable()
1052                      .setMatchPattern("value", "data4")
1053                      .setColumnMatcher(SimpleColumnMatcher.INSTANCE)));
1054 
1055     assertEquals(Arrays.asList(createExpectedRow("id", 3,
1056                                                  "value", "data" + 3)),
1057                  RowFilterTest.toList(
1058                      cursor.newIterable()
1059                      .setMatchPattern("value", "DaTa3")
1060                      .setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)));
1061 
1062     assertEquals(Arrays.asList(createExpectedRow("id", 2,
1063                                                  "value", "data" + 2)),
1064                  RowFilterTest.toList(
1065                      cursor.newIterable()
1066                      .addMatchPattern("value", "DaTa2")
1067                      .addMatchPattern("id", 2)
1068                      .setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)));
1069   }
1070 
1071   public void testIndexCursor() throws Exception
1072   {
1073     for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX, true)) {
1074 
1075       Database db = openMem(testDB);
1076       Table t1 = db.getTable("Table1");
1077       Index idx = t1.getIndex(IndexBuilder.PRIMARY_KEY_NAME);
1078       IndexCursor cursor = CursorBuilder.createCursor(idx);
1079 
1080       assertFalse(cursor.findFirstRowByEntry(-1));
1081       cursor.findClosestRowByEntry(-1);
1082       assertEquals(0, cursor.getCurrentRow().get("id"));
1083 
1084       assertTrue(cursor.findFirstRowByEntry(1));
1085       assertEquals(1, cursor.getCurrentRow().get("id"));
1086 
1087       cursor.findClosestRowByEntry(2);
1088       assertEquals(2, cursor.getCurrentRow().get("id"));
1089 
1090       assertFalse(cursor.findFirstRowByEntry(4));
1091       cursor.findClosestRowByEntry(4);
1092       assertTrue(cursor.isAfterLast());
1093 
1094       db.close();
1095     }
1096   }
1097 
1098   public void testIndexCursorDelete() throws Exception
1099   {
1100     for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX)) {
1101 
1102       Database db = openMem(testDB);
1103       Table t1 = db.getTable("Table1");
1104       Index idx = t1.getIndex("Table2Table1");
1105       IndexCursor cursor = CursorBuilder.createCursor(idx);
1106 
1107       List<String> expectedData = cursor.newEntryIterable(1)
1108             .addColumnNames("data")
1109         .stream().map(r -> r.getString("data"))
1110         .collect(Collectors.toList());
1111 
1112       assertEquals(Arrays.asList("baz11", "baz11-2"), expectedData);
1113 
1114       expectedData = new ArrayList<String>();
1115       for(Iterator<? extends Row> iter =
1116             cursor.newEntryIterable(1).iterator();
1117           iter.hasNext(); ) {
1118         expectedData.add(iter.next().getString("data"));
1119         iter.remove();
1120         try {
1121           iter.remove();
1122           fail("IllegalArgumentException should have been thrown");
1123         } catch(IllegalStateException e) {
1124           // success
1125         }
1126 
1127         if(!iter.hasNext()) {
1128           try {
1129             iter.next();
1130             fail("NoSuchElementException should have been thrown");
1131           } catch(NoSuchElementException e) {
1132             // success
1133           }
1134         }
1135       }
1136 
1137       assertEquals(Arrays.asList("baz11", "baz11-2"), expectedData);
1138 
1139       expectedData = new ArrayList<String>();
1140       for(Row row : cursor.newEntryIterable(1)
1141             .addColumnNames("data")) {
1142         expectedData.add(row.getString("data"));
1143       }
1144 
1145       assertTrue(expectedData.isEmpty());
1146 
1147       db.close();
1148     }
1149   }
1150 
1151   public void testCursorDelete() throws Exception
1152   {
1153     for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX)) {
1154 
1155       Database db = openMem(testDB);
1156       Table t1 = db.getTable("Table1");
1157       Cursor cursor = CursorBuilder.createCursor(t1);
1158 
1159       List<String> expectedData = cursor.newIterable().setColumnNames(
1160               Arrays.asList("otherfk1", "data")).stream()
1161         .filter(r -> r.get("otherfk1").equals(1))
1162         .map(r -> r.getString("data"))
1163         .collect(Collectors.toList());
1164 
1165       assertEquals(Arrays.asList("baz11", "baz11-2"), expectedData);
1166 
1167       expectedData = new ArrayList<String>();
1168       for(Iterator<? extends Row> iter = cursor.iterator();
1169           iter.hasNext(); ) {
1170         Row row = iter.next();
1171         if(row.get("otherfk1").equals(1)) {
1172           expectedData.add(row.getString("data"));
1173           iter.remove();
1174           try {
1175             iter.remove();
1176             fail("IllegalArgumentException should have been thrown");
1177           } catch(IllegalStateException e) {
1178             // success
1179           }
1180         }
1181 
1182         if(!iter.hasNext()) {
1183           try {
1184             iter.next();
1185             fail("NoSuchElementException should have been thrown");
1186           } catch(NoSuchElementException e) {
1187             // success
1188           }
1189         }
1190       }
1191 
1192       assertEquals(Arrays.asList("baz11", "baz11-2"), expectedData);
1193 
1194       expectedData = new ArrayList<String>();
1195       for(Row row : cursor.newIterable().setColumnNames(
1196               Arrays.asList("otherfk1", "data"))) {
1197         if(row.get("otherfk1").equals(1)) {
1198           expectedData.add(row.getString("data"));
1199         }
1200       }
1201 
1202       assertTrue(expectedData.isEmpty());
1203 
1204       db.close();
1205     }
1206   }
1207 
1208   public void testFindByRowId() throws Exception {
1209     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
1210       Database db = createTestTable(fileFormat);
1211 
1212       Table table = db.getTable("test");
1213       Cursor cursor = CursorBuilder.createCursor(table);
1214       doTestFindByRowId(cursor);
1215       db.close();
1216     }
1217   }
1218 
1219   public void testFindByRowIdIndex() throws Exception {
1220     for (final TestDB indexCursorDB : INDEX_CURSOR_DBS) {
1221       Database db = createTestIndexTable(indexCursorDB);
1222 
1223       Table table = db.getTable("test");
1224       Index idx = table.getIndexes().get(0);
1225 
1226       assertTable(createUnorderedTestTableData(), table);
1227 
1228       Cursor cursor = CursorBuilder.createCursor(idx);
1229       doTestFindByRowId(cursor);
1230 
1231       db.close();
1232     }
1233   }
1234 
1235   private static void doTestFindByRowId(Cursor cursor)
1236     throws Exception
1237   {
1238     for(int i = 0; i < 3; ++i) {
1239       cursor.moveToNextRow();
1240     }
1241 
1242     Row r1 = cursor.getCurrentRow();
1243 
1244     for(int i = 0; i < 3; ++i) {
1245       cursor.moveToNextRow();
1246     }
1247 
1248     Row r2 = cursor.getCurrentRow();
1249 
1250     doTestFindByRowId(cursor, r1, 2);
1251 
1252     doTestFindByRowId(cursor, r2, 5);
1253   }
1254 
1255   private static void doTestFindByRowId(Cursor cursor, Row row, int id)
1256     throws Exception
1257   {
1258     cursor.reset();
1259     assertTrue(cursor.findRow(row.getId()));
1260     Row rFound = cursor.getCurrentRow();
1261     assertEquals(id, rFound.get("id"));
1262     assertEquals(row, rFound);
1263     Cursor.Savepoint save = cursor.getSavepoint();
1264 
1265     assertTrue(cursor.moveToNextRow());
1266     assertEquals(id + 1, cursor.getCurrentRow().get("id"));
1267 
1268     cursor.restoreSavepoint(save);
1269 
1270     assertTrue(cursor.moveToPreviousRow());
1271     assertEquals(id - 1, cursor.getCurrentRow().get("id"));
1272 
1273     assertFalse(cursor.findRow(RowIdImpl.FIRST_ROW_ID));
1274 
1275     assertEquals(id - 1, cursor.getCurrentRow().get("id"));
1276   }
1277 
1278   public void testIterationEarlyExit() throws Exception {
1279     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
1280 
1281       Database db = createMem(fileFormat);
1282 
1283       Table table = newTable("test")
1284         .addColumn(newColumn("id", DataType.LONG))
1285         .addColumn(newColumn("value", DataType.TEXT))
1286         .addColumn(newColumn("memo", DataType.MEMO))
1287         .addIndex(newIndex("value_idx")
1288                   .addColumns("value"))
1289         .toTable(db);
1290 
1291       for(int i = 0; i < 20; ++i) {
1292         Object memo = "memo-" + i;
1293         table.addRow(i, "val-" + (i/2), memo);
1294       }
1295 
1296       // generate an "invalid" memo
1297       byte[] b = new byte[12];
1298       b[3] = (byte)0xC0;
1299       table.addRow(20, "val-9", ColumnImpl.rawDataWrapper(b));
1300 
1301       IndexCursor cursor = CursorBuilder.createCursor(
1302           table.getIndex("value_idx"));
1303 
1304       try {
1305         cursor.newIterable()
1306           .addMatchPattern("value", "val-9")
1307           .addMatchPattern("memo", "anything")
1308           .iterator().hasNext();
1309         fail("RuntimeIOException should have been thrown");
1310       } catch(RuntimeIOException ignored) {
1311         // success
1312       }
1313 
1314       List<Row> rows = new ArrayList<Row>();
1315       for (Row row : cursor.newIterable()
1316              .addMatchPattern("value", "val-5")
1317              .addMatchPattern("memo", "memo-11")) {
1318         rows.add(row);
1319       }
1320 
1321       assertEquals(rows, createExpectedTable(
1322                        createExpectedRow("id", 11,
1323                                          "value", "val-5",
1324                                          "memo", "memo-11")));
1325 
1326       assertFalse(cursor.newIterable()
1327                   .addMatchPattern("value", "val-31")
1328                   .addMatchPattern("memo", "anything")
1329                   .iterator().hasNext());
1330 
1331       db.close();
1332     }
1333   }
1334 
1335   public void testPartialIndexFind() throws Exception
1336   {
1337     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
1338 
1339       Database db = createMem(fileFormat);
1340 
1341       TableImpl t = (TableImpl)newTable("Test")
1342         .addColumn(newColumn("id", DataType.LONG))
1343         .addColumn(newColumn("data1", DataType.TEXT))
1344         .addColumn(newColumn("num2", DataType.LONG))
1345         .addColumn(newColumn("key3", DataType.TEXT))
1346         .addColumn(newColumn("value", DataType.TEXT))
1347         .addIndex(newIndex("idx3").addColumns("data1", "num2", "key3"))
1348         .toTable(db);
1349 
1350       Index idx = t.findIndexForColumns(Arrays.asList("data1"),
1351                                         TableImpl.IndexFeature.ANY_MATCH);
1352       assertEquals("idx3", idx.getName());
1353 
1354       idx = t.findIndexForColumns(Arrays.asList("data1", "num2"),
1355                                   TableImpl.IndexFeature.ANY_MATCH);
1356       assertEquals("idx3", idx.getName());
1357 
1358       idx = t.findIndexForColumns(Arrays.asList("data1", "num2", "key3"),
1359                                   TableImpl.IndexFeature.ANY_MATCH);
1360       assertEquals("idx3", idx.getName());
1361 
1362       assertNull(t.findIndexForColumns(Arrays.asList("num2"),
1363                                        TableImpl.IndexFeature.ANY_MATCH));
1364       assertNull(t.findIndexForColumns(Arrays.asList("data1", "key3"),
1365                                        TableImpl.IndexFeature.ANY_MATCH));
1366       assertNull(t.findIndexForColumns(Arrays.asList("data1"),
1367                                        TableImpl.IndexFeature.EXACT_MATCH));
1368 
1369 
1370       newIndex("idx2")
1371         .addColumns("data1", "num2")
1372         .addToTable(t);
1373 
1374       idx = t.findIndexForColumns(Arrays.asList("data1"),
1375                                   TableImpl.IndexFeature.ANY_MATCH);
1376       assertEquals("idx2", idx.getName());
1377 
1378       idx = t.findIndexForColumns(Arrays.asList("data1", "num2"),
1379                                   TableImpl.IndexFeature.ANY_MATCH);
1380       assertEquals("idx2", idx.getName());
1381 
1382       idx = t.findIndexForColumns(Arrays.asList("data1", "num2", "key3"),
1383                                   TableImpl.IndexFeature.ANY_MATCH);
1384       assertEquals("idx3", idx.getName());
1385 
1386       assertNull(t.findIndexForColumns(Arrays.asList("num2"),
1387                                        TableImpl.IndexFeature.ANY_MATCH));
1388       assertNull(t.findIndexForColumns(Arrays.asList("data1", "key3"),
1389                                        TableImpl.IndexFeature.ANY_MATCH));
1390       assertNull(t.findIndexForColumns(Arrays.asList("data1"),
1391                                        TableImpl.IndexFeature.EXACT_MATCH));
1392 
1393 
1394       newIndex("idx1")
1395         .addColumns("data1")
1396         .addToTable(t);
1397 
1398       idx = t.findIndexForColumns(Arrays.asList("data1"),
1399                                   TableImpl.IndexFeature.ANY_MATCH);
1400       assertEquals("idx1", idx.getName());
1401 
1402       idx = t.findIndexForColumns(Arrays.asList("data1", "num2"),
1403                                   TableImpl.IndexFeature.ANY_MATCH);
1404       assertEquals("idx2", idx.getName());
1405 
1406       idx = t.findIndexForColumns(Arrays.asList("data1", "num2", "key3"),
1407                                   TableImpl.IndexFeature.ANY_MATCH);
1408       assertEquals("idx3", idx.getName());
1409 
1410       assertNull(t.findIndexForColumns(Arrays.asList("num2"),
1411                                        TableImpl.IndexFeature.ANY_MATCH));
1412       assertNull(t.findIndexForColumns(Arrays.asList("data1", "key3"),
1413                                        TableImpl.IndexFeature.ANY_MATCH));
1414 
1415       db.close();
1416     }
1417   }
1418 
1419   public void testPartialIndexLookup() throws Exception
1420   {
1421     for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
1422 
1423       Database db = createMem(fileFormat);
1424 
1425       TableImpl t = (TableImpl)newTable("Test")
1426         .addColumn(newColumn("id", DataType.LONG))
1427         .addColumn(newColumn("data1", DataType.TEXT))
1428         .addColumn(newColumn("num2", DataType.LONG))
1429         .addColumn(newColumn("key3", DataType.TEXT))
1430         .addColumn(newColumn("value", DataType.TEXT))
1431         .addIndex(newIndex("idx3")
1432                   .addColumns(true, "data1")
1433                   .addColumns(false, "num2")
1434                   .addColumns(true, "key3")
1435                   )
1436         .toTable(db);
1437 
1438       int id = 1;
1439       for(String str : Arrays.asList("A", "B", "C", "D")) {
1440         for(int i = 4; i >= 0; --i) {
1441         // for(int i = 0; i < 5; ++i) {
1442           for(int j = 1; j < 3; ++j) {
1443             t.addRow(id, str, i, "K" + j, "value" + id);
1444             ++id;
1445           }
1446         }
1447       }
1448 
1449       Index idx = t.getIndex("idx3");
1450       doPartialIndexLookup(idx);
1451 
1452       idx = newIndex("idx2")
1453                   .addColumns(true, "data1")
1454                   .addColumns(false, "num2")
1455         .addToTable(t);
1456       doPartialIndexLookup(idx);
1457 
1458       idx = newIndex("idx1")
1459                   .addColumns(true, "data1")
1460         .addToTable(t);
1461       doPartialIndexLookup(idx);
1462 
1463       db.close();
1464     }
1465   }
1466 
1467   private static void doPartialIndexLookup(Index idx) throws Exception
1468   {
1469     int colCount = idx.getColumnCount();
1470     IndexCursor c = idx.newCursor().toIndexCursor();
1471 
1472     doFindFirstByEntry(c, 21, "C");
1473     doFindFirstByEntry(c, null, "Z");
1474 
1475     if(colCount > 1) {
1476       doFindFirstByEntry(c, 23, "C", 3);
1477       doFindFirstByEntry(c, null, "C", 20);
1478     }
1479 
1480     if(colCount > 2) {
1481       doFindFirstByEntry(c, 27, "C", 1, "K1");
1482       doFindFirstByEntry(c, null, "C", 4, "K3");
1483     }
1484 
1485     try {
1486       if(colCount > 2) {
1487         c.findFirstRowByEntry("C", 4, "K1", 14);
1488       } else if(colCount > 1) {
1489         c.findFirstRowByEntry("C", 4, "K1");
1490       } else {
1491         c.findFirstRowByEntry("C", 4);
1492       }
1493       fail("IllegalArgumentException should have been thrown");
1494     } catch(IllegalArgumentException expected) {
1495       // scucess
1496     }
1497 
1498     doFindByEntryRange(c, 11, 20, "B");
1499     doFindByEntry(c, new int[]{}, "Z");
1500 
1501     if(colCount > 1) {
1502       doFindByEntryRange(c, 13, 14, "B", 3);
1503       doFindByEntry(c, new int[]{}, "B", 20);
1504     }
1505 
1506     if(colCount > 2) {
1507       doFindByEntryRange(c, 14, 14, "B", 3, "K2");
1508       doFindByEntry(c, new int[]{}, "B", 3, "K3");
1509     }
1510 
1511     doFindByRow(idx, 13,
1512                 "data1", "B", "value", "value13");
1513     doFindByRow(idx, 13,
1514                 "data1", "B", "key3", "K1", "value", "value13");
1515     doFindByRow(idx, 13,
1516         "data1", "B", "num2", 3, "key3", "K1", "value", "value13");
1517     doFindByRow(idx, 13,
1518         "num2", 3, "value", "value13");
1519     doFindByRow(idx, 13,
1520         "value", "value13");
1521     doFindByRow(idx, null,
1522         "data1", "B", "num2", 5, "key3", "K1", "value", "value13");
1523     doFindByRow(idx, null,
1524         "data1", "B", "value", "value4");
1525 
1526     Column col = idx.getTable().getColumn("data1");
1527     doFindValue(idx, 21, col, "C");
1528     doFindValue(idx, null, col, "Z");
1529     col = idx.getTable().getColumn("value");
1530     doFindValue(idx, 21, col, "value21");
1531     doFindValue(idx, null, col, "valueZ");
1532   }
1533 
1534   private static void doFindFirstByEntry(IndexCursor c, Integer expectedId,
1535                                          Object... entry)
1536     throws Exception
1537   {
1538     if(expectedId != null) {
1539       assertTrue(c.findFirstRowByEntry(entry));
1540       assertEquals(expectedId, c.getCurrentRow().get("id"));
1541     } else {
1542       assertFalse(c.findFirstRowByEntry(entry));
1543     }
1544   }
1545 
1546   private static void doFindByEntryRange(IndexCursor c, int start, int end,
1547                                          Object... entry)
1548   {
1549     List<Integer> expectedIds = new ArrayList<Integer>();
1550     for(int i = start; i <= end; ++i) {
1551       expectedIds.add(i);
1552     }
1553     doFindByEntry(c, expectedIds, entry);
1554   }
1555 
1556   private static void doFindByEntry(IndexCursor c, int[] ids,
1557                                     Object... entry)
1558   {
1559     List<Integer> expectedIds = new ArrayList<Integer>();
1560     for(int id : ids) {
1561       expectedIds.add(id);
1562     }
1563     doFindByEntry(c, expectedIds, entry);
1564   }
1565 
1566   private static void doFindByEntry(IndexCursor c, List<Integer> expectedIds,
1567                                     Object... entry)
1568   {
1569     List<Integer> foundIds = new ArrayList<Integer>();
1570     for(Row row : c.newEntryIterable(entry)) {
1571       foundIds.add((Integer)row.get("id"));
1572     }
1573     assertEquals(expectedIds, foundIds);
1574   }
1575 
1576   private static void doFindByRow(Index idx, Integer id, Object... rowPairs)
1577     throws Exception
1578   {
1579     Map<String,Object> map = createExpectedRow(
1580         rowPairs);
1581     Row r = CursorBuilder.findRow(idx, map);
1582     if(id != null) {
1583       assertEquals(id, r.get("id"));
1584     } else {
1585       assertNull(r);
1586     }
1587   }
1588 
1589   private static void doFindValue(Index idx, Integer id,
1590                                   Column columnPattern, Object valuePattern)
1591     throws Exception
1592   {
1593     Object value = CursorBuilder.findValue(
1594         idx, idx.getTable().getColumn("id"), columnPattern, valuePattern);
1595     if(id != null) {
1596       assertEquals(id, value);
1597     } else {
1598       assertNull(value);
1599     }
1600   }
1601 }