1   /*
2   Copyright (c) 2007 Health Market Science, Inc.
3   
4   This library is free software; you can redistribute it and/or
5   modify it under the terms of the GNU Lesser General Public
6   License as published by the Free Software Foundation; either
7   version 2.1 of the License, or (at your option) any later version.
8   
9   This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  Lesser General Public License for more details.
13  
14  You should have received a copy of the GNU Lesser General Public
15  License along with this library; if not, write to the Free Software
16  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
17  USA
18  
19  You can contact Health Market Science at info@healthmarketscience.com
20  or at the following address:
21  
22  Health Market Science
23  2700 Horizon Drive
24  Suite 200
25  King of Prussia, PA 19406
26  */
27  
28  package com.healthmarketscience.jackcess;
29  
30  import java.io.File;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.TreeSet;
37  
38  import junit.framework.TestCase;
39  
40  import static com.healthmarketscience.jackcess.DatabaseTest.*;
41  
42  /**
43   * @author James Ahlborn
44   */
45  public class CursorTest extends TestCase {
46  
47    public CursorTest(String name) throws Exception {
48      super(name);
49    }
50  
51    private static List<Map<String,Object>> createTestTableData()
52      throws Exception
53    {
54      List<Map<String,Object>> expectedRows =
55        new ArrayList<Map<String,Object>>();
56      for(int i = 0; i < 10; ++i) {
57        expectedRows.add(createExpectedRow("id", i, "value", "data" + i));
58      }
59      return expectedRows;
60    }
61  
62    private static List<Map<String,Object>> createTestTableData(
63        int startIdx,
64        int endIdx)
65      throws Exception
66    {
67      List<Map<String,Object>> expectedRows = createTestTableData();
68      expectedRows.subList(endIdx, expectedRows.size()).clear();
69      expectedRows.subList(0, startIdx).clear();
70      return expectedRows;
71    }
72    
73    private static Database createTestTable() throws Exception {
74      Database db = create();
75  
76      Table table = new TableBuilder("test")
77        .addColumn(new ColumnBuilder("id", DataType.LONG).toColumn())
78        .addColumn(new ColumnBuilder("value", DataType.TEXT).toColumn())
79        .toTable(db);
80  
81      for(Map<String,Object> row : createTestTableData()) {
82        table.addRow(row.get("id"), row.get("value"));
83      }
84  
85      return db;
86    }
87  
88    private static List<Map<String,Object>> createUnorderedTestTableData()
89      throws Exception
90    {
91      List<Map<String,Object>> expectedRows =
92        new ArrayList<Map<String,Object>>();
93      int[] ids = new int[]{3, 7, 6, 1, 2, 9, 0, 5, 4, 8};
94      for(int i : ids) {
95        expectedRows.add(createExpectedRow("id", i, "value", "data" + i));
96      }
97      return expectedRows;
98    }  
99    
100   static Database createTestIndexTable() throws Exception {
101     Database db = openCopy(new File("test/data/indexCursorTest.mdb"));
102 
103     Table table = db.getTable("test");
104 
105     for(Map<String,Object> row : createUnorderedTestTableData()) {
106       table.addRow(row.get("id"), row.get("value"));
107     }
108     
109     return db;
110   }
111 
112   private static Cursor createIndexSubRangeCursor(Table table,
113                                                   Index idx,
114                                                   int type)
115     throws Exception
116   {
117     return new CursorBuilder(table)
118       .setIndex(idx)
119       .setStartEntry(3 - type)
120       .setStartRowInclusive(type == 0)
121       .setEndEntry(8 + type)
122       .setEndRowInclusive(type == 0)
123       .toCursor();
124   }
125   
126   public void testRowId() throws Exception {
127     // test special cases
128     RowId rowId1 = new RowId(1, 2);
129     RowId rowId2 = new RowId(1, 3);
130     RowId rowId3 = new RowId(2, 1);
131 
132     List<RowId> sortedRowIds = new ArrayList<RowId>(new TreeSet<RowId>(
133         Arrays.asList(rowId1, rowId2, rowId3, RowId.FIRST_ROW_ID,
134                       RowId.LAST_ROW_ID)));
135 
136     assertEquals(Arrays.asList(RowId.FIRST_ROW_ID, rowId1, rowId2, rowId3,
137                                RowId.LAST_ROW_ID),
138                  sortedRowIds);
139   }
140   
141   public void testSimple() throws Exception {
142     Database db = createTestTable();
143 
144     Table table = db.getTable("test");
145     Cursor cursor = Cursor.createCursor(table);
146     doTestSimple(table, cursor, null);
147     db.close();
148   }
149 
150   private void doTestSimple(Table table, Cursor cursor,
151                             List<Map<String,Object>> expectedRows)
152     throws Exception
153   {
154     if(expectedRows == null) {
155       expectedRows = createTestTableData();
156     }
157 
158     List<Map<String, Object>> foundRows =
159       new ArrayList<Map<String, Object>>();
160     for(Map<String, Object> row : cursor) {
161       foundRows.add(row);
162     }
163     assertEquals(expectedRows, foundRows);
164   }
165 
166   public void testMove() throws Exception {
167     Database db = createTestTable();
168 
169     Table table = db.getTable("test");
170     Cursor cursor = Cursor.createCursor(table);
171     doTestMove(table, cursor, null);
172     
173     db.close();
174   }
175 
176   private void doTestMove(Table table, Cursor cursor,
177                           List<Map<String,Object>> expectedRows)
178     throws Exception
179   {
180     if(expectedRows == null) {
181       expectedRows = createTestTableData();
182     }
183     expectedRows.subList(1, 4).clear();
184 
185     List<Map<String, Object>> foundRows =
186       new ArrayList<Map<String, Object>>();
187     assertTrue(cursor.isBeforeFirst());
188     assertFalse(cursor.isAfterLast());
189     foundRows.add(cursor.getNextRow());
190     assertEquals(3, cursor.moveNextRows(3));
191     assertFalse(cursor.isBeforeFirst());
192     assertFalse(cursor.isAfterLast());
193 
194     Map<String,Object> expectedRow = cursor.getCurrentRow();
195     Cursor.Savepoint savepoint = cursor.getSavepoint();
196     assertEquals(2, cursor.movePreviousRows(2));
197     assertEquals(2, cursor.moveNextRows(2));
198     assertTrue(cursor.moveToNextRow());
199     assertTrue(cursor.moveToPreviousRow());
200     assertEquals(expectedRow, cursor.getCurrentRow());
201     
202     while(cursor.moveToNextRow()) {
203       foundRows.add(cursor.getCurrentRow());
204     }
205     assertEquals(expectedRows, foundRows);
206     assertFalse(cursor.isBeforeFirst());
207     assertTrue(cursor.isAfterLast());
208 
209     assertEquals(0, cursor.moveNextRows(3));
210 
211     cursor.beforeFirst();
212     assertTrue(cursor.isBeforeFirst());
213     assertFalse(cursor.isAfterLast());
214 
215     cursor.afterLast();
216     assertFalse(cursor.isBeforeFirst());
217     assertTrue(cursor.isAfterLast());
218 
219     cursor.restoreSavepoint(savepoint);
220     assertEquals(expectedRow, cursor.getCurrentRow());    
221   }
222 
223   public void testSearch() throws Exception {
224     Database db = createTestTable();
225 
226     Table table = db.getTable("test");
227     Cursor cursor = Cursor.createCursor(table);
228     doTestSearch(table, cursor, null, 42, -13);
229     
230     db.close();
231   }
232 
233   private void doTestSearch(Table table, Cursor cursor, Index index,
234                             Integer... outOfRangeValues)
235     throws Exception
236   {
237     assertTrue(cursor.findRow(table.getColumn("id"), 3));
238     assertEquals(createExpectedRow("id", 3,
239                                    "value", "data" + 3),
240                  cursor.getCurrentRow());
241 
242     assertTrue(cursor.findRow(createExpectedRow(
243                                     "id", 6,
244                                     "value", "data" + 6)));
245     assertEquals(createExpectedRow("id", 6,
246                                    "value", "data" + 6),
247                  cursor.getCurrentRow());
248 
249     assertFalse(cursor.findRow(createExpectedRow(
250                                    "id", 8,
251                                    "value", "data" + 13)));
252     assertFalse(cursor.findRow(table.getColumn("id"), 13));
253     assertEquals(createExpectedRow("id", 6,
254                                    "value", "data" + 6),
255                  cursor.getCurrentRow());
256 
257     assertTrue(cursor.findRow(createExpectedRow(
258                                     "value", "data" + 7)));
259     assertEquals(createExpectedRow("id", 7,
260                                    "value", "data" + 7),
261                  cursor.getCurrentRow());
262     
263     assertTrue(cursor.findRow(table.getColumn("value"), "data" + 4));
264     assertEquals(createExpectedRow("id", 4,
265                                    "value", "data" + 4),
266                  cursor.getCurrentRow());
267 
268     for(Integer outOfRangeValue : outOfRangeValues) {
269       assertFalse(cursor.findRow(table.getColumn("id"),
270                                  outOfRangeValue));
271       assertFalse(cursor.findRow(table.getColumn("value"),
272                                  "data" + outOfRangeValue));
273       assertFalse(cursor.findRow(createExpectedRow(
274                                      "id", outOfRangeValue,
275                                      "value", "data" + outOfRangeValue)));
276     }
277     
278     assertEquals("data" + 5,
279                  Cursor.findValue(table,
280                                   table.getColumn("value"),
281                                   table.getColumn("id"), 5));
282     assertEquals(createExpectedRow("id", 5,
283                                    "value", "data" + 5),
284                  Cursor.findRow(table,
285                                 createExpectedRow("id", 5)));
286     if(index != null) {
287       assertEquals("data" + 5,
288                    Cursor.findValue(table, index,
289                                     table.getColumn("value"),
290                                     table.getColumn("id"), 5));
291       assertEquals(createExpectedRow("id", 5,
292                                      "value", "data" + 5),
293                    Cursor.findRow(table, index,
294                                   createExpectedRow("id", 5)));
295 
296       assertNull(Cursor.findValue(table, index,
297                                   table.getColumn("value"),
298                                   table.getColumn("id"),
299                                   -17));
300       assertNull(Cursor.findRow(table, index,
301                                 createExpectedRow("id", 13)));
302     }
303   }
304 
305   public void testReverse() throws Exception {
306     Database db = createTestTable();
307 
308     Table table = db.getTable("test");
309     Cursor cursor = Cursor.createCursor(table);
310     doTestReverse(table, cursor, null);
311 
312     db.close();
313   }
314 
315   private void doTestReverse(Table table, Cursor cursor,
316                              List<Map<String,Object>> expectedRows)
317     throws Exception
318   {
319     if(expectedRows == null) {
320       expectedRows = createTestTableData();
321     }
322     Collections.reverse(expectedRows);
323 
324     List<Map<String, Object>> foundRows =
325       new ArrayList<Map<String, Object>>();
326     for(Map<String, Object> row : cursor.reverseIterable()) {
327       foundRows.add(row);
328     }
329     assertEquals(expectedRows, foundRows);    
330   }
331   
332   public void testLiveAddition() throws Exception {
333     Database db = createTestTable();
334 
335     Table table = db.getTable("test");
336 
337     Cursor cursor1 = Cursor.createCursor(table);
338     Cursor cursor2 = Cursor.createCursor(table);
339     doTestLiveAddition(table, cursor1, cursor2, 11);
340     
341     db.close();
342   }
343 
344   private void doTestLiveAddition(Table table,
345                                   Cursor cursor1,
346                                   Cursor cursor2,
347                                   Integer newRowNum) throws Exception
348   {
349     cursor1.moveNextRows(11);
350     cursor2.moveNextRows(11);
351 
352     assertTrue(cursor1.isAfterLast());
353     assertTrue(cursor2.isAfterLast());
354 
355     table.addRow(newRowNum, "data" + newRowNum);
356     Map<String,Object> expectedRow = 
357       createExpectedRow("id", newRowNum, "value", "data" + newRowNum);
358 
359     assertFalse(cursor1.isAfterLast());
360     assertFalse(cursor2.isAfterLast());
361 
362     assertEquals(expectedRow, cursor1.getCurrentRow());
363     assertEquals(expectedRow, cursor2.getCurrentRow());
364     assertFalse(cursor1.moveToNextRow());
365     assertFalse(cursor2.moveToNextRow());
366     assertTrue(cursor1.isAfterLast());
367     assertTrue(cursor2.isAfterLast());
368   }
369 
370   
371   public void testLiveDeletion() throws Exception {
372     Database db = createTestTable();
373 
374     Table table = db.getTable("test");
375 
376     Cursor cursor1 = Cursor.createCursor(table);
377     Cursor cursor2 = Cursor.createCursor(table);
378     Cursor cursor3 = Cursor.createCursor(table);
379     Cursor cursor4 = Cursor.createCursor(table);
380     doTestLiveDeletion(table, cursor1, cursor2, cursor3, cursor4, 1);
381     
382     db.close();
383   }
384 
385   private void doTestLiveDeletion(Table table,
386                                   Cursor cursor1,
387                                   Cursor cursor2,
388                                   Cursor cursor3,
389                                   Cursor cursor4,
390                                   int firstValue) throws Exception
391   {
392     assertEquals(2, cursor1.moveNextRows(2));
393     assertEquals(3, cursor2.moveNextRows(3));
394     assertEquals(3, cursor3.moveNextRows(3));
395     assertEquals(4, cursor4.moveNextRows(4));
396 
397     Map<String,Object> expectedPrevRow =
398       createExpectedRow("id", firstValue, "value", "data" + firstValue);
399     ++firstValue;
400     Map<String,Object> expectedDeletedRow =
401       createExpectedRow("id", firstValue, "value", "data" + firstValue);
402     ++firstValue;
403     Map<String,Object> expectedNextRow =
404       createExpectedRow("id", firstValue, "value", "data" + firstValue);
405 
406     assertEquals(expectedDeletedRow, cursor2.getCurrentRow());
407     assertEquals(expectedDeletedRow, cursor3.getCurrentRow());
408     
409     assertFalse(cursor2.isCurrentRowDeleted());
410     assertFalse(cursor3.isCurrentRowDeleted());
411 
412     cursor2.deleteCurrentRow();
413 
414     assertTrue(cursor2.isCurrentRowDeleted());
415     assertTrue(cursor3.isCurrentRowDeleted());
416 
417     assertEquals(expectedNextRow, cursor1.getNextRow());
418     assertEquals(expectedNextRow, cursor2.getNextRow());
419     assertEquals(expectedNextRow, cursor3.getNextRow());
420     
421     assertEquals(expectedPrevRow, cursor3.getPreviousRow());
422 
423     assertTrue(cursor3.moveToNextRow());
424     cursor3.deleteCurrentRow();
425     assertTrue(cursor3.isCurrentRowDeleted());
426 
427     firstValue += 2;
428     expectedNextRow =
429       createExpectedRow("id", firstValue, "value", "data" + firstValue);
430     assertTrue(cursor3.moveToNextRow());
431     assertEquals(expectedNextRow, cursor3.getNextRow());
432 
433     cursor1.beforeFirst();
434     assertTrue(cursor1.moveToNextRow());
435     cursor1.deleteCurrentRow();
436     assertFalse(cursor1.isBeforeFirst());
437     assertFalse(cursor1.isAfterLast());
438     assertFalse(cursor1.moveToPreviousRow());
439     assertTrue(cursor1.isBeforeFirst());
440     assertFalse(cursor1.isAfterLast());
441 
442     cursor1.afterLast();
443     assertTrue(cursor1.moveToPreviousRow());
444     cursor1.deleteCurrentRow();
445     assertFalse(cursor1.isBeforeFirst());
446     assertFalse(cursor1.isAfterLast());
447     assertFalse(cursor1.moveToNextRow());
448     assertFalse(cursor1.isBeforeFirst());
449     assertTrue(cursor1.isAfterLast());
450 
451     cursor1.beforeFirst();
452     while(cursor1.moveToNextRow()) {
453       cursor1.deleteCurrentRow();
454     }
455 
456     assertTrue(cursor1.isAfterLast());
457     assertTrue(cursor2.isCurrentRowDeleted());
458     assertTrue(cursor3.isCurrentRowDeleted());
459     assertTrue(cursor4.isCurrentRowDeleted());
460   }
461 
462   public void testSimpleIndex() throws Exception {
463     Database db = createTestIndexTable();
464 
465     Table table = db.getTable("test");
466     Index idx = table.getIndexes().get(0);
467 
468     assertTable(createUnorderedTestTableData(), table);
469 
470     Cursor cursor = Cursor.createIndexCursor(table, idx);
471     doTestSimple(table, cursor, null);
472 
473     db.close();
474   }
475 
476   public void testMoveIndex() throws Exception {
477     Database db = createTestIndexTable();
478 
479     Table table = db.getTable("test");
480     Index idx = table.getIndexes().get(0);
481     Cursor cursor = Cursor.createIndexCursor(table, idx);
482     doTestMove(table, cursor, null);
483     
484     db.close();
485   }
486   
487   public void testReverseIndex() throws Exception {
488     Database db = createTestIndexTable();
489 
490     Table table = db.getTable("test");
491     Index idx = table.getIndexes().get(0);
492     Cursor cursor = Cursor.createIndexCursor(table, idx);
493     doTestReverse(table, cursor, null);
494 
495     db.close();
496   }
497 
498   public void testSearchIndex() throws Exception {
499     Database db = createTestIndexTable();
500 
501     Table table = db.getTable("test");
502     Index idx = table.getIndexes().get(0);
503     Cursor cursor = Cursor.createIndexCursor(table, idx);
504     doTestSearch(table, cursor, idx, 42, -13);
505     
506     db.close();
507   }
508 
509   public void testLiveAdditionIndex() throws Exception {
510     Database db = createTestIndexTable();
511 
512     Table table = db.getTable("test");
513     Index idx = table.getIndexes().get(0);
514 
515     Cursor cursor1 = Cursor.createIndexCursor(table, idx);
516     Cursor cursor2 = Cursor.createIndexCursor(table, idx);
517     doTestLiveAddition(table, cursor1, cursor2, 11);
518     
519     db.close();
520   }
521 
522   public void testLiveDeletionIndex() throws Exception {
523     Database db = createTestIndexTable();
524 
525     Table table = db.getTable("test");
526     Index idx = table.getIndexes().get(0);
527 
528     Cursor cursor1 = Cursor.createIndexCursor(table, idx);
529     Cursor cursor2 = Cursor.createIndexCursor(table, idx);
530     Cursor cursor3 = Cursor.createIndexCursor(table, idx);
531     Cursor cursor4 = Cursor.createIndexCursor(table, idx);
532     doTestLiveDeletion(table, cursor1, cursor2, cursor3, cursor4, 1);
533     
534     db.close();
535   }
536 
537   public void testSimpleIndexSubRange() throws Exception {
538     for(int i = 0; i < 2; ++i) {
539       Database db = createTestIndexTable();
540 
541       Table table = db.getTable("test");
542       Index idx = table.getIndexes().get(0);
543 
544       Cursor cursor = createIndexSubRangeCursor(table, idx, i);
545 
546       List<Map<String,Object>> expectedRows =
547         createTestTableData(3, 9);
548 
549       doTestSimple(table, cursor, expectedRows);
550     
551       db.close();
552     }
553   }
554   
555   public void testMoveIndexSubRange() throws Exception {
556     for(int i = 0; i < 2; ++i) {
557       Database db = createTestIndexTable();
558 
559       Table table = db.getTable("test");
560       Index idx = table.getIndexes().get(0);
561 
562       Cursor cursor = createIndexSubRangeCursor(table, idx, i);
563 
564       List<Map<String,Object>> expectedRows =
565         createTestTableData(3, 9);
566 
567       doTestMove(table, cursor, expectedRows);
568     
569       db.close();
570     }
571   }
572   
573   public void testSearchIndexSubRange() throws Exception {
574     for(int i = 0; i < 2; ++i) {
575       Database db = createTestIndexTable();
576 
577       Table table = db.getTable("test");
578       Index idx = table.getIndexes().get(0);
579 
580       Cursor cursor = createIndexSubRangeCursor(table, idx, i);
581 
582       doTestSearch(table, cursor, idx, 2, 9);
583     
584       db.close();
585     }
586   }
587 
588   public void testReverseIndexSubRange() throws Exception {
589     for(int i = 0; i < 2; ++i) {
590       Database db = createTestIndexTable();
591 
592       Table table = db.getTable("test");
593       Index idx = table.getIndexes().get(0);
594 
595       Cursor cursor = createIndexSubRangeCursor(table, idx, i);
596 
597       List<Map<String,Object>> expectedRows =
598         createTestTableData(3, 9);
599 
600       doTestReverse(table, cursor, expectedRows);
601 
602       db.close();
603     }
604   }
605 
606   public void testLiveAdditionIndexSubRange() throws Exception {
607     for(int i = 0; i < 2; ++i) {
608       Database db = createTestIndexTable();
609 
610       Table table = db.getTable("test");
611       Index idx = table.getIndexes().get(0);
612 
613       Cursor cursor1 = createIndexSubRangeCursor(table, idx, i);
614       Cursor cursor2 = createIndexSubRangeCursor(table, idx, i);
615 
616       doTestLiveAddition(table, cursor1, cursor2, 8);
617     
618       db.close();
619     }
620   }
621   
622   public void testLiveDeletionIndexSubRange() throws Exception {
623     for(int i = 0; i < 2; ++i) {
624       Database db = createTestIndexTable();
625 
626       Table table = db.getTable("test");
627       Index idx = table.getIndexes().get(0);
628 
629       Cursor cursor1 = createIndexSubRangeCursor(table, idx, i);
630       Cursor cursor2 = createIndexSubRangeCursor(table, idx, i);
631       Cursor cursor3 = createIndexSubRangeCursor(table, idx, i);
632       Cursor cursor4 = createIndexSubRangeCursor(table, idx, i);
633 
634       doTestLiveDeletion(table, cursor1, cursor2, cursor3, cursor4, 4);
635 
636       db.close();
637     }    
638   }
639 
640   public void testId() throws Exception
641   {
642     Database db = createTestIndexTable();
643 
644     Table table = db.getTable("test");
645     Index idx = table.getIndexes().get(0);
646 
647     Cursor tCursor = Cursor.createCursor(table);
648     Cursor iCursor = Cursor.createIndexCursor(table, idx);
649 
650     Cursor.Savepoint tSave = tCursor.getSavepoint();
651     Cursor.Savepoint iSave = iCursor.getSavepoint();
652 
653     tCursor.restoreSavepoint(tSave);
654     iCursor.restoreSavepoint(iSave);
655 
656     try {
657       tCursor.restoreSavepoint(iSave);
658       fail("IllegalArgumentException should have been thrown");
659     } catch(IllegalArgumentException e) {
660       // success
661     }
662 
663     try {
664       iCursor.restoreSavepoint(tSave);
665       fail("IllegalArgumentException should have been thrown");
666     } catch(IllegalArgumentException e) {
667       // success
668     }
669 
670     Cursor tCursor2 = Cursor.createCursor(table);
671     Cursor iCursor2 = Cursor.createIndexCursor(table, idx);
672 
673     tCursor2.restoreSavepoint(tSave);
674     iCursor2.restoreSavepoint(iSave);
675 
676     db.close();
677   }
678   
679 }