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.io.FileInputStream;
32  import java.io.FileNotFoundException;
33  import java.io.FileOutputStream;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.OutputStream;
37  import java.io.PrintWriter;
38  import java.math.BigDecimal;
39  import java.nio.ByteBuffer;
40  import java.sql.Types;
41  import java.text.DateFormat;
42  import java.text.SimpleDateFormat;
43  import java.util.ArrayList;
44  import java.util.Arrays;
45  import java.util.Calendar;
46  import java.util.Collections;
47  import java.util.Date;
48  import java.util.HashSet;
49  import java.util.LinkedHashMap;
50  import java.util.List;
51  import java.util.Map;
52  import java.util.Set;
53  import java.util.UUID;
54  
55  import junit.framework.TestCase;
56  
57  /**
58   * @author Tim McCune
59   */
60  public class DatabaseTest extends TestCase {
61  
62    static boolean _autoSync = Database.DEFAULT_AUTO_SYNC;
63    
64    public DatabaseTest(String name) throws Exception {
65      super(name);
66    }
67     
68    static Database open() throws Exception {
69      return open(new File("test/data/test.mdb"));
70    }
71    
72    static Database open(File file) throws Exception {
73      return Database.open(file, true, _autoSync);
74    }
75    
76    static Database create() throws Exception {
77      return create(false);
78    }
79  
80    static Database create(boolean keep) throws Exception {
81      return Database.create(createTempFile(keep), _autoSync);
82    }
83  
84    static Database openCopy(File srcFile) throws Exception {
85      return openCopy(srcFile, false);
86    }
87    
88    static Database openCopy(File srcFile, boolean keep) throws Exception {
89      File tmp = createTempFile(keep);
90      copyFile(srcFile, tmp);
91      return Database.open(tmp, false, _autoSync);
92    }
93    
94    public void testInvalidTableDefs() throws Exception {
95      Database db = create();
96  
97      try {
98        db.createTable("test", Collections.<Column>emptyList());
99        fail("created table with no columns?");
100     } catch(IllegalArgumentException e) {
101       // success
102     }
103 
104     try {
105       new TableBuilder("test")
106         .addColumn(new ColumnBuilder("A", DataType.TEXT).toColumn())
107         .addColumn(new ColumnBuilder("a", DataType.MEMO).toColumn())
108         .toTable(db);
109       fail("created table with duplicate column names?");
110     } catch(IllegalArgumentException e) {
111       // success
112     }
113 
114     try {
115       new TableBuilder("test")
116         .addColumn(new ColumnBuilder("A", DataType.TEXT)
117                    .setLengthInUnits(352).toColumn())
118         .toTable(db);
119       fail("created table with invalid column length?");
120     } catch(IllegalArgumentException e) {
121       // success
122     }
123 
124     new TableBuilder("test")
125       .addColumn(new ColumnBuilder("A", DataType.TEXT).toColumn())
126       .toTable(db);
127 
128     
129     try {
130       new TableBuilder("Test")
131         .addColumn(new ColumnBuilder("A", DataType.TEXT).toColumn())
132         .toTable(db);
133       fail("create duplicate tables?");
134     } catch(IllegalArgumentException e) {
135       // success
136     }
137 
138   }
139       
140   public void testReadDeletedRows() throws Exception {
141     Table table = open(new File("test/data/delTest.mdb")).getTable("Table");
142     int rows = 0;
143     while (table.getNextRow() != null) {
144       rows++;
145     }
146     assertEquals(2, rows); 
147   }
148   
149   public void testGetColumns() throws Exception {
150     List columns = open().getTable("Table1").getColumns();
151     assertEquals(9, columns.size());
152     checkColumn(columns, 0, "A", DataType.TEXT);
153     checkColumn(columns, 1, "B", DataType.TEXT);
154     checkColumn(columns, 2, "C", DataType.BYTE);
155     checkColumn(columns, 3, "D", DataType.INT);
156     checkColumn(columns, 4, "E", DataType.LONG);
157     checkColumn(columns, 5, "F", DataType.DOUBLE);
158     checkColumn(columns, 6, "G", DataType.SHORT_DATE_TIME);
159     checkColumn(columns, 7, "H", DataType.MONEY);
160     checkColumn(columns, 8, "I", DataType.BOOLEAN);
161   }
162   
163   static void checkColumn(List columns, int columnNumber, String name,
164       DataType dataType)
165     throws Exception
166   {
167     Column column = (Column) columns.get(columnNumber);
168     assertEquals(name, column.getName());
169     assertEquals(dataType, column.getType());
170   }
171   
172   public void testGetNextRow() throws Exception {
173     Database db = open();
174     assertEquals(3, db.getTableNames().size());
175     Table table = db.getTable("Table1");
176     
177     Map<String, Object> row = table.getNextRow();
178     assertEquals("abcdefg", row.get("A"));
179     assertEquals("hijklmnop", row.get("B"));
180     assertEquals(new Byte((byte) 2), row.get("C"));
181     assertEquals(new Short((short) 222), row.get("D"));
182     assertEquals(new Integer(333333333), row.get("E"));
183     assertEquals(new Double(444.555d), row.get("F"));
184     Calendar cal = Calendar.getInstance();
185     cal.setTime((Date) row.get("G"));
186     assertEquals(Calendar.SEPTEMBER, cal.get(Calendar.MONTH));
187     assertEquals(21, cal.get(Calendar.DAY_OF_MONTH));
188     assertEquals(1974, cal.get(Calendar.YEAR));
189     assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
190     assertEquals(0, cal.get(Calendar.MINUTE));
191     assertEquals(0, cal.get(Calendar.SECOND));
192     assertEquals(0, cal.get(Calendar.MILLISECOND));
193     assertEquals(Boolean.TRUE, row.get("I"));
194     
195     row = table.getNextRow();
196     assertEquals("a", row.get("A"));
197     assertEquals("b", row.get("B"));
198     assertEquals(new Byte((byte) 0), row.get("C"));
199     assertEquals(new Short((short) 0), row.get("D"));
200     assertEquals(new Integer(0), row.get("E"));
201     assertEquals(new Double(0d), row.get("F"));
202     cal = Calendar.getInstance();
203     cal.setTime((Date) row.get("G"));
204     assertEquals(Calendar.DECEMBER, cal.get(Calendar.MONTH));
205     assertEquals(12, cal.get(Calendar.DAY_OF_MONTH));
206     assertEquals(1981, cal.get(Calendar.YEAR));
207     assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
208     assertEquals(0, cal.get(Calendar.MINUTE));
209     assertEquals(0, cal.get(Calendar.SECOND));
210     assertEquals(0, cal.get(Calendar.MILLISECOND));
211     assertEquals(Boolean.FALSE, row.get("I"));
212   }
213   
214   public void testCreate() throws Exception {
215     Database db = create();
216     assertEquals(0, db.getTableNames().size());
217   }
218   
219   public void testWriteAndRead() throws Exception {
220     Database db = create();
221     createTestTable(db);
222     Object[] row = createTestRow();
223     row[3] = null;
224     Table table = db.getTable("Test");
225     int count = 1000;
226     for (int i = 0; i < count; i++) { 
227       table.addRow(row);
228     }
229     for (int i = 0; i < count; i++) {
230       Map<String, Object> readRow = table.getNextRow();
231       assertEquals(row[0], readRow.get("A"));
232       assertEquals(row[1], readRow.get("B"));
233       assertEquals(row[2], readRow.get("C"));
234       assertEquals(row[3], readRow.get("D"));
235       assertEquals(row[4], readRow.get("E"));
236       assertEquals(row[5], readRow.get("F"));
237       assertEquals(row[6], readRow.get("G"));
238       assertEquals(row[7], readRow.get("H"));
239     }
240   }
241   
242   public void testWriteAndReadInBatch() throws Exception {
243     Database db = create();
244     createTestTable(db);
245     int count = 1000;
246     List<Object[]> rows = new ArrayList<Object[]>(count);
247     Object[] row = createTestRow();
248     for (int i = 0; i < count; i++) {
249       rows.add(row);
250     }
251     Table table = db.getTable("Test");
252     table.addRows(rows);
253     for (int i = 0; i < count; i++) {
254       Map<String, Object> readRow = table.getNextRow();
255       assertEquals(row[0], readRow.get("A"));
256       assertEquals(row[1], readRow.get("B"));
257       assertEquals(row[2], readRow.get("C"));
258       assertEquals(row[3], readRow.get("D"));
259       assertEquals(row[4], readRow.get("E"));
260       assertEquals(row[5], readRow.get("F"));
261       assertEquals(row[6], readRow.get("G"));
262       assertEquals(row[7], readRow.get("H"));
263     }
264   }
265 
266   public void testDeleteCurrentRow() throws Exception {
267 
268     // make sure correct row is deleted
269     Database db = create();
270     createTestTable(db);
271     Object[] row1 = createTestRow("Tim1");
272     Object[] row2 = createTestRow("Tim2");
273     Object[] row3 = createTestRow("Tim3");
274     Table table = db.getTable("Test");
275     table.addRows(Arrays.asList(row1, row2, row3));
276     assertRowCount(3, table);
277     
278     table.reset();
279     table.getNextRow();
280     table.getNextRow();
281     table.deleteCurrentRow();
282 
283     table.reset();
284 
285     Map<String, Object> outRow = table.getNextRow();
286     assertEquals("Tim1", outRow.get("A"));
287     outRow = table.getNextRow();
288     assertEquals("Tim3", outRow.get("A"));
289     assertRowCount(2, table);
290 
291     // test multi row delete/add
292     db = create();
293     createTestTable(db);
294     Object[] row = createTestRow();
295     table = db.getTable("Test");
296     for (int i = 0; i < 10; i++) {
297       row[3] = i;
298       table.addRow(row);
299     }
300     row[3] = 1974;
301     assertRowCount(10, table);
302     table.reset();
303     table.getNextRow();
304     table.deleteCurrentRow();
305     assertRowCount(9, table);
306     table.reset();
307     table.getNextRow();
308     table.deleteCurrentRow();
309     assertRowCount(8, table);
310     table.reset();
311     for (int i = 0; i < 8; i++) {
312       table.getNextRow();
313     }
314     table.deleteCurrentRow();
315     assertRowCount(7, table);
316     table.addRow(row);
317     assertRowCount(8, table);
318     table.reset();
319     for (int i = 0; i < 3; i++) {
320       table.getNextRow();
321     }
322     table.deleteCurrentRow();
323     assertRowCount(7, table);
324     table.reset();
325     assertEquals(2, table.getNextRow().get("D"));
326   }
327 
328   public void testReadLongValue() throws Exception {
329 
330     Database db = open(new File("test/data/test2.mdb"));
331     Table table = db.getTable("MSP_PROJECTS");
332     Map<String, Object> row = table.getNextRow();
333     assertEquals("Jon Iles this is a a vawesrasoih aksdkl fas dlkjflkasjd flkjaslkdjflkajlksj dfl lkasjdf lkjaskldfj lkas dlk lkjsjdfkl; aslkdf lkasjkldjf lka skldf lka sdkjfl;kasjd falksjdfljaslkdjf laskjdfk jalskjd flkj aslkdjflkjkjasljdflkjas jf;lkasjd fjkas dasdf asd fasdf asdf asdmhf lksaiyudfoi jasodfj902384jsdf9 aw90se fisajldkfj lkasj dlkfslkd jflksjadf as", row.get("PROJ_PROP_AUTHOR"));
334     assertEquals("T", row.get("PROJ_PROP_COMPANY"));
335     assertEquals("Standard", row.get("PROJ_INFO_CAL_NAME"));
336     assertEquals("Project1", row.get("PROJ_PROP_TITLE"));
337     byte[] foundBinaryData = (byte[])row.get("RESERVED_BINARY_DATA");
338     byte[] expectedBinaryData =
339       toByteArray(new File("test/data/test2BinData.dat"));
340     assertTrue(Arrays.equals(expectedBinaryData, foundBinaryData));
341   }
342 
343   public void testWriteLongValue() throws Exception {
344 
345     Database db = create();
346 
347     Table table = 
348     new TableBuilder("test")
349       .addColumn(new ColumnBuilder("A", DataType.TEXT).toColumn())
350       .addColumn(new ColumnBuilder("B", DataType.MEMO).toColumn())
351       .addColumn(new ColumnBuilder("C", DataType.OLE).toColumn())
352       .toTable(db);
353 
354     String testStr = "This is a test";
355     StringBuilder strBuf = new StringBuilder();
356     for(int i = 0; i < 2030; ++i) {
357       char c = (char)('a' + (i % 26));
358       strBuf.append(c);
359     }
360     String longMemo = strBuf.toString();
361     byte[] oleValue = toByteArray(new File("test/data/test2BinData.dat"));
362     
363     
364     table.addRow(testStr, testStr, null);
365     table.addRow(testStr, longMemo, oleValue);
366 
367     table.reset();
368 
369     Map<String, Object> row = table.getNextRow();
370 
371     assertEquals(testStr, row.get("A"));
372     assertEquals(testStr, row.get("B"));
373     assertNull(row.get("C"));
374 
375     row = table.getNextRow();
376     
377     assertEquals(testStr, row.get("A"));
378     assertEquals(longMemo, row.get("B"));
379     assertTrue(Arrays.equals(oleValue, (byte[])row.get("C")));
380     
381   }
382 
383   public void testMissingFile() throws Exception {
384     File bogusFile = new File("fooby-dooby.mdb");
385     assertTrue(!bogusFile.exists());
386     try {
387       Database db = open(bogusFile);
388       fail("FileNotFoundException should have been thrown");
389     } catch(FileNotFoundException e) {
390     }
391     assertTrue(!bogusFile.exists());
392   }
393 
394   public void testReadWithDeletedCols() throws Exception {
395     Table table = open(new File("test/data/delColTest.mdb")).getTable("Table1");
396 
397     Map<String, Object> expectedRow0 = new LinkedHashMap<String, Object>();
398     expectedRow0.put("id", 0);
399     expectedRow0.put("id2", 2);
400     expectedRow0.put("data", "foo");
401     expectedRow0.put("data2", "foo2");
402 
403     Map<String, Object> expectedRow1 = new LinkedHashMap<String, Object>();
404     expectedRow1.put("id", 3);
405     expectedRow1.put("id2", 5);
406     expectedRow1.put("data", "bar");
407     expectedRow1.put("data2", "bar2");
408 
409     int rowNum = 0;
410     Map<String, Object> row = null;
411     while ((row = table.getNextRow()) != null) {
412       if(rowNum == 0) {
413         assertEquals(expectedRow0, row);
414       } else if(rowNum == 1) {
415         assertEquals(expectedRow1, row);
416       } else if(rowNum >= 2) {
417         fail("should only have 2 rows");
418       }
419       rowNum++;
420     }
421   }
422 
423   public void testCurrency() throws Exception {
424     Database db = create();
425 
426     Table table = new TableBuilder("test")
427       .addColumn(new ColumnBuilder("A", DataType.MONEY).toColumn())
428       .toTable(db);
429 
430     table.addRow(new BigDecimal("-2341234.03450"));
431     table.addRow(37L);
432     table.addRow("10000.45");
433 
434     table.reset();
435 
436     List<Object> foundValues = new ArrayList<Object>();
437     Map<String, Object> row = null;
438     while((row = table.getNextRow()) != null) {
439       foundValues.add(row.get("A"));
440     }
441 
442     assertEquals(Arrays.asList(
443                      new BigDecimal("-2341234.0345"),
444                      new BigDecimal("37.0000"),
445                      new BigDecimal("10000.4500")),
446                  foundValues);
447 
448     try {
449       table.addRow(new BigDecimal("342523234145343543.3453"));
450       fail("IOException should have been thrown");
451     } catch(IOException e) {
452       // ignored
453     }
454   }
455 
456   public void testGUID() throws Exception
457   {
458     Database db = create();
459 
460     Table table = new TableBuilder("test")
461       .addColumn(new ColumnBuilder("A", DataType.GUID).toColumn())
462       .toTable(db);
463 
464     table.addRow("{32A59F01-AA34-3E29-453F-4523453CD2E6}");
465     table.addRow("{32a59f01-aa34-3e29-453f-4523453cd2e6}");
466     table.addRow("{11111111-1111-1111-1111-111111111111}");
467     table.addRow("   {FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}   ");
468     table.addRow(UUID.fromString("32a59f01-1234-3e29-4aaf-4523453cd2e6"));
469 
470     table.reset();
471 
472     List<Object> foundValues = new ArrayList<Object>();
473     Map<String, Object> row = null;
474     while((row = table.getNextRow()) != null) {
475       foundValues.add(row.get("A"));
476     }
477 
478     assertEquals(Arrays.asList(
479                      "{32A59F01-AA34-3E29-453F-4523453CD2E6}",
480                      "{32A59F01-AA34-3E29-453F-4523453CD2E6}",
481                      "{11111111-1111-1111-1111-111111111111}",
482                      "{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}",
483                      "{32A59F01-1234-3E29-4AAF-4523453CD2E6}"),
484                  foundValues);
485 
486     try {
487       table.addRow("3245234");
488       fail("IOException should have been thrown");
489     } catch(IOException e) {
490       // ignored
491     }
492   }
493 
494   public void testNumeric() throws Exception
495   {
496     Database db = create();
497 
498     Column col = new ColumnBuilder("A", DataType.NUMERIC)
499       .setScale(4).setPrecision(8).toColumn();
500     assertTrue(col.isVariableLength());
501     
502     Table table = new TableBuilder("test")
503       .addColumn(col)
504       .addColumn(new ColumnBuilder("B", DataType.NUMERIC)
505                  .setScale(8).setPrecision(28).toColumn())
506       .toTable(db);
507 
508     table.addRow(new BigDecimal("-1234.03450"),
509                  new BigDecimal("23923434453436.36234219"));
510     table.addRow(37L, 37L);
511     table.addRow("1000.45", "-3452345321000");
512 
513     table.reset();
514 
515     List<Object> foundSmallValues = new ArrayList<Object>();
516     List<Object> foundBigValues = new ArrayList<Object>();
517     Map<String, Object> row = null;
518     while((row = table.getNextRow()) != null) {
519       foundSmallValues.add(row.get("A"));
520       foundBigValues.add(row.get("B"));
521     }
522 
523     assertEquals(Arrays.asList(
524                      new BigDecimal("-1234.0345"),
525                      new BigDecimal("37.0000"),
526                      new BigDecimal("1000.4500")),
527                  foundSmallValues);
528     assertEquals(Arrays.asList(
529                      new BigDecimal("23923434453436.36234219"),
530                      new BigDecimal("37.00000000"),
531                      new BigDecimal("-3452345321000.00000000")),
532                  foundBigValues);
533 
534     try {
535       table.addRow(new BigDecimal("3245234.234"),
536                    new BigDecimal("3245234.234"));
537       fail("IOException should have been thrown");
538     } catch(IOException e) {
539       // ignored
540     }
541   }
542 
543   public void testFixedNumeric() throws Exception
544   {
545     Database db = openCopy(new File("test/data/fixedNumericTest.mdb"));
546     Table t = db.getTable("test");
547 
548     boolean first = true;
549     for(Column col : t.getColumns()) {
550       if(first) {
551         assertTrue(col.isVariableLength());
552         assertEquals(DataType.MEMO, col.getType());
553         first = false;
554       } else {
555         assertFalse(col.isVariableLength());
556         assertEquals(DataType.NUMERIC, col.getType());
557       }
558     }
559       
560     Map<String, Object> row = t.getNextRow();
561     assertEquals("some data", row.get("col1"));
562     assertEquals(new BigDecimal("1"), row.get("col2"));
563     assertEquals(new BigDecimal("0"), row.get("col3"));
564     assertEquals(new BigDecimal("0"), row.get("col4"));
565     assertEquals(new BigDecimal("4"), row.get("col5"));
566     assertEquals(new BigDecimal("-1"), row.get("col6"));
567     assertEquals(new BigDecimal("1"), row.get("col7"));
568 
569     Object[] tmpRow = new Object[]{
570       "foo", new BigDecimal("1"), new BigDecimal(3), new BigDecimal("13"),
571       new BigDecimal("-17"), new BigDecimal("0"), new BigDecimal("8734")};
572     t.addRow(tmpRow);
573     t.reset();
574 
575     t.getNextRow();
576     row = t.getNextRow();
577     assertEquals(tmpRow[0], row.get("col1"));
578     assertEquals(tmpRow[1], row.get("col2"));
579     assertEquals(tmpRow[2], row.get("col3"));
580     assertEquals(tmpRow[3], row.get("col4"));
581     assertEquals(tmpRow[4], row.get("col5"));
582     assertEquals(tmpRow[5], row.get("col6"));
583     assertEquals(tmpRow[6], row.get("col7"));
584     
585     db.close();
586 
587   }  
588 
589   public void testMultiPageTableDef() throws Exception
590   {
591     List<Column> columns = open().getTable("Table2").getColumns();
592     assertEquals(89, columns.size());
593   }
594 
595   public void testOverflow() throws Exception
596   {
597     Database mdb = open(new File("test/data/overflowTest.mdb"));
598     Table table = mdb.getTable("Table1");
599 
600     // 7 rows, 3 and 5 are overflow
601     table.getNextRow();
602     table.getNextRow();
603 
604     Map<String, Object> row = table.getNextRow();
605     assertEquals(Arrays.<Object>asList(
606                      null, "row3col3", null, null, null, null, null,
607                      "row3col9", null),
608                  new ArrayList<Object>(row.values()));
609 
610     table.getNextRow();
611 
612     row = table.getNextRow();
613     assertEquals(Arrays.<Object>asList(
614                      null, "row5col2", null, null, null, null, null, null,
615                      null),
616                  new ArrayList<Object>(row.values()));
617 
618     table.reset();
619     assertRowCount(7, table);
620     
621   }
622 
623   public void testLongValueAsMiddleColumn() throws Exception
624   {
625 
626     Database db = create();
627     Table newTable = new TableBuilder("NewTable")
628       .addColumn(new ColumnBuilder("a").setSQLType(Types.INTEGER).toColumn())
629       .addColumn(new ColumnBuilder("b").setSQLType(Types.LONGVARCHAR).toColumn())
630       .addColumn(new ColumnBuilder("c").setSQLType(Types.VARCHAR).toColumn())
631       .toTable(db);
632     
633     String lval = createString(2000); // "--2000 chars long text--";
634     String tval = createString(40); // "--40chars long text--";
635     newTable.addRow(new Integer(1), lval, tval);
636 
637     newTable = db.getTable("NewTable");
638     Map<String, Object> readRow = newTable.getNextRow();
639     assertEquals(new Integer(1), readRow.get("a"));
640     assertEquals(lval, readRow.get("b"));
641     assertEquals(tval, readRow.get("c"));
642 
643   }
644 
645 
646   public void testUsageMapPromotion() throws Exception {
647     Database db = openCopy(new File("test/data/testPromotion.mdb"));
648     Table t = db.getTable("jobDB1");
649 
650     String lval = createString(255); // "--255 chars long text--";
651 
652     for(int i = 0; i < 1000; ++i) {
653       t.addRow(i, 13, 57, 47.0d, lval, lval, lval, lval, lval, lval);
654     }
655 
656     Set<Integer> ids = new HashSet<Integer>();
657     for(Map<String,Object> row : t) {
658       ids.add((Integer)row.get("ID"));
659     }
660     assertEquals(1000, ids.size());
661 
662     db.close();
663   }  
664 
665 
666   public void testLargeTableDef() throws Exception {
667     final int numColumns = 90;
668     Database db = create();
669 
670     List<Column> columns = new ArrayList<Column>();
671     List<String> colNames = new ArrayList<String>();
672     for(int i = 0; i < numColumns; ++i) {
673       String colName = "MyColumnName" + i;
674       colNames.add(colName);
675       columns.add(new ColumnBuilder(colName, DataType.TEXT).toColumn());
676     }
677 
678     db.createTable("test", columns);
679 
680     Table t = db.getTable("test");
681 
682     List<String> row = new ArrayList<String>();
683     Map<String,Object> expectedRowData = new LinkedHashMap<String, Object>();
684     for(int i = 0; i < numColumns; ++i) {
685       String value = "" + i + " some row data";
686       row.add(value);
687       expectedRowData.put(colNames.get(i), value);
688     }
689 
690     t.addRow(row.toArray());
691 
692     t.reset();
693     assertEquals(expectedRowData, t.getNextRow());
694     
695     db.close();
696   }
697 
698   public void testAutoNumber() throws Exception {
699     Database db = create();
700 
701     Table table = new TableBuilder("test")
702       .addColumn(new ColumnBuilder("a", DataType.LONG)
703                 .setAutoNumber(true).toColumn())
704       .addColumn(new ColumnBuilder("b", DataType.TEXT).toColumn())
705       .toTable(db);
706 
707     doTestAutoNumber(table);
708     
709     db.close();
710   }  
711 
712   public void testAutoNumberPK() throws Exception {
713     Database db = openCopy(new File("test/data/test.mdb"));
714 
715     Table table = db.getTable("Table3");
716 
717     doTestAutoNumber(table);
718     
719     db.close();
720   }  
721 
722   private void doTestAutoNumber(Table table) throws Exception
723   {
724     table.addRow(null, "row1");
725     table.addRow(13, "row2");
726     table.addRow("flubber", "row3");
727 
728     table.reset();
729 
730     table.addRow(Column.AUTO_NUMBER, "row4");
731     table.addRow(Column.AUTO_NUMBER, "row5");
732 
733     table.reset();
734 
735     List<Map<String, Object>> expectedRows =
736       createExpectedTable(
737           createExpectedRow(
738               "a", 1,
739               "b", "row1"),
740           createExpectedRow(
741               "a", 2,
742               "b", "row2"),
743           createExpectedRow(
744               "a", 3,
745               "b", "row3"),
746           createExpectedRow(
747               "a", 4,
748               "b", "row4"),
749           createExpectedRow(
750               "a", 5,
751               "b", "row5"));
752 
753     assertTable(expectedRows, table);    
754   }
755   
756   public void testWriteAndReadDate() throws Exception {
757     Database db = create();
758 
759     Table table = new TableBuilder("test")
760       .addColumn(new ColumnBuilder("name", DataType.TEXT).toColumn())
761       .addColumn(new ColumnBuilder("date", DataType.SHORT_DATE_TIME)
762                  .toColumn())
763       .toTable(db);
764     
765     // since jackcess does not really store millis, shave them off before
766     // storing the current date/time
767     long curTimeNoMillis = (System.currentTimeMillis() / 1000L);
768     curTimeNoMillis *= 1000L;
769     
770     DateFormat df = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
771     List<Date> dates =
772       new ArrayList<Date>(
773           Arrays.asList(
774               df.parse("19801231 00:00:00"),
775               df.parse("19930513 14:43:27"),
776               null,
777               df.parse("20210102 02:37:00"),
778               new Date(curTimeNoMillis)));
779 
780     Calendar c = Calendar.getInstance();
781     for(int year = 1801; year < 2050; year +=3) {
782       for(int month = 0; month <= 12; ++month) {
783         for(int day = 1; day < 29; day += 3) {
784           c.clear();
785           c.set(Calendar.YEAR, year);
786           c.set(Calendar.MONTH, month);
787           c.set(Calendar.DAY_OF_MONTH, day);
788           dates.add(c.getTime());
789         }
790       }
791     }
792 
793     for(Date d : dates) {
794       table.addRow("row " + d, d);
795     }
796 
797     List<Date> foundDates = new ArrayList<Date>();
798     for(Map<String,Object> row : table) {
799       foundDates.add((Date)row.get("date"));
800     }
801 
802     assertEquals(dates.size(), foundDates.size());
803     for(int i = 0; i < dates.size(); ++i) {
804       Date expected = dates.get(i);
805       Date found = foundDates.get(i);
806       if(expected == null) {
807         assertNull(found);
808       } else {
809         // there are some rounding issues due to dates being stored as
810         // doubles, but it results in a 1 millisecond difference, so i'm not
811         // going to worry about it
812         long expTime = expected.getTime();
813         long foundTime = found.getTime();
814         try {
815           assertTrue((expTime == foundTime) ||
816                      (Math.abs(expTime - foundTime) <= 1));
817         } catch(Error e) {
818           System.err.println("Expected " + expTime + ", found " + foundTime);
819           throw e;
820         }
821       }
822     }
823   }
824     
825   static Object[] createTestRow(String col1Val) {
826     return new Object[] {col1Val, "R", "McCune", 1234, (byte) 0xad, 555.66d,
827         777.88f, (short) 999, new Date()};
828   }
829 
830   static Object[] createTestRow() {
831     return createTestRow("Tim");
832   }
833   
834   static void createTestTable(Database db) throws Exception {
835     new TableBuilder("test")
836       .addColumn(new ColumnBuilder("A", DataType.TEXT).toColumn())
837       .addColumn(new ColumnBuilder("B", DataType.TEXT).toColumn())
838       .addColumn(new ColumnBuilder("C", DataType.TEXT).toColumn())
839       .addColumn(new ColumnBuilder("D", DataType.LONG).toColumn())
840       .addColumn(new ColumnBuilder("E", DataType.BYTE).toColumn())
841       .addColumn(new ColumnBuilder("F", DataType.DOUBLE).toColumn())
842       .addColumn(new ColumnBuilder("G", DataType.FLOAT).toColumn())
843       .addColumn(new ColumnBuilder("H", DataType.INT).toColumn())
844       .addColumn(new ColumnBuilder("I", DataType.SHORT_DATE_TIME).toColumn())
845       .toTable(db);
846   }
847     
848   static String createString(int len) {
849     StringBuilder builder = new StringBuilder(len);
850     for(int i = 0; i < len; ++i) {
851       builder.append((char)('a' + (i % 26)));
852     }
853     String str = builder.toString();
854     return str;
855   }
856 
857   static void assertRowCount(int expectedRowCount, Table table)
858     throws Exception
859   {
860     assertEquals(expectedRowCount, countRows(table));
861     assertEquals(expectedRowCount, table.getRowCount());
862   }
863   
864   static int countRows(Table table) throws Exception {
865     int rtn = 0;
866     for(Map<String, Object> row : Cursor.createCursor(table)) {
867       rtn++;
868     }
869     return rtn;
870   }
871 
872   static void assertTable(List<Map<String, Object>> expectedTable, Table table)
873   {
874     List<Map<String, Object>> foundTable =
875       new ArrayList<Map<String, Object>>();
876     for(Map<String, Object> row : Cursor.createCursor(table)) {
877       foundTable.add(row);
878     }
879     assertEquals(expectedTable, foundTable);
880   }
881   
882   static Map<String, Object> createExpectedRow(Object... rowElements) {
883     Map<String, Object> row = new LinkedHashMap<String, Object>();
884     for(int i = 0; i < rowElements.length; i += 2) {
885       row.put((String)rowElements[i],
886               rowElements[i + 1]);
887     }
888     return row;
889   }    
890 
891   @SuppressWarnings("unchecked")
892   static List<Map<String, Object>> createExpectedTable(Map... rows) {
893     return Arrays.<Map<String, Object>>asList(rows);
894   }    
895   
896   static void dumpDatabase(Database mdb) throws Exception {
897     dumpDatabase(mdb, new PrintWriter(System.out, true));
898   }
899 
900   static void dumpTable(Table table) throws Exception {
901     dumpTable(table, new PrintWriter(System.out, true));
902   }
903 
904   static void dumpDatabase(Database mdb, PrintWriter writer) throws Exception {
905     writer.println("DATABASE:");
906     for(Table table : mdb) {
907       dumpTable(table, writer);
908     }
909   }
910 
911   static void dumpTable(Table table, PrintWriter writer) throws Exception {
912     // make sure all indexes are read
913     for(Index index : table.getIndexes()) {
914       index.initialize();
915     }
916     
917     writer.println("TABLE: " + table.getName());
918     List<String> colNames = new ArrayList<String>();
919     for(Column col : table.getColumns()) {
920       colNames.add(col.getName());
921     }
922     writer.println("COLUMNS: " + colNames);
923     for(Map<String, Object> row : Cursor.createCursor(table)) {
924 
925       // make byte[] printable
926       for(Map.Entry<String, Object> entry : row.entrySet()) {
927         Object v = entry.getValue();
928         if(v instanceof byte[]) {
929           byte[] bv = (byte[])v;
930           entry.setValue(ByteUtil.toHexString(ByteBuffer.wrap(bv), bv.length));
931         }
932       }
933       
934       writer.println(row);
935     }
936   }
937 
938   static void copyFile(File srcFile, File dstFile)
939     throws IOException
940   {
941     // FIXME should really be using commons io FileUtils here, but don't want
942     // to add dep for one simple test method
943     byte[] buf = new byte[1024];
944     OutputStream ostream = new FileOutputStream(dstFile);
945     InputStream istream = new FileInputStream(srcFile);
946     try {
947       int numBytes = 0;
948       while((numBytes = istream.read(buf)) >= 0) {
949         ostream.write(buf, 0, numBytes);
950       }
951     } finally {
952       ostream.close();
953     }
954   }
955 
956   static File createTempFile(boolean keep) throws Exception {
957     File tmp = File.createTempFile("databaseTest", ".mdb");
958     if(keep) {
959       System.out.println("Created " + tmp);
960     } else {
961       tmp.deleteOnExit();
962     }
963     return tmp;
964   }
965 
966   static byte[] toByteArray(File file)
967     throws IOException
968   {
969     // FIXME should really be using commons io IOUtils here, but don't want
970     // to add dep for one simple test method
971     FileInputStream istream = new FileInputStream(file);
972     try {
973       byte[] bytes = new byte[(int)file.length()];
974       istream.read(bytes);
975       return bytes;
976     } finally {
977       istream.close();
978     }
979   }
980   
981 }