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.IOException;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.SortedSet;
38  import java.util.TreeSet;
39  
40  import junit.framework.TestCase;
41  
42  import static com.healthmarketscience.jackcess.DatabaseTest.*;
43  
44  /**
45   * @author James Ahlborn
46   */
47  public class IndexTest extends TestCase {
48  
49    /**
50     * Creates a new <code>IndexTest</code> instance.
51     *
52     */
53    public IndexTest(String name) {
54      super(name);
55    }
56  
57    public void testByteOrder() throws Exception {
58      byte b1 = (byte)0x00;
59      byte b2 = (byte)0x01;
60      byte b3 = (byte)0x7F;
61      byte b4 = (byte)0x80;
62      byte b5 = (byte)0xFF;
63  
64      assertTrue(ByteUtil.asUnsignedByte(b1) < ByteUtil.asUnsignedByte(b2));
65      assertTrue(ByteUtil.asUnsignedByte(b2) < ByteUtil.asUnsignedByte(b3));
66      assertTrue(ByteUtil.asUnsignedByte(b3) < ByteUtil.asUnsignedByte(b4));
67      assertTrue(ByteUtil.asUnsignedByte(b4) < ByteUtil.asUnsignedByte(b5));
68    }
69    
70    public void testByteCodeComparator() {
71      byte[] b0 = null;
72      byte[] b1 = new byte[]{(byte)0x00};
73      byte[] b2 = new byte[]{(byte)0x00, (byte)0x00};
74      byte[] b3 = new byte[]{(byte)0x00, (byte)0x01};
75      byte[] b4 = new byte[]{(byte)0x01};
76      byte[] b5 = new byte[]{(byte)0x80};
77      byte[] b6 = new byte[]{(byte)0xFF};
78      byte[] b7 = new byte[]{(byte)0xFF, (byte)0x00};
79      byte[] b8 = new byte[]{(byte)0xFF, (byte)0x01};
80  
81      List<byte[]> expectedList = Arrays.<byte[]>asList(b0, b1, b2, b3, b4,
82                                                        b5, b6, b7, b8);
83      SortedSet<byte[]> sortedSet = new TreeSet<byte[]>(
84          Index.BYTE_CODE_COMPARATOR);
85      sortedSet.addAll(expectedList);
86      assertEquals(expectedList, new ArrayList<byte[]>(sortedSet));
87      
88    }
89  
90    public void testPrimaryKey() throws Exception {
91      Table table = open().getTable("Table1");
92      Map<String, Boolean> foundPKs = new HashMap<String, Boolean>();
93      for(Index index : table.getIndexes()) {
94        foundPKs.put(index.getColumns().iterator().next().getName(),
95                     index.isPrimaryKey());
96      }
97      Map<String, Boolean> expectedPKs = new HashMap<String, Boolean>();
98      expectedPKs.put("A", Boolean.TRUE);
99      expectedPKs.put("B", Boolean.FALSE);
100     assertEquals(expectedPKs, foundPKs);
101   }
102   
103   public void testIndexSlots() throws Exception
104   {
105     Database mdb = open(new File("test/data/indexTest.mdb"));
106 
107     Table table = mdb.getTable("Table1");
108     for(Index idx : table.getIndexes()) {
109       idx.initialize();
110     }
111     assertEquals(4, table.getIndexes().size());
112     assertEquals(4, table.getIndexSlotCount());
113     checkIndexColumns(table,
114                       "id", "id",
115                       "PrimaryKey", "id",
116                       "Table2Table1", "otherfk1",
117                       "Table3Table1", "otherfk2");
118     
119     table = mdb.getTable("Table2");
120     for(Index idx : table.getIndexes()) {
121       idx.initialize();
122     }
123     assertEquals(2, table.getIndexes().size());
124     assertEquals(3, table.getIndexSlotCount());
125     checkIndexColumns(table,
126                       "id", "id",
127                       "PrimaryKey", "id");
128 
129     table = mdb.getTable("Table3");
130     for(Index idx : table.getIndexes()) {
131       idx.initialize();
132     }
133     assertEquals(2, table.getIndexes().size());
134     assertEquals(3, table.getIndexSlotCount());
135     checkIndexColumns(table,
136                       "id", "id",
137                       "PrimaryKey", "id");
138   }
139 
140   public void testComplexIndex() throws Exception
141   {
142     // this file has an index with "compressed" entries and node pages
143     File origFile = new File("test/data/compIndexTest.mdb");
144     Database db = open(origFile);
145     Table t = db.getTable("Table1");
146     Index index = t.getIndexes().get(0);
147     assertFalse(index.isInitialized());
148     assertEquals(512, countRows(t));
149     assertEquals(512, index.getEntryCount());
150     db.close();
151 
152     // copy to temp file and attempt to edit
153     db = openCopy(origFile);
154     t = db.getTable("Table1");
155     index = t.getIndexes().get(0);
156 
157     System.out.println("IndexTest: Index type: " + index.getClass());
158     try {
159       t.addRow(99, "abc", "def");
160       if(index instanceof SimpleIndex) {
161         // SimpleIndex doesn't support writing these indexes
162         fail("Should have thrown UnsupportedOperationException");
163       }
164     } catch(UnsupportedOperationException e) {
165       // success
166       if(index instanceof BigIndex) {
167         throw e;
168       }
169     }
170   }
171 
172   public void testEntryDeletion() throws Exception {
173     Table table = openCopy(new File("test/data/test.mdb")).getTable("Table1");
174 
175     for(int i = 0; i < 10; ++i) {
176       table.addRow("foo" + i, "bar" + i, (byte)42 + i, (short)53 + i, 13 * i,
177                    (6.7d / i), null, null, true);
178     }
179     table.reset();
180     assertRowCount(12, table);
181 
182     for(Index index : table.getIndexes()) {
183       assertEquals(12, index.getEntryCount());
184     }
185 
186     table.reset();
187     table.getNextRow();
188     table.getNextRow();
189     table.deleteCurrentRow();
190     table.getNextRow();
191     table.deleteCurrentRow();
192     table.getNextRow();
193     table.getNextRow();
194     table.deleteCurrentRow();
195     table.getNextRow();
196     table.getNextRow();
197     table.getNextRow();
198     table.deleteCurrentRow();
199 
200     table.reset();
201     assertRowCount(8, table);
202 
203     for(Index index : table.getIndexes()) {
204       assertEquals(8, index.getEntryCount());
205     }
206   }
207 
208   public void testIgnoreNulls() throws Exception
209   {
210     Database db = openCopy(new File("test/data/testIndexProperties.mdb"));
211 
212     doTestIgnoreNulls(db, "TableIgnoreNulls1");
213     doTestIgnoreNulls(db, "TableIgnoreNulls2");    
214 
215     db.close();
216   }
217 
218   private void doTestIgnoreNulls(Database db, String tableName)
219     throws Exception
220   {
221     Table orig = db.getTable(tableName);
222     Index origI = orig.getIndex("DataIndex");
223     Table temp = db.getTable(tableName + "_temp");
224     Index tempI = temp.getIndex("DataIndex");
225 
226     // copy from orig table to temp table
227     for(Map<String,Object> row : orig) {
228       temp.addRow(orig.asRow(row));
229     }
230 
231     assertEquals(origI.getEntryCount(), tempI.getEntryCount());
232 
233     Cursor origC = Cursor.createIndexCursor(orig, origI);
234     Cursor tempC = Cursor.createIndexCursor(temp, tempI);
235 
236     while(true) {
237       boolean origHasNext = origC.moveToNextRow();
238       boolean tempHasNext = tempC.moveToNextRow();
239       assertTrue(origHasNext == tempHasNext);
240       if(!origHasNext) {
241         break;
242       }
243 
244       Map<String,Object> origRow = origC.getCurrentRow();
245       Cursor.Position origCurPos = origC.getSavepoint().getCurrentPosition();
246       Map<String,Object> tempRow = tempC.getCurrentRow();
247       Cursor.Position tempCurPos = tempC.getSavepoint().getCurrentPosition();
248       
249       assertEquals(origRow, tempRow);
250       assertEquals(IndexCodesTest.entryToString(origCurPos),
251                    IndexCodesTest.entryToString(tempCurPos));
252     }
253   }
254 
255   public void testUnique() throws Exception
256   {
257     Database db = openCopy(new File("test/data/testIndexProperties.mdb"));
258 
259     Table t = db.getTable("TableUnique1_temp");
260     Index index = t.getIndex("DataIndex");
261 
262     doTestUnique(t, index, 1,
263                  null, true,
264                  "unique data", true,
265                  null, true,
266                  "more", false,
267                  "stuff", false,
268                  "unique data", false);
269 
270     t = db.getTable("TableUnique2_temp");
271     index = t.getIndex("DataIndex");
272 
273     doTestUnique(t, index, 2,
274                  null, null, true,
275                  "unique data", 42, true,
276                  "unique data", null, true,
277                  null, null, true,
278                  "some", 42, true,
279                  "more unique data", 13, true,
280                  null, -4242, true,
281                  "another row", -3462, false,
282                  null, 49, false,
283                  "more", null, false,
284                  "unique data", 42, false,
285                  "unique data", null, false,
286                  null, -4242, false);
287     
288     db.close();
289   }
290 
291   private void doTestUnique(Table t, Index index, int numValues,
292                             Object... testData)
293     throws Exception
294   {
295     for(int i = 0; i < testData.length; i += (numValues + 1)) {
296       Object[] row = new Object[numValues + 1];
297       row[0] = "testRow" + i;
298       for(int j = 1; j < (numValues + 1); ++j) {
299         row[j] = testData[i + j - 1];
300       }
301       boolean expectedSuccess = (Boolean)testData[i + numValues];
302 
303       IOException failure = null;
304       try {
305         index.addRow(row, new RowId(400 + i, 0));
306       } catch(IOException e) {
307         failure = e;
308       }
309       if(expectedSuccess) {
310         assertNull(failure);
311       } else {
312         assertTrue(failure != null);
313         assertTrue(failure.getMessage().contains("uniqueness"));
314       }
315     }
316   }
317 
318   public void testUniqueEntryCount() throws Exception {
319     Database db = openCopy(new File("test/data/test.mdb"));
320     Table table = db.getTable("Table1");
321     Index indA = table.getIndex("PrimaryKey");
322     Index indB = table.getIndex("B");
323 
324     assertEquals(2, indA.getUniqueEntryCount());
325     assertEquals(2, indB.getUniqueEntryCount());
326 
327     List<String> bElems = Arrays.asList("bar", null, "baz", "argle", null,
328                                         "bazzle", "37", "bar", "bar", "BAZ");
329     
330     for(int i = 0; i < 10; ++i) {
331       table.addRow("foo" + i, bElems.get(i), (byte)42 + i, (short)53 + i,
332                    13 * i, (6.7d / i), null, null, true);
333     }
334 
335     assertEquals(12, indA.getEntryCount());
336     assertEquals(12, indB.getEntryCount());
337     
338     assertEquals(12, indA.getUniqueEntryCount());
339     assertEquals(8, indB.getUniqueEntryCount());
340 
341     table = null;
342     indA = null;
343     indB = null;
344 
345     table = db.getTable("Table1");
346     indA = table.getIndex("PrimaryKey");
347     indB = table.getIndex("B");
348     
349     assertEquals(12, indA.getEntryCount());
350     assertEquals(12, indB.getEntryCount());
351     
352     assertEquals(12, indA.getUniqueEntryCount());
353     assertEquals(8, indB.getUniqueEntryCount());    
354 
355     Cursor c = Cursor.createCursor(table);
356     assertTrue(c.moveToNextRow());
357     Map<String,Object> row = c.getCurrentRow();
358     assertEquals("abcdefg", row.get("A"));
359     assertEquals("hijklmnop", row.get("B"));
360     c.deleteCurrentRow();
361 
362     assertEquals(11, indA.getEntryCount());
363     assertEquals(11, indB.getEntryCount());
364     
365     assertEquals(12, indA.getUniqueEntryCount());
366     assertEquals(8, indB.getUniqueEntryCount());        
367     
368     db.close();
369   }
370     
371   private void checkIndexColumns(Table table, String... idxInfo)
372     throws Exception
373   {
374     Map<String, String> expectedIndexes = new HashMap<String, String>();
375     for(int i = 0; i < idxInfo.length; i+=2) {
376       expectedIndexes.put(idxInfo[i], idxInfo[i+1]);
377     }
378 
379     for(Index idx : table.getIndexes()) {
380       String colName = expectedIndexes.get(idx.getName());
381       assertEquals(1, idx.getColumns().size());
382       assertEquals(colName, idx.getColumns().get(0).getName());
383       if("PrimaryKey".equals(idx.getName())) {
384         assertTrue(idx.isPrimaryKey());
385       } else {
386         assertFalse(idx.isPrimaryKey());
387       }
388     }
389   }
390   
391 }