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.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.SortedSet;
26  import java.util.TreeSet;
27  
28  import static com.healthmarketscience.jackcess.Database.*;
29  import com.healthmarketscience.jackcess.impl.ByteUtil;
30  import com.healthmarketscience.jackcess.impl.IndexCodesTest;
31  import com.healthmarketscience.jackcess.impl.IndexData;
32  import com.healthmarketscience.jackcess.impl.IndexImpl;
33  import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
34  import com.healthmarketscience.jackcess.impl.RowIdImpl;
35  import com.healthmarketscience.jackcess.impl.TableImpl;
36  import junit.framework.TestCase;
37  import static com.healthmarketscience.jackcess.TestUtil.*;
38  import static com.healthmarketscience.jackcess.DatabaseBuilder.*;
39  
40  /**
41   * @author James Ahlborn
42   */
43  public class IndexTest extends TestCase {
44  
45    public IndexTest(String name) {
46      super(name);
47    }
48  
49    @Override
50    protected void setUp() {
51      TestUtil.setTestAutoSync(false);
52    }
53  
54    @Override
55    protected void tearDown() {
56      TestUtil.clearTestAutoSync();
57    }
58  
59    public void testByteOrder() throws Exception {
60      byte b1 = (byte)0x00;
61      byte b2 = (byte)0x01;
62      byte b3 = (byte)0x7F;
63      byte b4 = (byte)0x80;
64      byte b5 = (byte)0xFF;
65  
66      assertTrue(ByteUtil.asUnsignedByte(b1) < ByteUtil.asUnsignedByte(b2));
67      assertTrue(ByteUtil.asUnsignedByte(b2) < ByteUtil.asUnsignedByte(b3));
68      assertTrue(ByteUtil.asUnsignedByte(b3) < ByteUtil.asUnsignedByte(b4));
69      assertTrue(ByteUtil.asUnsignedByte(b4) < ByteUtil.asUnsignedByte(b5));
70    }
71  
72    public void testByteCodeComparator() {
73      byte[] b0 = null;
74      byte[] b1 = new byte[]{(byte)0x00};
75      byte[] b2 = new byte[]{(byte)0x00, (byte)0x00};
76      byte[] b3 = new byte[]{(byte)0x00, (byte)0x01};
77      byte[] b4 = new byte[]{(byte)0x01};
78      byte[] b5 = new byte[]{(byte)0x80};
79      byte[] b6 = new byte[]{(byte)0xFF};
80      byte[] b7 = new byte[]{(byte)0xFF, (byte)0x00};
81      byte[] b8 = new byte[]{(byte)0xFF, (byte)0x01};
82  
83      List<byte[]> expectedList = Arrays.<byte[]>asList(b0, b1, b2, b3, b4,
84                                                        b5, b6, b7, b8);
85      SortedSet<byte[]> sortedSet = new TreeSet<byte[]>(
86          IndexData.BYTE_CODE_COMPARATOR);
87      sortedSet.addAll(expectedList);
88      assertEquals(expectedList, new ArrayList<byte[]>(sortedSet));
89  
90    }
91  
92    public void testPrimaryKey() throws Exception {
93      for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) {
94        Table table = open(testDB).getTable("Table1");
95        Map<String, Boolean> foundPKs = new HashMap<String, Boolean>();
96        Index pkIndex = null;
97        for(Index index : table.getIndexes()) {
98          foundPKs.put(index.getColumns().iterator().next().getName(),
99                       index.isPrimaryKey());
100         if(index.isPrimaryKey()) {
101           pkIndex= index;
102 
103         }
104       }
105       Map<String, Boolean> expectedPKs = new HashMap<String, Boolean>();
106       expectedPKs.put("A", Boolean.TRUE);
107       expectedPKs.put("B", Boolean.FALSE);
108       assertEquals(expectedPKs, foundPKs);
109       assertSame(pkIndex, table.getPrimaryKeyIndex());
110     }
111   }
112 
113   public void testLogicalIndexes() throws Exception
114   {
115     for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX, true)) {
116       Database mdb = open(testDB);
117 
118       TableImpl table = (TableImpl)mdb.getTable("Table1");
119       for(IndexImpl idx : table.getIndexes()) {
120         idx.initialize();
121       }
122       assertEquals(4, table.getIndexes().size());
123       assertEquals(4, table.getLogicalIndexCount());
124       checkIndexColumns(table,
125                         "id", "id",
126                         "PrimaryKey", "id",
127                         "Table2Table1", "otherfk1",
128                         "Table3Table1", "otherfk2");
129 
130       table = (TableImpl)mdb.getTable("Table2");
131       for(IndexImpl idx : table.getIndexes()) {
132         idx.initialize();
133       }
134       assertEquals(3, table.getIndexes().size());
135       assertEquals(2, table.getIndexDatas().size());
136       assertEquals(3, table.getLogicalIndexCount());
137       checkIndexColumns(table,
138                         "id", "id",
139                         "PrimaryKey", "id",
140                         ".rC", "id");
141 
142       IndexImpl pkIdx = table.getIndex("PrimaryKey");
143       IndexImpl fkIdx = table.getIndex(".rC");
144       assertNotSame(pkIdx, fkIdx);
145       assertTrue(fkIdx.isForeignKey());
146       assertSame(pkIdx.getIndexData(), fkIdx.getIndexData());
147       IndexData indexData = pkIdx.getIndexData();
148       assertEquals(Arrays.asList(pkIdx, fkIdx), indexData.getIndexes());
149       assertSame(pkIdx, indexData.getPrimaryIndex());
150 
151       table = (TableImpl)mdb.getTable("Table3");
152       for(IndexImpl idx : table.getIndexes()) {
153         idx.initialize();
154       }
155       assertEquals(3, table.getIndexes().size());
156       assertEquals(2, table.getIndexDatas().size());
157       assertEquals(3, table.getLogicalIndexCount());
158       checkIndexColumns(table,
159                         "id", "id",
160                         "PrimaryKey", "id",
161                         ".rC", "id");
162 
163       pkIdx = table.getIndex("PrimaryKey");
164       fkIdx = table.getIndex(".rC");
165       assertNotSame(pkIdx, fkIdx);
166       assertTrue(fkIdx.isForeignKey());
167       assertSame(pkIdx.getIndexData(), fkIdx.getIndexData());
168       indexData = pkIdx.getIndexData();
169       assertEquals(Arrays.asList(pkIdx, fkIdx), indexData.getIndexes());
170       assertSame(pkIdx, indexData.getPrimaryIndex());
171     }
172   }
173 
174   public void testComplexIndex() throws Exception
175   {
176     for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.COMP_INDEX)) {
177       // this file has an index with "compressed" entries and node pages
178       Database db = open(testDB);
179       TableImpl t = (TableImpl)db.getTable("Table1");
180       IndexImpl index = t.getIndexes().get(0);
181       assertFalse(index.isInitialized());
182       assertEquals(512, countRows(t));
183       assertEquals(512, index.getIndexData().getEntryCount());
184       db.close();
185 
186       // copy to temp file and attempt to edit
187       db = openCopy(testDB);
188       t = (TableImpl)db.getTable("Table1");
189       index = t.getIndexes().get(0);
190 
191       t.addRow(99, "abc", "def");
192     }
193   }
194 
195   public void testEntryDeletion() throws Exception {
196     for (final TestDB testDB : SUPPORTED_DBS_TEST) {
197       Table table = openCopy(testDB).getTable("Table1");
198 
199       for(int i = 0; i < 10; ++i) {
200         table.addRow("foo" + i, "bar" + i, (byte)42 + i, (short)53 + i, 13 * i,
201                      (6.7d / i), null, null, true);
202       }
203       table.reset();
204       assertRowCount(12, table);
205 
206       for(Index index : table.getIndexes()) {
207         assertEquals(12, ((IndexImpl)index).getIndexData().getEntryCount());
208       }
209 
210       table.reset();
211       table.getNextRow();
212       table.getNextRow();
213       table.getDefaultCursor().deleteCurrentRow();
214       table.getNextRow();
215       table.getDefaultCursor().deleteCurrentRow();
216       table.getNextRow();
217       table.getNextRow();
218       table.getDefaultCursor().deleteCurrentRow();
219       table.getNextRow();
220       table.getNextRow();
221       table.getNextRow();
222       table.getDefaultCursor().deleteCurrentRow();
223 
224       table.reset();
225       assertRowCount(8, table);
226 
227       for(Index index : table.getIndexes()) {
228         assertEquals(8, ((IndexImpl)index).getIndexData().getEntryCount());
229       }
230     }
231   }
232 
233   public void testIgnoreNulls() throws Exception
234   {
235     for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX_PROPERTIES)) {
236       Database db = openCopy(testDB);
237 
238       db.setEvaluateExpressions(false);
239 
240       doTestIgnoreNulls(db, "TableIgnoreNulls1");
241       doTestIgnoreNulls(db, "TableIgnoreNulls2");
242 
243       db.close();
244     }
245   }
246 
247   private void doTestIgnoreNulls(Database db, String tableName)
248     throws Exception
249   {
250     Table orig = db.getTable(tableName);
251     IndexImpl origI = (IndexImpl)orig.getIndex("DataIndex");
252     Table temp = db.getTable(tableName + "_temp");
253     IndexImpl tempI = (IndexImpl)temp.getIndex("DataIndex");
254 
255     // copy from orig table to temp table
256     for(Map<String,Object> row : orig) {
257       temp.addRow(orig.asRow(row));
258     }
259 
260     assertEquals(origI.getIndexData().getEntryCount(),
261                  tempI.getIndexData().getEntryCount());
262 
263     Cursor origC = origI.newCursor().toCursor();
264     Cursor tempC = tempI.newCursor().toCursor();
265 
266     while(true) {
267       boolean origHasNext = origC.moveToNextRow();
268       boolean tempHasNext = tempC.moveToNextRow();
269       assertTrue(origHasNext == tempHasNext);
270       if(!origHasNext) {
271         break;
272       }
273 
274       Map<String,Object> origRow = origC.getCurrentRow();
275       Cursor.Position origCurPos = origC.getSavepoint().getCurrentPosition();
276       Map<String,Object> tempRow = tempC.getCurrentRow();
277       Cursor.Position tempCurPos = tempC.getSavepoint().getCurrentPosition();
278 
279       assertEquals(origRow, tempRow);
280       assertEquals(IndexCodesTest.entryToString(origCurPos),
281                    IndexCodesTest.entryToString(tempCurPos));
282     }
283   }
284 
285   public void testUnique() throws Exception
286   {
287     for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX_PROPERTIES)) {
288       Database db = openCopy(testDB);
289 
290       Table t = db.getTable("TableUnique1_temp");
291       Index index = t.getIndex("DataIndex");
292 
293       doTestUnique(index, 1,
294                    null, true,
295                    "unique data", true,
296                    null, true,
297                    "more", false,
298                    "stuff", false,
299                    "unique data", false);
300 
301       t = db.getTable("TableUnique2_temp");
302       index = t.getIndex("DataIndex");
303 
304       doTestUnique(index, 2,
305                    null, null, true,
306                    "unique data", 42, true,
307                    "unique data", null, true,
308                    null, null, true,
309                    "some", 42, true,
310                    "more unique data", 13, true,
311                    null, -4242, true,
312                    "another row", -3462, false,
313                    null, 49, false,
314                    "more", null, false,
315                    "unique data", 42, false,
316                    "unique data", null, false,
317                    null, -4242, false);
318 
319       db.close();
320     }
321   }
322 
323   private void doTestUnique(Index index, int numValues,
324                             Object... testData)
325     throws Exception
326   {
327     for(int i = 0; i < testData.length; i += (numValues + 1)) {
328       Object[] row = new Object[numValues + 1];
329       row[0] = "testRow" + i;
330       for(int j = 1; j < (numValues + 1); ++j) {
331         row[j] = testData[i + j - 1];
332       }
333       boolean expectedSuccess = (Boolean)testData[i + numValues];
334 
335       IOException failure = null;
336       try {
337         ((IndexImpl)index).getIndexData().prepareAddRow(
338             row, new RowIdImpl(400 + i, 0), null).commit();
339       } catch(IOException e) {
340         failure = e;
341       }
342       if(expectedSuccess) {
343         assertNull(failure);
344       } else {
345         assertNotNull(failure);
346         assertTrue(failure.getMessage().contains("uniqueness"));
347       }
348     }
349   }
350 
351   public void testUniqueEntryCount() throws Exception {
352     for (final TestDB testDB : SUPPORTED_DBS_TEST) {
353       Database db = openCopy(testDB);
354       db.setDateTimeType(DateTimeType.DATE);
355       Table table = db.getTable("Table1");
356       IndexImpl indA = (IndexImpl)table.getIndex("PrimaryKey");
357       IndexImpl indB = (IndexImpl)table.getIndex("B");
358 
359       assertEquals(2, indA.getUniqueEntryCount());
360       assertEquals(2, indB.getUniqueEntryCount());
361 
362       List<String> bElems = Arrays.asList("bar", null, "baz", "argle", null,
363                                           "bazzle", "37", "bar", "bar", "BAZ");
364 
365       for(int i = 0; i < 10; ++i) {
366         table.addRow("foo" + i, bElems.get(i), (byte)42 + i, (short)53 + i,
367                      13 * i, (6.7d / i), null, null, true);
368       }
369 
370       assertEquals(12, indA.getIndexData().getEntryCount());
371       assertEquals(12, indB.getIndexData().getEntryCount());
372 
373       assertEquals(12, indA.getUniqueEntryCount());
374       assertEquals(8, indB.getUniqueEntryCount());
375 
376       table = null;
377       indA = null;
378       indB = null;
379 
380       table = db.getTable("Table1");
381       indA = (IndexImpl)table.getIndex("PrimaryKey");
382       indB = (IndexImpl)table.getIndex("B");
383 
384       assertEquals(12, indA.getIndexData().getEntryCount());
385       assertEquals(12, indB.getIndexData().getEntryCount());
386 
387       assertEquals(12, indA.getUniqueEntryCount());
388       assertEquals(8, indB.getUniqueEntryCount());
389 
390       Cursor c = CursorBuilder.createCursor(table);
391       assertTrue(c.moveToNextRow());
392 
393       final Row row = c.getCurrentRow();
394       // Row order is arbitrary, so v2007 row order difference is valid
395       if (testDB.getExpectedFileFormat().ordinal() >=
396           Database.FileFormat.V2007.ordinal()) {
397         TestUtil.checkTestDBTable1RowA(testDB, table, row);
398       } else {
399         TestUtil.checkTestDBTable1RowABCDEFG(testDB, table, row);
400       }
401       c.deleteCurrentRow();
402 
403       assertEquals(11, indA.getIndexData().getEntryCount());
404       assertEquals(11, indB.getIndexData().getEntryCount());
405 
406       assertEquals(12, indA.getUniqueEntryCount());
407       assertEquals(8, indB.getUniqueEntryCount());
408 
409       db.close();
410     }
411   }
412 
413   public void testReplId() throws Exception
414   {
415     for (final TestDB testDB : SUPPORTED_DBS_TEST) {
416       Database db = openCopy(testDB);
417       Table table = db.getTable("Table4");
418 
419       for(int i = 0; i< 20; ++i) {
420         table.addRow("row" + i, Column.AUTO_NUMBER);
421       }
422 
423       assertEquals(20, table.getRowCount());
424 
425       db.close();
426     }
427   }
428 
429   public void testIndexCreation() throws Exception
430   {
431     for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
432       Database db = create(fileFormat);
433 
434       Table t = newTable("TestTable")
435         .addColumn(newColumn("id", DataType.LONG))
436         .addColumn(newColumn("data", DataType.TEXT))
437         .setPrimaryKey("id")
438         .toTable(db);
439 
440       assertEquals(1, t.getIndexes().size());
441       IndexImpl idx = (IndexImpl)t.getIndexes().get(0);
442 
443       assertEquals(IndexBuilder.PRIMARY_KEY_NAME, idx.getName());
444       assertEquals(1, idx.getColumns().size());
445       assertEquals("id", idx.getColumns().get(0).getName());
446       assertTrue(idx.getColumns().get(0).isAscending());
447       assertTrue(idx.isPrimaryKey());
448       assertTrue(idx.isUnique());
449       assertFalse(idx.shouldIgnoreNulls());
450       assertNull(idx.getReference());
451 
452       t.addRow(2, "row2");
453       t.addRow(1, "row1");
454       t.addRow(3, "row3");
455 
456       Cursor c = t.newCursor()
457         .setIndexByName(IndexBuilder.PRIMARY_KEY_NAME).toCursor();
458 
459       for(int i = 1; i <= 3; ++i) {
460         Map<String,Object> row = c.getNextRow();
461         assertEquals(i, row.get("id"));
462         assertEquals("row" + i, row.get("data"));
463       }
464       assertFalse(c.moveToNextRow());
465     }
466   }
467 
468   public void testIndexCreationSharedData() throws Exception
469   {
470     for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
471       Database db = create(fileFormat);
472 
473       Table t = newTable("TestTable")
474         .addColumn(newColumn("id", DataType.LONG))
475         .addColumn(newColumn("data", DataType.TEXT))
476         .setPrimaryKey("id")
477         .addIndex(newIndex("Index1").addColumns("id"))
478         .addIndex(newIndex("Index2").addColumns("id"))
479         .addIndex(newIndex("Index3").addColumns(false, "id"))
480         .toTable(db);
481 
482       assertEquals(4, t.getIndexes().size());
483       IndexImpl idx = (IndexImpl)t.getIndexes().get(0);
484 
485       assertEquals(IndexBuilder.PRIMARY_KEY_NAME, idx.getName());
486       assertEquals(1, idx.getColumns().size());
487       assertEquals("id", idx.getColumns().get(0).getName());
488       assertTrue(idx.getColumns().get(0).isAscending());
489       assertTrue(idx.isPrimaryKey());
490       assertTrue(idx.isUnique());
491       assertFalse(idx.shouldIgnoreNulls());
492       assertNull(idx.getReference());
493 
494       IndexImpl idx1 = (IndexImpl)t.getIndexes().get(1);
495       IndexImpl idx2 = (IndexImpl)t.getIndexes().get(2);
496       IndexImpl idx3 = (IndexImpl)t.getIndexes().get(3);
497 
498       assertNotSame(idx.getIndexData(), idx1.getIndexData());
499       assertSame(idx1.getIndexData(), idx2.getIndexData());
500       assertNotSame(idx2.getIndexData(), idx3.getIndexData());
501 
502       t.addRow(2, "row2");
503       t.addRow(1, "row1");
504       t.addRow(3, "row3");
505 
506       Cursor c = t.newCursor()
507         .setIndexByName(IndexBuilder.PRIMARY_KEY_NAME).toCursor();
508 
509       for(int i = 1; i <= 3; ++i) {
510         Map<String,Object> row = c.getNextRow();
511         assertEquals(i, row.get("id"));
512         assertEquals("row" + i, row.get("data"));
513       }
514       assertFalse(c.moveToNextRow());
515     }
516   }
517 
518   public void testGetForeignKeyIndex() throws Exception
519   {
520     for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX, true)) {
521       Database db = open(testDB);
522       Table t1 = db.getTable("Table1");
523       Table t2 = db.getTable("Table2");
524       Table t3 = db.getTable("Table3");
525 
526       IndexImpl t2t1 = (IndexImpl)t1.getIndex("Table2Table1");
527       IndexImpl t3t1 = (IndexImpl)t1.getIndex("Table3Table1");
528 
529 
530       assertTrue(t2t1.isForeignKey());
531       assertNotNull(t2t1.getReference());
532       assertFalse(t2t1.getReference().isPrimaryTable());
533       assertFalse(t2t1.getReference().isCascadeUpdates());
534       assertTrue(t2t1.getReference().isCascadeDeletes());
535       doCheckForeignKeyIndex(t1, t2t1, t2);
536 
537       assertTrue(t3t1.isForeignKey());
538       assertNotNull(t3t1.getReference());
539       assertFalse(t3t1.getReference().isPrimaryTable());
540       assertTrue(t3t1.getReference().isCascadeUpdates());
541       assertFalse(t3t1.getReference().isCascadeDeletes());
542       doCheckForeignKeyIndex(t1, t3t1, t3);
543 
544       Index t1pk = t1.getIndex(IndexBuilder.PRIMARY_KEY_NAME);
545       assertNotNull(t1pk);
546       assertNull(((IndexImpl)t1pk).getReference());
547       assertNull(t1pk.getReferencedIndex());
548     }
549   }
550 
551   public void testConstraintViolation() throws Exception
552   {
553     for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
554       Database db = create(fileFormat);
555 
556       Table t = newTable("TestTable")
557         .addColumn(newColumn("id", DataType.LONG))
558         .addColumn(newColumn("data", DataType.TEXT))
559         .setPrimaryKey("id")
560         .addIndex(newIndex("data_ind")
561                   .addColumns("data").setUnique())
562         .toTable(db);
563 
564       for(int i = 0; i < 5; ++i) {
565         t.addRow(i, "row" + i);
566       }
567 
568       try {
569         t.addRow(3, "badrow");
570         fail("ConstraintViolationException should have been thrown");
571       } catch(ConstraintViolationException ce) {
572         // success
573       }
574 
575       assertEquals(5, t.getRowCount());
576 
577       List<Row> expectedRows =
578         createExpectedTable(
579             createExpectedRow(
580                 "id", 0, "data", "row0"),
581             createExpectedRow(
582                 "id", 1, "data", "row1"),
583             createExpectedRow(
584                 "id", 2, "data", "row2"),
585             createExpectedRow(
586                 "id", 3, "data", "row3"),
587             createExpectedRow(
588                 "id", 4, "data", "row4"));
589 
590       assertTable(expectedRows, t);
591 
592       IndexCursor pkCursor = CursorBuilder.createPrimaryKeyCursor(t);
593       assertCursor(expectedRows, pkCursor);
594 
595       assertCursor(expectedRows,
596                    CursorBuilder.createCursor(t.getIndex("data_ind")));
597 
598       List<Object[]> batch = new ArrayList<Object[]>();
599       batch.add(new Object[]{5, "row5"});
600       batch.add(new Object[]{6, "row6"});
601       batch.add(new Object[]{7, "row2"});
602       batch.add(new Object[]{8, "row8"});
603 
604       try {
605         t.addRows(batch);
606         fail("BatchUpdateException should have been thrown");
607       } catch(BatchUpdateException be) {
608         // success
609         assertTrue(be.getCause() instanceof ConstraintViolationException);
610         assertEquals(2, be.getUpdateCount());
611       }
612 
613       expectedRows = new ArrayList<Row>(expectedRows);
614       expectedRows.add(createExpectedRow("id", 5, "data", "row5"));
615       expectedRows.add(createExpectedRow("id", 6, "data", "row6"));
616 
617       assertTable(expectedRows, t);
618 
619       assertCursor(expectedRows, pkCursor);
620 
621       assertCursor(expectedRows,
622                    CursorBuilder.createCursor(t.getIndex("data_ind")));
623 
624       pkCursor.findFirstRowByEntry(4);
625       Row row4 = pkCursor.getCurrentRow();
626 
627       row4.put("id", 3);
628 
629       try {
630         t.updateRow(row4);
631         fail("ConstraintViolationException should have been thrown");
632       } catch(ConstraintViolationException ce) {
633         // success
634       }
635 
636       assertTable(expectedRows, t);
637 
638       assertCursor(expectedRows, pkCursor);
639 
640       assertCursor(expectedRows,
641                    CursorBuilder.createCursor(t.getIndex("data_ind")));
642 
643       db.close();
644     }
645   }
646 
647   public void testAutoNumberRecover() throws Exception
648   {
649     for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
650       Database db = create(fileFormat);
651 
652       Table t = newTable("TestTable")
653         .addColumn(newColumn("id", DataType.LONG).setAutoNumber(true))
654         .addColumn(newColumn("data", DataType.TEXT))
655         .setPrimaryKey("id")
656         .addIndex(newIndex("data_ind")
657                   .addColumns("data").setUnique())
658         .toTable(db);
659 
660       for(int i = 1; i < 3; ++i) {
661         t.addRow(null, "row" + i);
662       }
663 
664       try {
665         t.addRow(null, "row1");
666         fail("ConstraintViolationException should have been thrown");
667       } catch(ConstraintViolationException ce) {
668         // success
669       }
670 
671       t.addRow(null, "row3");
672 
673       assertEquals(3, t.getRowCount());
674 
675       List<Row> expectedRows =
676         createExpectedTable(
677             createExpectedRow(
678                 "id", 1, "data", "row1"),
679             createExpectedRow(
680                 "id", 2, "data", "row2"),
681             createExpectedRow(
682                 "id", 3, "data", "row3"));
683 
684       assertTable(expectedRows, t);
685 
686       IndexCursor pkCursor = CursorBuilder.createPrimaryKeyCursor(t);
687       assertCursor(expectedRows, pkCursor);
688 
689       assertCursor(expectedRows,
690                    CursorBuilder.createCursor(t.getIndex("data_ind")));
691 
692       List<Object[]> batch = new ArrayList<Object[]>();
693       batch.add(new Object[]{null, "row4"});
694       batch.add(new Object[]{null, "row5"});
695       batch.add(new Object[]{null, "row3"});
696 
697       try {
698         t.addRows(batch);
699         fail("BatchUpdateException should have been thrown");
700       } catch(BatchUpdateException be) {
701         // success
702         assertTrue(be.getCause() instanceof ConstraintViolationException);
703         assertEquals(2, be.getUpdateCount());
704       }
705 
706       expectedRows = new ArrayList<Row>(expectedRows);
707       expectedRows.add(createExpectedRow("id", 4, "data", "row4"));
708       expectedRows.add(createExpectedRow("id", 5, "data", "row5"));
709 
710       assertTable(expectedRows, t);
711 
712       assertCursor(expectedRows, pkCursor);
713 
714       assertCursor(expectedRows,
715                    CursorBuilder.createCursor(t.getIndex("data_ind")));
716 
717       db.close();
718     }
719   }
720 
721   public void testBinaryIndex() throws Exception
722   {
723     for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.BINARY_INDEX)) {
724       Database db = open(testDB);
725 
726       Table table = db.getTable("Test");
727 
728       Index idx = table.getIndex("BinAscIdx");
729       doTestBinaryIndex(idx, "BinAsc", false);
730 
731       idx = table.getIndex("BinDscIdx");
732       doTestBinaryIndex(idx, "BinDsc", true);
733 
734       db.close();
735     }
736   }
737 
738   private static void doTestBinaryIndex(Index idx, String colName, boolean forward)
739     throws Exception
740   {
741     IndexCursor ic = CursorBuilder.createCursor(idx);
742 
743     for(Row row : idx.getTable().getDefaultCursor().newIterable().setForward(forward)) {
744       int id = row.getInt("ID");
745       byte[] data = row.getBytes(colName);
746 
747       boolean found = false;
748       for(Row idxRow : ic.newEntryIterable(data)) {
749 
750         assertTrue(Arrays.equals(data, idxRow.getBytes(colName)));
751         if(id == idxRow.getInt("ID")) {
752           found = true;
753         }
754       }
755 
756       assertTrue(found);
757     }
758   }
759 
760   private void doCheckForeignKeyIndex(Table ta, Index ia, Table tb)
761     throws Exception
762   {
763     IndexImpl ib = (IndexImpl)ia.getReferencedIndex();
764     assertNotNull(ib);
765     assertSame(tb, ib.getTable());
766 
767     assertNotNull(ib.getReference());
768     assertSame(ia, ib.getReferencedIndex());
769     assertTrue(ib.getReference().isPrimaryTable());
770   }
771 
772   private void checkIndexColumns(Table table, String... idxInfo)
773     throws Exception
774   {
775     Map<String, String> expectedIndexes = new HashMap<String, String>();
776     for(int i = 0; i < idxInfo.length; i+=2) {
777       expectedIndexes.put(idxInfo[i], idxInfo[i+1]);
778     }
779 
780     for(Index idx : table.getIndexes()) {
781       String colName = expectedIndexes.get(idx.getName());
782       assertEquals(1, idx.getColumns().size());
783       assertEquals(colName, idx.getColumns().get(0).getName());
784       if("PrimaryKey".equals(idx.getName())) {
785         assertTrue(idx.isPrimaryKey());
786       } else {
787         assertFalse(idx.isPrimaryKey());
788       }
789     }
790   }
791 
792 }