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.lang.System.Logger;
21 import java.nio.ByteBuffer;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.List;
25 import java.util.Map;
26
27 import com.healthmarketscience.jackcess.CursorBuilder;
28 import com.healthmarketscience.jackcess.Index;
29 import com.healthmarketscience.jackcess.IndexBuilder;
30 import com.healthmarketscience.jackcess.PropertyMap;
31
32
33
34
35
36
37
38 public class IndexImpl implements Index
39 {
40 protected static final Logger LOG = System.getLogger(IndexImpl.class.getName());
41
42
43 static final Comparator<IndexImpl> DEFAULT_ORDER_COMPARATOR =
44 Comparator.comparingInt(IndexImpl::getIndexNumber);
45
46
47 public static final byte PRIMARY_KEY_INDEX_TYPE = (byte)1;
48
49
50 public static final byte FOREIGN_KEY_INDEX_TYPE = (byte)2;
51
52
53 private static final byte CASCADE_UPDATES_FLAG = (byte)1;
54
55 private static final byte CASCADE_DELETES_FLAG = (byte)1;
56
57
58 private static final byte CASCADE_NULL_FLAG = (byte)2;
59
60
61 static final byte FK_PRIMARY_TABLE_TYPE = (byte)1;
62
63 static final byte FK_SECONDARY_TABLE_TYPE = (byte)2;
64
65
66 private static final int INVALID_INDEX_NUMBER = -1;
67
68
69
70 private final IndexData _data;
71
72 private PropertyMap _props;
73
74 private final int _indexNumber;
75
76 private final byte _indexType;
77
78 private String _name;
79
80 private final ForeignKeyReference _reference;
81
82 protected IndexImpl(ByteBuffer tableBuffer, List<IndexData> indexDatas,
83 JetFormat format)
84 {
85 ByteUtil.forward(tableBuffer, format.SKIP_BEFORE_INDEX_SLOT);
86 _indexNumber = tableBuffer.getInt();
87 int indexDataNumber = tableBuffer.getInt();
88
89
90 byte relIndexType = tableBuffer.get();
91 int relIndexNumber = tableBuffer.getInt();
92 int relTablePageNumber = tableBuffer.getInt();
93 byte cascadeUpdatesFlag = tableBuffer.get();
94 byte cascadeDeletesFlag = tableBuffer.get();
95
96 _indexType = tableBuffer.get();
97
98 if((_indexType == FOREIGN_KEY_INDEX_TYPE) &&
99 (relIndexNumber != INVALID_INDEX_NUMBER)) {
100 _reference = new ForeignKeyReference(
101 relIndexType, relIndexNumber, relTablePageNumber,
102 ((cascadeUpdatesFlag & CASCADE_UPDATES_FLAG) != 0),
103 ((cascadeDeletesFlag & CASCADE_DELETES_FLAG) != 0),
104 ((cascadeDeletesFlag & CASCADE_NULL_FLAG) != 0));
105 } else {
106 _reference = null;
107 }
108
109 ByteUtil.forward(tableBuffer, format.SKIP_AFTER_INDEX_SLOT);
110
111 _data = indexDatas.get(indexDataNumber);
112
113 _data.addIndex(this);
114 }
115
116 public IndexData getIndexData() {
117 return _data;
118 }
119
120 @Override
121 public TableImpl getTable() {
122 return getIndexData().getTable();
123 }
124
125 public JetFormat getFormat() {
126 return getTable().getFormat();
127 }
128
129 public PageChannel getPageChannel() {
130 return getTable().getPageChannel();
131 }
132
133 public int getIndexNumber() {
134 return _indexNumber;
135 }
136
137 public byte getIndexFlags() {
138 return getIndexData().getIndexFlags();
139 }
140
141 public int getUniqueEntryCount() {
142 return getIndexData().getUniqueEntryCount();
143 }
144
145 public int getUniqueEntryCountOffset() {
146 return getIndexData().getUniqueEntryCountOffset();
147 }
148
149 @Override
150 public String getName() {
151 return _name;
152 }
153
154 void setName(String name) {
155 _name = name;
156 }
157
158 @Override
159 public PropertyMap getProperties() throws IOException {
160 if(_props == null) {
161 _props = getTable().getPropertyMaps().getIndex(getName());
162 }
163 return _props;
164 }
165
166 @Override
167 public boolean isPrimaryKey() {
168 return _indexType == PRIMARY_KEY_INDEX_TYPE;
169 }
170
171 @Override
172 public boolean isForeignKey() {
173 return _indexType == FOREIGN_KEY_INDEX_TYPE;
174 }
175
176 public ForeignKeyReference getReference() {
177 return _reference;
178 }
179
180 @Override
181 public IndexImpl getReferencedIndex() throws IOException {
182
183 if(_reference == null) {
184 return null;
185 }
186
187 TableImpl refTable = getTable().getDatabase().getTable(
188 _reference.getOtherTablePageNumber());
189
190 if(refTable == null) {
191 throw new IOException(withErrorContext(
192 "Reference to missing table " + _reference.getOtherTablePageNumber()));
193 }
194
195 IndexImpl refIndex = null;
196 int idxNumber = _reference.getOtherIndexNumber();
197 for(IndexImpl idx : refTable.getIndexes()) {
198 if(idx.getIndexNumber() == idxNumber) {
199 refIndex = idx;
200 break;
201 }
202 }
203
204 if(refIndex == null) {
205 throw new IOException(withErrorContext(
206 "Reference to missing index " + idxNumber +
207 " on table " + refTable.getName()));
208 }
209
210
211
212 ForeignKeyReference otherRef = refIndex.getReference();
213 if((otherRef == null) ||
214 (otherRef.getOtherTablePageNumber() !=
215 getTable().getTableDefPageNumber()) ||
216 (otherRef.getOtherIndexNumber() != _indexNumber)) {
217 throw new IOException(withErrorContext(
218 "Found unexpected index " + refIndex.getName() +
219 " on table " + refTable.getName() + " with reference " + otherRef));
220 }
221
222 return refIndex;
223 }
224
225 @Override
226 public boolean shouldIgnoreNulls() {
227 return getIndexData().shouldIgnoreNulls();
228 }
229
230 @Override
231 public boolean isUnique() {
232 return getIndexData().isUnique();
233 }
234
235 @Override
236 public boolean isRequired() {
237 return getIndexData().isRequired();
238 }
239
240 @Override
241 public List<IndexData.ColumnDescriptor> getColumns() {
242 return getIndexData().getColumns();
243 }
244
245 @Override
246 public int getColumnCount() {
247 return getIndexData().getColumnCount();
248 }
249
250 @Override
251 public CursorBuilder newCursor() {
252 return getTable().newCursor().setIndex(this);
253 }
254
255
256
257
258 public boolean isInitialized() {
259 return getIndexData().isInitialized();
260 }
261
262
263
264
265
266
267 public void initialize() throws IOException {
268 getIndexData().initialize();
269 }
270
271
272
273
274
275
276 public IndexData.EntryCursor cursor()
277 throws IOException
278 {
279 return cursor(null, true, null, true);
280 }
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295 public IndexData.EntryCursor cursor(Object[] startRow,
296 boolean startInclusive,
297 Object[] endRow,
298 boolean endInclusive)
299 throws IOException
300 {
301 return getIndexData().cursor(startRow, startInclusive, endRow,
302 endInclusive);
303 }
304
305
306
307
308
309
310
311
312 public Object[] constructIndexRowFromEntry(Object... values)
313 {
314 return getIndexData().constructIndexRowFromEntry(values);
315 }
316
317
318
319
320
321
322
323
324
325
326 public Object[] constructPartialIndexRowFromEntry(
327 Object filler, Object... values)
328 {
329 return getIndexData().constructPartialIndexRowFromEntry(filler, values);
330 }
331
332
333
334
335
336
337
338 public Object[] constructIndexRow(String colName, Object value)
339 {
340 return constructIndexRow(Collections.singletonMap(colName, value));
341 }
342
343
344
345
346
347
348
349
350 public Object[] constructPartialIndexRow(Object filler, String colName, Object value)
351 {
352 return constructPartialIndexRow(filler, Collections.singletonMap(colName, value));
353 }
354
355
356
357
358
359
360
361 public Object[] constructIndexRow(Map<String,?> row)
362 {
363 return getIndexData().constructIndexRow(row);
364 }
365
366
367
368
369
370
371
372
373
374
375 public Object[] constructPartialIndexRow(Object filler, Map<String,?> row)
376 {
377 return getIndexData().constructPartialIndexRow(filler, row);
378 }
379
380 @Override
381 public String toString() {
382 ToStringBuilder sb = ToStringBuilder.builder(this)
383 .append("name", "(" + getTable().getName() + ") " + _name)
384 .append("number", _indexNumber)
385 .append("isPrimaryKey", isPrimaryKey())
386 .append("isForeignKey", isForeignKey());
387 if(_reference != null) {
388 sb.append("foreignKeyReference", _reference);
389 }
390 sb.append("data", _data);
391 return sb.toString();
392 }
393
394
395
396
397
398
399 protected static void writeDefinitions(
400 TableCreator creator, ByteBuffer buffer)
401 {
402
403 for(IndexBuilder idx : creator.getIndexes()) {
404 writeDefinition(creator, idx, buffer);
405 }
406
407
408 for(IndexBuilder idx : creator.getIndexes()) {
409 TableImpl.writeName(buffer, idx.getName(), creator.getCharset());
410 }
411 }
412
413 protected static void writeDefinition(
414 TableMutator mutator, IndexBuilder idx, ByteBuffer buffer)
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 ToStringBuilder.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 }