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