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