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