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