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.io.IOException;
20 import java.nio.ByteBuffer;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.Map;
24
25 import com.healthmarketscience.jackcess.CursorBuilder;
26 import com.healthmarketscience.jackcess.Index;
27 import com.healthmarketscience.jackcess.IndexBuilder;
28 import org.apache.commons.lang3.builder.ToStringBuilder;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31
32
33
34
35
36
37
38 public class IndexImpl implements Index, Comparable<IndexImpl>
39 {
40 protected static final Log LOG = LogFactory.getLog(IndexImpl.class);
41
42
43 public static final byte PRIMARY_KEY_INDEX_TYPE = (byte)1;
44
45
46 public static final byte FOREIGN_KEY_INDEX_TYPE = (byte)2;
47
48
49 private static final byte CASCADE_UPDATES_FLAG = (byte)1;
50
51 private static final byte CASCADE_DELETES_FLAG = (byte)1;
52
53
54 private static final byte CASCADE_NULL_FLAG = (byte)2;
55
56
57 static final byte FK_PRIMARY_TABLE_TYPE = (byte)1;
58
59 static final byte FK_SECONDARY_TABLE_TYPE = (byte)2;
60
61
62 private static final int INVALID_INDEX_NUMBER = -1;
63
64
65
66 private final IndexData _data;
67
68 private final int _indexNumber;
69
70 private final byte _indexType;
71
72 private String _name;
73
74 private final ForeignKeyReference _reference;
75
76 protected IndexImpl(ByteBuffer tableBuffer, List<IndexData> indexDatas,
77 JetFormat format)
78 throws IOException
79 {
80 ByteUtil.forward(tableBuffer, format.SKIP_BEFORE_INDEX_SLOT);
81 _indexNumber = tableBuffer.getInt();
82 int indexDataNumber = tableBuffer.getInt();
83
84
85 byte relIndexType = tableBuffer.get();
86 int relIndexNumber = tableBuffer.getInt();
87 int relTablePageNumber = tableBuffer.getInt();
88 byte cascadeUpdatesFlag = tableBuffer.get();
89 byte cascadeDeletesFlag = tableBuffer.get();
90
91 _indexType = tableBuffer.get();
92
93 if((_indexType == FOREIGN_KEY_INDEX_TYPE) &&
94 (relIndexNumber != INVALID_INDEX_NUMBER)) {
95 _reference = new ForeignKeyReference(
96 relIndexType, relIndexNumber, relTablePageNumber,
97 ((cascadeUpdatesFlag & CASCADE_UPDATES_FLAG) != 0),
98 ((cascadeDeletesFlag & CASCADE_DELETES_FLAG) != 0),
99 ((cascadeDeletesFlag & CASCADE_NULL_FLAG) != 0));
100 } else {
101 _reference = null;
102 }
103
104 ByteUtil.forward(tableBuffer, format.SKIP_AFTER_INDEX_SLOT);
105
106 _data = indexDatas.get(indexDataNumber);
107
108 _data.addIndex(this);
109 }
110
111 public IndexData getIndexData() {
112 return _data;
113 }
114
115 @Override
116 public TableImpl getTable() {
117 return getIndexData().getTable();
118 }
119
120 public JetFormat getFormat() {
121 return getTable().getFormat();
122 }
123
124 public PageChannel getPageChannel() {
125 return getTable().getPageChannel();
126 }
127
128 public int getIndexNumber() {
129 return _indexNumber;
130 }
131
132 public byte getIndexFlags() {
133 return getIndexData().getIndexFlags();
134 }
135
136 public int getUniqueEntryCount() {
137 return getIndexData().getUniqueEntryCount();
138 }
139
140 public int getUniqueEntryCountOffset() {
141 return getIndexData().getUniqueEntryCountOffset();
142 }
143
144 @Override
145 public String getName() {
146 return _name;
147 }
148
149 void setName(String name) {
150 _name = name;
151 }
152
153 @Override
154 public boolean isPrimaryKey() {
155 return _indexType == PRIMARY_KEY_INDEX_TYPE;
156 }
157
158 @Override
159 public boolean isForeignKey() {
160 return _indexType == FOREIGN_KEY_INDEX_TYPE;
161 }
162
163 public ForeignKeyReference getReference() {
164 return _reference;
165 }
166
167 @Override
168 public IndexImpl getReferencedIndex() throws IOException {
169
170 if(_reference == null) {
171 return null;
172 }
173
174 TableImpl refTable = getTable().getDatabase().getTable(
175 _reference.getOtherTablePageNumber());
176
177 if(refTable == null) {
178 throw new IOException(withErrorContext(
179 "Reference to missing table " + _reference.getOtherTablePageNumber()));
180 }
181
182 IndexImpl refIndex = null;
183 int idxNumber = _reference.getOtherIndexNumber();
184 for(IndexImpl idx : refTable.getIndexes()) {
185 if(idx.getIndexNumber() == idxNumber) {
186 refIndex = idx;
187 break;
188 }
189 }
190
191 if(refIndex == null) {
192 throw new IOException(withErrorContext(
193 "Reference to missing index " + idxNumber +
194 " on table " + refTable.getName()));
195 }
196
197
198
199 ForeignKeyReference otherRef = refIndex.getReference();
200 if((otherRef == null) ||
201 (otherRef.getOtherTablePageNumber() !=
202 getTable().getTableDefPageNumber()) ||
203 (otherRef.getOtherIndexNumber() != _indexNumber)) {
204 throw new IOException(withErrorContext(
205 "Found unexpected index " + refIndex.getName() +
206 " on table " + refTable.getName() + " with reference " + otherRef));
207 }
208
209 return refIndex;
210 }
211
212 @Override
213 public boolean shouldIgnoreNulls() {
214 return getIndexData().shouldIgnoreNulls();
215 }
216
217 @Override
218 public boolean isUnique() {
219 return getIndexData().isUnique();
220 }
221
222 @Override
223 public boolean isRequired() {
224 return getIndexData().isRequired();
225 }
226
227 @Override
228 public List<IndexData.ColumnDescriptor> getColumns() {
229 return getIndexData().getColumns();
230 }
231
232 @Override
233 public int getColumnCount() {
234 return getIndexData().getColumnCount();
235 }
236
237 @Override
238 public CursorBuilder newCursor() {
239 return getTable().newCursor().setIndex(this);
240 }
241
242
243
244
245 public boolean isInitialized() {
246 return getIndexData().isInitialized();
247 }
248
249
250
251
252
253
254 public void initialize() throws IOException {
255 getIndexData().initialize();
256 }
257
258
259
260
261
262
263 public IndexData.EntryCursor cursor()
264 throws IOException
265 {
266 return cursor(null, true, null, true);
267 }
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282 public IndexData.EntryCursor cursor(Object[] startRow,
283 boolean startInclusive,
284 Object[] endRow,
285 boolean endInclusive)
286 throws IOException
287 {
288 return getIndexData().cursor(startRow, startInclusive, endRow,
289 endInclusive);
290 }
291
292
293
294
295
296
297
298
299 public Object[] constructIndexRowFromEntry(Object... values)
300 {
301 return getIndexData().constructIndexRowFromEntry(values);
302 }
303
304
305
306
307
308
309
310
311
312
313 public Object[] constructPartialIndexRowFromEntry(
314 Object filler, Object... values)
315 {
316 return getIndexData().constructPartialIndexRowFromEntry(filler, values);
317 }
318
319
320
321
322
323
324
325 public Object[] constructIndexRow(String colName, Object value)
326 {
327 return constructIndexRow(Collections.singletonMap(colName, value));
328 }
329
330
331
332
333
334
335
336
337 public Object[] constructPartialIndexRow(Object filler, String colName, Object value)
338 {
339 return constructPartialIndexRow(filler, Collections.singletonMap(colName, value));
340 }
341
342
343
344
345
346
347
348 public Object[] constructIndexRow(Map<String,?> row)
349 {
350 return getIndexData().constructIndexRow(row);
351 }
352
353
354
355
356
357
358
359
360
361
362 public Object[] constructPartialIndexRow(Object filler, Map<String,?> row)
363 {
364 return getIndexData().constructPartialIndexRow(filler, row);
365 }
366
367 @Override
368 public String toString() {
369 ToStringBuilder sb = CustomToStringStyle.builder(this)
370 .append("name", "(" + getTable().getName() + ") " + _name)
371 .append("number", _indexNumber)
372 .append("isPrimaryKey", isPrimaryKey())
373 .append("isForeignKey", isForeignKey());
374 if(_reference != null) {
375 sb.append("foreignKeyReference", _reference);
376 }
377 sb.append("data", _data);
378 return sb.toString();
379 }
380
381 @Override
382 public int compareTo(IndexImpl other) {
383 if (_indexNumber > other.getIndexNumber()) {
384 return 1;
385 } else if (_indexNumber < other.getIndexNumber()) {
386 return -1;
387 } else {
388 return 0;
389 }
390 }
391
392
393
394
395
396
397 protected static void writeDefinitions(
398 TableCreator creator, ByteBuffer buffer)
399 throws IOException
400 {
401
402 for(IndexBuilder idx : creator.getIndexes()) {
403 writeDefinition(creator, idx, buffer);
404 }
405
406
407 for(IndexBuilder idx : creator.getIndexes()) {
408 TableImpl.writeName(buffer, idx.getName(), creator.getCharset());
409 }
410 }
411
412 protected static void writeDefinition(
413 TableMutator mutator, IndexBuilder idx, ByteBuffer buffer)
414 throws IOException
415 {
416 TableMutator.IndexDataState idxDataState = mutator.getIndexDataState(idx);
417
418
419 buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER);
420 buffer.putInt(idx.getIndexNumber());
421 buffer.putInt(idxDataState.getIndexDataNumber());
422
423 byte idxType = idx.getType();
424 if(idxType != FOREIGN_KEY_INDEX_TYPE) {
425 buffer.put((byte)0);
426 buffer.putInt(INVALID_INDEX_NUMBER);
427 buffer.putInt(0);
428 buffer.put((byte)0);
429 buffer.put((byte)0);
430 } else {
431 ForeignKeyReference reference = mutator.getForeignKey(idx);
432 buffer.put(reference.getTableType());
433 buffer.putInt(reference.getOtherIndexNumber());
434 buffer.putInt(reference.getOtherTablePageNumber());
435 byte updateFlags = 0;
436 if(reference.isCascadeUpdates()) {
437 updateFlags |= CASCADE_UPDATES_FLAG;
438 }
439 byte deleteFlags = 0;
440 if(reference.isCascadeDeletes()) {
441 deleteFlags |= CASCADE_DELETES_FLAG;
442 }
443 if(reference.isCascadeNullOnDelete()) {
444 deleteFlags |= CASCADE_NULL_FLAG;
445 }
446 buffer.put(updateFlags);
447 buffer.put(deleteFlags);
448 }
449 buffer.put(idxType);
450 buffer.putInt(0);
451 }
452
453 private String withErrorContext(String msg) {
454 return withErrorContext(msg, getTable().getDatabase(), getName());
455 }
456
457 private static String withErrorContext(String msg, DatabaseImpl db,
458 String idxName) {
459 return msg + " (Db=" + db.getName() + ";Index=" + idxName + ")";
460 }
461
462
463
464
465
466
467 public static class ForeignKeyReference
468 {
469 private final byte _tableType;
470 private final int _otherIndexNumber;
471 private final int _otherTablePageNumber;
472 private final boolean _cascadeUpdates;
473 private final boolean _cascadeDeletes;
474 private final boolean _cascadeNull;
475
476 public ForeignKeyReference(
477 byte tableType, int otherIndexNumber, int otherTablePageNumber,
478 boolean cascadeUpdates, boolean cascadeDeletes, boolean cascadeNull)
479 {
480 _tableType = tableType;
481 _otherIndexNumber = otherIndexNumber;
482 _otherTablePageNumber = otherTablePageNumber;
483 _cascadeUpdates = cascadeUpdates;
484 _cascadeDeletes = cascadeDeletes;
485 _cascadeNull = cascadeNull;
486 }
487
488 public byte getTableType() {
489 return _tableType;
490 }
491
492 public boolean isPrimaryTable() {
493 return(getTableType() == FK_PRIMARY_TABLE_TYPE);
494 }
495
496 public int getOtherIndexNumber() {
497 return _otherIndexNumber;
498 }
499
500 public int getOtherTablePageNumber() {
501 return _otherTablePageNumber;
502 }
503
504 public boolean isCascadeUpdates() {
505 return _cascadeUpdates;
506 }
507
508 public boolean isCascadeDeletes() {
509 return _cascadeDeletes;
510 }
511
512 public boolean isCascadeNullOnDelete() {
513 return _cascadeNull;
514 }
515
516 @Override
517 public String toString() {
518 return CustomToStringStyle.builder(this)
519 .append("otherIndexNumber", _otherIndexNumber)
520 .append("otherTablePageNum", _otherTablePageNumber)
521 .append("isPrimaryTable", isPrimaryTable())
522 .append("isCascadeUpdates", isCascadeUpdates())
523 .append("isCascadeDeletes", isCascadeDeletes())
524 .append("isCascadeNullOnDelete", isCascadeNullOnDelete())
525 .toString();
526 }
527 }
528 }