View Javadoc
1   /*
2   Copyright (c) 2005 Health Market Science, Inc.
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
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   * Access table index data.  This is the actual data which backs a logical
42   * Index, where one or more logical indexes can be backed by the same index
43   * data.
44   *
45   * @author Tim McCune
46   */
47  public class IndexData {
48  
49    protected static final Log LOG = LogFactory.getLog(Index.class);
50  
51    /** special entry which is less than any other entry */
52    public static final Entry FIRST_ENTRY =
53      createSpecialEntry(RowIdImpl.FIRST_ROW_ID);
54  
55    /** special entry which is greater than any other entry */
56    public static final Entry LAST_ENTRY =
57      createSpecialEntry(RowIdImpl.LAST_ROW_ID);
58  
59    /** special object which will always be greater than any other value, when
60        searching for an index entry range in a multi-value index */
61    public static final Object MAX_VALUE = new Object();
62  
63    /** special object which will always be greater than any other value, when
64        searching for an index entry range in a multi-value index */
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    /** Max number of columns in an index */
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; // always seems to be set on indexes in access 2000+
89  
90    private static final int MAGIC_INDEX_NUMBER = 1923;
91  
92    private static final ByteOrder ENTRY_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
93  
94    /** type attributes for Entries which simplify comparisons */
95    public enum EntryType {
96      /** comparable type indicating this Entry should always compare less than
97          valid RowIds */
98      ALWAYS_FIRST,
99      /** comparable type indicating this Entry should always compare less than
100         other valid entries with equal entryBytes */
101     FIRST_VALID,
102     /** comparable type indicating this RowId should always compare
103         normally */
104     NORMAL,
105     /** comparable type indicating this Entry should always compare greater
106         than other valid entries with equal entryBytes */
107     LAST_VALID,
108     /** comparable type indicating this Entry should always compare greater
109         than valid RowIds */
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   /** name, generated on demand */
143   private String _name;
144   /** owning table */
145   private final TableImpl _table;
146   /** 0-based index data number */
147   private final int _number;
148   /** Page number of the root index data */
149   private int _rootPageNumber;
150   /** offset within the tableDefinition buffer of the uniqueEntryCount for
151       this index */
152   private final int _uniqueEntryCountOffset;
153   /** The number of unique entries which have been added to this index.  note,
154       however, that it is never decremented, only incremented (as observed in
155       Access). */
156   private int _uniqueEntryCount;
157   /** List of columns and flags */
158   private final List<ColumnDescriptor> _columns =
159     new ArrayList<ColumnDescriptor>();
160   /** the logical indexes which this index data backs */
161   private final List<Index> _indexes = new ArrayList<Index>();
162   /** flags for this index */
163   private byte _indexFlags;
164   /** Usage map of pages that this index owns */
165   private UsageMap _ownedPages;
166   /** <code>true</code> if the index entries have been initialized,
167       <code>false</code> otherwise */
168   private boolean _initialized;
169   /** modification count for the table, keeps cursors up-to-date */
170   private int _modCount;
171   /** temp buffer used to read/write the index pages */
172   private final TempBufferHolder _indexBufferH =
173     TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true);
174   /** temp buffer used to create index entries */
175   private ByteStream _entryBuffer;
176   /** max size for all the entries written to a given index data page */
177   private final int _maxPageEntrySize;
178   /** whether or not this index data is backing a primary key logical index */
179   private boolean _primaryKey;
180   /** if non-null, the reason why we cannot create entries for this index */
181   private String _unsupportedReason;
182   /** Cache which manages the index pages */
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    * Creates an IndexData appropriate for the given table, using information
198    * from the given table definition buffer.
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    * @return the "main" logical index which is backed by this data.
243    */
244   public Index getPrimaryIndex() {
245     return _indexes.get(0);
246   }
247 
248   /**
249    * @return All of the Indexes backed by this data (unmodifiable List)
250    */
251   public List<Index> getIndexes() {
252     return Collections.unmodifiableList(_indexes);
253   }
254 
255   /**
256    * Adds a logical index which this data is backing.
257    */
258   void addIndex(Index index) {
259 
260     // we keep foreign key indexes at the back of the list.  this way the
261     // primary index will be a non-foreign key index (if any)
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       // also, keep track of whether or not this is a primary key index
275       _primaryKey |= index.isPrimaryKey();
276     }
277 
278     // force name to be regenerated
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    * Whether or not {@code null} values are actually recorded in the index.
304    */
305   public boolean shouldIgnoreNulls() {
306     return((_indexFlags & IGNORE_NULLS_INDEX_FLAG) != 0);
307   }
308 
309   /**
310    * Whether or not index entries must be unique.
311    * <p>
312    * Some notes about uniqueness:
313    * <ul>
314    * <li>Access does not seem to consider multiple {@code null} entries
315    *     invalid for a unique index</li>
316    * <li>text indexes collapse case, and Access seems to compare <b>only</b>
317    *     the index entry bytes, therefore two strings which differ only in
318    *     case <i>will violate</i> the unique constraint</li>
319    * </ul>
320    */
321   public boolean isUnique() {
322     return(isBackingPrimaryKey() || ((_indexFlags & UNIQUE_INDEX_FLAG) != 0));
323   }
324 
325   /**
326    * Whether or not values are required in the columns.
327    */
328   public boolean isRequired() {
329     return((_indexFlags & REQUIRED_INDEX_FLAG) != 0);
330   }
331 
332   /**
333    * Returns the Columns for this index (unmodifiable)
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    * Whether or not the complete index state has been read.
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    * Returns the number of database pages owned by this index data.
375    * @usage _intermediate_method_
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    * Used by unit tests to validate the internal status of the index.
391    * @param forceLoad if {@code false} only validate currently loaded index
392    *                  data pages, otherwise, load and validate all index pages
393    * @usage _advanced_method_
394    */
395   public void validate(boolean forceLoad) throws IOException {
396     _pageCache.validate(forceLoad);
397   }
398 
399   /**
400    * Returns the number of index entries in the index.  Only called by unit
401    * tests.
402    * <p>
403    * Forces index initialization.
404    * @usage _advanced_method_
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    * Forces initialization of this index (actual parsing of index pages).
421    * normally, the index will not be initialized until the entries are
422    * actually needed.
423    */
424   public void initialize() throws IOException {
425     if(!_initialized) {
426       _pageCache.setRootPageNumber(getRootPageNumber());
427       _initialized = true;
428     }
429   }
430 
431   /**
432    * Writes the current index state to the database.
433    * <p>
434    * Forces index initialization.
435    */
436   public void update() throws IOException
437   {
438     // make sure we've parsed the entries
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    * Read the rest of the index info from a tableBuffer
450    * @param tableBuffer table definition buffer to read from initial info
451    * @param availableColumns Columns that this index may use
452    */
453   public void read(ByteBuffer tableBuffer, List<ColumnImpl> availableColumns)
454     throws IOException
455   {
456     ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX); //Forward past Unknown
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         // find the desired column by column number (which is not necessarily
463         // the same as the column index)
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); //Forward past Unknown
485     _indexFlags = tableBuffer.get();
486     ByteUtil.forward(tableBuffer, getFormat().SKIP_AFTER_INDEX_FLAGS); //Forward past other stuff
487   }
488 
489   /**
490    * Writes the index row count definitions into a table definition buffer.
491    * @param creator description of the indexes to write
492    * @param buffer Buffer to write to
493    */
494   protected static void writeRowCountDefinitions(
495       TableCreator creator, ByteBuffer buffer)
496   {
497     writeRowCountDefinitions(creator, buffer, creator.getIndexCount());
498   }
499 
500   /**
501    * Writes the index row count definitions into a table definition buffer.
502    * @param creator description of the indexes to write
503    * @param buffer Buffer to write to
504    * @param idxCount num indexes to write
505    */
506   protected static void writeRowCountDefinitions(
507       TableMutator creator, ByteBuffer buffer, int idxCount)
508   {
509     // index row counts (empty data)
510     ByteUtil.forward(buffer, (idxCount *
511                               creator.getFormat().SIZE_INDEX_DEFINITION));
512   }
513 
514   /**
515    * Writes the index definitions into a table definition buffer.
516    * @param creator description of the indexes to write
517    * @param buffer Buffer to write to
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    * Writes the index definitions into a table definition buffer.
532    * @param creator description of the indexes to write
533    * @param buffer Buffer to write to
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); // seemingly constant magic value
546 
547     // write column information (always MAX_COLUMNS entries)
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         // determine column info
558         IndexBuilder.Column idxCol = idxColumns.get(i);
559         flags = idxCol.getFlags();
560 
561         // find actual table column number
562         columnNumber = creator.getColumnNumber(idxCol.getName());
563         if(columnNumber == COLUMN_UNUSED) {
564           // should never happen as this is validated before
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); // table column number
573       buffer.put(flags); // column flags (e.g. ordering)
574     }
575 
576     buffer.put(idxDataState.getUmapRowNumber()); // umap row
577     ByteUtil.put3ByteInt(buffer, idxDataState.getUmapPageNumber()); // umap page
578 
579     // write empty root index page
580     creator.getPageChannel().writePage(rootPageBuffer,
581                                        idxDataState.getRootPageNumber());
582 
583     buffer.putInt(idxDataState.getRootPageNumber());
584     buffer.putInt(0); // unknown
585     buffer.put(idx.getFlags()); // index flags (unique, etc.)
586     ByteUtil.forward(buffer, 5); // unknown
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    * Prepares to add a row to this index.  All constraints are checked before
600    * this method returns.
601    * <p>
602    * Forces index initialization.
603    *
604    * @param row Row to add
605    * @param rowId rowId of the row to be added
606    *
607    * @return a PendingChange which can complete the addition or roll it back
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       // nothing to do
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     // make sure we've parsed the entries
633     initialize();
634 
635     return prepareAddEntry(new Entry(createEntryBytes(row), rowId), isNullEntry,
636                            row, change);
637   }
638 
639   /**
640    * Adds an entry to the correct index dataPage, maintaining the order.
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       // this is a new entry
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       // determine if the addition of this entry would break the uniqueness
658       // constraint.  See isUnique() for some notes about uniqueness as
659       // defined by Access.
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    * Completes a prepared row addition.
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       // if we are adding a duplicate entry, or replacing an existing entry,
690       // then the unique entry count doesn't change
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    * Prepares to update a row in this index.  All constraints are checked
702    * before this method returns.
703    * <p>
704    * Forces index initialization.
705    *
706    * @param oldRow Row to be removed
707    * @param newRow Row to be added
708    * @param rowId rowId of the row to be updated
709    *
710    * @return a PendingChange which can complete the update or roll it back
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       // need to undo the deletion before bailing
725       change.rollback();
726       throw e;
727     }
728   }
729 
730   /**
731    * Removes a row from this index
732    * <p>
733    * Forces index initialization.
734    *
735    * @param row Row to remove
736    * @param rowId rowId of the row to be removed
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       // nothing to do
750       return null;
751     }
752 
753     // make sure we've parsed the entries
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    * Undoes a previous row deletion.
770    */
771   private void rollbackDeletedRow(Entry removedEntry)
772     throws IOException
773   {
774     if(removedEntry == null) {
775       // no change was made
776       return;
777     }
778 
779     // unfortunately, stuff might have shuffled around when we first removed
780     // the row, so in order to re-insert it, we need to re-find and insert it.
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    * Removes an entry from the relevant index dataPage, maintaining the order.
790    * Will search by RowId if entry is not found (in case a partial entry was
791    * provided).
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       // the caller may have only read some of the row data, if this is the
801       // case, just search for the page/row numbers
802       // TODO, we could force caller to get relevant values?
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       // found it!
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    * Gets a new cursor for this index.
844    * <p>
845    * Forces index initialization.
846    */
847   public EntryCursor cursor()
848     throws IOException
849   {
850     return cursor(null, true, null, true);
851   }
852 
853   /**
854    * Gets a new cursor for this index, narrowed to the range defined by the
855    * given startRow and endRow.
856    * <p>
857    * Forces index initialization.
858    *
859    * @param startRow the first row of data for the cursor, or {@code null} for
860    *                 the first entry
861    * @param startInclusive whether or not startRow is inclusive or exclusive
862    * @param endRow the last row of data for the cursor, or {@code null} for
863    *               the last entry
864    * @param endInclusive whether or not endRow is inclusive or exclusive
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       // reuse startEntryBytes if startRow and endRow are same array.  this is
884       // common for "lookup" code
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       // given entry was not found exactly.  our current position is now
904       // really between two indexes, but we cannot support that as an integer
905       // value, so we set a flag instead
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     // get the next index (between-ness is handled internally)
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    * Returns the Position before the given one, or {@code null} if none.
940    */
941   private Position getPreviousPosition(Position curPos)
942     throws IOException
943   {
944     // get the previous index (between-ness is handled internally)
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    * Returns the valid insertion point for an index indicating a missing
970    * entry.
971    */
972   protected static int missingIndexToInsertionPoint(int idx) {
973     return -(idx + 1);
974   }
975 
976   /**
977    * Constructs an array of values appropriate for this index from the given
978    * column values, expected to match the columns for this index.
979    * @return the appropriate sparse array of data
980    * @throws IllegalArgumentException if the wrong number of values are
981    *         provided
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    * Constructs an array of values appropriate for this index from the given
1000    * column values, possibly only providing a prefix subset of the index
1001    * columns (at least one value must be provided).  If a prefix entry is
1002    * provided, any missing, trailing index entry values will use the given
1003    * filler value.
1004    * @return the appropriate sparse array of data
1005    * @throws IllegalArgumentException if at least one value is not provided
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    * Constructs an array of values appropriate for this index from the given
1031    * column value.
1032    * @return the appropriate sparse array of data or {@code null} if not all
1033    *         columns for this index were provided
1034    */
1035   public Object[] constructIndexRow(String colName, Object value)
1036   {
1037     return constructIndexRow(Collections.singletonMap(colName, value));
1038   }
1039 
1040   /**
1041    * Constructs an array of values appropriate for this index from the given
1042    * column value, which must be the first column of the index.  Any missing,
1043    * trailing index entry values will use the given filler value.
1044    * @return the appropriate sparse array of data or {@code null} if no prefix
1045    *         list of columns for this index were provided
1046    */
1047   public Object[] constructPartialIndexRow(Object filler, String colName, Object value)
1048   {
1049     return constructPartialIndexRow(filler, Collections.singletonMap(colName, value));
1050   }
1051 
1052   /**
1053    * Constructs an array of values appropriate for this index from the given
1054    * column values.
1055    * @return the appropriate sparse array of data or {@code null} if not all
1056    *         columns for this index were provided
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    * Constructs an array of values appropriate for this index from the given
1075    * column values, possibly only using a subset of the given values.  A
1076    * partial row can be created if one or more prefix column values are
1077    * provided.  If a prefix can be found, any missing, trailing index entry
1078    * values will use the given filler value.
1079    * @return the appropriate sparse array of data or {@code null} if no prefix
1080    *         list of columns for this index were provided
1081    */
1082   public Object[] constructPartialIndexRow(Object filler, Map<String,?> row)
1083   {
1084     // see if we have at least one prefix column
1085     int numCols = 0;
1086     for(ColumnDescriptor col : _columns) {
1087       if(!row.containsKey(col.getName())) {
1088         if(numCols == 0) {
1089           // can't do it, need at least first column
1090           return null;
1091         }
1092         break;
1093       }
1094       ++numCols;
1095     }
1096 
1097     // fill in the row with either the prefix values or the filler value, as
1098     // appropriate
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    * Write the given index page out to a buffer
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    * Writes the data page info to the given buffer.
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 );  //Page type
1159     buffer.put((byte) 0x01);  //Unknown
1160     buffer.putShort((short) 0); //Free space
1161     buffer.putInt(tdefPageNumber);
1162 
1163     buffer.putInt(0); //Unknown
1164     buffer.putInt(dataPage.getPrevPageNumber()); //Prev page
1165     buffer.putInt(dataPage.getNextPageNumber()); //Next page
1166     buffer.putInt(dataPage.getChildTailPageNumber()); //ChildTail page
1167 
1168     byte[] entryPrefix = dataPage.getEntryPrefix();
1169     buffer.putShort((short) entryPrefix.length); // entry prefix byte count
1170     buffer.put((byte) 0); //Unknown
1171 
1172     byte[] entryMask = new byte[format.SIZE_INDEX_ENTRY_MASK];
1173     // first entry includes the prefix
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     // first entry includes the prefix
1183     buffer.put(entryPrefix);
1184 
1185     for(Entry entry : dataPage.getEntries()) {
1186       entry.write(buffer, entryPrefix);
1187     }
1188 
1189     // update free space
1190     buffer.putShort(2, (short) (format.PAGE_SIZE - buffer.position()));
1191   }
1192 
1193   /**
1194    * Reads an index page, populating the correct collection based on the page
1195    * type (node or leaf).
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     // note, "header" data is in LITTLE_ENDIAN format, entry data is in
1207     // BIG_ENDIAN format
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           // determine if we can read straight from the index page (if no
1230           // entryPrefix).  otherwise, create temp buf with complete entry.
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             // read any shared entry prefix
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    * Returns a new Entry of the correct type for the given data and page type.
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    * Returns an entry buffer containing the relevant data for an entry given
1291    * the valuePrefix.
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     // combine valuePrefix and rest of entry from indexPage, then prep for
1301     // reading
1302     tmpEntryBuffer.put(valuePrefix);
1303     tmpEntryBuffer.put(indexPage.array(), indexPage.position(), entryLen);
1304     tmpEntryBuffer.flip();
1305 
1306     return tmpEntryBuffer;
1307   }
1308 
1309   /**
1310    * Determines if the given index page is a leaf or node page.
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    * Determines the number of {@code null} values for this index from the
1326    * given row.
1327    */
1328   private int countNullValues(Object[] values)
1329   {
1330     if(values == null) {
1331       return _columns.size();
1332     }
1333 
1334     // annoyingly, the values array could come from different sources, one
1335     // of which will make it a different size than the other.  we need to
1336     // handle both situations.
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    * Creates the entry bytes for a row of values.
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         // ignore it, we could not parse it
1367         continue;
1368       }
1369 
1370       if(value == MIN_VALUE) {
1371         // null is the "least" value (note the column "ascending" flag is
1372         // irrelevant here because the entry bytes are _always_ interpreted
1373         // least to greatest)
1374         _entryBuffer.write(getNullEntryFlag(true));
1375         continue;
1376       }
1377       if(value == MAX_VALUE) {
1378         // the opposite null is the "greatest" value (note the column
1379         // "ascending" flag is irrelevant here because the entry bytes are
1380         // _always_ interpreted least to greatest)
1381         _entryBuffer.write(getNullEntryFlag(false));
1382         continue;
1383       }
1384 
1385       col.writeValue(value, _entryBuffer);
1386     }
1387 
1388     return _entryBuffer.toByteArray();
1389   }
1390 
1391   /**
1392    * Finds the data page for the given entry.
1393    */
1394   protected DataPage findDataPage(Entry entry)
1395     throws IOException
1396   {
1397     return _pageCache.findCacheDataPage(entry);
1398   }
1399 
1400   /**
1401    * Gets the data page for the pageNumber.
1402    */
1403   protected DataPage getDataPage(int pageNumber)
1404     throws IOException
1405   {
1406     return _pageCache.getCacheDataPage(pageNumber);
1407   }
1408 
1409   /**
1410    * Flips the first bit in the byte at the given index.
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    * Flips all the bits in the byte array.
1421    */
1422   private static byte[] flipBytes(byte[] value) {
1423     return flipBytes(value, 0, value.length);
1424   }
1425 
1426   /**
1427    * Flips the bits in the specified bytes in the byte array.
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    * Writes the value of the given column type to a byte array and returns it.
1438    */
1439   private static byte[] encodeNumberColumnValue(Object value, ColumnImpl column)
1440     throws IOException
1441   {
1442     // always write in big endian order
1443     return column.write(value, 0, ENTRY_BYTE_ORDER).array();
1444   }
1445 
1446   /**
1447    * Writes a binary value using the general binary entry encoding rules.
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     // reserve space for the full entry
1457     bout.ensureNewCapacity(entryLen);
1458 
1459     // binary data is written in 8 byte segments with a trailing length byte.
1460     // The length byte is the amount of valid bytes in the segment (where 9
1461     // indicates that there is more data _after_ this segment).
1462     byte[] partialEntryBytes = new byte[9];
1463 
1464     // bit twiddling rules:
1465     // - isAsc  => nothing
1466     // - !isAsc => flipBytes, _but keep intermediate 09 unflipped_!
1467 
1468     // first, write any intermediate segements
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         // note, we do _not_ flip the length byte for intermediate segments
1476         flipBytes(partialEntryBytes, 0, 8);
1477       }
1478 
1479       // we are writing intermediate segments (there is more data after this
1480       // segment), so the length is always 9.
1481       partialEntryBytes[8] = (byte)9;
1482 
1483       pos += 8;
1484       segmentLen -= 8;
1485 
1486       bout.write(partialEntryBytes);
1487     }
1488 
1489     // write the last segment (with slightly different rules)
1490     if(segmentLen > 0) {
1491 
1492       System.arraycopy(valueBytes, pos, partialEntryBytes, 0, segmentLen);
1493 
1494       // clear out an intermediate bytes between the real data and the final
1495       // length byte
1496       for(int i = segmentLen; i < 8; ++i) {
1497         partialEntryBytes[i] = 0;
1498       }
1499 
1500       partialEntryBytes[8] = (byte)segmentLen;
1501 
1502       if(!isAsc) {
1503         // note, we _do_ flip the last length byte
1504         flipBytes(partialEntryBytes, 0, 9);
1505       }
1506 
1507       bout.write(partialEntryBytes);
1508     }
1509   }
1510 
1511   /**
1512    * Creates one of the special index entries.
1513    */
1514   private static Entry createSpecialEntry(RowIdImpl rowId) {
1515     return new Entry((byte[])null, rowId);
1516   }
1517 
1518   /**
1519    * Constructs a ColumnDescriptor of the relevant type for the given Column.
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       // unsupported sort order
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       // we can't modify this index at this point in time
1568       setUnsupportedReason("unsupported data type " + col.getType() +
1569                            " for index", col);
1570       return new ReadOnlyColumnDescriptor(col, flags);
1571     }
1572   }
1573 
1574   /**
1575    * Returns the EntryType based on the given entry info.
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       // this is a "special" entry (first/last)
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    * Returns the maximum amount of entry data which can be encoded on any
1594    * index page.
1595    */
1596   private static int calcMaxPageEntrySize(JetFormat format)
1597   {
1598     // the max data we can fit on a page is the min of the space on the page
1599     // vs the number of bytes which can be encoded in the entry mask
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    * Information about the columns in an index.  Also encodes new index
1620    * values.
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         // write null value
1667         bout.write(getNullEntryFlag(isAscending()));
1668         return;
1669       }
1670 
1671       // write the start flag
1672       bout.write(getStartEntryFlag(isAscending()));
1673       // write the rest of the value
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    * ColumnDescriptor for integer based columns.
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       // bit twiddling rules:
1707       // - isAsc  => flipFirstBit
1708       // - !isAsc => flipFirstBit, flipBytes
1709 
1710       flipFirstBitInByte(valueBytes, 0);
1711       if(!isAscending()) {
1712         flipBytes(valueBytes);
1713       }
1714 
1715       bout.write(valueBytes);
1716     }
1717   }
1718 
1719   /**
1720    * ColumnDescriptor for floating point based columns.
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       // determine if the number is negative by testing if the first bit is
1738       // set
1739       boolean isNegative = ((valueBytes[0] & 0x80) != 0);
1740 
1741       // bit twiddling rules:
1742       // isAsc && !isNeg => flipFirstBit
1743       // isAsc && isNeg => flipBytes
1744       // !isAsc && !isNeg => flipFirstBit, flipBytes
1745       // !isAsc && isNeg => nothing
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    * ColumnDescriptor for fixed point based columns (legacy sort order).
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       // reverse the sign byte (after any previous byte flipping)
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       // determine if the number is negative by testing if the first bit is
1788       // set
1789       boolean isNegative = ((valueBytes[0] & 0x80) != 0);
1790 
1791       // bit twiddling rules:
1792       // isAsc && !isNeg => setReverseSignByte             => FF 00 00 ...
1793       // isAsc && isNeg => flipBytes, setReverseSignByte   => 00 FF FF ...
1794       // !isAsc && !isNeg => flipBytes, setReverseSignByte => FF FF FF ...
1795       // !isAsc && isNeg => setReverseSignByte             => 00 00 00 ...
1796 
1797       // v2007 bit twiddling rules (old ordering was a bug, MS kb 837148):
1798       // isAsc && !isNeg => setSignByte 0xFF            => FF 00 00 ...
1799       // isAsc && isNeg => setSignByte 0xFF, flipBytes  => 00 FF FF ...
1800       // !isAsc && !isNeg => setSignByte 0xFF           => FF 00 00 ...
1801       // !isAsc && isNeg => setSignByte 0xFF, flipBytes => 00 FF FF ...
1802       handleNegationAndOrder(isNegative, valueBytes);
1803 
1804       bout.write(valueBytes);
1805     }
1806   }
1807 
1808   /**
1809    * ColumnDescriptor for new-style fixed point based columns.
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       // see notes above in FixedPointColumnDescriptor for bit twiddling rules
1825 
1826       // reverse the sign byte (before any byte flipping)
1827       valueBytes[0] = (byte)0xFF;
1828 
1829       if(isNegative == isAscending()) {
1830         flipBytes(valueBytes);
1831       }
1832     }
1833   }
1834 
1835   /**
1836    * ColumnDescriptor for byte based columns.
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       // bit twiddling rules:
1853       // - isAsc  => nothing
1854       // - !isAsc => flipBytes
1855       if(!isAscending()) {
1856         flipBytes(valueBytes);
1857       }
1858 
1859       bout.write(valueBytes);
1860     }
1861   }
1862 
1863   /**
1864    * ColumnDescriptor for boolean columns.
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       // null values are handled as booleans
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    * ColumnDescriptor for "general legacy" sort order text based columns.
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    * ColumnDescriptor for "general" sort order (2010+) text based columns.
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    * ColumnDescriptor for "general 97" sort order text based columns.
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    * ColumnDescriptor for guid columns.
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    * ColumnDescriptor for BINARY columns.
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    * ColumnDescriptor for extended date/time based columns.
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       // entry (which is 42 bytes of data) is encoded in blocks of 8 bytes
2013       // separated by '\t' char
2014 
2015       // note that for desc, all bytes are flipped _except_ separator char
2016       byte[] trailer = ASC_EXT_DATE_TRAILER;
2017       if(!isAscending()) {
2018         flipBytes(valueBytes);
2019         trailer = DESC_EXT_DATE_TRAILER;
2020       }
2021 
2022       // first 5 blocks are all value data
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       // last two data bytes and then the trailer
2031       bout.write(valueBytes, valIdx, 2);
2032       bout.write(trailer);
2033     }
2034   }
2035 
2036   /**
2037    * ColumnDescriptor for columns which we cannot currently write.
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    * A single leaf entry in an index (points to a single row)
2058    */
2059   public static class Entry implements Comparable<Entry>
2060   {
2061     /** page/row on which this row is stored */
2062     private final RowIdImpl _rowId;
2063     /** the entry value */
2064     private final byte[] _entryBytes;
2065     /** comparable type for the entry */
2066     private final EntryType _type;
2067 
2068     /**
2069      * Create a new entry
2070      * @param entryBytes encoded bytes for this index entry
2071      * @param rowId rowId in which the row is stored
2072      * @param type the type of the entry
2073      */
2074     private Entry(byte[] entryBytes, RowIdImpl rowId, EntryType type) {
2075       _rowId = rowId;
2076       _entryBytes = entryBytes;
2077       _type = type;
2078     }
2079 
2080     /**
2081      * Create a new entry
2082      * @param entryBytes encoded bytes for this index entry
2083      * @param rowId rowId in which the row is stored
2084      */
2085     private Entry(byte[] entryBytes, RowIdImpl rowId)
2086     {
2087       this(entryBytes, rowId, determineEntryType(entryBytes, rowId));
2088     }
2089 
2090     /**
2091      * Read an existing entry in from a buffer
2092      */
2093     private Entry(ByteBuffer buffer, int entryLen)
2094       throws IOException
2095     {
2096       this(buffer, entryLen, 0);
2097     }
2098 
2099     /**
2100      * Read an existing entry in from a buffer
2101      */
2102     private Entry(ByteBuffer buffer, int entryLen, int extraTrailingLen)
2103       throws IOException
2104     {
2105       // we need 4 trailing bytes for the rowId, plus whatever the caller
2106       // wants
2107       int colEntryLen = entryLen - (4 + extraTrailingLen);
2108 
2109       // read the entry bytes
2110       _entryBytes = ByteUtil.getBytes(buffer, colEntryLen);
2111 
2112       // read the rowId
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      * Size of this entry in the db.
2146      */
2147     protected int size() {
2148       // need 4 trailing bytes for the rowId
2149       return _entryBytes.length + 4;
2150     }
2151 
2152     /**
2153      * Write this entry into a buffer
2154      */
2155     protected void write(ByteBuffer buffer,
2156                          byte[] prefix)
2157       throws IOException
2158     {
2159       if(prefix.length <= _entryBytes.length) {
2160 
2161         // write entry bytes, not including prefix
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         // the prefix includes part of the page number, write to temp buffer
2170         // and copy last bytes to output buffer
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         // since the row number would never be the same if the page number is
2181         // the same, nothing past the page number should ever be included in
2182         // the prefix.
2183         // FIXME, this could happen if page has only one row...
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      * @return {@code true} iff the entryBytes are equal between this
2220      *         Entry and the given Entry
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         // comparing two valid entries.  first, compare by actual byte values
2235         int entryCmp = BYTE_CODE_COMPARATOR.compare(
2236             _entryBytes, other._entryBytes);
2237         if(entryCmp != 0) {
2238           return entryCmp;
2239         }
2240 
2241       } else {
2242 
2243         // if the entries are of mixed validity (or both invalid), we defer
2244         // next to the EntryType
2245         int typeCmp = _type.compareTo(other._type);
2246         if(typeCmp != 0) {
2247           return typeCmp;
2248         }
2249       }
2250 
2251       // at this point we let the RowId decide the final result
2252       return _rowId.compareTo(other.getRowId());
2253     }
2254 
2255     /**
2256      * Returns a copy of this entry as a node Entry with the given
2257      * subPageNumber.
2258      */
2259     protected Entry asNodeEntry(Integer subPageNumber) {
2260       return new NodeEntry(_entryBytes, _rowId, _type, subPageNumber);
2261     }
2262 
2263   }
2264 
2265   /**
2266    * A single node entry in an index (points to a sub-page in the index)
2267    */
2268   private static final class NodeEntry extends Entry {
2269 
2270     /** index page number of the page to which this node entry refers */
2271     private final Integer _subPageNumber;
2272 
2273     /**
2274      * Create a new node entry
2275      * @param entryBytes encoded bytes for this index entry
2276      * @param rowId rowId in which the row is stored
2277      * @param type the type of the entry
2278      * @param subPageNumber the sub-page to which this node entry refers
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      * Read an existing node entry in from a buffer
2288      */
2289     private NodeEntry(ByteBuffer buffer, int entryLen)
2290       throws IOException
2291     {
2292       // we need 4 trailing bytes for the sub-page number
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       // need 4 trailing bytes for the sub-page number
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    * Utility class to traverse the entries in the Index.  Remains valid in the
2340    * face of index entry modifications.
2341    */
2342   public final class EntryCursor
2343   {
2344     /** handler for moving the page cursor forward */
2345     private final DirHandler _forwardDirHandler = new ForwardDirHandler();
2346     /** handler for moving the page cursor backward */
2347     private final DirHandler _reverseDirHandler = new ReverseDirHandler();
2348     /** the first (exclusive) row id for this cursor */
2349     private Position _firstPos;
2350     /** the last (exclusive) row id for this cursor */
2351     private Position _lastPos;
2352     /** the current entry */
2353     private Position _curPos;
2354     /** the previous entry */
2355     private Position _prevPos;
2356     /** the last read modification count on the Index.  we track this so that
2357         the cursor can detect updates to the index while traversing and act
2358         accordingly */
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      * Returns the DirHandler for the given direction
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      * Returns the first entry (exclusive) as defined by this cursor.
2386      */
2387     public Entry getFirstEntry() {
2388       return _firstPos.getEntry();
2389     }
2390 
2391     /**
2392      * Returns the last entry (exclusive) as defined by this cursor.
2393      */
2394     public Entry getLastEntry() {
2395       return _lastPos.getEntry();
2396     }
2397 
2398     /**
2399      * Returns {@code true} if this cursor is up-to-date with respect to its
2400      * index.
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      * Repositions the cursor so that the next row will be the first entry
2426      * &gt;= the given row.
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      * Repositions the cursor so that the previous row will be the first
2437      * entry &lt;= the given row.
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      * @return valid entry if there was a next entry,
2448      *         {@code #getLastEntry} otherwise
2449      */
2450     public Entry getNextEntry() throws IOException {
2451       return getAnotherPosition(CursorImpl.MOVE_FORWARD).getEntry();
2452     }
2453 
2454     /**
2455      * @return valid entry if there was a next entry,
2456      *         {@code #getFirstEntry} otherwise
2457      */
2458     public Entry getPreviousEntry() throws IOException {
2459       return getAnotherPosition(CursorImpl.MOVE_REVERSE).getEntry();
2460     }
2461 
2462     /**
2463      * Restores a current position for the cursor (current position becomes
2464      * previous position).
2465      */
2466     protected void restorePosition(Entry curEntry)
2467       throws IOException
2468     {
2469       restorePosition(curEntry, _curPos.getEntry());
2470     }
2471 
2472     /**
2473      * Restores a current and previous position for the cursor.
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      * Gets another entry in the given direction, returning the new entry.
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           // drop through and retry moving to another entry
2503         } else {
2504           // at end, no more
2505           return _curPos;
2506         }
2507       }
2508 
2509       checkForModification();
2510 
2511       _prevPos = _curPos;
2512       _curPos = handler.getAnotherPosition(_curPos);
2513       return _curPos;
2514     }
2515 
2516     /**
2517      * Checks the index for modifications and updates state accordingly.
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      * Updates the given position, taking boundaries into account.
2532      */
2533     private Position updatePosition(Entry entry)
2534       throws IOException
2535     {
2536       if(!entry.isValid()) {
2537         // no use searching if "updating" the first/last pos
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      * Updates any the boundary info (_firstPos/_lastPos).
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      * Handles moving the cursor in a given direction.  Separates cursor
2577      * logic from value storage.
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      * Handles moving the cursor forward.
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      * Handles moving the cursor backward.
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    * Simple value object for maintaining some cursor state.
2637    */
2638   private static final class Position implements Comparable<Position> {
2639     /** the last known page of the given entry */
2640     private final DataPage _dataPage;
2641     /** the last known index of the given entry */
2642     private final int _idx;
2643     /** the entry at the given index */
2644     private final Entry _entry;
2645     /** {@code true} if this entry does not currently exist in the entry list,
2646         {@code false} otherwise (this is equivalent to adding -0.5 to the
2647         _idx) */
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       // note, _idx does not need to be advanced if it was pointing at a
2673       // between position
2674       return(_between ? _idx : (_idx + 1));
2675     }
2676 
2677     int getPrevIndex() {
2678       // note, we ignore the between flag here because the index will be
2679       // pointing at the correct next index in either the between or
2680       // non-between case
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         // "simple" index comparison (handle between-ness)
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       // compare the entries.
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    * Object used to maintain state about an Index page.
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       // when written to the index page, the entryPrefix bytes will only be
2773       // written for the first entry, so we subtract the entry prefix size
2774       // from all the other entries to determine the compressed size
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    * Simple implementation of a DataPage
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    * Utility class which maintains information about a pending index update.
2865    * An instance of this class can be used to complete the change (by calling
2866    * {@link #commit}) or undo the change (by calling {@link #rollback}).
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      * Returns the next pending change, if any
2878      */
2879     public PendingChange getNext() {
2880       return _next;
2881     }
2882 
2883     /**
2884      * Completes the pending change.
2885      */
2886     public abstract void commit() throws IOException;
2887 
2888     /**
2889      * Undoes the pending change.
2890      */
2891     public abstract void rollback() throws IOException;
2892   }
2893 
2894   /**
2895    * PendingChange for a row addition.
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    * PendingChange for a row update (which is essentially a deletion followed
2936    * by an addition).
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 }