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