1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
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
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
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 }