1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl;
18
19 import java.lang.reflect.Field;
20 import java.nio.ByteBuffer;
21 import java.util.HashMap;
22 import java.util.Map;
23
24 import com.healthmarketscience.jackcess.ColumnBuilder;
25 import com.healthmarketscience.jackcess.Cursor;
26 import com.healthmarketscience.jackcess.CursorBuilder;
27 import com.healthmarketscience.jackcess.DataType;
28 import com.healthmarketscience.jackcess.Database;
29 import com.healthmarketscience.jackcess.DateTimeType;
30 import com.healthmarketscience.jackcess.Index;
31 import com.healthmarketscience.jackcess.IndexBuilder;
32 import com.healthmarketscience.jackcess.Row;
33 import com.healthmarketscience.jackcess.Table;
34 import com.healthmarketscience.jackcess.TableBuilder;
35 import static com.healthmarketscience.jackcess.TestUtil.*;
36 import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
37 import static org.junit.jupiter.api.Assertions.*;
38 import org.junit.jupiter.api.Test;
39
40
41
42
43 public class IndexCodesTest {
44
45 private static final Map<Character,String> SPECIAL_CHARS =
46 new HashMap<Character,String>();
47 static {
48 SPECIAL_CHARS.put('\b', "\\b");
49 SPECIAL_CHARS.put('\t', "\\t");
50 SPECIAL_CHARS.put('\n', "\\n");
51 SPECIAL_CHARS.put('\f', "\\f");
52 SPECIAL_CHARS.put('\r', "\\r");
53 SPECIAL_CHARS.put('\"', "\\\"");
54 SPECIAL_CHARS.put('\'', "\\'");
55 SPECIAL_CHARS.put('\\', "\\\\");
56 }
57
58 @Test
59 public void testIndexCodes() throws Exception
60 {
61 doTestDb(Basename.INDEX_CODES);
62 }
63
64 @Test
65 public void testEmoticons() throws Exception
66 {
67 doTestDb(Basename.EMOTICONS);
68 }
69
70
71
72
73
74
75
76 @Test
77 public void testInternationalExtCodes() throws Exception
78 {
79 try(Database db = create(Database.FileFormat.V2010)) {
80
81 IndexData.ColumnDescriptor col = getTextIndexColumn(db);
82
83
84
85 assertIndexKey("7f7f02010101a0ff0280ff8000", col, repeat('\u3041', 1));
86 assertIndexKey("7f7f02010101ff0280ff8000", col, repeat('\u3042', 1));
87 assertIndexKey(
88 "7f7f027f027f027f027f027f027f027f02010101aaaaa8ff028080ff808000",
89 col, repeat('\u3041', 8));
90 assertIndexKey(
91 "7f7f027f027f027f027f027f027f027f02010101ff028080ff808000",
92 col, repeat('\u3042', 8));
93 assertIndexKey(
94 "7f7f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f02" +
95 "010101aaaaaaaaaaa0ff02808080ff80808000",
96 col, repeat('\u3041', 16));
97 }
98 }
99
100
101
102
103
104
105
106 @Test
107 public void testGeneralSurrogates() throws Exception
108 {
109 try(Database db = create(Database.FileFormat.V2010)) {
110
111 IndexData.ColumnDescriptor col = getTextIndexColumn(db);
112
113
114 assertIndexKey("7fb002b4f80e020e02013f3f00", col, pair(0x10000) + "aa");
115 assertIndexKey("7fb03fb6fc0e020e02013f3f00", col, pair(0x1F600) + "aa");
116
117
118 assertIndexKey("7ffe02b4f80e020e02013e3f00", col, pair(0x20000) + "aa");
119 assertIndexKey("7ffe41b8ff0e020e02013e3f00", col, pair(0x2FFFF) + "aa");
120
121
122
123 assertIndexKey("7fb4f80e020e02013f00", col, pair(0x30000) + "aa");
124 assertIndexKey("7fb8ff0e020e02013f00", col, pair(0xEFFFF) + "aa");
125
126
127 assertIndexKey("7fff02b4f80e020e02013e3f00", col, pair(0xF0000) + "aa");
128 assertIndexKey("7fff81b8ff0e020e02013e3f00", col, pair(0x10FFFF) + "aa");
129
130
131 assertIndexKey("7f0e02fe02b4f80e0201023e3f00", col,
132 "a" + pair(0x20000) + "a");
133 assertIndexKey("7f0e020e02b4f80e020102023f00", col,
134 "aa" + pair(0x30000) + "a");
135 }
136 }
137
138
139
140
141
142
143 @Test
144 public void testGeneralLegacySurrogates() throws Exception
145 {
146 try(Database db = create(Database.FileFormat.V2003)) {
147
148 IndexData.ColumnDescriptor col = getTextIndexColumn(db);
149
150
151
152 for(int cp : new int[]{0x10000, 0x20000, 0x30000, 0xF0000, 0x10FFFF}) {
153 assertIndexKey("7f4a4a0100", col, pair(cp) + "aa");
154 assertIndexKey("7f4a4a0100", col, "a" + pair(cp) + "a");
155 assertIndexKey("7f4a4a0100", col, "aa" + pair(cp));
156 assertIndexKey("7f4a4a4a0100", col, "aa" + pair(cp) + "a");
157 }
158 }
159 }
160
161 private static String pair(int codePoint) {
162 return new String(Character.toChars(codePoint));
163 }
164
165
166
167
168
169
170 @Test
171 public void testTruncatedKey() throws Exception
172 {
173 try(Database db = create(Database.FileFormat.V2010)) {
174
175 IndexData.ColumnDescriptor col = getTextIndexColumn(db);
176
177
178
179
180 byte[] fullKey = encodeIndexKey(col, repeat('\u3041', 200));
181 assertEquals(533, fullKey.length);
182
183
184 assertEquals(
185 "7f7f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f02" +
186 "7f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f" +
187 "027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f02" +
188 "7f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f" +
189 "027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f02" +
190 "7f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f" +
191 "027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f02" +
192 "7f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f" +
193 "027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f02" +
194 "7f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f" +
195 "027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f02" +
196 "7f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f027f" +
197 "027f027f02010101aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
198 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
199 "aaaaaaaaaaaaaaaaa8ff0280808080808080808080808080808080808080808080" +
200 "80808080808080ff8080808080d06b",
201 toCompactHex(IndexData.truncateEntryBytes(fullKey)));
202 }
203 }
204
205 private static IndexData.ColumnDescriptor getTextIndexColumn(Database db)
206 throws Exception
207 {
208 Table t = new TableBuilder("test")
209 .addColumn(new ColumnBuilder("data", DataType.MEMO))
210 .addIndex(new IndexBuilder("dataidx").addColumns("data"))
211 .toTable(db);
212 IndexImpl idx = (IndexImpl)t.getIndex("dataidx");
213 return idx.getIndexData().getColumns().get(0);
214 }
215
216 private static void assertIndexKey(String expected,
217 IndexData.ColumnDescriptor col,
218 String value)
219 throws Exception
220 {
221 assertEquals(expected, toCompactHex(encodeIndexKey(col, value)),
222 "key for " + toUnicodeStr(value));
223 }
224
225 private static byte[] encodeIndexKey(IndexData.ColumnDescriptor col,
226 String value)
227 throws Exception
228 {
229 ByteUtil.ByteStream bout = new ByteUtil.ByteStream();
230 col.writeValue(value, bout);
231 return bout.toByteArray();
232 }
233
234 private static String repeat(char c, int num)
235 {
236 StringBuilder sb = new StringBuilder(num);
237 for(int i = 0; i < num; ++i) {
238 sb.append(c);
239 }
240 return sb.toString();
241 }
242
243 private static String toCompactHex(byte[] bytes)
244 {
245 StringBuilder sb = new StringBuilder(bytes.length * 2);
246 for(byte b : bytes) {
247 sb.append(String.format("%02x", b));
248 }
249 return sb.toString();
250 }
251
252 private static void doTestDb(Basename dbBaseName) throws Exception
253 {
254 for (final TestDB testDB : TestDB.getSupportedForBasename(dbBaseName, true)) {
255 try (Database db = openMem(testDB)) {
256 db.setDateTimeType(DateTimeType.DATE);
257
258 for(Table t : db) {
259 for(Index index : t.getIndexes()) {
260 checkIndexEntries(testDB, t, index);
261 }
262 }
263 }
264 }
265 }
266
267 public static void checkIndexEntries(final TestDB testDB, Table t, Index index) throws Exception
268 {
269 Cursor cursor = CursorBuilder.createCursor(index);
270 while(cursor.moveToNextRow()) {
271
272 Row row = cursor.getCurrentRow();
273
274 Object data = row.get("data");
275 if((testDB.getExpectedFileFormat() == Database.FileFormat.V1997) &&
276 (data instanceof String) && ((String)data).contains("\uFFFD")) {
277
278 continue;
279 }
280
281 Cursor.Position curPos = cursor.getSavepoint().getCurrentPosition();
282 boolean success = false;
283 try {
284 findRow(testDB, t, index, row, curPos);
285 success = true;
286 } finally {
287 if(!success) {
288 System.out.println("CurPos: " + curPos);
289 System.out.println("Value: " + row + ": " +
290 toUnicodeStr(row.get("data")));
291 }
292 }
293 }
294
295 }
296
297 private static void findRow(final TestDB testDB, Table t, Index index,
298 Row expectedRow,
299 Cursor.Position expectedPos)
300 throws Exception
301 {
302 Object[] idxRow = ((IndexImpl)index).constructIndexRow(expectedRow);
303 Cursor cursor = CursorBuilder.createCursor(index, idxRow, idxRow);
304
305 Cursor.Position startPos = cursor.getSavepoint().getCurrentPosition();
306
307 cursor.beforeFirst();
308 while(cursor.moveToNextRow()) {
309 Row row = cursor.getCurrentRow();
310 if(expectedRow.equals(row)) {
311
312 Cursor.Position curPos = cursor.getSavepoint().getCurrentPosition();
313 assertEquals(entryToString(expectedPos), entryToString(curPos));
314 return;
315 }
316 }
317
318 fail("testDB: " + testDB + ";\nCould not find expected row " + expectedRow + " starting at " +
319 entryToString(startPos));
320 }
321
322 public static String toUnicodeStr(Object obj) {
323 StringBuilder sb = new StringBuilder();
324 for(char c : obj.toString().toCharArray()) {
325 sb.append(toUnicodeStr(c)).append(" ");
326 }
327 return sb.toString();
328 }
329
330 private static String toUnicodeStr(char c) {
331 String specialStr = SPECIAL_CHARS.get(c);
332 if(specialStr != null) {
333 return specialStr;
334 }
335
336 String digits = Integer.toHexString(c).toUpperCase();
337 while(digits.length() < 4) {
338 digits = "0" + digits;
339 }
340 return "\\u" + digits;
341 }
342
343 public static String entryToString(Cursor.Position curPos)
344 throws Exception
345 {
346 Field eField = curPos.getClass().getDeclaredField("_entry");
347 eField.setAccessible(true);
348 IndexData.Entry entry = (IndexData.Entry)eField.get(curPos);
349 Field ebField = entry.getClass().getDeclaredField("_entryBytes");
350 ebField.setAccessible(true);
351 byte[] entryBytes = (byte[])ebField.get(entry);
352
353 return ByteUtil.toHexString(ByteBuffer.wrap(entryBytes),
354 0, entryBytes.length, false);
355 }
356
357 }