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