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.io.UncheckedIOException;
21 import java.lang.System.Logger;
22 import java.nio.ByteBuffer;
23 import java.nio.ByteOrder;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.List;
30 import java.util.Map;
31
32 import com.healthmarketscience.jackcess.ConstraintViolationException;
33 import com.healthmarketscience.jackcess.Index;
34 import com.healthmarketscience.jackcess.IndexBuilder;
35 import static com.healthmarketscience.jackcess.impl.ByteUtil.ByteStream;
36 import static com.healthmarketscience.jackcess.impl.IndexCodes.*;
37
38
39
40
41
42
43
44
45 public class IndexData {
46
47 protected static final Logger LOG = System.getLogger(Index.class.getName());
48
49
50 public static final Entry FIRST_ENTRY =
51 createSpecialEntry(RowIdImpl.FIRST_ROW_ID);
52
53
54 public static final Entry LAST_ENTRY =
55 createSpecialEntry(RowIdImpl.LAST_ROW_ID);
56
57
58
59 public static final Object MAX_VALUE = new Object();
60
61
62
63 public static final Object MIN_VALUE = new Object();
64
65 private static final DataPage NEW_ROOT_DATA_PAGE = new RootDataPage();
66
67 protected static final int INVALID_INDEX_PAGE_NUMBER = 0;
68
69
70 public static final int MAX_COLUMNS = 10;
71
72 protected static final byte[] EMPTY_PREFIX = new byte[0];
73
74 private static final byte[] ASC_EXT_DATE_TRAILER =
75 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02};
76 private static final byte[] DESC_EXT_DATE_TRAILER =
77 flipBytes(ByteUtil.copyOf(ASC_EXT_DATE_TRAILER, ASC_EXT_DATE_TRAILER.length));
78
79 static final short COLUMN_UNUSED = -1;
80
81 public static final byte ASCENDING_COLUMN_FLAG = (byte)0x01;
82
83 public static final byte UNIQUE_INDEX_FLAG = (byte)0x01;
84 public static final byte IGNORE_NULLS_INDEX_FLAG = (byte)0x02;
85 public static final byte REQUIRED_INDEX_FLAG = (byte)0x08;
86 public static final byte UNKNOWN_INDEX_FLAG = (byte)0x80;
87
88
89
90 private static final byte COMPLEX_INDEX_MARKER = (byte)0x02;
91
92 private static final int MAGIC_INDEX_NUMBER = 1923;
93
94 private static final ByteOrder ENTRY_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
95
96
97
98
99
100
101 private static final int MAX_KEY_UNITS = 255;
102
103 private static final int KEY_DIGEST_LENGTH = 2;
104
105
106 private static final int KEY_DIGEST_POLY = 0x8005;
107 private static final int[] KEY_DIGEST_TABLE = createKeyDigestTable();
108
109
110 public enum EntryType {
111
112
113 ALWAYS_FIRST,
114
115
116 FIRST_VALID,
117
118
119 NORMAL,
120
121
122 LAST_VALID,
123
124
125 ALWAYS_LAST;
126 }
127
128
129 private enum IndexStatus {
130
131 VALID,
132
133
134 BROKEN_WRITE,
135
136 READ_ONLY
137 }
138
139 public static final Comparator<byte[]> BYTE_CODE_COMPARATOR =
140 new Comparator<byte[]>() {
141 @Override
142 public int compare(byte[] left, byte[] right) {
143 if(left == right) {
144 return 0;
145 }
146 if(left == null) {
147 return -1;
148 }
149 if(right == null) {
150 return 1;
151 }
152
153 int len = Math.min(left.length, right.length);
154 int pos = 0;
155 while((pos < len) && (left[pos] == right[pos])) {
156 ++pos;
157 }
158 if(pos < len) {
159 return ((ByteUtil.asUnsignedByte(left[pos]) <
160 ByteUtil.asUnsignedByte(right[pos])) ? -1 : 1);
161 }
162 return ((left.length < right.length) ? -1 :
163 ((left.length > right.length) ? 1 : 0));
164 }
165 };
166
167
168
169 private String _name;
170
171 private final TableImpl _table;
172
173 private final int _number;
174
175 private int _rootPageNumber;
176
177
178 private final int _uniqueEntryCountOffset;
179
180
181
182 private int _uniqueEntryCount;
183
184 private final List<ColumnDescriptor> _columns =
185 new ArrayList<>();
186
187 private final List<Index> _indexes = new ArrayList<>();
188
189 private byte _indexFlags;
190
191 private UsageMap _ownedPages;
192
193
194 private boolean _initialized;
195
196 private int _modCount;
197
198 private final TempBufferHolder _indexBufferH =
199 TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true);
200
201 private ByteStream _entryBuffer;
202
203 private final int _maxPageEntrySize;
204
205 private boolean _primaryKey;
206
207 private IndexStatus _status = IndexStatus.VALID;
208
209 private String _unsupportedReason;
210
211 private final IndexPageCache _pageCache;
212
213 protected IndexData(TableImpl table, int number, int uniqueEntryCount,
214 int uniqueEntryCountOffset)
215 {
216 _table = table;
217 _number = number;
218 _uniqueEntryCount = uniqueEntryCount;
219 _uniqueEntryCountOffset = uniqueEntryCountOffset;
220 _maxPageEntrySize = calcMaxPageEntrySize(_table.getFormat());
221 _pageCache = new IndexPageCache(this);
222 }
223
224
225
226
227
228 public static IndexData create(TableImpl table, ByteBuffer tableBuffer,
229 int number, JetFormat format)
230 {
231 int uniqueEntryCountOffset =
232 (format.OFFSET_INDEX_DEF_BLOCK +
233 (number * format.SIZE_INDEX_DEFINITION) + 4);
234 int uniqueEntryCount = tableBuffer.getInt(uniqueEntryCountOffset);
235
236 return new IndexData(table, number, uniqueEntryCount, uniqueEntryCountOffset);
237 }
238
239 public String getName() {
240 if(_name == null) {
241 if(_indexes.size() == 1) {
242 _name = _indexes.get(0).getName();
243 } else if(!_indexes.isEmpty()) {
244 List<String> names = new ArrayList<>(_indexes.size());
245 for(Index idx : _indexes) {
246 names.add(idx.getName());
247 }
248 _name = names.toString();
249 } else {
250 _name = String.valueOf(_number);
251 }
252 }
253 return _name;
254 }
255
256 public TableImpl getTable() {
257 return _table;
258 }
259
260 public JetFormat getFormat() {
261 return getTable().getFormat();
262 }
263
264 public PageChannel getPageChannel() {
265 return getTable().getPageChannel();
266 }
267
268
269
270
271 public Index getPrimaryIndex() {
272 return _indexes.get(0);
273 }
274
275
276
277
278 public List<Index> getIndexes() {
279 return Collections.unmodifiableList(_indexes);
280 }
281
282
283
284
285 void addIndex(Index index) {
286
287
288
289 if(index.isForeignKey()) {
290 _indexes.add(index);
291 } else {
292 int pos = _indexes.size();
293 while(pos > 0) {
294 if(!_indexes.get(pos - 1).isForeignKey()) {
295 break;
296 }
297 --pos;
298 }
299 _indexes.add(pos, index);
300
301
302 _primaryKey |= index.isPrimaryKey();
303 }
304
305
306 _name = null;
307 }
308
309 public byte getIndexFlags() {
310 return _indexFlags;
311 }
312
313 public int getIndexDataNumber() {
314 return _number;
315 }
316
317 public int getUniqueEntryCount() {
318 return _uniqueEntryCount;
319 }
320
321 public int getUniqueEntryCountOffset() {
322 return _uniqueEntryCountOffset;
323 }
324
325 protected boolean isBackingPrimaryKey() {
326 return _primaryKey;
327 }
328
329
330
331
332 public boolean shouldIgnoreNulls() {
333 return((_indexFlags & IGNORE_NULLS_INDEX_FLAG) != 0);
334 }
335
336
337
338
339
340
341
342
343
344
345
346
347
348 public boolean isUnique() {
349 return(isBackingPrimaryKey() || ((_indexFlags & UNIQUE_INDEX_FLAG) != 0));
350 }
351
352
353
354
355 public boolean isRequired() {
356 return((_indexFlags & REQUIRED_INDEX_FLAG) != 0);
357 }
358
359
360
361
362 public List<ColumnDescriptor> getColumns() {
363 return Collections.unmodifiableList(_columns);
364 }
365
366 public int getColumnCount() {
367 return _columns.size();
368 }
369
370
371
372
373 public boolean isInitialized() {
374 return _initialized;
375 }
376
377 protected int getRootPageNumber() {
378 return _rootPageNumber;
379 }
380
381 private void setUnsupportedReason(String reason, IndexStatus status,
382 ColumnImpl col) {
383 _status = status;
384 _unsupportedReason = withErrorContext(reason);
385 String suffix = (status == IndexStatus.READ_ONLY) ?
386 "making read-only" : "index not suitable for lookups";
387 if(!col.getTable().isSystem()) {
388 LOG.log(Logger.Level.WARNING, _unsupportedReason + ", " + suffix);
389 } else {
390 if(LOG.isLoggable(Logger.Level.DEBUG)) {
391 LOG.log(Logger.Level.DEBUG, _unsupportedReason + ", " + suffix);
392 }
393 }
394 }
395
396 String getUnsupportedReason() {
397 return _unsupportedReason;
398 }
399
400 boolean isValid() {
401 return _status == IndexStatus.VALID;
402 }
403
404 boolean isReadOnly() {
405 return _status == IndexStatus.READ_ONLY;
406 }
407
408 protected int getMaxPageEntrySize() {
409 return _maxPageEntrySize;
410 }
411
412
413
414
415
416 public int getOwnedPageCount() {
417 return _ownedPages.getPageCount();
418 }
419
420 void addOwnedPage(int pageNumber) throws IOException {
421 _ownedPages.addPageNumber(pageNumber);
422 }
423
424 void removeOwnedPage(int pageNumber) throws IOException {
425 _ownedPages.removePageNumber(pageNumber);
426 }
427
428 void collectUsageMapPages(Collection<Integer> pages) {
429 pages.add(_ownedPages.getTablePageNumber());
430 }
431
432
433
434
435
436
437
438 public void validate(boolean forceLoad) throws IOException {
439 initialize();
440 _pageCache.validate(forceLoad);
441 }
442
443
444
445
446
447
448
449
450 public int getEntryCount()
451 throws IOException
452 {
453 initialize();
454 EntryCursor cursor = cursor();
455 Entry endEntry = cursor.getLastEntry();
456 int count = 0;
457 while(!endEntry.equals(cursor.getNextEntry())) {
458 ++count;
459 }
460 return count;
461 }
462
463
464
465
466
467
468 public void initialize() throws IOException {
469 if(!_initialized) {
470 _pageCache.setRootPageNumber(getRootPageNumber());
471 _initialized = true;
472 }
473 }
474
475
476
477
478
479
480 public void update() throws IOException
481 {
482
483 initialize();
484
485 if(isReadOnly()) {
486 throw new UnsupportedOperationException(
487 "Cannot write indexes of this type due to " + _unsupportedReason);
488 }
489 _pageCache.write();
490 }
491
492
493
494
495
496
497 public void read(ByteBuffer tableBuffer, List<ColumnImpl> availableColumns)
498 throws IOException
499 {
500 ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX);
501
502 for (int i = 0; i < MAX_COLUMNS; i++) {
503 short columnNumber = tableBuffer.getShort();
504 byte colFlags = tableBuffer.get();
505 if (columnNumber != COLUMN_UNUSED) {
506
507
508 ColumnImpl idxCol = null;
509 for(ColumnImpl col : availableColumns) {
510 if(col.getColumnNumber() == columnNumber) {
511 idxCol = col;
512 break;
513 }
514 }
515 if(idxCol == null) {
516 throw new IOException(withErrorContext(
517 "Could not find column with number "
518 + columnNumber + " for index"));
519 }
520 _columns.add(newColumnDescriptor(idxCol, colFlags));
521 }
522 }
523
524 _ownedPages = UsageMap.read(getTable().getDatabase(), tableBuffer);
525
526 _rootPageNumber = tableBuffer.getInt();
527
528 ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX_FLAGS);
529 _indexFlags = tableBuffer.get();
530 ByteUtil.forward(tableBuffer, getFormat().SKIP_AFTER_INDEX_FLAGS);
531 }
532
533
534
535
536
537
538 protected static void writeRowCountDefinitions(
539 TableCreator creator, ByteBuffer buffer)
540 {
541 writeRowCountDefinitions(creator, buffer, creator.getIndexCount());
542 }
543
544
545
546
547
548
549
550 protected static void writeRowCountDefinitions(
551 TableMutator creator, ByteBuffer buffer, int idxCount)
552 {
553
554 ByteUtil.forward(buffer, (idxCount *
555 creator.getFormat().SIZE_INDEX_DEFINITION));
556 }
557
558
559
560
561
562
563 protected static void writeDefinitions(
564 TableCreator creator, ByteBuffer buffer)
565 throws IOException
566 {
567 ByteBuffer rootPageBuffer = createRootPageBuffer(creator);
568
569 for(TableMutator.IndexDataState idxDataState : creator.getIndexDataStates()) {
570 writeDefinition(creator, buffer, idxDataState, rootPageBuffer);
571 }
572 }
573
574
575
576
577
578
579 @SuppressWarnings("resource")
580 protected static void writeDefinition(
581 TableMutator creator, ByteBuffer buffer,
582 TableMutator.IndexDataState idxDataState, ByteBuffer rootPageBuffer)
583 throws IOException
584 {
585 if(rootPageBuffer == null) {
586 rootPageBuffer = createRootPageBuffer(creator);
587 }
588
589 buffer.putInt(MAGIC_INDEX_NUMBER);
590
591
592 IndexBuilder idx = idxDataState.getFirstIndex();
593 List<IndexBuilder.Column> idxColumns = idx.getColumns();
594 boolean isComplexIndex = false;
595 for(int i = 0; i < MAX_COLUMNS; ++i) {
596
597 short columnNumber = COLUMN_UNUSED;
598 byte flags = 0;
599
600 if(i < idxColumns.size()) {
601
602
603 IndexBuilder.Column idxCol = idxColumns.get(i);
604 flags = idxCol.getFlags();
605
606
607 columnNumber = creator.getColumnNumber(idxCol.getName());
608 isComplexIndex |= creator.isComplexColumn(idxCol.getName());
609 if(columnNumber == COLUMN_UNUSED) {
610
611 throw new IllegalArgumentException(
612 withErrorContext(
613 "Column with name " + idxCol.getName() + " not found",
614 creator.getDatabase(), creator.getTableName(), idx.getName()));
615 }
616 }
617
618 buffer.putShort(columnNumber);
619 buffer.put(flags);
620 }
621
622 buffer.put(idxDataState.getUmapRowNumber());
623 ByteUtil.put3ByteInt(buffer, idxDataState.getUmapPageNumber());
624
625
626 creator.getPageChannel().writePage(rootPageBuffer,
627 idxDataState.getRootPageNumber());
628
629 buffer.putInt(idxDataState.getRootPageNumber());
630 buffer.putInt(0);
631 buffer.put(idx.getFlags());
632 buffer.put(isComplexIndex ? COMPLEX_INDEX_MARKER : 0);
633 ByteUtil.forward(buffer, 4);
634 }
635
636 private static ByteBuffer createRootPageBuffer(TableMutator creator)
637 throws IOException
638 {
639 ByteBuffer rootPageBuffer = creator.getPageChannel().createPageBuffer();
640 writeDataPage(rootPageBuffer, NEW_ROOT_DATA_PAGE,
641 creator.getTdefPageNumber(), creator.getFormat());
642 return rootPageBuffer;
643 }
644
645
646
647
648
649
650
651
652
653
654
655
656 public PendingChange prepareAddRow(Object[] row, RowIdImpl rowId,
657 PendingChange nextChange)
658 throws IOException
659 {
660 return prepareAddRow(row, rowId, new AddRowPendingChange(nextChange));
661 }
662
663 private PendingChange prepareAddRow(Object[] row, RowIdImpl rowId,
664 AddRowPendingChange change)
665 throws IOException
666 {
667 int nullCount = countNullValues(row);
668 boolean isNullEntry = (nullCount == _columns.size());
669 if(shouldIgnoreNulls() && isNullEntry) {
670
671 return change;
672 }
673 if((nullCount > 0) && (isBackingPrimaryKey() || isRequired())) {
674 throw new ConstraintViolationException(withErrorContext(
675 "Null value found in row " + Arrays.asList(row) +
676 " for primary key or required index"));
677 }
678
679
680 initialize();
681
682 return prepareAddEntry(new Entry(createEntryBytes(row), rowId), isNullEntry,
683 row, change);
684 }
685
686
687
688
689 private PendingChange prepareAddEntry(Entry newEntry, boolean isNullEntry,
690 Object[] row, AddRowPendingChange change)
691 throws IOException
692 {
693 DataPage dataPage = findDataPage(newEntry);
694 int idx = dataPage.findEntry(newEntry);
695 if(idx < 0) {
696
697
698 idx = missingIndexToInsertionPoint(idx);
699
700 Position newPos = new Position(dataPage, idx, newEntry, true);
701 Position nextPos = getNextPosition(newPos);
702 Position prevPos = getPreviousPosition(newPos);
703
704
705
706
707 boolean isDupeEntry =
708 (((nextPos != null) &&
709 newEntry.equalsEntryBytes(nextPos.getEntry())) ||
710 ((prevPos != null) &&
711 newEntry.equalsEntryBytes(prevPos.getEntry())));
712 if(isUnique() && !isNullEntry && isDupeEntry) {
713 throw new ConstraintViolationException(withErrorContext(
714 "New row " + Arrays.asList(row) +
715 " violates uniqueness constraint for index"));
716 }
717
718 change.setAddRow(newEntry, dataPage, idx, isDupeEntry);
719
720 } else {
721
722 change.setOldRow(newEntry);
723 }
724 return change;
725 }
726
727
728
729
730 private void commitAddRow(Entry newEntry, DataPage dataPage, int idx,
731 boolean isDupeEntry, Entry oldEntry)
732 throws IOException
733 {
734 if(newEntry != null) {
735 dataPage.addEntry(idx, newEntry);
736
737
738 if(!isDupeEntry && (oldEntry == null)) {
739 ++_uniqueEntryCount;
740 }
741 ++_modCount;
742 } else {
743 LOG.log(Logger.Level.WARNING, withErrorContext("Added duplicate index entry " + oldEntry));
744 }
745 }
746
747
748
749
750
751
752
753
754
755
756
757
758
759 public PendingChange prepareUpdateRow(Object[] oldRow, RowIdImpl rowId,
760 Object[] newRow,
761 PendingChange nextChange)
762 throws IOException
763 {
764 UpdateRowPendingChange change = new UpdateRowPendingChange(nextChange);
765 change.setOldRow(deleteRowImpl(oldRow, rowId));
766
767 try {
768 prepareAddRow(newRow, rowId, change);
769 return change;
770 } catch(ConstraintViolationException e) {
771
772 change.rollback();
773 throw e;
774 }
775 }
776
777
778
779
780
781
782
783
784
785 public void deleteRow(Object[] row, RowIdImpl rowId)
786 throws IOException
787 {
788 deleteRowImpl(row, rowId);
789 }
790
791 private Entry deleteRowImpl(Object[] row, RowIdImpl rowId)
792 throws IOException
793 {
794 int nullCount = countNullValues(row);
795 if(shouldIgnoreNulls() && (nullCount == _columns.size())) {
796
797 return null;
798 }
799
800
801 initialize();
802
803 Entry oldEntry = new Entry(createEntryBytes(row), rowId);
804 Entry removedEntry = removeEntry(oldEntry);
805 if(removedEntry != null) {
806 ++_modCount;
807 } else {
808 LOG.log(Logger.Level.WARNING, withErrorContext(
809 "Failed removing index entry " + oldEntry + " for row: " +
810 Arrays.asList(row)));
811 }
812 return removedEntry;
813 }
814
815
816
817
818 private void rollbackDeletedRow(Entry removedEntry)
819 throws IOException
820 {
821 if(removedEntry == null) {
822
823 return;
824 }
825
826
827
828 DataPage dataPage = findDataPage(removedEntry);
829 int idx = dataPage.findEntry(removedEntry);
830 if(idx < 0) {
831 dataPage.addEntry(missingIndexToInsertionPoint(idx), removedEntry);
832 }
833 }
834
835
836
837
838
839
840 private Entry removeEntry(Entry oldEntry)
841 throws IOException
842 {
843 DataPage dataPage = findDataPage(oldEntry);
844 int idx = dataPage.findEntry(oldEntry);
845 boolean doRemove = false;
846 if(idx < 0) {
847
848
849
850 EntryCursor cursor = cursor();
851 Position tmpPos = null;
852 Position endPos = cursor._lastPos;
853 while(!endPos.equals(
854 tmpPos = cursor.getAnotherPosition(CursorImpl.MOVE_FORWARD))) {
855 if(tmpPos.getEntry().getRowId().equals(oldEntry.getRowId())) {
856 dataPage = tmpPos.getDataPage();
857 idx = tmpPos.getIndex();
858 doRemove = true;
859 break;
860 }
861 }
862 } else {
863 doRemove = true;
864 }
865
866 Entry removedEntry = null;
867 if(doRemove) {
868
869 removedEntry = dataPage.removeEntry(idx);
870 }
871
872 return removedEntry;
873 }
874
875 public static void commitAll(PendingChange change) throws IOException {
876 while(change != null) {
877 change.commit();
878 change = change.getNext();
879 }
880 }
881
882 public static void rollbackAll(PendingChange change) throws IOException {
883 while(change != null) {
884 change.rollback();
885 change = change.getNext();
886 }
887 }
888
889
890
891
892
893
894 public EntryCursor cursor()
895 throws IOException
896 {
897 return cursor(null, true, null, true);
898 }
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913 public EntryCursor cursor(Object[] startRow,
914 boolean startInclusive,
915 Object[] endRow,
916 boolean endInclusive)
917 throws IOException
918 {
919 initialize();
920 Entry startEntry = FIRST_ENTRY;
921 byte[] startEntryBytes = null;
922 if(startRow != null) {
923 startEntryBytes = createEntryBytes(startRow);
924 startEntry = new Entry(startEntryBytes,
925 (startInclusive ?
926 RowIdImpl.FIRST_ROW_ID : RowIdImpl.LAST_ROW_ID));
927 }
928 Entry endEntry = LAST_ENTRY;
929 if(endRow != null) {
930
931
932 byte[] endEntryBytes = ((startRow == endRow) ?
933 startEntryBytes :
934 createEntryBytes(endRow));
935 endEntry = new Entry(endEntryBytes,
936 (endInclusive ?
937 RowIdImpl.LAST_ROW_ID : RowIdImpl.FIRST_ROW_ID));
938 }
939 return new EntryCursor(findEntryPosition(startEntry),
940 findEntryPosition(endEntry));
941 }
942
943 private Position findEntryPosition(Entry entry)
944 throws IOException
945 {
946 DataPage dataPage = findDataPage(entry);
947 int idx = dataPage.findEntry(entry);
948 boolean between = false;
949 if(idx < 0) {
950
951
952
953 idx = missingIndexToInsertionPoint(idx);
954 between = true;
955 }
956 return new Position(dataPage, idx, entry, between);
957 }
958
959 private Position getNextPosition(Position curPos)
960 throws IOException
961 {
962
963 int nextIdx = curPos.getNextIndex();
964 Position nextPos = null;
965 if(nextIdx < curPos.getDataPage().getEntries().size()) {
966 nextPos = new Position(curPos.getDataPage(), nextIdx);
967 } else {
968 int nextPageNumber = curPos.getDataPage().getNextPageNumber();
969 DataPage nextDataPage = null;
970 while(nextPageNumber != INVALID_INDEX_PAGE_NUMBER) {
971 DataPage dp = getDataPage(nextPageNumber);
972 if(!dp.isEmpty()) {
973 nextDataPage = dp;
974 break;
975 }
976 nextPageNumber = dp.getNextPageNumber();
977 }
978 if(nextDataPage != null) {
979 nextPos = new Position(nextDataPage, 0);
980 }
981 }
982 return nextPos;
983 }
984
985
986
987
988 private Position getPreviousPosition(Position curPos)
989 throws IOException
990 {
991
992 int prevIdx = curPos.getPrevIndex();
993 Position prevPos = null;
994 if(prevIdx >= 0) {
995 prevPos = new Position(curPos.getDataPage(), prevIdx);
996 } else {
997 int prevPageNumber = curPos.getDataPage().getPrevPageNumber();
998 DataPage prevDataPage = null;
999 while(prevPageNumber != INVALID_INDEX_PAGE_NUMBER) {
1000 DataPage dp = getDataPage(prevPageNumber);
1001 if(!dp.isEmpty()) {
1002 prevDataPage = dp;
1003 break;
1004 }
1005 prevPageNumber = dp.getPrevPageNumber();
1006 }
1007 if(prevDataPage != null) {
1008 prevPos = new Position(prevDataPage,
1009 (prevDataPage.getEntries().size() - 1));
1010 }
1011 }
1012 return prevPos;
1013 }
1014
1015
1016
1017
1018
1019 protected static int missingIndexToInsertionPoint(int idx) {
1020 return -(idx + 1);
1021 }
1022
1023
1024
1025
1026
1027
1028
1029
1030 public Object[] constructIndexRowFromEntry(Object... values)
1031 {
1032 if(values.length != _columns.size()) {
1033 throw new IllegalArgumentException(withErrorContext(
1034 "Wrong number of column values given " + values.length +
1035 ", expected " + _columns.size()));
1036 }
1037 int valIdx = 0;
1038 Object[] idxRow = new Object[getTable().getColumnCount()];
1039 for(ColumnDescriptor col : _columns) {
1040 idxRow[col.getColumnIndex()] = values[valIdx++];
1041 }
1042 return idxRow;
1043 }
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054 public Object[] constructPartialIndexRowFromEntry(
1055 Object filler, Object... values)
1056 {
1057 if(values.length == 0) {
1058 throw new IllegalArgumentException(withErrorContext(
1059 "At least one column value must be provided"));
1060 }
1061 if(values.length > _columns.size()) {
1062 throw new IllegalArgumentException(withErrorContext(
1063 "Too many column values given " + values.length +
1064 ", expected at most " + _columns.size()));
1065 }
1066 int valIdx = 0;
1067 Object[] idxRow = new Object[getTable().getColumnCount()];
1068 for(ColumnDescriptor col : _columns) {
1069 idxRow[col.getColumnIndex()] =
1070 ((valIdx < values.length) ? values[valIdx] : filler);
1071 ++valIdx;
1072 }
1073 return idxRow;
1074 }
1075
1076
1077
1078
1079
1080
1081
1082 public Object[] constructIndexRow(String colName, Object value)
1083 {
1084 return constructIndexRow(Collections.singletonMap(colName, value));
1085 }
1086
1087
1088
1089
1090
1091
1092
1093
1094 public Object[] constructPartialIndexRow(Object filler, String colName, Object value)
1095 {
1096 return constructPartialIndexRow(filler, Collections.singletonMap(colName, value));
1097 }
1098
1099
1100
1101
1102
1103
1104
1105 public Object[] constructIndexRow(Map<String,?> row)
1106 {
1107 for(ColumnDescriptor col : _columns) {
1108 if(!row.containsKey(col.getName())) {
1109 return null;
1110 }
1111 }
1112
1113 Object[] idxRow = new Object[getTable().getColumnCount()];
1114 for(ColumnDescriptor col : _columns) {
1115 idxRow[col.getColumnIndex()] = row.get(col.getName());
1116 }
1117 return idxRow;
1118 }
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129 public Object[] constructPartialIndexRow(Object filler, Map<String,?> row)
1130 {
1131
1132 int numCols = 0;
1133 for(ColumnDescriptor col : _columns) {
1134 if(!row.containsKey(col.getName())) {
1135 if(numCols == 0) {
1136
1137 return null;
1138 }
1139 break;
1140 }
1141 ++numCols;
1142 }
1143
1144
1145
1146 Object[] idxRow = new Object[getTable().getColumnCount()];
1147 int valIdx = 0;
1148 for(ColumnDescriptor col : _columns) {
1149 idxRow[col.getColumnIndex()] =
1150 ((valIdx < numCols) ? row.get(col.getName()) : filler);
1151 ++valIdx;
1152 }
1153 return idxRow;
1154 }
1155
1156 @Override
1157 public String toString() {
1158 ToStringBuilder sb = ToStringBuilder.builder(this)
1159 .append("dataNumber", _number)
1160 .append("pageNumber", _rootPageNumber)
1161 .append("isBackingPrimaryKey", isBackingPrimaryKey())
1162 .append("isUnique", isUnique())
1163 .append("ignoreNulls", shouldIgnoreNulls())
1164 .append("isRequired", isRequired())
1165 .append("columns", _columns)
1166 .append("initialized", _initialized);
1167 if(_initialized) {
1168 try {
1169 sb.append("entryCount", getEntryCount());
1170 } catch(IOException e) {
1171 throw new UncheckedIOException(e);
1172 }
1173 }
1174 sb.append("pageCache", _pageCache);
1175 return sb.toString();
1176 }
1177
1178
1179
1180
1181 protected void writeDataPage(DataPage dataPage)
1182 throws IOException
1183 {
1184 if(dataPage.getCompressedEntrySize() > _maxPageEntrySize) {
1185 throw new IllegalStateException(withErrorContext("data page is too large"));
1186 }
1187
1188 ByteBuffer buffer = _indexBufferH.getPageBuffer(getPageChannel());
1189
1190 writeDataPage(buffer, dataPage, getTable().getTableDefPageNumber(),
1191 getFormat());
1192
1193 getPageChannel().writePage(buffer, dataPage.getPageNumber());
1194 }
1195
1196
1197
1198
1199 protected static void writeDataPage(ByteBuffer buffer, DataPage dataPage,
1200 int tdefPageNumber, JetFormat format)
1201 throws IOException
1202 {
1203 buffer.put(dataPage.isLeaf() ?
1204 PageTypes.INDEX_LEAF :
1205 PageTypes.INDEX_NODE );
1206 buffer.put((byte) 0x01);
1207 buffer.putShort((short) 0);
1208 buffer.putInt(tdefPageNumber);
1209
1210 buffer.putInt(0);
1211 buffer.putInt(dataPage.getPrevPageNumber());
1212 buffer.putInt(dataPage.getNextPageNumber());
1213 buffer.putInt(dataPage.getChildTailPageNumber());
1214
1215 byte[] entryPrefix = dataPage.getEntryPrefix();
1216 buffer.putShort((short) entryPrefix.length);
1217 buffer.put((byte)dataPage.getLevel());
1218
1219 byte[] entryMask = new byte[format.SIZE_INDEX_ENTRY_MASK];
1220
1221 int totalSize = entryPrefix.length;
1222 for(Entry entry : dataPage.getEntries()) {
1223 totalSize += (entry.size() - entryPrefix.length);
1224 int idx = totalSize / 8;
1225 entryMask[idx] |= (1 << (totalSize % 8));
1226 }
1227 buffer.put(entryMask);
1228
1229
1230 buffer.put(entryPrefix);
1231
1232 for(Entry entry : dataPage.getEntries()) {
1233 entry.write(buffer, entryPrefix);
1234 }
1235
1236
1237 buffer.putShort(2, (short) (format.PAGE_SIZE - buffer.position()));
1238 }
1239
1240
1241
1242
1243
1244 protected void readDataPage(DataPage dataPage)
1245 throws IOException
1246 {
1247 ByteBuffer buffer = _indexBufferH.getPageBuffer(getPageChannel());
1248 getPageChannel().readPage(buffer, dataPage.getPageNumber());
1249
1250 boolean isLeaf = isLeafPage(buffer);
1251 dataPage.setLeaf(isLeaf);
1252 dataPage.setLevel(readLevel(buffer, getFormat()));
1253
1254
1255
1256
1257
1258 int entryPrefixLength = ByteUtil.getUnsignedVarInt(
1259 buffer, getFormat().OFFSET_INDEX_COMPRESSED_BYTE_COUNT,
1260 getFormat().SIZE_INDEX_COMPRESSED_BYTE_COUNT);
1261 int entryMaskLength = getFormat().SIZE_INDEX_ENTRY_MASK;
1262 int entryMaskPos = getFormat().OFFSET_INDEX_ENTRY_MASK;
1263 int entryPos = entryMaskPos + entryMaskLength;
1264 int lastStart = 0;
1265 int totalEntrySize = 0;
1266 byte[] entryPrefix = null;
1267 List<Entry> entries = new ArrayList<>();
1268 TempBufferHolder tmpEntryBufferH =
1269 TempBufferHolder.newHolder(TempBufferHolder.Type.HARD, true,
1270 ENTRY_BYTE_ORDER);
1271
1272 Entry prevEntry = FIRST_ENTRY;
1273 for (int i = 0; i < entryMaskLength; i++) {
1274 byte entryMask = buffer.get(entryMaskPos + i);
1275 for (int j = 0; j < 8; j++) {
1276 if ((entryMask & (1 << j)) != 0) {
1277 int length = (i * 8) + j - lastStart;
1278 buffer.position(entryPos + lastStart);
1279
1280
1281
1282 ByteBuffer curEntryBuffer = buffer;
1283 int curEntryLen = length;
1284 if(entryPrefix != null) {
1285 curEntryBuffer = getTempEntryBuffer(
1286 buffer, length, entryPrefix, tmpEntryBufferH);
1287 curEntryLen += entryPrefix.length;
1288 }
1289 totalEntrySize += curEntryLen;
1290
1291 Entry entry = newEntry(curEntryBuffer, curEntryLen, isLeaf);
1292 if(prevEntry.compareTo(entry) >= 0) {
1293 throw new IOException(withErrorContext(
1294 "Unexpected order in index entries, " +
1295 prevEntry + " >= " + entry));
1296 }
1297
1298 entries.add(entry);
1299
1300 if((entries.size() == 1) && (entryPrefixLength > 0)) {
1301
1302 entryPrefix = new byte[entryPrefixLength];
1303 buffer.position(entryPos + lastStart);
1304 buffer.get(entryPrefix);
1305 }
1306
1307 lastStart += length;
1308 prevEntry = entry;
1309 }
1310 }
1311 }
1312
1313 dataPage.setEntryPrefix(entryPrefix != null ? entryPrefix : EMPTY_PREFIX);
1314 dataPage.setEntries(entries);
1315 dataPage.setTotalEntrySize(totalEntrySize);
1316
1317 int prevPageNumber = buffer.getInt(getFormat().OFFSET_PREV_INDEX_PAGE);
1318 int nextPageNumber = buffer.getInt(getFormat().OFFSET_NEXT_INDEX_PAGE);
1319 int childTailPageNumber =
1320 buffer.getInt(getFormat().OFFSET_CHILD_TAIL_INDEX_PAGE);
1321
1322 dataPage.setPrevPageNumber(prevPageNumber);
1323 dataPage.setNextPageNumber(nextPageNumber);
1324 dataPage.setChildTailPageNumber(childTailPageNumber);
1325 }
1326
1327
1328
1329
1330 private static Entry newEntry(ByteBuffer buffer, int entryLength,
1331 boolean isLeaf)
1332 {
1333 if(isLeaf) {
1334 return new Entry(buffer, entryLength);
1335 }
1336 return new NodeEntry(buffer, entryLength);
1337 }
1338
1339
1340
1341
1342
1343 private ByteBuffer getTempEntryBuffer(
1344 ByteBuffer indexPage, int entryLen, byte[] valuePrefix,
1345 TempBufferHolder tmpEntryBufferH)
1346 {
1347 ByteBuffer tmpEntryBuffer = tmpEntryBufferH.getBuffer(
1348 getPageChannel(), valuePrefix.length + entryLen);
1349
1350
1351
1352 tmpEntryBuffer.put(valuePrefix);
1353 tmpEntryBuffer.put(indexPage.array(), indexPage.position(), entryLen);
1354 tmpEntryBuffer.flip();
1355
1356 return tmpEntryBuffer;
1357 }
1358
1359
1360
1361
1362 private static int readLevel(ByteBuffer buffer, JetFormat format)
1363 {
1364 int levelOffset = format.OFFSET_INDEX_LEVEL;
1365 return ((levelOffset >= 0) ? ByteUtil.getUnsignedByte(buffer, levelOffset)
1366 : 0);
1367 }
1368
1369
1370
1371
1372 private boolean isLeafPage(ByteBuffer buffer)
1373 throws IOException
1374 {
1375 byte pageType = buffer.get(0);
1376 if(pageType == PageTypes.INDEX_LEAF) {
1377 return true;
1378 } else if(pageType == PageTypes.INDEX_NODE) {
1379 return false;
1380 }
1381 throw new IOException(withErrorContext("Unexpected page type " + pageType));
1382 }
1383
1384
1385
1386
1387
1388 private int countNullValues(Object[] values)
1389 {
1390 if(values == null) {
1391 return _columns.size();
1392 }
1393
1394
1395
1396
1397 int nullCount = 0;
1398 for(ColumnDescriptor col : _columns) {
1399 Object value = values[col.getColumnIndex()];
1400 if(col.isNullValue(value)) {
1401 ++nullCount;
1402 }
1403 }
1404
1405 return nullCount;
1406 }
1407
1408
1409
1410
1411
1412 byte[] createEntryBytes(Object[] values) throws IOException
1413 {
1414 if(values == null) {
1415 return null;
1416 }
1417
1418 if(_entryBuffer == null) {
1419 _entryBuffer = new ByteStream();
1420 }
1421 _entryBuffer.reset();
1422
1423 for(ColumnDescriptor col : _columns) {
1424
1425 Object value = values[col.getColumnIndex()];
1426 if(ColumnImpl.isRawData(value)) {
1427
1428 continue;
1429 }
1430
1431 if(value == MIN_VALUE) {
1432
1433
1434
1435 _entryBuffer.write(getNullEntryFlag(true));
1436 continue;
1437 }
1438 if(value == MAX_VALUE) {
1439
1440
1441
1442 _entryBuffer.write(getNullEntryFlag(false));
1443 continue;
1444 }
1445
1446 col.writeValue(value, _entryBuffer);
1447 }
1448
1449 return truncateEntryBytes(_entryBuffer.toByteArray(),
1450 MAX_KEY_UNITS * getFormat().SIZE_TEXT_FIELD_UNIT);
1451 }
1452
1453
1454
1455
1456
1457
1458
1459 static byte[] truncateEntryBytes(byte[] entryBytes, int maxLength)
1460 {
1461 if(entryBytes.length <= maxLength) {
1462 return entryBytes;
1463 }
1464 int kept = maxLength - KEY_DIGEST_LENGTH;
1465
1466
1467
1468
1469
1470 int digest = 0;
1471 for(int i = kept; i < (entryBytes.length - 1); ++i) {
1472 digest = ((digest >>> 8) ^ KEY_DIGEST_TABLE[digest & 0xFF] ^
1473 (entryBytes[i] & 0xFF)) & 0xFFFF;
1474 }
1475 digest = (digest ^ ((entryBytes[entryBytes.length - 1] & 0xFF) << 8)) &
1476 0xFFFF;
1477
1478 byte[] truncated = ByteUtil.copyOf(entryBytes, maxLength);
1479 truncated[kept] = (byte)(digest >>> 8);
1480 truncated[kept + 1] = (byte)digest;
1481
1482 return truncated;
1483 }
1484
1485
1486
1487
1488
1489
1490
1491 private static int[] createKeyDigestTable()
1492 {
1493 int[] table = new int[256];
1494 for(int i = 0; i < table.length; ++i) {
1495 int reg = i << 8;
1496 for(int j = 0; j < 8; ++j) {
1497 reg = ((reg << 1) & 0xFFFF) ^
1498 (((reg & 0x8000) != 0) ? KEY_DIGEST_POLY : 0);
1499 }
1500 table[i] = ((reg & 0xFF) << 8) | (reg >>> 8);
1501 }
1502 return table;
1503 }
1504
1505
1506
1507
1508 protected DataPage findDataPage(Entry entry)
1509 throws IOException
1510 {
1511 return _pageCache.findCacheDataPage(entry);
1512 }
1513
1514
1515
1516
1517 protected DataPage getDataPage(int pageNumber)
1518 throws IOException
1519 {
1520 return _pageCache.getCacheDataPage(pageNumber);
1521 }
1522
1523
1524
1525
1526 private static byte[] flipFirstBitInByte(byte[] value, int index)
1527 {
1528 value[index] = (byte)(value[index] ^ 0x80);
1529
1530 return value;
1531 }
1532
1533
1534
1535
1536 private static byte[] flipBytes(byte[] value) {
1537 return flipBytes(value, 0, value.length);
1538 }
1539
1540
1541
1542
1543 static byte[] flipBytes(byte[] value, int offset, int length) {
1544 for(int i = offset; i < (offset + length); ++i) {
1545 value[i] = (byte)(~value[i]);
1546 }
1547 return value;
1548 }
1549
1550
1551
1552
1553 private static byte[] encodeNumberColumnValue(Object value, ColumnImpl column)
1554 throws IOException
1555 {
1556
1557 return column.write(value, 0, ENTRY_BYTE_ORDER).array();
1558 }
1559
1560
1561
1562
1563 private static void writeGeneralBinaryEntry(byte[] valueBytes, boolean isAsc,
1564 ByteStream bout)
1565 {
1566 int dataLen = valueBytes.length;
1567 int extraLen = (dataLen + 7) / 8;
1568 int entryLen = ((dataLen + extraLen + 8) / 9) * 9;
1569
1570
1571 bout.ensureNewCapacity(entryLen);
1572
1573
1574
1575
1576 byte[] partialEntryBytes = new byte[9];
1577
1578
1579
1580
1581
1582
1583 int segmentLen = dataLen;
1584 int pos = 0;
1585 while(segmentLen > 8) {
1586
1587 System.arraycopy(valueBytes, pos, partialEntryBytes, 0, 8);
1588 if(!isAsc) {
1589
1590 flipBytes(partialEntryBytes, 0, 8);
1591 }
1592
1593
1594
1595 partialEntryBytes[8] = (byte)9;
1596
1597 pos += 8;
1598 segmentLen -= 8;
1599
1600 bout.write(partialEntryBytes);
1601 }
1602
1603
1604 if(segmentLen > 0) {
1605
1606 System.arraycopy(valueBytes, pos, partialEntryBytes, 0, segmentLen);
1607
1608
1609
1610 for(int i = segmentLen; i < 8; ++i) {
1611 partialEntryBytes[i] = 0;
1612 }
1613
1614 partialEntryBytes[8] = (byte)segmentLen;
1615
1616 if(!isAsc) {
1617
1618 flipBytes(partialEntryBytes, 0, 9);
1619 }
1620
1621 bout.write(partialEntryBytes);
1622 }
1623 }
1624
1625
1626
1627
1628 private static Entry createSpecialEntry(RowIdImpl rowId) {
1629 return new Entry((byte[])null, rowId);
1630 }
1631
1632
1633
1634
1635 private ColumnDescriptor newColumnDescriptor(ColumnImpl col, byte flags)
1636 {
1637 switch(col.getType()) {
1638 case TEXT:
1639 case MEMO:
1640 ColumnImpl.SortOrder sortOrder = col.getTextSortOrder();
1641 GeneralLegacyIndexCodes textCodes = getTextIndexCodes(sortOrder);
1642 if(textCodes != null) {
1643 return new TextColumnDescriptor(col, flags, textCodes);
1644 }
1645
1646 if(col.getTable().getDatabase().isWriteBrokenIndex()) {
1647
1648
1649
1650
1651
1652
1653 setUnsupportedReason("unsupported collating sort order " + sortOrder +
1654 " for text index", IndexStatus.BROKEN_WRITE, col);
1655 return new TextColumnDescriptor(col, flags,
1656 GeneralLegacyIndexCodes.GEN_LEG_INSTANCE);
1657 }
1658 setUnsupportedReason("unsupported collating sort order " + sortOrder +
1659 " for text index", IndexStatus.READ_ONLY, col);
1660 return new ReadOnlyColumnDescriptor(col, flags);
1661 case INT:
1662 case LONG:
1663 case MONEY:
1664 case COMPLEX_TYPE:
1665 case BIG_INT:
1666 return new IntegerColumnDescriptor(col, flags);
1667 case FLOAT:
1668 case DOUBLE:
1669 case SHORT_DATE_TIME:
1670 return new FloatingPointColumnDescriptor(col, flags);
1671 case NUMERIC:
1672 return (col.getFormat().LEGACY_NUMERIC_INDEXES ?
1673 new LegacyFixedPointColumnDescriptor(col, flags) :
1674 new FixedPointColumnDescriptor(col, flags));
1675 case BYTE:
1676 return new ByteColumnDescriptor(col, flags);
1677 case BOOLEAN:
1678 return new BooleanColumnDescriptor(col, flags);
1679 case GUID:
1680 return new GuidColumnDescriptor(col, flags);
1681 case BINARY:
1682 return new BinaryColumnDescriptor(col, flags);
1683 case EXT_DATE_TIME:
1684 return new ExtDateColumnDescriptor(col, flags);
1685
1686 default:
1687
1688 setUnsupportedReason("unsupported data type " + col.getType() +
1689 " for index", IndexStatus.READ_ONLY, col);
1690 return new ReadOnlyColumnDescriptor(col, flags);
1691 }
1692 }
1693
1694
1695
1696
1697 private static EntryType determineEntryType(byte[] entryBytes, RowIdImpl rowId)
1698 {
1699 if(entryBytes != null) {
1700 return ((rowId.getType() == RowIdImpl.Type.NORMAL) ?
1701 EntryType.NORMAL :
1702 ((rowId.getType() == RowIdImpl.Type.ALWAYS_FIRST) ?
1703 EntryType.FIRST_VALID : EntryType.LAST_VALID));
1704 } else if(!rowId.isValid()) {
1705
1706 return ((rowId.getType() == RowIdImpl.Type.ALWAYS_FIRST) ?
1707 EntryType.ALWAYS_FIRST : EntryType.ALWAYS_LAST);
1708 }
1709 throw new IllegalArgumentException("Values was null for valid entry");
1710 }
1711
1712
1713
1714
1715
1716 private static int calcMaxPageEntrySize(JetFormat format)
1717 {
1718
1719
1720 int pageDataSize = (format.PAGE_SIZE -
1721 (format.OFFSET_INDEX_ENTRY_MASK +
1722 format.SIZE_INDEX_ENTRY_MASK));
1723 int entryMaskSize = (format.SIZE_INDEX_ENTRY_MASK * 8);
1724 return Math.min(pageDataSize, entryMaskSize);
1725 }
1726
1727 String withErrorContext(String msg) {
1728 return withErrorContext(msg, getTable().getDatabase(), getTable().getName(),
1729 getName());
1730 }
1731
1732 private static String withErrorContext(String msg, DatabaseImpl db,
1733 String tableName, String idxName) {
1734 return msg + " (Db=" + db.getName() + ";Table=" + tableName +
1735 ";Index=" + idxName + ")";
1736 }
1737
1738
1739
1740
1741
1742 public static abstract class ColumnDescriptor implements Index.Column
1743 {
1744 private final ColumnImpl _column;
1745 private final byte _flags;
1746
1747 private ColumnDescriptor(ColumnImpl column, byte flags)
1748 {
1749 _column = column;
1750 _flags = flags;
1751 }
1752
1753 @Override
1754 public ColumnImpl getColumn() {
1755 return _column;
1756 }
1757
1758 public byte getFlags() {
1759 return _flags;
1760 }
1761
1762 @Override
1763 public boolean isAscending() {
1764 return((getFlags() & ASCENDING_COLUMN_FLAG) != 0);
1765 }
1766
1767 @Override
1768 public int getColumnIndex() {
1769 return getColumn().getColumnIndex();
1770 }
1771
1772 @Override
1773 public String getName() {
1774 return getColumn().getName();
1775 }
1776
1777 protected boolean isNullValue(Object value) {
1778 return (value == null);
1779 }
1780
1781 protected final void writeValue(Object value, ByteStream bout)
1782 throws IOException
1783 {
1784 if(isNullValue(value)) {
1785
1786 bout.write(getNullEntryFlag(isAscending()));
1787 return;
1788 }
1789
1790
1791 bout.write(getStartEntryFlag(isAscending()));
1792
1793 writeNonNullValue(value, bout);
1794 }
1795
1796 protected abstract void writeNonNullValue(Object value, ByteStream bout)
1797 throws IOException;
1798
1799 @Override
1800 public String toString() {
1801 return ToStringBuilder.builder(this)
1802 .append("column", getColumn())
1803 .append("flags", getFlags() + " " + (isAscending() ? "(ASC)" : "(DSC)"))
1804 .toString();
1805 }
1806 }
1807
1808
1809
1810
1811 private static final class IntegerColumnDescriptor extends ColumnDescriptor
1812 {
1813 private IntegerColumnDescriptor(ColumnImpl column, byte flags)
1814 {
1815 super(column, flags);
1816 }
1817
1818 @Override
1819 protected void writeNonNullValue(Object value, ByteStream bout)
1820 throws IOException
1821 {
1822 byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
1823
1824
1825
1826
1827
1828 flipFirstBitInByte(valueBytes, 0);
1829 if(!isAscending()) {
1830 flipBytes(valueBytes);
1831 }
1832
1833 bout.write(valueBytes);
1834 }
1835 }
1836
1837
1838
1839
1840 private static final class FloatingPointColumnDescriptor
1841 extends ColumnDescriptor
1842 {
1843 private FloatingPointColumnDescriptor(ColumnImpl column, byte flags)
1844 {
1845 super(column, flags);
1846 }
1847
1848 @Override
1849 protected void writeNonNullValue(Object value, ByteStream bout)
1850 throws IOException
1851 {
1852 byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
1853
1854
1855
1856 boolean isNegative = ((valueBytes[0] & 0x80) != 0);
1857
1858
1859
1860
1861
1862
1863
1864 if(!isNegative) {
1865 flipFirstBitInByte(valueBytes, 0);
1866 }
1867 if(isNegative == isAscending()) {
1868 flipBytes(valueBytes);
1869 }
1870
1871 bout.write(valueBytes);
1872 }
1873 }
1874
1875
1876
1877
1878 private static class LegacyFixedPointColumnDescriptor
1879 extends ColumnDescriptor
1880 {
1881 private LegacyFixedPointColumnDescriptor(ColumnImpl column, byte flags)
1882 {
1883 super(column, flags);
1884 }
1885
1886 protected void handleNegationAndOrder(boolean isNegative,
1887 byte[] valueBytes)
1888 {
1889 if(isNegative == isAscending()) {
1890 flipBytes(valueBytes);
1891 }
1892
1893
1894 valueBytes[0] = (isNegative ? (byte)0x00 : (byte)0xFF);
1895 }
1896
1897 @Override
1898 protected void writeNonNullValue(Object value, ByteStream bout)
1899 throws IOException
1900 {
1901 byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
1902
1903
1904
1905 boolean isNegative = ((valueBytes[0] & 0x80) != 0);
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918 handleNegationAndOrder(isNegative, valueBytes);
1919
1920 bout.write(valueBytes);
1921 }
1922 }
1923
1924
1925
1926
1927 private static final class FixedPointColumnDescriptor
1928 extends LegacyFixedPointColumnDescriptor
1929 {
1930 private FixedPointColumnDescriptor(ColumnImpl column, byte flags)
1931 {
1932 super(column, flags);
1933 }
1934
1935 @Override
1936 protected void handleNegationAndOrder(boolean isNegative,
1937 byte[] valueBytes)
1938 {
1939
1940
1941
1942 valueBytes[0] = (byte)0xFF;
1943
1944 if(isNegative == isAscending()) {
1945 flipBytes(valueBytes);
1946 }
1947 }
1948 }
1949
1950
1951
1952
1953 private static final class ByteColumnDescriptor extends ColumnDescriptor
1954 {
1955 private ByteColumnDescriptor(ColumnImpl column, byte flags)
1956 {
1957 super(column, flags);
1958 }
1959
1960 @Override
1961 protected void writeNonNullValue(Object value, ByteStream bout)
1962 throws IOException
1963 {
1964 byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
1965
1966
1967
1968
1969 if(!isAscending()) {
1970 flipBytes(valueBytes);
1971 }
1972
1973 bout.write(valueBytes);
1974 }
1975 }
1976
1977
1978
1979
1980 private static final class BooleanColumnDescriptor extends ColumnDescriptor
1981 {
1982 private BooleanColumnDescriptor(ColumnImpl column, byte flags)
1983 {
1984 super(column, flags);
1985 }
1986
1987 @Override
1988 protected boolean isNullValue(Object value) {
1989
1990 return false;
1991 }
1992
1993 @Override
1994 protected void writeNonNullValue(Object value, ByteStream bout)
1995 {
1996 bout.write(
1997 ColumnImpl.toBooleanValue(value) ?
1998 (isAscending() ? ASC_BOOLEAN_TRUE : DESC_BOOLEAN_TRUE) :
1999 (isAscending() ? ASC_BOOLEAN_FALSE : DESC_BOOLEAN_FALSE));
2000 }
2001 }
2002
2003
2004
2005
2006
2007 private static final class TextColumnDescriptor extends ColumnDescriptor
2008 {
2009 private final GeneralLegacyIndexCodes _codes;
2010
2011 private TextColumnDescriptor(ColumnImpl column, byte flags,
2012 GeneralLegacyIndexCodes codes)
2013 {
2014 super(column, flags);
2015 _codes = codes;
2016 }
2017
2018 @Override
2019 protected void writeNonNullValue(Object value, ByteStream bout)
2020 throws IOException
2021 {
2022 _codes.writeNonNullIndexTextValue(value, bout, isAscending());
2023 }
2024 }
2025
2026
2027
2028
2029 private static final class GuidColumnDescriptor extends ColumnDescriptor
2030 {
2031 private GuidColumnDescriptor(ColumnImpl column, byte flags)
2032 {
2033 super(column, flags);
2034 }
2035
2036 @Override
2037 protected void writeNonNullValue(Object value, ByteStream bout)
2038 throws IOException
2039 {
2040 writeGeneralBinaryEntry(
2041 encodeNumberColumnValue(value, getColumn()), isAscending(),
2042 bout);
2043 }
2044 }
2045
2046
2047
2048
2049
2050 private static final class BinaryColumnDescriptor extends ColumnDescriptor
2051 {
2052 private BinaryColumnDescriptor(ColumnImpl column, byte flags)
2053 {
2054 super(column, flags);
2055 }
2056
2057 @Override
2058 protected void writeNonNullValue(Object value, ByteStream bout)
2059 throws IOException
2060 {
2061 writeGeneralBinaryEntry(
2062 ColumnImpl.toByteArray(value), isAscending(), bout);
2063 }
2064 }
2065
2066
2067
2068
2069 private static final class ExtDateColumnDescriptor extends ColumnDescriptor
2070 {
2071 private ExtDateColumnDescriptor(ColumnImpl column, byte flags)
2072 {
2073 super(column, flags);
2074 }
2075
2076 @Override
2077 protected void writeNonNullValue(Object value, ByteStream bout)
2078 throws IOException
2079 {
2080 byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
2081
2082
2083
2084
2085
2086 byte[] trailer = ASC_EXT_DATE_TRAILER;
2087 if(!isAscending()) {
2088 flipBytes(valueBytes);
2089 trailer = DESC_EXT_DATE_TRAILER;
2090 }
2091
2092
2093 int valIdx = 0;
2094 for(int i = 0; i < 5; ++i) {
2095 bout.write(valueBytes, valIdx, 8);
2096 bout.write((byte)0x09);
2097 valIdx += 8;
2098 }
2099
2100
2101 bout.write(valueBytes, valIdx, 2);
2102 bout.write(trailer);
2103 }
2104 }
2105
2106
2107
2108
2109 private final class ReadOnlyColumnDescriptor extends ColumnDescriptor
2110 {
2111 private ReadOnlyColumnDescriptor(ColumnImpl column, byte flags)
2112 {
2113 super(column, flags);
2114 }
2115
2116 @Override
2117 protected void writeNonNullValue(Object value, ByteStream bout)
2118 {
2119 throw new UnsupportedOperationException(
2120 "Cannot write indexes of this type due to " + _unsupportedReason);
2121 }
2122 }
2123
2124
2125
2126
2127 public static class Entry implements Comparable<Entry>
2128 {
2129
2130 private final RowIdImpl _rowId;
2131
2132 private final byte[] _entryBytes;
2133
2134 private final EntryType _type;
2135
2136
2137
2138
2139
2140
2141
2142 private Entry(byte[] entryBytes, RowIdImpl rowId, EntryType type) {
2143 _rowId = rowId;
2144 _entryBytes = entryBytes;
2145 _type = type;
2146 }
2147
2148
2149
2150
2151
2152
2153 private Entry(byte[] entryBytes, RowIdImpl rowId)
2154 {
2155 this(entryBytes, rowId, determineEntryType(entryBytes, rowId));
2156 }
2157
2158
2159
2160
2161 private Entry(ByteBuffer buffer, int entryLen)
2162 {
2163 this(buffer, entryLen, 0);
2164 }
2165
2166
2167
2168
2169 private Entry(ByteBuffer buffer, int entryLen, int extraTrailingLen)
2170 {
2171
2172
2173 int colEntryLen = entryLen - (4 + extraTrailingLen);
2174
2175
2176 _entryBytes = ByteUtil.getBytes(buffer, colEntryLen);
2177
2178
2179 int page = ByteUtil.get3ByteInt(buffer, ENTRY_BYTE_ORDER);
2180 int row = ByteUtil.getUnsignedByte(buffer);
2181
2182 _rowId = new RowIdImpl(page, row);
2183 _type = EntryType.NORMAL;
2184 }
2185
2186 public RowIdImpl getRowId() {
2187 return _rowId;
2188 }
2189
2190 public EntryType getType() {
2191 return _type;
2192 }
2193
2194 public Integer getSubPageNumber() {
2195 throw new UnsupportedOperationException();
2196 }
2197
2198 public boolean isLeafEntry() {
2199 return true;
2200 }
2201
2202 public boolean isValid() {
2203 return(_entryBytes != null);
2204 }
2205
2206 protected final byte[] getEntryBytes() {
2207 return _entryBytes;
2208 }
2209
2210
2211
2212
2213 protected int size() {
2214
2215 return _entryBytes.length + 4;
2216 }
2217
2218
2219
2220
2221 protected void write(ByteBuffer buffer,
2222 byte[] prefix)
2223 {
2224 if(prefix.length <= _entryBytes.length) {
2225
2226
2227 buffer.put(_entryBytes, prefix.length,
2228 (_entryBytes.length - prefix.length));
2229 ByteUtil.put3ByteInt(buffer, getRowId().getPageNumber(),
2230 ENTRY_BYTE_ORDER);
2231
2232 } else if(prefix.length <= (_entryBytes.length + 3)) {
2233
2234
2235
2236 ByteBuffer tmp = ByteBuffer.allocate(3);
2237 ByteUtil.put3ByteInt(tmp, getRowId().getPageNumber(),
2238 ENTRY_BYTE_ORDER);
2239 tmp.flip();
2240 tmp.position(prefix.length - _entryBytes.length);
2241 buffer.put(tmp);
2242
2243 } else {
2244
2245
2246
2247
2248
2249 throw new IllegalStateException("prefix should never be this long");
2250 }
2251
2252 buffer.put((byte)getRowId().getRowNumber());
2253 }
2254
2255 protected final ToStringBuilder entryBytesToStringBuilder(
2256 ToStringBuilder sb) {
2257 if(isValid()) {
2258 sb.append("bytes", _entryBytes);
2259 }
2260 return sb;
2261 }
2262
2263 @Override
2264 public String toString() {
2265 return entryBytesToStringBuilder(
2266 ToStringBuilder.valueBuilder(this)
2267 .append("rowId", _rowId))
2268 .toString();
2269 }
2270
2271 @Override
2272 public int hashCode() {
2273 return _rowId.hashCode();
2274 }
2275
2276 @Override
2277 public boolean equals(Object o) {
2278 return((this == o) ||
2279 ((o != null) && (getClass() == o.getClass()) &&
2280 (compareTo((Entry)o) == 0)));
2281 }
2282
2283
2284
2285
2286
2287 public boolean equalsEntryBytes(Entry o) {
2288 return(BYTE_CODE_COMPARATOR.compare(_entryBytes, o._entryBytes) == 0);
2289 }
2290
2291 @Override
2292 public int compareTo(Entry other) {
2293 if (this == other) {
2294 return 0;
2295 }
2296
2297 if(isValid() && other.isValid()) {
2298
2299
2300 int entryCmp = BYTE_CODE_COMPARATOR.compare(
2301 _entryBytes, other._entryBytes);
2302 if(entryCmp != 0) {
2303 return entryCmp;
2304 }
2305
2306 } else {
2307
2308
2309
2310 int typeCmp = _type.compareTo(other._type);
2311 if(typeCmp != 0) {
2312 return typeCmp;
2313 }
2314 }
2315
2316
2317 return _rowId.compareTo(other.getRowId());
2318 }
2319
2320
2321
2322
2323
2324 protected Entry asNodeEntry(Integer subPageNumber) {
2325 return new NodeEntry(_entryBytes, _rowId, _type, subPageNumber);
2326 }
2327
2328 }
2329
2330
2331
2332
2333 private static final class NodeEntry extends Entry {
2334
2335
2336 private final Integer _subPageNumber;
2337
2338
2339
2340
2341
2342
2343
2344
2345 private NodeEntry(byte[] entryBytes, RowIdImpl rowId, EntryType type,
2346 Integer subPageNumber) {
2347 super(entryBytes, rowId, type);
2348 _subPageNumber = subPageNumber;
2349 }
2350
2351
2352
2353
2354 private NodeEntry(ByteBuffer buffer, int entryLen)
2355 {
2356
2357 super(buffer, entryLen, 4);
2358
2359 _subPageNumber = ByteUtil.getInt(buffer, ENTRY_BYTE_ORDER);
2360 }
2361
2362 @Override
2363 public Integer getSubPageNumber() {
2364 return _subPageNumber;
2365 }
2366
2367 @Override
2368 public boolean isLeafEntry() {
2369 return false;
2370 }
2371
2372 @Override
2373 protected int size() {
2374
2375 return super.size() + 4;
2376 }
2377
2378 @Override
2379 protected void write(ByteBuffer buffer, byte[] prefix) {
2380 super.write(buffer, prefix);
2381 ByteUtil.putInt(buffer, _subPageNumber, ENTRY_BYTE_ORDER);
2382 }
2383
2384 @Override
2385 public boolean equals(Object o) {
2386 return((this == o) ||
2387 ((o != null) && (getClass() == o.getClass()) &&
2388 (compareTo((Entry)o) == 0) &&
2389 (getSubPageNumber().equals(((Entry)o).getSubPageNumber()))));
2390 }
2391
2392 @Override
2393 public String toString() {
2394 return entryBytesToStringBuilder(
2395 ToStringBuilder.valueBuilder(this)
2396 .append("rowId", getRowId())
2397 .append("subPage", _subPageNumber))
2398 .toString();
2399 }
2400 }
2401
2402
2403
2404
2405
2406 public final class EntryCursor
2407 {
2408
2409 private final DirHandler _forwardDirHandler = new ForwardDirHandler();
2410
2411 private final DirHandler _reverseDirHandler = new ReverseDirHandler();
2412
2413 private Position _firstPos;
2414
2415 private Position _lastPos;
2416
2417 private Position _curPos;
2418
2419 private Position _prevPos;
2420
2421
2422
2423 private int _lastModCount;
2424
2425 private EntryCursor(Position firstPos, Position lastPos)
2426 {
2427 _firstPos = firstPos;
2428 _lastPos = lastPos;
2429 _lastModCount = getIndexModCount();
2430 reset();
2431 }
2432
2433
2434
2435
2436 private DirHandler getDirHandler(boolean moveForward) {
2437 return (moveForward ? _forwardDirHandler : _reverseDirHandler);
2438 }
2439
2440 public IndexData getIndexData() {
2441 return IndexData.this;
2442 }
2443
2444 private int getIndexModCount() {
2445 return IndexData.this._modCount;
2446 }
2447
2448
2449
2450
2451 public Entry getFirstEntry() {
2452 return _firstPos.getEntry();
2453 }
2454
2455
2456
2457
2458 public Entry getLastEntry() {
2459 return _lastPos.getEntry();
2460 }
2461
2462
2463
2464
2465
2466 public boolean isUpToDate() {
2467 return(getIndexModCount() == _lastModCount);
2468 }
2469
2470 public void reset() {
2471 beforeFirst();
2472 }
2473
2474 public void beforeFirst() {
2475 reset(CursorImpl.MOVE_FORWARD);
2476 }
2477
2478 public void afterLast() {
2479 reset(CursorImpl.MOVE_REVERSE);
2480 }
2481
2482 protected void reset(boolean moveForward)
2483 {
2484 _curPos = getDirHandler(moveForward).getBeginningPosition();
2485 _prevPos = _curPos;
2486 }
2487
2488
2489
2490
2491
2492 public void beforeEntry(Object[] row)
2493 throws IOException
2494 {
2495 restorePosition(new Entry(IndexData.this.createEntryBytes(row),
2496 RowIdImpl.FIRST_ROW_ID));
2497 }
2498
2499
2500
2501
2502
2503 public void afterEntry(Object[] row)
2504 throws IOException
2505 {
2506 restorePosition(new Entry(IndexData.this.createEntryBytes(row),
2507 RowIdImpl.LAST_ROW_ID));
2508 }
2509
2510
2511
2512
2513
2514 public Entry getNextEntry() throws IOException {
2515 return getAnotherPosition(CursorImpl.MOVE_FORWARD).getEntry();
2516 }
2517
2518
2519
2520
2521
2522 public Entry getPreviousEntry() throws IOException {
2523 return getAnotherPosition(CursorImpl.MOVE_REVERSE).getEntry();
2524 }
2525
2526
2527
2528
2529
2530 protected void restorePosition(Entry curEntry)
2531 throws IOException
2532 {
2533 restorePosition(curEntry, _curPos.getEntry());
2534 }
2535
2536
2537
2538
2539 protected void restorePosition(Entry curEntry, Entry prevEntry)
2540 throws IOException
2541 {
2542 if(!_curPos.equalsEntry(curEntry) ||
2543 !_prevPos.equalsEntry(prevEntry))
2544 {
2545 if(!isUpToDate()) {
2546 updateBounds();
2547 _lastModCount = getIndexModCount();
2548 }
2549 _prevPos = updatePosition(prevEntry);
2550 _curPos = updatePosition(curEntry);
2551 } else {
2552 checkForModification();
2553 }
2554 }
2555
2556
2557
2558
2559 private Position getAnotherPosition(boolean moveForward)
2560 throws IOException
2561 {
2562 DirHandler handler = getDirHandler(moveForward);
2563 if(_curPos.equals(handler.getEndPosition())) {
2564 if(!isUpToDate()) {
2565 restorePosition(_prevPos.getEntry());
2566
2567 } else {
2568
2569 return _curPos;
2570 }
2571 }
2572
2573 checkForModification();
2574
2575 _prevPos = _curPos;
2576 _curPos = handler.getAnotherPosition(_curPos);
2577 return _curPos;
2578 }
2579
2580
2581
2582
2583 private void checkForModification()
2584 throws IOException
2585 {
2586 if(!isUpToDate()) {
2587 updateBounds();
2588 _prevPos = updatePosition(_prevPos.getEntry());
2589 _curPos = updatePosition(_curPos.getEntry());
2590 _lastModCount = getIndexModCount();
2591 }
2592 }
2593
2594
2595
2596
2597 private Position updatePosition(Entry entry)
2598 throws IOException
2599 {
2600 if(!entry.isValid()) {
2601
2602 if(_firstPos.equalsEntry(entry)) {
2603 return _firstPos;
2604 } else if(_lastPos.equalsEntry(entry)) {
2605 return _lastPos;
2606 } else {
2607 throw new IllegalArgumentException(
2608 withErrorContext("Invalid entry given " + entry));
2609 }
2610 }
2611
2612 Position pos = findEntryPosition(entry);
2613 if(pos.compareTo(_lastPos) >= 0) {
2614 return _lastPos;
2615 } else if(pos.compareTo(_firstPos) <= 0) {
2616 return _firstPos;
2617 }
2618 return pos;
2619 }
2620
2621
2622
2623
2624 private void updateBounds()
2625 throws IOException
2626 {
2627 _firstPos = findEntryPosition(_firstPos.getEntry());
2628 _lastPos = findEntryPosition(_lastPos.getEntry());
2629 }
2630
2631 @Override
2632 public String toString() {
2633 return ToStringBuilder.valueBuilder(this)
2634 .append("curPosition", _curPos)
2635 .append("prevPosition", _prevPos)
2636 .toString();
2637 }
2638
2639
2640
2641
2642
2643 private abstract class DirHandler {
2644 public abstract Position getAnotherPosition(Position curPos)
2645 throws IOException;
2646 public abstract Position getBeginningPosition();
2647 public abstract Position getEndPosition();
2648 }
2649
2650
2651
2652
2653 private final class ForwardDirHandler extends DirHandler {
2654 @Override
2655 public Position getAnotherPosition(Position curPos)
2656 throws IOException
2657 {
2658 Position newPos = getNextPosition(curPos);
2659 if((newPos == null) || (newPos.compareTo(_lastPos) >= 0)) {
2660 newPos = _lastPos;
2661 }
2662 return newPos;
2663 }
2664 @Override
2665 public Position getBeginningPosition() {
2666 return _firstPos;
2667 }
2668 @Override
2669 public Position getEndPosition() {
2670 return _lastPos;
2671 }
2672 }
2673
2674
2675
2676
2677 private final class ReverseDirHandler extends DirHandler {
2678 @Override
2679 public Position getAnotherPosition(Position curPos)
2680 throws IOException
2681 {
2682 Position newPos = getPreviousPosition(curPos);
2683 if((newPos == null) || (newPos.compareTo(_firstPos) <= 0)) {
2684 newPos = _firstPos;
2685 }
2686 return newPos;
2687 }
2688 @Override
2689 public Position getBeginningPosition() {
2690 return _lastPos;
2691 }
2692 @Override
2693 public Position getEndPosition() {
2694 return _firstPos;
2695 }
2696 }
2697 }
2698
2699
2700
2701
2702 private static final class Position implements Comparable<Position> {
2703
2704 private final DataPage _dataPage;
2705
2706 private final int _idx;
2707
2708 private final Entry _entry;
2709
2710
2711
2712 private final boolean _between;
2713
2714 private Position(DataPage dataPage, int idx)
2715 {
2716 this(dataPage, idx, dataPage.getEntries().get(idx), false);
2717 }
2718
2719 private Position(DataPage dataPage, int idx, Entry entry, boolean between)
2720 {
2721 _dataPage = dataPage;
2722 _idx = idx;
2723 _entry = entry;
2724 _between = between;
2725 }
2726
2727 DataPage getDataPage() {
2728 return _dataPage;
2729 }
2730
2731 int getIndex() {
2732 return _idx;
2733 }
2734
2735 int getNextIndex() {
2736
2737
2738 return(_between ? _idx : (_idx + 1));
2739 }
2740
2741 int getPrevIndex() {
2742
2743
2744
2745 return(_idx - 1);
2746 }
2747
2748 Entry getEntry() {
2749 return _entry;
2750 }
2751
2752 boolean equalsEntry(Entry entry) {
2753 return _entry.equals(entry);
2754 }
2755
2756 @Override
2757 public int compareTo(Position other)
2758 {
2759 if(this == other) {
2760 return 0;
2761 }
2762
2763 if(_dataPage.equals(other._dataPage)) {
2764
2765 int idxCmp = ((_idx < other._idx) ? -1 :
2766 ((_idx > other._idx) ? 1 :
2767 ((_between == other._between) ? 0 :
2768 (_between ? -1 : 1))));
2769 if(idxCmp != 0) {
2770 return idxCmp;
2771 }
2772 }
2773
2774
2775 return _entry.compareTo(other._entry);
2776 }
2777
2778 @Override
2779 public int hashCode() {
2780 return _entry.hashCode();
2781 }
2782
2783 @Override
2784 public boolean equals(Object o) {
2785 return((this == o) ||
2786 ((o != null) && (getClass() == o.getClass()) &&
2787 (compareTo((Position)o) == 0)));
2788 }
2789
2790 @Override
2791 public String toString() {
2792 return ToStringBuilder.valueBuilder(this)
2793 .append("page", _dataPage.getPageNumber())
2794 .append("idx", _idx)
2795 .append("entry", _entry)
2796 .append("between", _between)
2797 .toString();
2798 }
2799 }
2800
2801
2802
2803
2804 protected static abstract class DataPage {
2805
2806 public abstract int getPageNumber();
2807
2808 public abstract boolean isLeaf();
2809 public abstract void setLeaf(boolean isLeaf);
2810
2811
2812
2813
2814
2815 public abstract int getLevel() throws IOException;
2816 public abstract void setLevel(int level);
2817
2818 public abstract int getPrevPageNumber();
2819 public abstract void setPrevPageNumber(int pageNumber);
2820 public abstract int getNextPageNumber();
2821 public abstract void setNextPageNumber(int pageNumber);
2822 public abstract int getChildTailPageNumber();
2823 public abstract void setChildTailPageNumber(int pageNumber);
2824
2825 public abstract int getTotalEntrySize();
2826 public abstract void setTotalEntrySize(int totalSize);
2827 public abstract byte[] getEntryPrefix();
2828 public abstract void setEntryPrefix(byte[] entryPrefix);
2829
2830 public abstract List<Entry> getEntries();
2831 public abstract void setEntries(List<Entry> entries);
2832
2833 public abstract void addEntry(int idx, Entry entry)
2834 throws IOException;
2835 public abstract Entry removeEntry(int idx)
2836 throws IOException;
2837
2838 public final boolean isEmpty() {
2839 return getEntries().isEmpty();
2840 }
2841
2842 public final int getCompressedEntrySize() {
2843
2844
2845
2846 return getTotalEntrySize() -
2847 (getEntryPrefix().length * (getEntries().size() - 1));
2848 }
2849
2850 public final int findEntry(Entry entry) {
2851 return Collections.binarySearch(getEntries(), entry);
2852 }
2853
2854 @Override
2855 public final int hashCode() {
2856 return getPageNumber();
2857 }
2858
2859 @Override
2860 public final boolean equals(Object o) {
2861 return((this == o) ||
2862 ((o != null) && (getClass() == o.getClass()) &&
2863 (getPageNumber() == ((DataPage)o).getPageNumber())));
2864 }
2865
2866 @Override
2867 public final String toString() {
2868 List<Entry> entries = getEntries();
2869
2870 String objName =
2871 (isLeaf() ? "Leaf" : "Node") + "DataPage[" + getPageNumber() +
2872 "] " + getPrevPageNumber() + ", " + getNextPageNumber() + ", (" +
2873 getChildTailPageNumber() + ")";
2874 ToStringBuilder sb = ToStringBuilder.valueBuilder(objName);
2875
2876 if((isLeaf() && !entries.isEmpty())) {
2877 sb.append("entryRange", "[" + entries.get(0) + ", " +
2878 entries.get(entries.size() - 1) + "]");
2879 } else {
2880 sb.append("entries", entries);
2881 }
2882 return sb.toString();
2883 }
2884 }
2885
2886
2887
2888
2889 private static final class RootDataPage extends DataPage {
2890
2891 @Override
2892 public int getPageNumber() { return 0; }
2893
2894 @Override
2895 public boolean isLeaf() { return true; }
2896 @Override
2897 public void setLeaf(boolean isLeaf) { }
2898
2899 @Override
2900 public int getLevel() { return 0; }
2901 @Override
2902 public void setLevel(int level) { }
2903
2904 @Override
2905 public int getPrevPageNumber() { return 0; }
2906 @Override
2907 public void setPrevPageNumber(int pageNumber) { }
2908
2909 @Override
2910 public int getNextPageNumber() { return 0; }
2911 @Override
2912 public void setNextPageNumber(int pageNumber) { }
2913
2914 @Override
2915 public int getChildTailPageNumber() { return 0; }
2916 @Override
2917 public void setChildTailPageNumber(int pageNumber) { }
2918
2919 @Override
2920 public int getTotalEntrySize() { return 0; }
2921 @Override
2922 public void setTotalEntrySize(int totalSize) { }
2923
2924 @Override
2925 public byte[] getEntryPrefix() { return EMPTY_PREFIX; }
2926 @Override
2927 public void setEntryPrefix(byte[] entryPrefix) { }
2928
2929 @Override
2930 public List<Entry> getEntries() { return Collections.emptyList(); }
2931 @Override
2932 public void setEntries(List<Entry> entries) { }
2933 @Override
2934 public void addEntry(int idx, Entry entry) { }
2935 @Override
2936 public Entry removeEntry(int idx) { return null; }
2937 }
2938
2939
2940
2941
2942
2943
2944 public static abstract class PendingChange
2945 {
2946 private final PendingChange _next;
2947
2948 private PendingChange(PendingChange next) {
2949 _next = next;
2950 }
2951
2952
2953
2954
2955 public PendingChange getNext() {
2956 return _next;
2957 }
2958
2959
2960
2961
2962 public abstract void commit() throws IOException;
2963
2964
2965
2966
2967 public abstract void rollback() throws IOException;
2968 }
2969
2970
2971
2972
2973 private class AddRowPendingChange extends PendingChange
2974 {
2975 protected Entry _addEntry;
2976 protected DataPage _addDataPage;
2977 protected int _addIdx;
2978 protected boolean _isDupe;
2979 protected Entry _oldEntry;
2980
2981 private AddRowPendingChange(PendingChange next) {
2982 super(next);
2983 }
2984
2985 public void setAddRow(Entry addEntry, DataPage dataPage, int idx,
2986 boolean isDupe) {
2987 _addEntry = addEntry;
2988 _addDataPage = dataPage;
2989 _addIdx = idx;
2990 _isDupe = isDupe;
2991 }
2992
2993 public void setOldRow(Entry oldEntry) {
2994 _oldEntry = oldEntry;
2995 }
2996
2997 @Override
2998 public void commit() throws IOException {
2999 commitAddRow(_addEntry, _addDataPage, _addIdx, _isDupe, _oldEntry);
3000 }
3001
3002 @Override
3003 public void rollback() throws IOException {
3004 _addEntry = null;
3005 _addDataPage = null;
3006 _addIdx = -1;
3007 }
3008 }
3009
3010
3011
3012
3013
3014 private class UpdateRowPendingChange extends AddRowPendingChange
3015 {
3016 private UpdateRowPendingChange(PendingChange next) {
3017 super(next);
3018 }
3019
3020 @Override
3021 public void rollback() throws IOException {
3022 super.rollback();
3023 rollbackDeletedRow(_oldEntry);
3024 }
3025 }
3026
3027 }