View Javadoc
1   /*
2   Copyright (c) 2011 James Ahlborn
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.nio.ByteBuffer;
20  import java.util.Arrays;
21  import java.util.Date;
22  import java.util.List;
23  
24  import com.healthmarketscience.jackcess.complex.Attachment;
25  import com.healthmarketscience.jackcess.complex.ComplexDataType;
26  import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
27  import com.healthmarketscience.jackcess.complex.SingleValue;
28  import com.healthmarketscience.jackcess.complex.UnsupportedValue;
29  import com.healthmarketscience.jackcess.complex.Version;
30  import com.healthmarketscience.jackcess.impl.ByteUtil;
31  import com.healthmarketscience.jackcess.impl.ColumnImpl;
32  import com.healthmarketscience.jackcess.impl.PageChannel;
33  import junit.framework.TestCase;
34  import static com.healthmarketscience.jackcess.TestUtil.*;
35  import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
36  
37  
38  /**
39   *
40   * @author James Ahlborn
41   */
42  public class ComplexColumnTest extends TestCase 
43  {
44  
45    public ComplexColumnTest(String name) {
46      super(name);
47    }
48  
49    public void testVersions() throws Exception
50    {
51      for(final TestDB testDB : TestDB.getSupportedForBasename(Basename.COMPLEX)) {
52        Database db = openCopy(testDB);
53        db.setTimeZone(TEST_TZ);
54  
55        Table t1 = db.getTable("Table1");
56        Column col = t1.getColumn("append-memo-data");
57        assertTrue(col.isAppendOnly());
58        Column verCol = col.getVersionHistoryColumn();
59        assertNotNull(verCol);
60        assertEquals(ComplexDataType.VERSION_HISTORY,
61                     verCol.getComplexInfo().getType());
62  
63        for(Row row : t1) {
64          String rowId = row.getString("id");
65          ComplexValueForeignKey complexValueFk =
66            (ComplexValueForeignKey)verCol.getRowValue(row);
67  
68          String curValue = (String)col.getRowValue(row);
69        
70          if(rowId.equals("row1")) {
71            checkVersions(1, complexValueFk, curValue);
72          } else if(rowId.equals("row2")) {
73            checkVersions(2, complexValueFk, curValue,
74                          "row2-memo", new Date(1315876862334L));
75          } else if(rowId.equals("row3")) {
76            checkVersions(3, complexValueFk, curValue,
77                          "row3-memo-again", new Date(1315876965382L),
78                          "row3-memo-revised", new Date(1315876953077L),
79                          "row3-memo", new Date(1315876879126L));
80          } else if(rowId.equals("row4")) {
81            checkVersions(4, complexValueFk, curValue,
82                          "row4-memo", new Date(1315876945758L));
83          } else {
84            assertTrue(false);
85          }
86        }
87  
88        Object[] row8 = {"row8", Column.AUTO_NUMBER, "some-data", "row8-memo",
89                         Column.AUTO_NUMBER, Column.AUTO_NUMBER};
90        t1.addRow(row8);
91  
92        ComplexValueForeignKey row8ValFk = (ComplexValueForeignKey)
93          verCol.getRowValue(row8);
94        Date upTime = new Date();
95        row8ValFk.addVersion("row8-memo", upTime);
96        checkVersions(row8ValFk.get(), row8ValFk, "row8-memo",
97                      "row8-memo", upTime);    
98  
99        Cursor cursor = CursorBuilder.createCursor(t1);
100       assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row3"));
101       ComplexValueForeignKey row3ValFk = (ComplexValueForeignKey)
102         cursor.getCurrentRowValue(verCol);
103       cursor.setCurrentRowValue(col, "new-value");
104       Version v = row3ValFk.addVersion("new-value", upTime);
105       checkVersions(3, row3ValFk, "new-value",
106                     "new-value", upTime,
107                     "row3-memo-again", new Date(1315876965382L),
108                     "row3-memo-revised", new Date(1315876953077L),
109                     "row3-memo", new Date(1315876879126L));
110 
111       try {
112         v.update();
113         fail("UnsupportedOperationException should have been thrown");
114       } catch(UnsupportedOperationException expected) {
115         // success
116       }
117 
118       checkVersions(3, row3ValFk, "new-value",
119                     "new-value", upTime,
120                     "row3-memo-again", new Date(1315876965382L),
121                     "row3-memo-revised", new Date(1315876953077L),
122                     "row3-memo", new Date(1315876879126L));
123     
124       try {
125         v.delete();
126         fail("UnsupportedOperationException should have been thrown");
127       } catch(UnsupportedOperationException expected) {
128         // success
129       }
130 
131       checkVersions(3, row3ValFk, "new-value",
132                     "new-value", upTime,
133                     "row3-memo-again", new Date(1315876965382L),
134                     "row3-memo-revised", new Date(1315876953077L),
135                     "row3-memo", new Date(1315876879126L));
136     
137       try {
138         v.getComplexValueForeignKey().deleteAllValues();
139         fail("UnsupportedOperationException should have been thrown");
140       } catch(UnsupportedOperationException expected) {
141         // success
142       }
143 
144       checkVersions(3, row3ValFk, "new-value",
145                     "new-value", upTime,
146                     "row3-memo-again", new Date(1315876965382L),
147                     "row3-memo-revised", new Date(1315876953077L),
148                     "row3-memo", new Date(1315876879126L));
149     
150       db.close();
151     }
152   }
153 
154   public void testAttachments() throws Exception
155   {
156     for(final TestDB testDB : TestDB.getSupportedForBasename(Basename.COMPLEX)) {
157       
158       Database db = openCopy(testDB);
159 
160       Table t1 = db.getTable("Table1");
161       Column col = t1.getColumn("attach-data");
162       assertEquals(ComplexDataType.ATTACHMENT,
163                    col.getComplexInfo().getType());
164 
165       for(Row row : t1) {
166         String rowId = row.getString("id");
167         ComplexValueForeignKey complexValueFk =
168           (ComplexValueForeignKey)col.getRowValue(row);
169 
170         if(rowId.equals("row1")) {
171           checkAttachments(1, complexValueFk);
172         } else if(rowId.equals("row2")) {
173           checkAttachments(2, complexValueFk, "test_data.txt", "test_data2.txt");
174         } else if(rowId.equals("row3")) {
175           checkAttachments(3, complexValueFk);
176         } else if(rowId.equals("row4")) {
177           checkAttachments(4, complexValueFk, "test_data2.txt");
178         } else {
179           assertTrue(false);
180         }
181       }
182 
183       Object[] row8 = {"row8", Column.AUTO_NUMBER, "some-data", "row8-memo",
184                        Column.AUTO_NUMBER, Column.AUTO_NUMBER};
185       t1.addRow(row8);
186 
187       ComplexValueForeignKey row8ValFk = (ComplexValueForeignKey)
188         col.getRowValue(row8);
189       row8ValFk.addAttachment(null, "test_data.txt", "txt",
190                               getFileBytes("test_data.txt"), null, null);
191       checkAttachments(row8ValFk.get(), row8ValFk, "test_data.txt");
192       row8ValFk.addEncodedAttachment(null, "test_data2.txt", "txt",
193                                      getEncodedFileBytes("test_data2.txt"), null,
194                                      null);
195       checkAttachments(row8ValFk.get(), row8ValFk, "test_data.txt", 
196                        "test_data2.txt");
197 
198       Cursor cursor = CursorBuilder.createCursor(t1);
199       assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row4"));
200       ComplexValueForeignKey row4ValFk = (ComplexValueForeignKey)
201         cursor.getCurrentRowValue(col);
202       Attachment a = row4ValFk.addAttachment(null, "test_data.txt", "txt",
203                                              getFileBytes("test_data.txt"), null,
204                                              null);
205       checkAttachments(4, row4ValFk, "test_data2.txt", "test_data.txt");
206 
207       a.setFileType("zip");
208       a.setFileName("some_data.zip");
209       byte[] newBytes = "this is not a zip file".getBytes("US-ASCII");
210       a.setFileData(newBytes);
211       a.update();
212 
213       Attachment updated = row4ValFk.getAttachments().get(1);
214       assertNotSame(updated, a);
215       assertEquals("zip", updated.getFileType());
216       assertEquals("some_data.zip", updated.getFileName());
217       assertTrue(Arrays.equals(newBytes, updated.getFileData()));
218       byte[] encBytes = updated.getEncodedFileData();
219       assertEquals(newBytes.length + 28, encBytes.length);
220       ByteBuffer bb = PageChannel.wrap(encBytes);
221       assertEquals(0, bb.getInt());
222       assertTrue(ByteUtil.matchesRange(bb, 28, newBytes));
223 
224       updated.delete();
225       checkAttachments(4, row4ValFk, "test_data2.txt");
226       row4ValFk.getAttachments().get(0).delete();
227       checkAttachments(4, row4ValFk);
228 
229       assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row2"));
230       ComplexValueForeignKey row2ValFk = (ComplexValueForeignKey)
231         cursor.getCurrentRowValue(col);
232       row2ValFk.deleteAllValues();
233       checkAttachments(2, row2ValFk);      
234     
235       db.close();
236     }
237   }
238 
239   public void testMultiValues() throws Exception
240   {
241     for(final TestDB testDB : TestDB.getSupportedForBasename(Basename.COMPLEX)) {
242       
243       Database db = openCopy(testDB);
244 
245       Table t1 = db.getTable("Table1");
246       Column col = t1.getColumn("multi-value-data");
247       assertEquals(ComplexDataType.MULTI_VALUE,
248                    col.getComplexInfo().getType());
249 
250       for(Row row : t1) {
251         String rowId = row.getString("id");
252         ComplexValueForeignKey complexValueFk =
253           (ComplexValueForeignKey)col.getRowValue(row);
254 
255         if(rowId.equals("row1")) {
256           checkMultiValues(1, complexValueFk);
257         } else if(rowId.equals("row2")) {
258           checkMultiValues(2, complexValueFk, "value1", "value4");
259         } else if(rowId.equals("row3")) {
260           checkMultiValues(3, complexValueFk,
261                            "value1", "value2", "value3", "value4");
262         } else if(rowId.equals("row4")) {
263           checkMultiValues(4, complexValueFk);
264         } else {
265           assertTrue(false);
266         }
267       }     
268 
269       Object[] row8 = {"row8", Column.AUTO_NUMBER, "some-data", "row8-memo",
270                        Column.AUTO_NUMBER, Column.AUTO_NUMBER};
271       t1.addRow(row8);
272 
273       ComplexValueForeignKey row8ValFk = (ComplexValueForeignKey)
274         col.getRowValue(row8);
275       row8ValFk.addMultiValue("value1");
276       row8ValFk.addMultiValue("value2");
277       checkMultiValues(row8ValFk.get(), row8ValFk, "value1", "value2");
278 
279       Cursor cursor = CursorBuilder.createCursor(t1);
280       assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row2"));
281       ComplexValueForeignKey row2ValFk = (ComplexValueForeignKey)
282         cursor.getCurrentRowValue(col);
283       SingleValue v = row2ValFk.addMultiValue("value2");
284       row2ValFk.addMultiValue("value3");
285       checkMultiValues(2, row2ValFk, "value1", "value4", "value2", "value3");
286 
287       v.set("value5");
288       v.update();
289       checkMultiValues(2, row2ValFk, "value1", "value4", "value5", "value3");
290 
291       v.delete();
292       checkMultiValues(2, row2ValFk, "value1", "value4", "value3");
293       row2ValFk.getMultiValues().get(0).delete();
294       checkMultiValues(2, row2ValFk, "value4", "value3");
295       row2ValFk.getMultiValues().get(1).delete();
296       checkMultiValues(2, row2ValFk, "value4");
297       row2ValFk.getMultiValues().get(0).delete();
298       checkMultiValues(2, row2ValFk);
299 
300       assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row3"));
301       ComplexValueForeignKey row3ValFk = (ComplexValueForeignKey)
302         cursor.getCurrentRowValue(col);
303       row3ValFk.deleteAllValues();
304       checkMultiValues(3, row3ValFk);
305 
306       // test multi-value col props
307       PropertyMap props = col.getProperties();
308       assertEquals(Boolean.TRUE, props.getValue(PropertyMap.ALLOW_MULTI_VALUE_PROP));
309       assertEquals("Value List", props.getValue(PropertyMap.ROW_SOURCE_TYPE_PROP));
310       assertEquals("\"value1\";\"value2\";\"value3\";\"value4\"", 
311                    props.getValue(PropertyMap.ROW_SOURCE_PROP));
312     
313       db.close();
314     }
315   }
316   
317   public void testUnsupported() throws Exception
318   {
319     for(final TestDB testDB : TestDB.getSupportedForBasename(Basename.UNSUPPORTED)) {
320       
321       Database db = openCopy(testDB);
322 
323       Table t1 = db.getTable("Test");
324       Column col = t1.getColumn("UnknownComplex");
325       assertEquals(ComplexDataType.UNSUPPORTED,
326                    col.getComplexInfo().getType());
327 
328       for(Row row : t1) {
329         Integer rowId = row.getInt("ID");
330         ComplexValueForeignKey complexValueFk =
331           (ComplexValueForeignKey)col.getRowValue(row);
332 
333         if(rowId.equals(1)) {
334           checkUnsupportedValues(1, complexValueFk, 
335                                  "RawData[(5) FF FE 62 61  7A]");
336         } else if(rowId.equals(2)) {
337           checkUnsupportedValues(2, complexValueFk, "RawData[(5) FF FE 66 6F  6F]", "RawData[(5) FF FE 62 61  7A]");
338         } else if(rowId.equals(3)) {
339           checkUnsupportedValues(3, complexValueFk);
340         } else {
341           assertTrue(false);
342         }
343       }     
344     
345       db.close();
346     }
347   }
348   
349   private static void checkVersions(
350       int cValId, ComplexValueForeignKey complexValueFk,
351       String curValue, Object... versionInfos)
352     throws Exception
353   {
354     assertEquals(cValId, complexValueFk.get());
355 
356     List<Version> versions = complexValueFk.getVersions();
357     if(versionInfos.length == 0) {
358       assertTrue(versions.isEmpty());
359       assertNull(curValue);
360     } else {
361       assertEquals(versionInfos.length / 2, versions.size());
362       assertEquals(curValue, versions.get(0).getValue());
363       for(int i = 0; i < versionInfos.length; i+=2) {
364         String value = (String)versionInfos[i];
365         Date modDate = (Date)versionInfos[i+1];
366         Version v = versions.get(i/2);
367         assertEquals(value, v.getValue());
368         assertSameDate(modDate, v.getModifiedDate());
369       }
370     }
371   }
372 
373   private static void checkAttachments(
374       int cValId, ComplexValueForeignKey complexValueFk,
375       String... fileNames)
376     throws Exception
377   {
378     assertEquals(cValId, complexValueFk.get());
379     
380     List<Attachment> attachments = complexValueFk.getAttachments();
381     if(fileNames.length == 0) {
382       assertTrue(attachments.isEmpty());
383     } else {
384       assertEquals(fileNames.length, attachments.size());
385       for(int i = 0; i < fileNames.length; ++i) {
386         String fname = fileNames[i];
387         Attachment a = attachments.get(i);
388         assertEquals(fname, a.getFileName());
389         assertEquals("txt", a.getFileType());
390         assertTrue(Arrays.equals(getFileBytes(fname), a.getFileData()));
391         assertTrue(Arrays.equals(getEncodedFileBytes(fname), 
392                                  a.getEncodedFileData()));
393       }
394     }
395   }
396   
397   private static void checkMultiValues(
398       int cValId, ComplexValueForeignKey complexValueFk,
399       Object... expectedValues)
400     throws Exception
401   {
402     assertEquals(cValId, complexValueFk.get());
403 
404     List<SingleValue> values = complexValueFk.getMultiValues();
405     if(expectedValues.length == 0) {
406       assertTrue(values.isEmpty());
407     } else {
408       assertEquals(expectedValues.length, values.size());
409       for(int i = 0; i < expectedValues.length; ++i) {
410         Object value = expectedValues[i];
411         SingleValue v = values.get(i);
412         assertEquals(value, v.get());
413       }
414     }    
415   }
416 
417   private static void checkUnsupportedValues(
418       int cValId, ComplexValueForeignKey complexValueFk,
419       String... expectedValues)
420     throws Exception
421   {
422     assertEquals(cValId, complexValueFk.get());
423 
424     List<UnsupportedValue> values = complexValueFk.getUnsupportedValues();
425     if(expectedValues.length == 0) {
426       assertTrue(values.isEmpty());
427     } else {
428       assertEquals(expectedValues.length, values.size());
429       for(int i = 0; i < expectedValues.length; ++i) {
430         String value = expectedValues[i];
431         UnsupportedValue v = values.get(i);
432         assertEquals(1, v.getValues().size());
433         Object rv = v.get("Value");
434         assertTrue(ColumnImpl.isRawData(rv));
435         assertEquals(value, rv.toString());
436       }
437     }    
438   }
439 
440   private static byte[] getFileBytes(String fname) throws Exception
441   {
442     if("test_data.txt".equals(fname)) {
443       return TEST_BYTES;
444     }
445     if("test_data2.txt".equals(fname)) {
446       return TEST2_BYTES;
447     }
448     throw new RuntimeException("unexpected bytes");
449   }
450   
451   private static byte[] getEncodedFileBytes(String fname) throws Exception
452   {
453     if("test_data.txt".equals(fname)) {
454       return TEST_ENC_BYTES;
455     }
456     if("test_data2.txt".equals(fname)) {
457       return TEST2_ENC_BYTES;
458     }
459     throw new RuntimeException("unexpected bytes");
460   }
461   
462   private static byte b(int i) { return (byte)i; }
463   
464   private static byte[] getAsciiBytes(String str) {
465     try {
466       return str.getBytes("US-ASCII");
467     } catch(Exception e) {
468       throw new RuntimeException(e);
469     }
470   }
471   
472   private static final byte[] TEST_ENC_BYTES = new byte[] {
473     b(0x01),b(0x00),b(0x00),b(0x00),b(0x3A),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62),
474     b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89),b(0x25),b(0x89),b(0x0A),b(0x69),b(0xF9),
475     b(0x45),b(0x0A),b(0x89),b(0x25),b(0x25),b(0x89),b(0xC9),b(0x19),b(0xB9),b(0xA9),b(0x79),b(0x25),b(0x7A),b(0x00),b(0x52),b(0xA9),b(0x0F),b(0x7A)
476   };
477 
478   private static final byte[] TEST_BYTES = getAsciiBytes("this is some test data for attachment.");
479   
480   private static final byte[] TEST2_ENC_BYTES = new byte[] {
481     b(0x01),b(0x00),b(0x00),b(0x00),b(0x3F),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62),
482     b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0xB9),b(0xF9),b(0x45),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89),
483     b(0x25),b(0x89),b(0x0A),b(0x69),b(0xF9),b(0x45),b(0x0A),b(0x89),b(0x25),b(0x25),b(0x89),b(0xC9),b(0x19),b(0xB9),b(0xA9),b(0x79),b(0x25),b(0x7A),b(0x00),b(0xA5),b(0x0B),b(0x11),b(0x4D)
484   };
485 
486   private static final byte[] TEST2_BYTES = getAsciiBytes("this is some more test data for attachment.");
487   
488 }