1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package com.healthmarketscience.jackcess;
29
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32 import java.nio.ByteBuffer;
33 import java.nio.ByteOrder;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.Iterator;
39 import java.util.LinkedList;
40 import java.util.List;
41 import java.util.Map;
42
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45
46 import static com.healthmarketscience.jackcess.IndexCodes.*;
47
48
49
50
51
52
53 public abstract class Index implements Comparable<Index> {
54
55 protected static final Log LOG = LogFactory.getLog(Index.class);
56
57
58 public static final Entry FIRST_ENTRY =
59 createSpecialEntry(RowId.FIRST_ROW_ID);
60
61
62 public static final Entry LAST_ENTRY =
63 createSpecialEntry(RowId.LAST_ROW_ID);
64
65 protected static final int INVALID_INDEX_PAGE_NUMBER = 0;
66
67
68 private static final int MAX_COLUMNS = 10;
69
70 protected static final byte[] EMPTY_PREFIX = new byte[0];
71
72 private static final short COLUMN_UNUSED = -1;
73
74 private static final byte ASCENDING_COLUMN_FLAG = (byte)0x01;
75
76 private static final byte UNIQUE_INDEX_FLAG = (byte)0x01;
77 private static final byte IGNORE_NULLS_INDEX_FLAG = (byte)0x02;
78
79
80 private static final byte PRIMARY_KEY_INDEX_TYPE = (byte)1;
81
82
83 private static final byte FOREIGN_KEY_INDEX_TYPE = (byte)2;
84
85 private static final int MAX_TEXT_INDEX_CHAR_LENGTH =
86 (JetFormat.TEXT_FIELD_MAX_LENGTH / JetFormat.TEXT_FIELD_UNIT_SIZE);
87
88
89 public enum EntryType {
90
91
92 ALWAYS_FIRST,
93
94
95 FIRST_VALID,
96
97
98 NORMAL,
99
100
101 LAST_VALID,
102
103
104 ALWAYS_LAST;
105 }
106
107 static final Comparator<byte[]> BYTE_CODE_COMPARATOR =
108 new Comparator<byte[]>() {
109 public int compare(byte[] left, byte[] right) {
110 if(left == right) {
111 return 0;
112 }
113 if(left == null) {
114 return -1;
115 }
116 if(right == null) {
117 return 1;
118 }
119
120 int len = Math.min(left.length, right.length);
121 int pos = 0;
122 while((pos < len) && (left[pos] == right[pos])) {
123 ++pos;
124 }
125 if(pos < len) {
126 return ((ByteUtil.asUnsignedByte(left[pos]) <
127 ByteUtil.asUnsignedByte(right[pos])) ? -1 : 1);
128 }
129 return ((left.length < right.length) ? -1 :
130 ((left.length > right.length) ? 1 : 0));
131 }
132 };
133
134
135
136 private final Table _table;
137
138 private int _rootPageNumber;
139
140
141 private final int _uniqueEntryCountOffset;
142
143
144
145 private int _uniqueEntryCount;
146
147 private final List<ColumnDescriptor> _columns =
148 new ArrayList<ColumnDescriptor>();
149
150 private int _indexNumber;
151
152 private byte _indexFlags;
153
154 private byte _indexType;
155
156 private String _name;
157
158
159 private boolean _initialized;
160
161 private int _modCount;
162
163 private final TempBufferHolder _indexBufferH =
164 TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true);
165
166 private final int _maxPageEntrySize;
167
168 boolean _readOnly;
169
170 protected Index(Table table, int uniqueEntryCount,
171 int uniqueEntryCountOffset)
172 {
173 _table = table;
174 _uniqueEntryCount = uniqueEntryCount;
175 _uniqueEntryCountOffset = uniqueEntryCountOffset;
176 _maxPageEntrySize = calcMaxPageEntrySize(_table.getFormat());
177 }
178
179 public Table getTable() {
180 return _table;
181 }
182
183 public JetFormat getFormat() {
184 return getTable().getFormat();
185 }
186
187 public PageChannel getPageChannel() {
188 return getTable().getPageChannel();
189 }
190
191 public void setIndexNumber(int indexNumber) {
192 _indexNumber = indexNumber;
193 }
194
195 public int getIndexNumber() {
196 return _indexNumber;
197 }
198
199 public void setIndexType(byte indexType) {
200 _indexType = indexType;
201 }
202
203 public byte getIndexFlags() {
204 return _indexFlags;
205 }
206
207 public int getUniqueEntryCount() {
208 return _uniqueEntryCount;
209 }
210
211 public int getUniqueEntryCountOffset() {
212 return _uniqueEntryCountOffset;
213 }
214
215 public String getName() {
216 return _name;
217 }
218
219 public void setName(String name) {
220 _name = name;
221 }
222
223 public boolean isPrimaryKey() {
224 return _indexType == PRIMARY_KEY_INDEX_TYPE;
225 }
226
227 public boolean isForeignKey() {
228 return _indexType == FOREIGN_KEY_INDEX_TYPE;
229 }
230
231
232
233
234 public boolean shouldIgnoreNulls() {
235 return((_indexFlags & IGNORE_NULLS_INDEX_FLAG) != 0);
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249
250 public boolean isUnique() {
251 return(isPrimaryKey() || ((_indexFlags & UNIQUE_INDEX_FLAG) != 0));
252 }
253
254
255
256
257 public List<ColumnDescriptor> getColumns() {
258 return Collections.unmodifiableList(_columns);
259 }
260
261
262
263
264 public boolean isInitialized() {
265 return _initialized;
266 }
267
268 protected int getRootPageNumber() {
269 return _rootPageNumber;
270 }
271
272 protected void setReadOnly() {
273 _readOnly = true;
274 }
275
276 protected int getMaxPageEntrySize() {
277 return _maxPageEntrySize;
278 }
279
280
281
282
283
284
285
286 protected int getEntryCount()
287 throws IOException
288 {
289 initialize();
290 EntryCursor cursor = cursor();
291 Entry endEntry = cursor.getLastEntry();
292 int count = 0;
293 while(!endEntry.equals(cursor.getNextEntry())) {
294 ++count;
295 }
296 return count;
297 }
298
299
300
301
302
303
304 public void initialize() throws IOException {
305 if(!_initialized) {
306 readIndexEntries();
307 _initialized = true;
308 }
309 }
310
311
312
313
314
315
316 public void update() throws IOException
317 {
318
319 initialize();
320
321 if(_readOnly) {
322 throw new UnsupportedOperationException(
323 "FIXME cannot write indexes of this type yet, see Database javadoc for info on enabling large index support");
324 }
325 updateImpl();
326 }
327
328
329
330
331
332
333 public void read(ByteBuffer tableBuffer, List<Column> availableColumns)
334 throws IOException
335 {
336 for (int i = 0; i < MAX_COLUMNS; i++) {
337 short columnNumber = tableBuffer.getShort();
338 byte colFlags = tableBuffer.get();
339 if (columnNumber != COLUMN_UNUSED) {
340
341
342 Column idxCol = null;
343 for(Column col : availableColumns) {
344 if(col.getColumnNumber() == columnNumber) {
345 idxCol = col;
346 break;
347 }
348 }
349 if(idxCol == null) {
350 throw new IOException("Could not find column with number "
351 + columnNumber + " for index " + getName());
352 }
353 _columns.add(newColumnDescriptor(idxCol, colFlags));
354 }
355 }
356 tableBuffer.getInt();
357 _rootPageNumber = tableBuffer.getInt();
358 tableBuffer.getInt();
359 _indexFlags = tableBuffer.get();
360 tableBuffer.position(tableBuffer.position() + 5);
361 }
362
363
364
365
366
367
368
369
370
371 public void addRow(Object[] row, RowId rowId)
372 throws IOException
373 {
374 int nullCount = countNullValues(row);
375 boolean isNullEntry = (nullCount == _columns.size());
376 if(shouldIgnoreNulls() && isNullEntry) {
377
378 return;
379 }
380 if(isPrimaryKey() && (nullCount > 0)) {
381 throw new IOException("Null value found in row " + Arrays.asList(row)
382 + " for primary key index " + this);
383 }
384
385
386 initialize();
387
388 Entry newEntry = new Entry(createEntryBytes(row), rowId);
389 if(addEntry(newEntry, isNullEntry, row)) {
390 ++_modCount;
391 } else {
392 LOG.warn("Added duplicate index entry " + newEntry + " for row: " +
393 Arrays.asList(row));
394 }
395 }
396
397
398
399
400 private boolean addEntry(Entry newEntry, boolean isNullEntry, Object[] row)
401 throws IOException
402 {
403 DataPage dataPage = findDataPage(newEntry);
404 int idx = dataPage.findEntry(newEntry);
405 if(idx < 0) {
406
407 idx = missingIndexToInsertionPoint(idx);
408
409 Position newPos = new Position(dataPage, idx, newEntry, true);
410 Position nextPos = getNextPosition(newPos);
411 Position prevPos = getPreviousPosition(newPos);
412
413
414
415
416 boolean isDupeEntry =
417 (((nextPos != null) &&
418 newEntry.equalsEntryBytes(nextPos.getEntry())) ||
419 ((prevPos != null) &&
420 newEntry.equalsEntryBytes(prevPos.getEntry())));
421 if(isUnique() && !isNullEntry && isDupeEntry)
422 {
423 throw new IOException(
424 "New row " + Arrays.asList(row) +
425 " violates uniqueness constraint for index " + this);
426 }
427
428 if(!isDupeEntry) {
429 ++_uniqueEntryCount;
430 }
431
432 dataPage.addEntry(idx, newEntry);
433 return true;
434 }
435 return false;
436 }
437
438
439
440
441
442
443
444
445
446 public void deleteRow(Object[] row, RowId rowId)
447 throws IOException
448 {
449 int nullCount = countNullValues(row);
450 if(shouldIgnoreNulls() && (nullCount == _columns.size())) {
451
452 return;
453 }
454
455
456 initialize();
457
458 Entry oldEntry = new Entry(createEntryBytes(row), rowId);
459 if(removeEntry(oldEntry)) {
460 ++_modCount;
461 } else {
462 LOG.warn("Failed removing index entry " + oldEntry + " for row: " +
463 Arrays.asList(row));
464 }
465 }
466
467
468
469
470
471
472 private boolean removeEntry(Entry oldEntry)
473 throws IOException
474 {
475 DataPage dataPage = findDataPage(oldEntry);
476 int idx = dataPage.findEntry(oldEntry);
477 boolean doRemove = false;
478 if(idx < 0) {
479
480
481
482 EntryCursor cursor = cursor();
483 Position tmpPos = null;
484 Position endPos = cursor._lastPos;
485 while(!endPos.equals(
486 tmpPos = cursor.getAnotherPosition(Cursor.MOVE_FORWARD))) {
487 if(tmpPos.getEntry().getRowId().equals(oldEntry.getRowId())) {
488 dataPage = tmpPos.getDataPage();
489 idx = tmpPos.getIndex();
490 doRemove = true;
491 break;
492 }
493 }
494 } else {
495 doRemove = true;
496 }
497
498 if(doRemove) {
499
500 dataPage.removeEntry(idx);
501 }
502
503 return doRemove;
504 }
505
506
507
508
509
510
511 public EntryCursor cursor()
512 throws IOException
513 {
514 return cursor(null, true, null, true);
515 }
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530 public EntryCursor cursor(Object[] startRow,
531 boolean startInclusive,
532 Object[] endRow,
533 boolean endInclusive)
534 throws IOException
535 {
536 initialize();
537 Entry startEntry = FIRST_ENTRY;
538 byte[] startEntryBytes = null;
539 if(startRow != null) {
540 startEntryBytes = createEntryBytes(startRow);
541 startEntry = new Entry(startEntryBytes,
542 (startInclusive ?
543 RowId.FIRST_ROW_ID : RowId.LAST_ROW_ID));
544 }
545 Entry endEntry = LAST_ENTRY;
546 if(endRow != null) {
547
548
549 byte[] endEntryBytes = ((startRow == endRow) ?
550 startEntryBytes :
551 createEntryBytes(endRow));
552 endEntry = new Entry(endEntryBytes,
553 (endInclusive ?
554 RowId.LAST_ROW_ID : RowId.FIRST_ROW_ID));
555 }
556 return new EntryCursor(findEntryPosition(startEntry),
557 findEntryPosition(endEntry));
558 }
559
560 private Position findEntryPosition(Entry entry)
561 throws IOException
562 {
563 DataPage dataPage = findDataPage(entry);
564 int idx = dataPage.findEntry(entry);
565 boolean between = false;
566 if(idx < 0) {
567
568
569
570 idx = missingIndexToInsertionPoint(idx);
571 between = true;
572 }
573 return new Position(dataPage, idx, entry, between);
574 }
575
576 private Position getNextPosition(Position curPos)
577 throws IOException
578 {
579
580 int nextIdx = curPos.getNextIndex();
581 Position nextPos = null;
582 if(nextIdx < curPos.getDataPage().getEntries().size()) {
583 nextPos = new Position(curPos.getDataPage(), nextIdx);
584 } else {
585 int nextPageNumber = curPos.getDataPage().getNextPageNumber();
586 DataPage nextDataPage = null;
587 while(nextPageNumber != INVALID_INDEX_PAGE_NUMBER) {
588 DataPage dp = getDataPage(nextPageNumber);
589 if(!dp.isEmpty()) {
590 nextDataPage = dp;
591 break;
592 }
593 nextPageNumber = dp.getNextPageNumber();
594 }
595 if(nextDataPage != null) {
596 nextPos = new Position(nextDataPage, 0);
597 }
598 }
599 return nextPos;
600 }
601
602
603
604
605 private Position getPreviousPosition(Position curPos)
606 throws IOException
607 {
608
609 int prevIdx = curPos.getPrevIndex();
610 Position prevPos = null;
611 if(prevIdx >= 0) {
612 prevPos = new Position(curPos.getDataPage(), prevIdx);
613 } else {
614 int prevPageNumber = curPos.getDataPage().getPrevPageNumber();
615 DataPage prevDataPage = null;
616 while(prevPageNumber != INVALID_INDEX_PAGE_NUMBER) {
617 DataPage dp = getDataPage(prevPageNumber);
618 if(!dp.isEmpty()) {
619 prevDataPage = dp;
620 break;
621 }
622 prevPageNumber = dp.getPrevPageNumber();
623 }
624 if(prevDataPage != null) {
625 prevPos = new Position(prevDataPage,
626 (prevDataPage.getEntries().size() - 1));
627 }
628 }
629 return prevPos;
630 }
631
632
633
634
635
636 protected static int missingIndexToInsertionPoint(int idx) {
637 return -(idx + 1);
638 }
639
640
641
642
643
644
645
646
647 public Object[] constructIndexRowFromEntry(Object... values)
648 {
649 if(values.length != _columns.size()) {
650 throw new IllegalArgumentException(
651 "Wrong number of column values given " + values.length +
652 ", expected " + _columns.size());
653 }
654 int valIdx = 0;
655 Object[] idxRow = new Object[getTable().getColumnCount()];
656 for(ColumnDescriptor col : _columns) {
657 idxRow[col.getColumnIndex()] = values[valIdx++];
658 }
659 return idxRow;
660 }
661
662
663
664
665
666
667
668 public Object[] constructIndexRow(String colName, Object value)
669 {
670 return constructIndexRow(Collections.singletonMap(colName, value));
671 }
672
673
674
675
676
677
678
679 public Object[] constructIndexRow(Map<String,Object> row)
680 {
681 for(ColumnDescriptor col : _columns) {
682 if(!row.containsKey(col.getName())) {
683 return null;
684 }
685 }
686
687 Object[] idxRow = new Object[getTable().getColumnCount()];
688 for(ColumnDescriptor col : _columns) {
689 idxRow[col.getColumnIndex()] = row.get(col.getName());
690 }
691 return idxRow;
692 }
693
694 @Override
695 public String toString() {
696 StringBuilder rtn = new StringBuilder();
697 rtn.append("\tName: (" + _table.getName() + ") " + _name);
698 rtn.append("\n\tNumber: " + _indexNumber);
699 rtn.append("\n\tPage number: " + _rootPageNumber);
700 rtn.append("\n\tIs Primary Key: " + isPrimaryKey());
701 rtn.append("\n\tIs Foreign Key: " + isForeignKey());
702 rtn.append("\n\tIs Unique: " + isUnique());
703 rtn.append("\n\tIgnore Nulls: " + shouldIgnoreNulls());
704 rtn.append("\n\tColumns: " + _columns);
705 rtn.append("\n\tInitialized: " + _initialized);
706 if(_initialized) {
707 try {
708 rtn.append("\n\tEntryCount: " + getEntryCount());
709 } catch(IOException e) {
710 throw new RuntimeException(e);
711 }
712 }
713 rtn.append("\n\n");
714 return rtn.toString();
715 }
716
717 public int compareTo(Index other) {
718 if (_indexNumber > other.getIndexNumber()) {
719 return 1;
720 } else if (_indexNumber < other.getIndexNumber()) {
721 return -1;
722 } else {
723 return 0;
724 }
725 }
726
727
728
729
730 protected void writeDataPage(DataPage dataPage)
731 throws IOException
732 {
733 if(dataPage.getCompressedEntrySize() > _maxPageEntrySize) {
734 if(this instanceof SimpleIndex) {
735 throw new UnsupportedOperationException(
736 "FIXME cannot write large index yet, see Database javadoc for info on enabling large index support");
737 }
738 throw new IllegalStateException("data page is too large");
739 }
740
741 ByteBuffer buffer = _indexBufferH.getPageBuffer(getPageChannel());
742 buffer.put(dataPage.isLeaf() ?
743 PageTypes.INDEX_LEAF :
744 PageTypes.INDEX_NODE );
745 buffer.put((byte) 0x01);
746 buffer.putShort((short) 0);
747 buffer.putInt(getTable().getTableDefPageNumber());
748
749 buffer.putInt(0);
750 buffer.putInt(dataPage.getPrevPageNumber());
751 buffer.putInt(dataPage.getNextPageNumber());
752 buffer.putInt(dataPage.getChildTailPageNumber());
753
754 byte[] entryPrefix = dataPage.getEntryPrefix();
755 buffer.putShort((short) entryPrefix.length);
756 buffer.put((byte) 0);
757
758 byte[] entryMask = new byte[getFormat().SIZE_INDEX_ENTRY_MASK];
759
760 int totalSize = entryPrefix.length;
761 for(Entry entry : dataPage.getEntries()) {
762 totalSize += (entry.size() - entryPrefix.length);
763 int idx = totalSize / 8;
764 entryMask[idx] |= (1 << (totalSize % 8));
765 }
766 buffer.put(entryMask);
767
768
769 buffer.put(entryPrefix);
770
771 for(Entry entry : dataPage.getEntries()) {
772 entry.write(buffer, entryPrefix);
773 }
774
775
776 buffer.putShort(2, (short) (getFormat().PAGE_SIZE - buffer.position()));
777
778 getPageChannel().writePage(buffer, dataPage.getPageNumber());
779 }
780
781
782
783
784
785 protected void readDataPage(DataPage dataPage)
786 throws IOException
787 {
788 ByteBuffer buffer = _indexBufferH.getPageBuffer(getPageChannel());
789 getPageChannel().readPage(buffer, dataPage.getPageNumber());
790
791 boolean isLeaf = isLeafPage(buffer);
792 dataPage.setLeaf(isLeaf);
793
794
795
796 int entryPrefixLength = ByteUtil.getUnsignedShort(
797 buffer, getFormat().OFFSET_INDEX_COMPRESSED_BYTE_COUNT);
798 int entryMaskLength = getFormat().SIZE_INDEX_ENTRY_MASK;
799 int entryMaskPos = getFormat().OFFSET_INDEX_ENTRY_MASK;
800 int entryPos = entryMaskPos + entryMaskLength;
801 int lastStart = 0;
802 int totalEntrySize = 0;
803 byte[] entryPrefix = null;
804 List<Entry> entries = new ArrayList<Entry>();
805 TempBufferHolder tmpEntryBufferH =
806 TempBufferHolder.newHolder(TempBufferHolder.Type.HARD, true,
807 ByteOrder.BIG_ENDIAN);
808
809 Entry prevEntry = FIRST_ENTRY;
810 for (int i = 0; i < entryMaskLength; i++) {
811 byte entryMask = buffer.get(entryMaskPos + i);
812 for (int j = 0; j < 8; j++) {
813 if ((entryMask & (1 << j)) != 0) {
814 int length = (i * 8) + j - lastStart;
815 buffer.position(entryPos + lastStart);
816
817
818
819 ByteBuffer curEntryBuffer = buffer;
820 int curEntryLen = length;
821 if(entryPrefix != null) {
822 curEntryBuffer = getTempEntryBuffer(
823 buffer, length, entryPrefix, tmpEntryBufferH);
824 curEntryLen += entryPrefix.length;
825 }
826 totalEntrySize += curEntryLen;
827
828 Entry entry = newEntry(curEntryBuffer, curEntryLen, isLeaf);
829 if(prevEntry.compareTo(entry) >= 0) {
830 throw new IOException("Unexpected order in index entries, " +
831 prevEntry + " >= " + entry);
832 }
833
834 entries.add(entry);
835
836 if((entries.size() == 1) && (entryPrefixLength > 0)) {
837
838 entryPrefix = new byte[entryPrefixLength];
839 buffer.position(entryPos + lastStart);
840 buffer.get(entryPrefix);
841 }
842
843 lastStart += length;
844 prevEntry = entry;
845 }
846 }
847 }
848
849 dataPage.setEntryPrefix(entryPrefix != null ? entryPrefix : EMPTY_PREFIX);
850 dataPage.setEntries(entries);
851 dataPage.setTotalEntrySize(totalEntrySize);
852
853 int prevPageNumber = buffer.getInt(getFormat().OFFSET_PREV_INDEX_PAGE);
854 int nextPageNumber = buffer.getInt(getFormat().OFFSET_NEXT_INDEX_PAGE);
855 int childTailPageNumber =
856 buffer.getInt(getFormat().OFFSET_CHILD_TAIL_INDEX_PAGE);
857
858 dataPage.setPrevPageNumber(prevPageNumber);
859 dataPage.setNextPageNumber(nextPageNumber);
860 dataPage.setChildTailPageNumber(childTailPageNumber);
861 }
862
863
864
865
866 private Entry newEntry(ByteBuffer buffer, int entryLength, boolean isLeaf)
867 throws IOException
868 {
869 if(isLeaf) {
870 return new Entry(buffer, entryLength);
871 }
872 return new NodeEntry(buffer, entryLength);
873 }
874
875
876
877
878
879 private ByteBuffer getTempEntryBuffer(
880 ByteBuffer indexPage, int entryLen, byte[] valuePrefix,
881 TempBufferHolder tmpEntryBufferH)
882 {
883 ByteBuffer tmpEntryBuffer = tmpEntryBufferH.getBuffer(
884 getPageChannel(), valuePrefix.length + entryLen);
885
886
887
888 tmpEntryBuffer.put(valuePrefix);
889 tmpEntryBuffer.put(indexPage.array(), indexPage.position(), entryLen);
890 tmpEntryBuffer.flip();
891
892 return tmpEntryBuffer;
893 }
894
895
896
897
898 private static boolean isLeafPage(ByteBuffer buffer)
899 throws IOException
900 {
901 byte pageType = buffer.get(0);
902 if(pageType == PageTypes.INDEX_LEAF) {
903 return true;
904 } else if(pageType == PageTypes.INDEX_NODE) {
905 return false;
906 }
907 throw new IOException("Unexpected page type " + pageType);
908 }
909
910
911
912
913
914 private int countNullValues(Object[] values)
915 {
916 if(values == null) {
917 return _columns.size();
918 }
919
920
921
922
923 int nullCount = 0;
924 for(ColumnDescriptor col : _columns) {
925 Object value = values[col.getColumnIndex()];
926 if(col.isNullValue(value)) {
927 ++nullCount;
928 }
929 }
930
931 return nullCount;
932 }
933
934
935
936
937 private byte[] createEntryBytes(Object[] values) throws IOException
938 {
939 if(values == null) {
940 return null;
941 }
942
943 ByteArrayOutputStream bout = new ByteArrayOutputStream();
944
945
946
947
948 for(ColumnDescriptor col : _columns) {
949 Object value = values[col.getColumnIndex()];
950 col.writeValue(value, bout);
951 }
952
953 return bout.toByteArray();
954 }
955
956
957
958
959
960 protected abstract void updateImpl() throws IOException;
961
962
963
964
965 protected abstract void readIndexEntries()
966 throws IOException;
967
968
969
970
971 protected abstract DataPage findDataPage(Entry entry)
972 throws IOException;
973
974
975
976
977 protected abstract DataPage getDataPage(int pageNumber)
978 throws IOException;
979
980
981
982
983 private static byte[] flipFirstBitInByte(byte[] value, int index)
984 {
985 value[index] = (byte)(value[index] ^ 0x80);
986
987 return value;
988 }
989
990
991
992
993 private static byte[] flipBytes(byte[] value) {
994 for(int i = 0; i < value.length; ++i) {
995 value[i] = (byte)(~value[i]);
996 }
997 return value;
998 }
999
1000
1001
1002
1003 private static byte[] encodeNumberColumnValue(Object value, Column column)
1004 throws IOException
1005 {
1006
1007 return column.write(value, 0, ByteOrder.BIG_ENDIAN).array();
1008 }
1009
1010
1011
1012
1013
1014 private static void writeNonNullIndexTextValue(
1015 Object value, ByteArrayOutputStream bout, boolean isAscending)
1016 throws IOException
1017 {
1018
1019 String str = Column.toCharSequence(value).toString();
1020
1021
1022
1023 if(str.length() > MAX_TEXT_INDEX_CHAR_LENGTH) {
1024 str = str.substring(0, MAX_TEXT_INDEX_CHAR_LENGTH);
1025 }
1026
1027 ByteArrayOutputStream tmpBout = bout;
1028 if(!isAscending) {
1029
1030
1031 tmpBout = new ByteArrayOutputStream();
1032 }
1033
1034
1035 List<ExtraCodes> unprintableCodes = null;
1036 List<ExtraCodes> internationalCodes = null;
1037 int charOffset = 0;
1038 for(int i = 0; i < str.length(); ++i) {
1039 char c = str.charAt(i);
1040 Character cKey = c;
1041
1042 byte[] bytes = CODES.get(cKey);
1043 if(bytes != null) {
1044
1045 tmpBout.write(bytes);
1046 ++charOffset;
1047 continue;
1048 }
1049
1050 bytes = UNPRINTABLE_CODES.get(cKey);
1051 if(bytes != null) {
1052
1053 if(bytes.length > 0) {
1054 if(unprintableCodes == null) {
1055 unprintableCodes = new LinkedList<ExtraCodes>();
1056 }
1057
1058
1059 unprintableCodes.add(new ExtraCodes(charOffset, bytes));
1060 }
1061
1062
1063 continue;
1064 }
1065
1066 InternationalCodes inatCodes = INTERNATIONAL_CODES.get(cKey);
1067 if(inatCodes != null) {
1068
1069
1070
1071 tmpBout.write(inatCodes._inlineCodes);
1072
1073 if(internationalCodes == null) {
1074 internationalCodes = new LinkedList<ExtraCodes>();
1075 }
1076
1077
1078 internationalCodes.add(new ExtraCodes(charOffset,
1079 inatCodes._extraCodes));
1080
1081 ++charOffset;
1082 continue;
1083 }
1084
1085
1086 throw new IOException("unmapped string index value " + c);
1087 }
1088
1089
1090 tmpBout.write(END_TEXT);
1091
1092 boolean hasExtraText = ((unprintableCodes != null) ||
1093 (internationalCodes != null));
1094 if(hasExtraText) {
1095
1096
1097 if(internationalCodes != null) {
1098
1099
1100
1101 charOffset = 0;
1102 Iterator<ExtraCodes> iter = internationalCodes.iterator();
1103 while(iter.hasNext()) {
1104 ExtraCodes extraCodes = iter.next();
1105 while(charOffset < extraCodes._charOffset) {
1106 tmpBout.write(INTERNATIONAL_EXTRA_PLACEHOLDER);
1107 ++charOffset;
1108 }
1109 tmpBout.write(extraCodes._extraCodes);
1110 ++charOffset;
1111 }
1112 }
1113
1114
1115 if(unprintableCodes != null) {
1116
1117
1118 tmpBout.write(UNPRINTABLE_COMMON_PREFIX);
1119
1120
1121
1122 Iterator<ExtraCodes> iter = unprintableCodes.iterator();
1123 while(iter.hasNext()) {
1124 ExtraCodes extraCodes = iter.next();
1125 int offset =
1126 (UNPRINTABLE_COUNT_START +
1127 (UNPRINTABLE_COUNT_MULTIPLIER * extraCodes._charOffset))
1128 | UNPRINTABLE_OFFSET_FLAGS;
1129
1130
1131 tmpBout.write((offset >> 8) & 0xFF);
1132 tmpBout.write(offset & 0xFF);
1133
1134 tmpBout.write(UNPRINTABLE_MIDFIX);
1135 tmpBout.write(extraCodes._extraCodes);
1136 }
1137 }
1138
1139 }
1140
1141
1142 if(!isAscending) {
1143
1144
1145
1146 tmpBout.write(END_EXTRA_TEXT);
1147
1148
1149
1150 bout.write(flipBytes(tmpBout.toByteArray()));
1151
1152 }
1153
1154
1155 bout.write(END_EXTRA_TEXT);
1156 }
1157
1158
1159
1160
1161 private static Entry createSpecialEntry(RowId rowId) {
1162 return new Entry((byte[])null, rowId);
1163 }
1164
1165
1166
1167
1168 private ColumnDescriptor newColumnDescriptor(Column col, byte flags)
1169 throws IOException
1170 {
1171 switch(col.getType()) {
1172 case TEXT:
1173 case MEMO:
1174 return new TextColumnDescriptor(col, flags);
1175 case INT:
1176 case LONG:
1177 case MONEY:
1178 return new IntegerColumnDescriptor(col, flags);
1179 case FLOAT:
1180 case DOUBLE:
1181 case SHORT_DATE_TIME:
1182 return new FloatingPointColumnDescriptor(col, flags);
1183 case NUMERIC:
1184 return new FixedPointColumnDescriptor(col, flags);
1185 case BYTE:
1186 return new ByteColumnDescriptor(col, flags);
1187 case BOOLEAN:
1188 return new BooleanColumnDescriptor(col, flags);
1189
1190 default:
1191
1192 setReadOnly();
1193 return new ReadOnlyColumnDescriptor(col, flags);
1194 }
1195 }
1196
1197
1198
1199
1200 private static EntryType determineEntryType(byte[] entryBytes, RowId rowId)
1201 {
1202 if(entryBytes != null) {
1203 return ((rowId.getType() == RowId.Type.NORMAL) ?
1204 EntryType.NORMAL :
1205 ((rowId.getType() == RowId.Type.ALWAYS_FIRST) ?
1206 EntryType.FIRST_VALID : EntryType.LAST_VALID));
1207 } else if(!rowId.isValid()) {
1208
1209 return ((rowId.getType() == RowId.Type.ALWAYS_FIRST) ?
1210 EntryType.ALWAYS_FIRST : EntryType.ALWAYS_LAST);
1211 }
1212 throw new IllegalArgumentException("Values was null for valid entry");
1213 }
1214
1215
1216
1217
1218
1219 private static int calcMaxPageEntrySize(JetFormat format)
1220 {
1221
1222
1223 int pageDataSize = (format.PAGE_SIZE -
1224 (format.OFFSET_INDEX_ENTRY_MASK +
1225 format.SIZE_INDEX_ENTRY_MASK));
1226 int entryMaskSize = (format.SIZE_INDEX_ENTRY_MASK * 8);
1227 return Math.min(pageDataSize, entryMaskSize);
1228 }
1229
1230
1231
1232
1233
1234 public static abstract class ColumnDescriptor
1235 {
1236 private final Column _column;
1237 private final byte _flags;
1238
1239 private ColumnDescriptor(Column column, byte flags)
1240 throws IOException
1241 {
1242 _column = column;
1243 _flags = flags;
1244 }
1245
1246 public Column getColumn() {
1247 return _column;
1248 }
1249
1250 public byte getFlags() {
1251 return _flags;
1252 }
1253
1254 public boolean isAscending() {
1255 return((getFlags() & ASCENDING_COLUMN_FLAG) != 0);
1256 }
1257
1258 public int getColumnIndex() {
1259 return getColumn().getColumnIndex();
1260 }
1261
1262 public String getName() {
1263 return getColumn().getName();
1264 }
1265
1266 protected boolean isNullValue(Object value) {
1267 return (value == null);
1268 }
1269
1270 protected final void writeValue(Object value, ByteArrayOutputStream bout)
1271 throws IOException
1272 {
1273 if(isNullValue(value)) {
1274
1275 bout.write(getNullEntryFlag(isAscending()));
1276 return;
1277 }
1278
1279
1280 bout.write(getStartEntryFlag(isAscending()));
1281
1282 writeNonNullValue(value, bout);
1283 }
1284
1285 protected abstract void writeNonNullValue(
1286 Object value, ByteArrayOutputStream bout)
1287 throws IOException;
1288
1289 @Override
1290 public String toString() {
1291 return "ColumnDescriptor " + getColumn() + "\nflags: " + getFlags();
1292 }
1293 }
1294
1295
1296
1297
1298 private static final class IntegerColumnDescriptor extends ColumnDescriptor
1299 {
1300 private IntegerColumnDescriptor(Column column, byte flags)
1301 throws IOException
1302 {
1303 super(column, flags);
1304 }
1305
1306 @Override
1307 protected void writeNonNullValue(
1308 Object value, ByteArrayOutputStream bout)
1309 throws IOException
1310 {
1311 byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
1312
1313
1314
1315
1316
1317 flipFirstBitInByte(valueBytes, 0);
1318 if(!isAscending()) {
1319 flipBytes(valueBytes);
1320 }
1321
1322 bout.write(valueBytes);
1323 }
1324 }
1325
1326
1327