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