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.BufferedWriter;
20  import java.io.IOException;
21  import java.io.StringWriter;
22  import java.lang.System.Logger;
23  import java.nio.BufferOverflowException;
24  import java.nio.ByteBuffer;
25  import java.nio.charset.Charset;
26  import java.time.LocalDateTime;
27  import java.util.AbstractMap;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.Comparator;
33  import java.util.HashMap;
34  import java.util.Iterator;
35  import java.util.LinkedHashSet;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.TreeSet;
40  
41  import com.healthmarketscience.jackcess.BatchUpdateException;
42  import com.healthmarketscience.jackcess.Column;
43  import com.healthmarketscience.jackcess.ColumnBuilder;
44  import com.healthmarketscience.jackcess.ConstraintViolationException;
45  import com.healthmarketscience.jackcess.CursorBuilder;
46  import com.healthmarketscience.jackcess.Index;
47  import com.healthmarketscience.jackcess.IndexBuilder;
48  import com.healthmarketscience.jackcess.InvalidValueException;
49  import com.healthmarketscience.jackcess.JackcessException;
50  import com.healthmarketscience.jackcess.PropertyMap;
51  import com.healthmarketscience.jackcess.Row;
52  import com.healthmarketscience.jackcess.RowId;
53  import com.healthmarketscience.jackcess.Table;
54  import com.healthmarketscience.jackcess.expr.Identifier;
55  import com.healthmarketscience.jackcess.util.ErrorHandler;
56  import com.healthmarketscience.jackcess.util.ExportUtil;
57  
58  /**
59   * A single database table
60   * <p>
61   * Is not thread-safe.
62   *
63   * @author Tim McCune
64   * @usage _intermediate_class_
65   */
66  public class TableImpl implements Table, PropertyMaps.Owner
67  {
68    private static final Logger LOG = System.getLogger(TableImpl.class.getName());
69  
70    private static final short OFFSET_MASK = (short)0x1FFF;
71  
72    private static final short DELETED_ROW_MASK = (short)0x8000;
73  
74    private static final short OVERFLOW_ROW_MASK = (short)0x4000;
75  
76    static final int MAGIC_TABLE_NUMBER = 1625;
77  
78    private static final int MAX_BYTE = 256;
79  
80    /**
81     * Table type code for system tables
82     * @usage _intermediate_class_
83     */
84    public static final byte TYPE_SYSTEM = 0x53;
85    /**
86     * Table type code for user tables
87     * @usage _intermediate_class_
88     */
89    public static final byte TYPE_USER = 0x4e;
90  
91    public enum IndexFeature {
92      EXACT_MATCH, EXACT_UNIQUE_ONLY, ANY_MATCH;
93    }
94  
95    /** comparator which sorts variable length columns based on their index into
96        the variable length offset table */
97    private static final Comparator<ColumnImpl> VAR_LEN_COLUMN_COMPARATOR =
98      Comparator.comparingInt(ColumnImpl::getVarLenTableIndex);
99  
100   /** owning database */
101   private final DatabaseImpl _database;
102   /** additional table flags from the catalog entry */
103   private final int _flags;
104   /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */
105   private final byte _tableType;
106   /** Number of actual indexes on the table */
107   private int _indexCount;
108   /** Number of logical indexes for the table */
109   private int _logicalIndexCount;
110   /** page number of the definition of this table */
111   private final int _tableDefPageNumber;
112   /** max Number of columns in the table (includes previous deletions) */
113   private short _maxColumnCount;
114   /** max Number of variable columns in the table (includes deletions) */
115   private short _maxVarColumnCount;
116   /** List of columns in this table, ordered by column number */
117   private final List<ColumnImpl> _columns = new ArrayList<>();
118   /** List of variable length columns in this table, ordered by offset */
119   private final List<ColumnImpl> _varColumns = new ArrayList<>();
120   /** List of autonumber columns in this table, ordered by column number */
121   private final List<ColumnImpl> _autoNumColumns = new ArrayList<>(1);
122   /** handler for calculated columns */
123   private final CalcColEvaluator _calcColEval = new CalcColEvaluator();
124   /** List of indexes on this table (multiple logical indexes may be backed by
125       the same index data) */
126   private final List<IndexImpl> _indexes = new ArrayList<>();
127   /** List of index datas on this table (the actual backing data for an
128       index) */
129   private final List<IndexData> _indexDatas = new ArrayList<>();
130   /** List of columns in this table which are in one or more indexes */
131   private final Set<ColumnImpl> _indexColumns = new LinkedHashSet<>();
132   /** Table name as stored in Database */
133   private final String _name;
134   /** Usage map of pages that this table owns */
135   private final UsageMap _ownedPages;
136   /** Usage map of pages that this table owns with free space on them */
137   private final UsageMap _freeSpacePages;
138   /** Number of rows in the table */
139   private int _rowCount;
140   /** last long auto number for the table */
141   private int _lastLongAutoNumber;
142   /** last complex type auto number for the table */
143   private int _lastComplexTypeAutoNumber;
144   /** modification count for the table, keeps row-states up-to-date */
145   private int _modCount;
146   /** page buffer used to update data pages when adding rows */
147   private final TempPageHolder _addRowBufferH =
148     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
149   /** page buffer used to update the table def page */
150   private final TempPageHolder _tableDefBufferH =
151     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
152   /** buffer used to writing rows of data */
153   private final TempBufferHolder _writeRowBufferH =
154     TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true);
155   /** page buffer used to write out-of-row "long value" data */
156   private final TempPageHolder _longValueBufferH =
157     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
158   /** optional error handler to use when row errors are encountered */
159   private ErrorHandler _tableErrorHandler;
160   /** properties for this table */
161   private PropertyMap _props;
162   /** properties group for this table (and columns) */
163   private PropertyMaps _propertyMaps;
164   /** name map for this table, read on first use */
165   private TableNameMapImpl _nameMap;
166   /** optional flag indicating whether or not auto numbers can be directly
167       inserted by the user */
168   private Boolean _allowAutoNumInsert;
169   /** foreign-key enforcer for this table */
170   private final FKEnforcer _fkEnforcer;
171   /** table validator if any (and enabled) */
172   private RowValidatorEvalContext _rowValidator;
173   /** description of an inconsistency between the table definition and the
174       column definitions, {@code null} if they agree.  rows cannot be written
175       to such a table, but they can still be read */
176   private String _writeDefError;
177 
178   /** default cursor for iterating through the table, kept here for basic
179       table traversal */
180   private CursorImpl _defaultCursor;
181 
182   /**
183    * Only used by unit tests
184    * @usage _advanced_method_
185    */
186   protected TableImpl(boolean testing, List<ColumnImpl> columns)
187   {
188     if(!testing) {
189       throw new IllegalArgumentException();
190     }
191     _database = null;
192     _tableDefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
193     _name = null;
194 
195     _columns.addAll(columns);
196     for(ColumnImpl col : _columns) {
197       if(col.getType().isVariableLength()) {
198         _varColumns.add(col);
199       }
200     }
201     _maxColumnCount = (short)_columns.size();
202     _maxVarColumnCount = (short)_varColumns.size();
203     initAutoNumberColumns();
204 
205     _fkEnforcer = null;
206     _flags = 0;
207     _tableType = TYPE_USER;
208     _indexCount = 0;
209     _logicalIndexCount = 0;
210     _ownedPages = null;
211     _freeSpacePages = null;
212   }
213 
214   /**
215    * @param database database which owns this table
216    * @param tableBuffer Buffer to read the table with
217    * @param pageNumber Page number of the table definition
218    * @param name Table name
219    */
220   protected TableImpl(DatabaseImpl database, ByteBuffer tableBuffer,
221                       int pageNumber, String name, int flags)
222     throws IOException
223   {
224     _database = database;
225     _tableDefPageNumber = pageNumber;
226     _name = name;
227     _flags = flags;
228 
229     // read table definition
230     tableBuffer = loadCompleteTableDefinitionBuffer(tableBuffer, null);
231 
232     _rowCount = tableBuffer.getInt(getFormat().OFFSET_NUM_ROWS);
233     _lastLongAutoNumber = tableBuffer.getInt(getFormat().OFFSET_NEXT_AUTO_NUMBER);
234     if(getFormat().OFFSET_NEXT_COMPLEX_AUTO_NUMBER >= 0) {
235       _lastComplexTypeAutoNumber = tableBuffer.getInt(
236           getFormat().OFFSET_NEXT_COMPLEX_AUTO_NUMBER);
237     }
238     _tableType = tableBuffer.get(getFormat().OFFSET_TABLE_TYPE);
239     _maxColumnCount = tableBuffer.getShort(getFormat().OFFSET_MAX_COLS);
240     _maxVarColumnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_VAR_COLS);
241     short columnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_COLS);
242     _logicalIndexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEX_SLOTS);
243     _indexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEXES);
244 
245     tableBuffer.position(getFormat().OFFSET_OWNED_PAGES);
246     _ownedPages = UsageMap.read(getDatabase(), tableBuffer);
247     tableBuffer.position(getFormat().OFFSET_FREE_SPACE_PAGES);
248     _freeSpacePages = UsageMap.read(getDatabase(), tableBuffer);
249 
250     for (int i = 0; i < _indexCount; i++) {
251       _indexDatas.add(IndexData.create(this, tableBuffer, i, getFormat()));
252     }
253 
254     readColumnDefinitions(tableBuffer, columnCount);
255 
256     validateColumnDefs();
257 
258     readIndexDefinitions(tableBuffer);
259 
260     // read column usage map info
261     while((tableBuffer.remaining() >= 2) &&
262           readColumnUsageMaps(tableBuffer)) {
263       // keep reading ...
264     }
265 
266     // re-sort columns if necessary
267     if(getDatabase().getColumnOrder() != ColumnOrder.DATA) {
268       Collections.sort(_columns, ColumnImpl.DISPLAY_ORDER_COMPARATOR);
269     }
270 
271     for(ColumnImpl col : _columns) {
272       // some columns need to do extra work after the table is completely
273       // loaded
274       col.postTableLoadInit();
275     }
276 
277     _fkEnforcer = new FKEnforcer(this);
278 
279     if(!isSystem()) {
280       // after fully constructed, allow column/row validators to be configured
281       // (but only for user tables)
282       for(ColumnImpl col : _columns) {
283         col.initColumnValidator();
284       }
285 
286       reloadRowValidator();
287     }
288   }
289 
290   private void reloadRowValidator() throws IOException {
291 
292     // reset table row validator before proceeding
293     _rowValidator = null;
294 
295     if(!getDatabase().isEvaluateExpressions()) {
296       return;
297     }
298 
299     PropertyMap props = getProperties();
300 
301     String exprStr = PropertyMaps.getTrimmedStringProperty(
302         props, PropertyMap.VALIDATION_RULE_PROP);
303 
304     if(exprStr != null) {
305       String helpStr = PropertyMaps.getTrimmedStringProperty(
306           props, PropertyMap.VALIDATION_TEXT_PROP);
307 
308       _rowValidator = new RowValidatorEvalContext(this)
309         .setExpr(exprStr, helpStr);
310     }
311   }
312 
313   @Override
314   public String getName() {
315     return _name;
316   }
317 
318   @Override
319   public boolean isHidden() {
320     return((_flags & DatabaseImpl.HIDDEN_OBJECT_FLAG) != 0);
321   }
322 
323   @Override
324   public boolean isSystem() {
325     return(_tableType != TYPE_USER);
326   }
327 
328   /**
329    * @usage _advanced_method_
330    */
331   public int getMaxColumnCount() {
332     return _maxColumnCount;
333   }
334 
335   @Override
336   public int getColumnCount() {
337     return _columns.size();
338   }
339 
340   @Override
341   public DatabaseImpl getDatabase() {
342     return _database;
343   }
344 
345   /**
346    * @usage _advanced_method_
347    */
348   public JetFormat getFormat() {
349     return getDatabase().getFormat();
350   }
351 
352   /**
353    * @usage _advanced_method_
354    */
355   public PageChannel getPageChannel() {
356     return getDatabase().getPageChannel();
357   }
358 
359   @Override
360   public ErrorHandler getErrorHandler() {
361     return((_tableErrorHandler != null) ? _tableErrorHandler :
362            getDatabase().getErrorHandler());
363   }
364 
365   @Override
366   public void setErrorHandler(ErrorHandler newErrorHandler) {
367     _tableErrorHandler = newErrorHandler;
368   }
369 
370   public int getTableDefPageNumber() {
371     return _tableDefPageNumber;
372   }
373 
374   @Override
375   public boolean isAllowAutoNumberInsert() {
376     return ((_allowAutoNumInsert != null) ? (boolean)_allowAutoNumInsert :
377             getDatabase().isAllowAutoNumberInsert());
378   }
379 
380   @Override
381   public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
382     _allowAutoNumInsert = allowAutoNumInsert;
383   }
384 
385   /**
386    * @usage _advanced_method_
387    */
388   public RowState createRowState() {
389     return new RowState(TempBufferHolder.Type.HARD);
390   }
391 
392   /**
393    * @usage _advanced_method_
394    */
395   public UsageMap.PageCursor getOwnedPagesCursor() {
396     return _ownedPages.cursor();
397   }
398 
399   /**
400    * Returns the <i>approximate</i> number of database pages owned by this
401    * table and all related indexes (this number does <i>not</i> take into
402    * account pages used for large OLE/MEMO fields).
403    * <p>
404    * To calculate the approximate number of bytes owned by a table:
405    * <code>
406    * int approxTableBytes = (table.getApproximateOwnedPageCount() *
407    *                         table.getFormat().PAGE_SIZE);
408    * </code>
409    * @usage _intermediate_method_
410    */
411   public int getApproximateOwnedPageCount() {
412 
413     // add a page for the table def (although that might actually be more than
414     // one page)
415     int count = _ownedPages.getPageCount() + 1;
416 
417     for(ColumnImpl col : _columns) {
418       count += col.getOwnedPageCount();
419     }
420 
421     // note, we count owned pages from _physical_ indexes, not logical indexes
422     // (otherwise we could double count pages)
423     for(IndexData indexData : _indexDatas) {
424       count += indexData.getOwnedPageCount();
425     }
426 
427     return count;
428   }
429 
430   protected TempPageHolder getLongValueBuffer() {
431     return _longValueBufferH;
432   }
433 
434   @Override
435   public List<ColumnImpl> getColumns() {
436     return Collections.unmodifiableList(_columns);
437   }
438 
439   @Override
440   public ColumnImpl getColumn(String name) {
441     for(ColumnImpl column : _columns) {
442       if(column.getName().equalsIgnoreCase(name)) {
443         return column;
444       }
445     }
446     throw new IllegalArgumentException(withErrorContext(
447             "Column with name " + name + " does not exist in this table"));
448   }
449 
450   public boolean hasColumn(String name) {
451     for(ColumnImpl column : _columns) {
452       if(column.getName().equalsIgnoreCase(name)) {
453         return true;
454       }
455     }
456     return false;
457   }
458 
459   @Override
460   public PropertyMap getProperties() throws IOException {
461     if(_props == null) {
462       _props = getPropertyMaps().getDefault();
463     }
464     return _props;
465   }
466 
467   @Override
468   public TableNameMapImpl getNameMap() throws IOException {
469     if(_nameMap == null) {
470       byte[] bytes = (byte[])getProperties().getValue(
471           PropertyMap.NAME_MAP_PROP);
472       if(bytes != null) {
473         _nameMap = TableNameMapImpl.read(bytes, this);
474       }
475     }
476     return _nameMap;
477   }
478 
479   @Override
480   public LocalDateTime getCreatedDate() throws IOException {
481     return getDatabase().getCreateDateForObject(_tableDefPageNumber);
482   }
483 
484   @Override
485   public LocalDateTime getUpdatedDate() throws IOException {
486     return getDatabase().getUpdateDateForObject(_tableDefPageNumber);
487   }
488 
489   /**
490    * @return all PropertyMaps for this table (and columns)
491    * @usage _advanced_method_
492    */
493   public PropertyMaps getPropertyMaps() throws IOException {
494     if(_propertyMaps == null) {
495       _propertyMaps = getDatabase().getPropertiesForObject(
496           _tableDefPageNumber, this);
497     }
498     return _propertyMaps;
499   }
500 
501   @Override
502   public void propertiesUpdated() throws IOException {
503     // the name map property may have been replaced
504     _nameMap = null;
505 
506     // propagate update to columns
507     for(ColumnImpl col : _columns) {
508       col.propertiesUpdated();
509     }
510 
511     reloadRowValidator();
512 
513     // calculated columns will need to be re-sorted (their expressions may
514     // have changed when their properties were updated)
515     _calcColEval.reSort();
516   }
517 
518   @Override
519   public List<IndexImpl> getIndexes() {
520     return Collections.unmodifiableList(_indexes);
521   }
522 
523   @Override
524   public IndexImpl getIndex(String name) {
525     for(IndexImpl index : _indexes) {
526       if(index.getName().equalsIgnoreCase(name)) {
527         return index;
528       }
529     }
530     throw new IllegalArgumentException(withErrorContext(
531             "Index with name " + name + " does not exist on this table"));
532   }
533 
534   @Override
535   public IndexImpl getPrimaryKeyIndex() {
536     for(IndexImpl index : _indexes) {
537       if(index.isPrimaryKey()) {
538         return index;
539       }
540     }
541     throw new IllegalArgumentException(withErrorContext(
542             "No primary key index found"));
543   }
544 
545   @Override
546   public IndexImpl getForeignKeyIndex(Table otherTable) {
547     for(IndexImpl index : _indexes) {
548       if(index.isForeignKey() && (index.getReference() != null) &&
549          (index.getReference().getOtherTablePageNumber() ==
550           ((TableImpl)otherTable).getTableDefPageNumber())) {
551         return index;
552       }
553     }
554     throw new IllegalArgumentException(withErrorContext(
555         "No foreign key reference to " +
556         otherTable.getName() + " found"));
557   }
558 
559   /**
560    * @return All of the IndexData on this table (unmodifiable List)
561    * @usage _advanced_method_
562    */
563   public List<IndexData> getIndexDatas() {
564     return Collections.unmodifiableList(_indexDatas);
565   }
566 
567   /**
568    * Only called by unit tests
569    * @usage _advanced_method_
570    */
571   public int getLogicalIndexCount() {
572     return _logicalIndexCount;
573   }
574 
575   int getIndexCount() {
576     return _indexCount;
577   }
578 
579   public IndexImpl findIndexForColumns(Collection<String> searchColumns,
580                                        IndexFeature feature) {
581 
582     IndexImpl partialIndex = null;
583     for(IndexImpl index : _indexes) {
584 
585       Collection<? extends Index.Column> indexColumns = index.getColumns();
586       if(indexColumns.size() < searchColumns.size()) {
587         continue;
588       }
589       boolean exactMatch = (indexColumns.size() == searchColumns.size());
590 
591       Iterator<String> sIter = searchColumns.iterator();
592       Iterator<? extends Index.Column> iIter = indexColumns.iterator();
593       boolean searchMatches = true;
594       while(sIter.hasNext()) {
595         String sColName = sIter.next();
596         String iColName = iIter.next().getName();
597         if((sColName != iColName) &&
598            ((sColName == null) || !sColName.equalsIgnoreCase(iColName))) {
599           searchMatches = false;
600           break;
601         }
602       }
603 
604       if(searchMatches) {
605 
606         if(exactMatch && ((feature != IndexFeature.EXACT_UNIQUE_ONLY) ||
607                           index.isUnique())) {
608           return index;
609         }
610 
611         if(!exactMatch && (feature == IndexFeature.ANY_MATCH) &&
612            ((partialIndex == null) ||
613             (indexColumns.size() < partialIndex.getColumnCount()))) {
614           // this is a better partial index match
615           partialIndex = index;
616         }
617       }
618     }
619 
620     return partialIndex;
621   }
622 
623   List<ColumnImpl> getAutoNumberColumns() {
624     return _autoNumColumns;
625   }
626 
627   @Override
628   public CursorImpl getDefaultCursor() {
629     if(_defaultCursor == null) {
630       _defaultCursor = CursorImpl.createCursor(this);
631     }
632     return _defaultCursor;
633   }
634 
635   @Override
636   public CursorBuilder newCursor() {
637     return new CursorBuilder(this);
638   }
639 
640   @Override
641   public void reset() {
642     getDefaultCursor().reset();
643   }
644 
645   @Override
646   public Rowf="../../../../com/healthmarketscience/jackcess/Row.html#Row">Row deleteRow(Row row) throws IOException {
647     deleteRow(row.getId());
648     return row;
649   }
650 
651   /**
652    * Delete the row with the given id.  Provided RowId must have previously
653    * been returned from this Table.
654    * @return the given rowId
655    * @throws IllegalStateException if the given row is not valid
656    * @usage _intermediate_method_
657    */
658   public RowId"../../../../com/healthmarketscience/jackcess/RowId.html#RowId">RowId deleteRow(RowId rowId) throws IOException {
659     deleteRow(getDefaultCursor().getRowState(), (RowIdImpl)rowId);
660     return rowId;
661   }
662 
663   /**
664    * Delete the row for the given rowId.
665    * @usage _advanced_method_
666    */
667   public void deleteRow(RowState rowState, RowIdImpl rowId)
668     throws IOException
669   {
670     requireValidRowId(rowId);
671 
672     getPageChannel().startWrite();
673     try {
674 
675       // ensure that the relevant row state is up-to-date
676       ByteBuffer rowBuffer = positionAtRowHeader(rowState, rowId);
677 
678       if(rowState.isDeleted()) {
679         // don't care about duplicate deletion
680         return;
681       }
682       requireNonDeletedRow(rowState, rowId);
683 
684       // delete flag always gets set in the "header" row (even if data is on
685       // overflow row)
686       int pageNumber = rowState.getHeaderRowId().getPageNumber();
687       int rowNumber = rowState.getHeaderRowId().getRowNumber();
688 
689       // attempt to fill in index column values
690       Object[] rowValues = null;
691       if(!_indexDatas.isEmpty()) {
692 
693         // move to row data to get index values
694         rowBuffer = positionAtRowData(rowState, rowId);
695 
696         for(ColumnImpl idxCol : _indexColumns) {
697           getRowColumn(getFormat(), rowBuffer, idxCol, rowState, null);
698         }
699 
700         // use any read rowValues to help update the indexes
701         rowValues = rowState.getRowCacheValues();
702 
703         // check foreign keys before proceeding w/ deletion
704         _fkEnforcer.deleteRow(rowValues);
705 
706         // move back to the header
707         rowBuffer = positionAtRowHeader(rowState, rowId);
708       }
709 
710       // finally, pull the trigger
711       int rowIndex = getRowStartOffset(rowNumber, getFormat());
712       rowBuffer.putShort(rowIndex, (short)(rowBuffer.getShort(rowIndex)
713                                            | DELETED_ROW_MASK | OVERFLOW_ROW_MASK));
714       writeDataPage(rowBuffer, pageNumber);
715 
716       // update the indexes
717       for(IndexData indexData : _indexDatas) {
718         indexData.deleteRow(rowValues, rowId);
719       }
720 
721       // make sure table def gets updated
722       updateTableDefinition(-1);
723 
724     } finally {
725       getPageChannel().finishWrite();
726     }
727   }
728 
729   @Override
730   public Row getNextRow() throws IOException {
731     return getDefaultCursor().getNextRow();
732   }
733 
734   /**
735    * Reads a single column from the given row.
736    * @usage _advanced_method_
737    */
738   public Object getRowValue(RowState rowState, RowIdImpl rowId,
739                             ColumnImpl column)
740     throws IOException
741   {
742     if(this != column.getTable()) {
743       throw new IllegalArgumentException(withErrorContext(
744           "Given column " + column + " is not from this table"));
745     }
746     requireValidRowId(rowId);
747 
748     // position at correct row
749     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
750     requireNonDeletedRow(rowState, rowId);
751 
752     return getRowColumn(getFormat(), rowBuffer, column, rowState, null);
753   }
754 
755   /**
756    * Reads some columns from the given row.
757    * @param columnNames Only column names in this collection will be returned
758    * @usage _advanced_method_
759    */
760   public RowImpl getRow(
761       RowState rowState, RowIdImpl rowId, Collection<String> columnNames)
762     throws IOException
763   {
764     requireValidRowId(rowId);
765 
766     // position at correct row
767     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
768     requireNonDeletedRow(rowState, rowId);
769 
770     return getRow(getFormat(), rowState, rowBuffer, _columns, columnNames);
771   }
772 
773   /**
774    * Reads the row data from the given row buffer.  Leaves limit unchanged.
775    * Saves parsed row values to the given rowState.
776    */
777   private static RowImpl getRow(
778       JetFormat format,
779       RowState rowState,
780       ByteBuffer rowBuffer,
781       Collection<ColumnImpl> columns,
782       Collection<String> columnNames)
783     throws IOException
784   {
785     RowImplckcess/impl/RowImpl.html#RowImpl">RowImpl rtn = new RowImpl(rowState.getHeaderRowId(), columns.size());
786     for(ColumnImpl column : columns) {
787 
788       if((columnNames == null) || (columnNames.contains(column.getName()))) {
789         // Add the value to the row data
790         column.setRowValue(
791             rtn, getRowColumn(format, rowBuffer, column, rowState, null));
792       }
793     }
794     return rtn;
795   }
796 
797   /**
798    * Reads the column data from the given row buffer.  Leaves limit unchanged.
799    * Caches the returned value in the rowState.
800    */
801   private static Object getRowColumn(JetFormat format,
802                                      ByteBuffer rowBuffer,
803                                      ColumnImpl column,
804                                      RowState rowState,
805                                      Map<ColumnImpl,byte[]> rawVarValues)
806     throws IOException
807   {
808     byte[] columnData = null;
809     try {
810 
811       NullMask nullMask = rowState.getNullMask(rowBuffer);
812       boolean isNull = nullMask.isNull(column);
813       if(column.storeInNullMask()) {
814           // Boolean values are stored in the null mask.  see note about
815           // caching below
816         return rowState.setRowCacheValue(column.getColumnIndex(),
817                                          column.readFromNullMask(isNull));
818       } else if(isNull) {
819         // well, that's easy! (no need to update cache w/ null)
820         return null;
821       }
822 
823       Object cachedValue = rowState.getRowCacheValue(column.getColumnIndex());
824       if(cachedValue != null) {
825         // we already have it, use it
826         return cachedValue;
827       }
828 
829       // reset position to row start
830       rowBuffer.reset();
831 
832       // locate the column data bytes
833       int rowStart = rowBuffer.position();
834       int colDataPos = 0;
835       int colDataLen = 0;
836       if(!column.isVariableLength()) {
837 
838         // read fixed length value (non-boolean at this point)
839         int dataStart = rowStart + format.OFFSET_COLUMN_FIXED_DATA_ROW_OFFSET;
840         colDataPos = dataStart + column.getFixedDataOffset();
841         colDataLen = column.getFixedDataSize();
842 
843       } else {
844         int varDataStart;
845         int varDataEnd;
846 
847         if(format.SIZE_ROW_VAR_COL_OFFSET == 2) {
848 
849           // read simple var length value
850           int varColumnOffsetPos =
851             (rowBuffer.limit() - nullMask.byteSize() - 4) -
852             (column.getVarLenTableIndex() * 2);
853 
854           varDataStart = rowBuffer.getShort(varColumnOffsetPos);
855           varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);
856 
857         } else {
858 
859           // read jump-table based var length values
860           short[] varColumnOffsets = readJumpTableVarColOffsets(
861               rowState, rowBuffer, rowStart, nullMask);
862 
863           varDataStart = varColumnOffsets[column.getVarLenTableIndex()];
864           varDataEnd = varColumnOffsets[column.getVarLenTableIndex() + 1];
865         }
866 
867         colDataPos = rowStart + varDataStart;
868         colDataLen = varDataEnd - varDataStart;
869       }
870 
871       // grab the column data
872       rowBuffer.position(colDataPos);
873       columnData = ByteUtil.getBytes(rowBuffer, colDataLen);
874 
875       if((rawVarValues != null) && column.isVariableLength()) {
876         // caller wants raw value as well
877         rawVarValues.put(column, columnData);
878       }
879 
880       // parse the column data.  we cache the row values in order to be able
881       // to update the index on row deletion.  note, most of the returned
882       // values are immutable, except for binary data (returned as byte[]),
883       // but binary data shouldn't be indexed anyway.
884       return rowState.setRowCacheValue(column.getColumnIndex(),
885                                        column.read(columnData));
886 
887     } catch(Exception e) {
888 
889       // cache "raw" row value.  see note about caching above
890       rowState.setRowCacheValue(column.getColumnIndex(),
891                                 ColumnImpl.rawDataWrapper(columnData));
892 
893       return rowState.handleRowError(column, columnData, e);
894     }
895   }
896 
897   private static short[] readJumpTableVarColOffsets(
898       RowState rowState, ByteBuffer rowBuffer, int rowStart,
899       NullMask nullMask)
900   {
901     short[] varColOffsets = rowState.getVarColOffsets();
902     if(varColOffsets != null) {
903       return varColOffsets;
904     }
905 
906     // calculate offsets using jump-table info
907     int nullMaskSize = nullMask.byteSize();
908     int rowEnd = rowStart + rowBuffer.remaining() - 1;
909     int numVarCols = ByteUtil.getUnsignedByte(rowBuffer,
910                                               rowEnd - nullMaskSize);
911     varColOffsets = new short[numVarCols + 1];
912 
913     int rowLen = rowEnd - rowStart + 1;
914     int numJumps = (rowLen - 1) / MAX_BYTE;
915     int colOffset = rowEnd - nullMaskSize - numJumps - 1;
916 
917     // If last jump is a dummy value, ignore it
918     if(((colOffset - rowStart - numVarCols) / MAX_BYTE) < numJumps) {
919       numJumps--;
920     }
921 
922     int jumpsUsed = 0;
923     for(int i = 0; i < numVarCols + 1; i++) {
924 
925       while((jumpsUsed < numJumps) &&
926          (i == ByteUtil.getUnsignedByte(
927               rowBuffer, rowEnd - nullMaskSize-jumpsUsed - 1))) {
928         jumpsUsed++;
929       }
930 
931       varColOffsets[i] = (short)
932         (ByteUtil.getUnsignedByte(rowBuffer, colOffset - i)
933          + (jumpsUsed * MAX_BYTE));
934     }
935 
936     rowState.setVarColOffsets(varColOffsets);
937     return varColOffsets;
938   }
939 
940   /**
941    * Reads the null mask from the given row buffer.  Leaves limit unchanged.
942    */
943   private NullMask getRowNullMask(ByteBuffer rowBuffer)
944   {
945     // reset position to row start
946     rowBuffer.reset();
947 
948     // Number of columns in this row
949     int columnCount = ByteUtil.getUnsignedVarInt(
950         rowBuffer, getFormat().SIZE_ROW_COLUMN_COUNT);
951 
952     // read null mask
953     NullMask/impl/NullMask.html#NullMask">NullMask nullMask = new NullMask(columnCount);
954     rowBuffer.position(rowBuffer.limit() - nullMask.byteSize());  //Null mask at end
955     nullMask.read(rowBuffer);
956 
957     return nullMask;
958   }
959 
960   /**
961    * Sets a new buffer to the correct row header page using the given rowState
962    * according to the given rowId.  Deleted state is
963    * determined, but overflow row pointers are not followed.
964    *
965    * @return a ByteBuffer of the relevant page, or null if row was invalid
966    * @usage _advanced_method_
967    */
968   public static ByteBuffer positionAtRowHeader(RowState rowState,
969                                                RowIdImpl rowId)
970     throws IOException
971   {
972     ByteBuffer rowBuffer = rowState.setHeaderRow(rowId);
973 
974     if(rowState.isAtHeaderRow()) {
975       // this task has already been accomplished
976       return rowBuffer;
977     }
978 
979     if(!rowState.isValid()) {
980       // this was an invalid page/row
981       rowState.setStatus(RowStateStatus.AT_HEADER);
982       return null;
983     }
984 
985     // note, we don't use findRowStart here cause we need the unmasked value
986     short rowStart = rowBuffer.getShort(
987         getRowStartOffset(rowId.getRowNumber(),
988                           rowState.getTable().getFormat()));
989 
990     // check the deleted, overflow flags for the row (the "real" flags are
991     // always set on the header row)
992     RowStatus rowStatus = RowStatus.NORMAL;
993     if(isDeletedRow(rowStart)) {
994       rowStatus = RowStatus.DELETED;
995     } else if(isOverflowRow(rowStart)) {
996       rowStatus = RowStatus.OVERFLOW;
997     }
998 
999     rowState.setRowStatus(rowStatus);
1000     rowState.setStatus(RowStateStatus.AT_HEADER);
1001     return rowBuffer;
1002   }
1003 
1004   /**
1005    * Sets the position and limit in a new buffer using the given rowState
1006    * according to the given row number and row end, following overflow row
1007    * pointers as necessary.
1008    *
1009    * @return a ByteBuffer narrowed to the actual row data, or null if row was
1010    *         invalid or deleted
1011    * @usage _advanced_method_
1012    */
1013   public static ByteBuffer positionAtRowData(RowState rowState,
1014                                              RowIdImpl rowId)
1015     throws IOException
1016   {
1017     positionAtRowHeader(rowState, rowId);
1018     if(!rowState.isValid() || rowState.isDeleted()) {
1019       // row is invalid or deleted
1020       rowState.setStatus(RowStateStatus.AT_FINAL);
1021       return null;
1022     }
1023 
1024     ByteBuffer rowBuffer = rowState.getFinalPage();
1025     int rowNum = rowState.getFinalRowId().getRowNumber();
1026     JetFormat format = rowState.getTable().getFormat();
1027 
1028     if(rowState.isAtFinalRow()) {
1029       // we've already found the final row data
1030       return PageChannel.narrowBuffer(
1031           rowBuffer,
1032           findRowStart(rowBuffer, rowNum, format),
1033           findRowEnd(rowBuffer, rowNum, format));
1034     }
1035 
1036     while(true) {
1037 
1038       // note, we don't use findRowStart here cause we need the unmasked value
1039       short rowStart = rowBuffer.getShort(getRowStartOffset(rowNum, format));
1040       short rowEnd = findRowEnd(rowBuffer, rowNum, format);
1041 
1042       // note, at this point we know the row is not deleted, so ignore any
1043       // subsequent deleted flags (as overflow rows are always marked deleted
1044       // anyway)
1045       boolean overflowRow = isOverflowRow(rowStart);
1046 
1047       // now, strip flags from rowStart offset
1048       rowStart = (short)(rowStart & OFFSET_MASK);
1049 
1050       if (overflowRow) {
1051 
1052         if((rowEnd - rowStart) < 4) {
1053           throw new IOException(rowState.getTable().withErrorContext(
1054                                     "invalid overflow row info"));
1055         }
1056 
1057         // Overflow page.  the "row" data in the current page points to
1058         // another page/row
1059         int overflowRowNum = ByteUtil.getUnsignedByte(rowBuffer, rowStart);
1060         int overflowPageNum = ByteUtil.get3ByteInt(rowBuffer, rowStart + 1);
1061         rowBuffer = rowState.setOverflowRow(
1062             new RowIdImpl(overflowPageNum, overflowRowNum));
1063         rowNum = overflowRowNum;
1064 
1065       } else {
1066 
1067         rowState.setStatus(RowStateStatus.AT_FINAL);
1068         return PageChannel.narrowBuffer(rowBuffer, rowStart, rowEnd);
1069       }
1070     }
1071   }
1072 
1073   @Override
1074   public Iterator<Row> iterator() {
1075     return getDefaultCursor().iterator();
1076   }
1077 
1078   /**
1079    * Writes a new table defined by the given TableCreator to the database.
1080    * @usage _advanced_method_
1081    */
1082   protected static void writeTableDefinition(TableCreator creator)
1083     throws IOException
1084   {
1085     // first, create the usage map page
1086     createUsageMapDefinitionBuffer(creator);
1087 
1088     // next, determine how big the table def will be (in case it will be more
1089     // than one page)
1090     JetFormat format = creator.getFormat();
1091     int idxDataLen = (creator.getIndexCount() *
1092                       (format.SIZE_INDEX_DEFINITION +
1093                        format.SIZE_INDEX_COLUMN_BLOCK)) +
1094       (creator.getLogicalIndexCount() * format.SIZE_INDEX_INFO_BLOCK);
1095     int colUmapLen = creator.getLongValueColumns().size() * 10;
1096     int totalTableDefSize = format.SIZE_TDEF_HEADER +
1097       (format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) +
1098       idxDataLen + colUmapLen + format.SIZE_TDEF_TRAILER;
1099 
1100     // total up the amount of space used by the column and index names (2
1101     // bytes per char + 2 bytes for the length)
1102     for(ColumnBuilder col : creator.getColumns()) {
1103       totalTableDefSize += DBMutator.calculateNameLength(col.getName());
1104     }
1105 
1106     for(IndexBuilder idx : creator.getIndexes()) {
1107       totalTableDefSize += DBMutator.calculateNameLength(idx.getName());
1108     }
1109 
1110 
1111     // now, create the table definition
1112     ByteBuffer buffer = PageChannel.createBuffer(Math.max(totalTableDefSize,
1113                                                           format.PAGE_SIZE));
1114     writeTableDefinitionHeader(creator, buffer, totalTableDefSize);
1115 
1116     if(creator.hasIndexes()) {
1117       // index row counts
1118       IndexData.writeRowCountDefinitions(creator, buffer);
1119     }
1120 
1121     // column definitions
1122     ColumnImpl.writeDefinitions(creator, buffer);
1123 
1124     if(creator.hasIndexes()) {
1125       // index and index data definitions
1126       IndexData.writeDefinitions(creator, buffer);
1127       IndexImpl.writeDefinitions(creator, buffer);
1128     }
1129 
1130     // column usage map references
1131     ColumnImpl.writeColUsageMapDefinitions(creator, buffer);
1132 
1133     //End of tabledef
1134     buffer.put((byte) 0xff);
1135     buffer.put((byte) 0xff);
1136     buffer.flip();
1137 
1138     // write table buffer to database
1139     writeTableDefinitionBuffer(buffer, creator.getTdefPageNumber(), creator,
1140                                Collections.<Integer>emptyList());
1141   }
1142 
1143   private static void writeTableDefinitionBuffer(
1144       ByteBuffer buffer, int tdefPageNumber,
1145       TableMutator mutator, List<Integer> reservedPages)
1146     throws IOException
1147   {
1148     buffer.rewind();
1149     int totalTableDefSize = buffer.remaining();
1150     JetFormat format = mutator.getFormat();
1151     PageChannel pageChannel = mutator.getPageChannel();
1152 
1153     // write table buffer to database
1154     if(totalTableDefSize <= format.PAGE_SIZE) {
1155 
1156       // easy case, fits on one page
1157 
1158       // overwrite page free space
1159       buffer.putShort(format.OFFSET_FREE_SPACE,
1160                       (short)(Math.max(
1161                                 format.PAGE_SIZE - totalTableDefSize - 8, 0)));
1162       // Write the tdef page to disk.
1163       buffer.clear();
1164       pageChannel.writePage(buffer, tdefPageNumber);
1165 
1166     } else {
1167 
1168       // need to split across multiple pages
1169 
1170       ByteBuffer partialTdef = pageChannel.createPageBuffer();
1171       buffer.rewind();
1172       int nextTdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
1173       while(buffer.hasRemaining()) {
1174 
1175         // reset for next write
1176         partialTdef.clear();
1177 
1178         if(nextTdefPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
1179 
1180           // this is the first page.  note, the first page already has the
1181           // page header, so no need to write it here
1182           nextTdefPageNumber = tdefPageNumber;
1183 
1184         } else {
1185 
1186           // write page header
1187           writeTablePageHeader(partialTdef);
1188         }
1189 
1190         // copy the next page of tdef bytes
1191         int curTdefPageNumber = nextTdefPageNumber;
1192         int writeLen = Math.min(partialTdef.remaining(), buffer.remaining());
1193         partialTdef.put(buffer.array(), buffer.position(), writeLen);
1194         ByteUtil.forward(buffer, writeLen);
1195 
1196         if(buffer.hasRemaining()) {
1197           // need a next page
1198           if(reservedPages.isEmpty()) {
1199             nextTdefPageNumber = pageChannel.allocateNewPage();
1200           } else {
1201             nextTdefPageNumber = reservedPages.remove(0);
1202           }
1203           partialTdef.putInt(format.OFFSET_NEXT_TABLE_DEF_PAGE,
1204                              nextTdefPageNumber);
1205         }
1206 
1207         // update page free space
1208         partialTdef.putShort(format.OFFSET_FREE_SPACE,
1209                              (short)(Math.max(
1210                                        partialTdef.remaining() - 8, 0)));
1211 
1212         // write partial page to disk
1213         pageChannel.writePage(partialTdef, curTdefPageNumber);
1214       }
1215 
1216     }
1217 
1218   }
1219 
1220   /**
1221    * Writes a column defined by the given TableUpdater to this table.
1222    * @usage _advanced_method_
1223    */
1224   protected ColumnImpl mutateAddColumn(TableUpdater mutator) throws IOException
1225   {
1226     ColumnBuilder column = mutator.getColumn();
1227     JetFormat format = mutator.getFormat();
1228     boolean isVarCol = column.isVariableLength();
1229     boolean isLongVal = column.getType().isLongValue();
1230 
1231     ////
1232     // calculate how much more space we need in the table def
1233     if(isLongVal) {
1234       mutator.addTdefLen(10);
1235     }
1236 
1237     mutator.addTdefLen(format.SIZE_COLUMN_DEF_BLOCK);
1238 
1239     int nameByteLen = DBMutator.calculateNameLength(column.getName());
1240     mutator.addTdefLen(nameByteLen);
1241 
1242     ////
1243     // load current table definition and add space for new info
1244     ByteBuffer tableBuffer = loadCompleteTableDefinitionBufferForUpdate(
1245         mutator);
1246 
1247     ColumnImpl newCol = null;
1248     int umapPos = -1;
1249     boolean success = false;
1250     try {
1251 
1252       ////
1253       // update various bits of the table def
1254       ByteUtil.forward(tableBuffer, 29);
1255       tableBuffer.putShort((short)(_maxColumnCount + 1));
1256       short varColCount = (short)(_maxVarColumnCount + (isVarCol ? 1 : 0));
1257       tableBuffer.putShort(varColCount);
1258       tableBuffer.putShort((short)(_columns.size() + 1));
1259 
1260       // move to end of column def blocks
1261       tableBuffer.position(format.SIZE_TDEF_HEADER +
1262                            (_indexCount * format.SIZE_INDEX_DEFINITION) +
1263                            (_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
1264 
1265       // figure out the data offsets for the new column
1266       int varOffset = 0;
1267       // find the variable offset
1268       for(ColumnImpl col : _varColumns) {
1269         if(col.isVariableLength() && (col.getVarLenTableIndex() >= varOffset)) {
1270           varOffset = col.getVarLenTableIndex() + 1;
1271         }
1272       }
1273 
1274       int fixedOffset = 0;
1275       if(!column.isVariableLength() && !column.storeInNullMask()) {
1276         // find the fixed offset
1277         for(ColumnImpl col : _columns) {
1278           if(!col.isVariableLength() &&
1279              (col.getFixedDataOffset() >= fixedOffset)) {
1280             fixedOffset = col.getFixedDataOffset() + col.getFixedDataSize();
1281           }
1282         }
1283       }
1284 
1285       mutator.setColumnOffsets(fixedOffset, varOffset, varOffset);
1286 
1287       // insert space for the column definition and write it
1288       int colDefPos = tableBuffer.position();
1289       ByteUtil.insertEmptyData(tableBuffer, format.SIZE_COLUMN_DEF_BLOCK);
1290       ColumnImpl.writeDefinition(mutator, column, tableBuffer);
1291 
1292       // skip existing column names and write new name
1293       skipNames(tableBuffer, _columns.size());
1294       ByteUtil.insertEmptyData(tableBuffer, nameByteLen);
1295       writeName(tableBuffer, column.getName(), mutator.getCharset());
1296 
1297       if(isLongVal) {
1298 
1299         // allocate usage maps for the long value col
1300         Map.Entry<Integer,Integer> umapInfo = addUsageMaps(2, null);
1301         TableMutator.ColumnState colState = mutator.getColumnState(column);
1302         colState.setUmapPageNumber(umapInfo.getKey());
1303         byte rowNum = umapInfo.getValue().byteValue();
1304         colState.setUmapOwnedRowNumber(rowNum);
1305         colState.setUmapFreeRowNumber((byte)(rowNum + 1));
1306 
1307         // skip past index defs
1308         ByteUtil.forward(tableBuffer, (_indexCount *
1309                                        format.SIZE_INDEX_COLUMN_BLOCK));
1310         ByteUtil.forward(tableBuffer,
1311                          (_logicalIndexCount * format.SIZE_INDEX_INFO_BLOCK));
1312         skipNames(tableBuffer, _logicalIndexCount);
1313 
1314         // skip existing usage maps
1315         while(tableBuffer.remaining() >= 2) {
1316           if(tableBuffer.getShort() == IndexData.COLUMN_UNUSED) {
1317             // found end of tdef, we want to insert before this
1318             ByteUtil.forward(tableBuffer, -2);
1319             break;
1320           }
1321 
1322           ByteUtil.forward(tableBuffer, 8);
1323 
1324           // keep reading ...
1325         }
1326 
1327         // write new column usage map info
1328         umapPos = tableBuffer.position();
1329         ByteUtil.insertEmptyData(tableBuffer, 10);
1330         ColumnImpl.writeColUsageMapDefinition(
1331             mutator, column, tableBuffer);
1332       }
1333 
1334       // sanity check the updates
1335       validateTableDefUpdate(mutator, tableBuffer);
1336 
1337       // before writing the new table def, create the column
1338       newCol = ColumnImpl.create(this, tableBuffer, colDefPos,
1339                                  column.getName(), _columns.size());
1340       newCol.setColumnIndex(_columns.size());
1341 
1342       ////
1343       // write updated table def back to the database
1344       writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
1345                                  mutator.getNextPages());
1346       success = true;
1347 
1348     } finally {
1349       if(!success) {
1350         // need to discard modified table buffer
1351         _tableDefBufferH.invalidate();
1352       }
1353     }
1354 
1355     ////
1356     // now, update current TableImpl
1357 
1358     _columns.add(newCol);
1359     ++_maxColumnCount;
1360     if(newCol.isVariableLength()) {
1361       _varColumns.add(newCol);
1362       ++_maxVarColumnCount;
1363     }
1364     if(newCol.isAutoNumber()) {
1365       _autoNumColumns.add(newCol);
1366     }
1367     if(newCol.isCalculated()) {
1368       _calcColEval.add(newCol);
1369     }
1370 
1371     if(umapPos >= 0) {
1372       // read column usage map
1373       tableBuffer.position(umapPos);
1374       readColumnUsageMaps(tableBuffer);
1375     }
1376 
1377     newCol.postTableLoadInit();
1378 
1379     if(!isSystem()) {
1380       // after fully constructed, allow column validator to be configured (but
1381       // only for user tables)
1382       newCol.initColumnValidator();
1383     }
1384 
1385     // save any column properties
1386     Map<String,PropertyMap.Property> colProps = column.getProperties();
1387     if(colProps != null) {
1388       newCol.getProperties().putAll(colProps.values());
1389       getProperties().save();
1390     }
1391 
1392     completeTableMutation(tableBuffer);
1393 
1394     return newCol;
1395   }
1396 
1397   /**
1398    * Writes a index defined by the given TableUpdater to this table.
1399    * @usage _advanced_method_
1400    */
1401   protected IndexData mutateAddIndexData(TableUpdater mutator) throws IOException
1402   {
1403     IndexBuilder index = mutator.getIndex();
1404     JetFormat format = mutator.getFormat();
1405 
1406     ////
1407     // calculate how much more space we need in the table def
1408     mutator.addTdefLen(format.SIZE_INDEX_DEFINITION +
1409                        format.SIZE_INDEX_COLUMN_BLOCK);
1410 
1411     ////
1412     // load current table definition and add space for new info
1413     ByteBuffer tableBuffer = loadCompleteTableDefinitionBufferForUpdate(
1414         mutator);
1415 
1416     IndexData newIdxData = null;
1417     boolean success = false;
1418     try {
1419 
1420       ////
1421       // update various bits of the table def
1422       ByteUtil.forward(tableBuffer, 39);
1423       tableBuffer.putInt(_indexCount + 1);
1424 
1425       // move to end of index data def blocks
1426       tableBuffer.position(format.SIZE_TDEF_HEADER +
1427                            (_indexCount * format.SIZE_INDEX_DEFINITION));
1428 
1429       // write index row count definition (empty initially)
1430       ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_DEFINITION);
1431       IndexData.writeRowCountDefinitions(mutator, tableBuffer, 1);
1432 
1433       // skip columns and column names
1434       ByteUtil.forward(tableBuffer,
1435                        (_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
1436       skipNames(tableBuffer, _columns.size());
1437 
1438       // move to end of current index datas
1439       ByteUtil.forward(tableBuffer, (_indexCount *
1440                                      format.SIZE_INDEX_COLUMN_BLOCK));
1441 
1442       // allocate usage maps and root page
1443       TableMutator.IndexDataState idxDataState = mutator.getIndexDataState(index);
1444       int rootPageNumber = getPageChannel().allocateNewPage();
1445       Map.Entry<Integer,Integer> umapInfo = addUsageMaps(1, rootPageNumber);
1446       idxDataState.setRootPageNumber(rootPageNumber);
1447       idxDataState.setUmapPageNumber(umapInfo.getKey());
1448       idxDataState.setUmapRowNumber(umapInfo.getValue().byteValue());
1449 
1450       // write index data def
1451       int idxDataDefPos = tableBuffer.position();
1452       ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_COLUMN_BLOCK);
1453       IndexData.writeDefinition(mutator, tableBuffer, idxDataState, null);
1454 
1455       // sanity check the updates
1456       validateTableDefUpdate(mutator, tableBuffer);
1457 
1458       // before writing the new table def, create the index data
1459       tableBuffer.position(0);
1460       newIdxData = IndexData.create(
1461           this, tableBuffer, idxDataState.getIndexDataNumber(), format);
1462       tableBuffer.position(idxDataDefPos);
1463       newIdxData.read(tableBuffer, _columns);
1464 
1465       ////
1466       // write updated table def back to the database
1467       writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
1468                                  mutator.getNextPages());
1469       success = true;
1470 
1471     } finally {
1472       if(!success) {
1473         // need to discard modified table buffer
1474         _tableDefBufferH.invalidate();
1475       }
1476     }
1477 
1478     ////
1479     // now, update current TableImpl
1480 
1481     for(IndexData.ColumnDescriptor iCol : newIdxData.getColumns()) {
1482       _indexColumns.add(iCol.getColumn());
1483     }
1484 
1485     ++_indexCount;
1486     _indexDatas.add(newIdxData);
1487 
1488     completeTableMutation(tableBuffer);
1489 
1490     // don't forget to populate the new index
1491     populateIndexData(newIdxData);
1492 
1493     return newIdxData;
1494   }
1495 
1496   private void populateIndexData(IndexData idxData)
1497     throws IOException
1498   {
1499     // grab the columns involved in this index
1500     List<ColumnImpl> idxCols = new ArrayList<>();
1501     for(IndexData.ColumnDescriptor col : idxData.getColumns()) {
1502       idxCols.add(col.getColumn());
1503     }
1504 
1505     // iterate through all the rows and add them to the index
1506     Object[] rowVals = new Object[_columns.size()];
1507     for(Row row : getDefaultCursor().newIterable().addColumns(idxCols)) {
1508       for(Column col : idxCols) {
1509         col.setRowValue(rowVals, col.getRowValue(row));
1510       }
1511 
1512       IndexData.commitAll(
1513           idxData.prepareAddRow(rowVals, (RowIdImpl)row.getId(), null));
1514     }
1515 
1516     updateTableDefinition(0);
1517   }
1518 
1519   /**
1520    * Writes a index defined by the given TableUpdater to this table.
1521    * @usage _advanced_method_
1522    */
1523   protected IndexImpl mutateAddIndex(TableUpdater mutator) throws IOException
1524   {
1525     IndexBuilder index = mutator.getIndex();
1526     JetFormat format = mutator.getFormat();
1527 
1528     ////
1529     // calculate how much more space we need in the table def
1530     mutator.addTdefLen(format.SIZE_INDEX_INFO_BLOCK);
1531 
1532     int nameByteLen = DBMutator.calculateNameLength(index.getName());
1533     mutator.addTdefLen(nameByteLen);
1534 
1535     ////
1536     // load current table definition and add space for new info
1537     ByteBuffer tableBuffer = loadCompleteTableDefinitionBufferForUpdate(
1538         mutator);
1539 
1540     IndexImpl newIdx = null;
1541     boolean success = false;
1542     try {
1543 
1544       ////
1545       // update various bits of the table def
1546       ByteUtil.forward(tableBuffer, 35);
1547       tableBuffer.putInt(_logicalIndexCount + 1);
1548 
1549       // move to end of index data def blocks
1550       tableBuffer.position(format.SIZE_TDEF_HEADER +
1551                            (_indexCount * format.SIZE_INDEX_DEFINITION));
1552 
1553       // skip columns and column names
1554       ByteUtil.forward(tableBuffer,
1555                        (_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
1556       skipNames(tableBuffer, _columns.size());
1557 
1558       // move to end of current index datas
1559       ByteUtil.forward(tableBuffer, (_indexCount *
1560                                      format.SIZE_INDEX_COLUMN_BLOCK));
1561       // move to end of current indexes
1562       ByteUtil.forward(tableBuffer, (_logicalIndexCount *
1563                                      format.SIZE_INDEX_INFO_BLOCK));
1564 
1565       int idxDefPos = tableBuffer.position();
1566       ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_INFO_BLOCK);
1567       IndexImpl.writeDefinition(mutator, index, tableBuffer);
1568 
1569       // skip existing index names and write new name
1570       skipNames(tableBuffer, _logicalIndexCount);
1571       ByteUtil.insertEmptyData(tableBuffer, nameByteLen);
1572       writeName(tableBuffer, index.getName(), mutator.getCharset());
1573 
1574       // sanity check the updates
1575       validateTableDefUpdate(mutator, tableBuffer);
1576 
1577       // before writing the new table def, create the index
1578       tableBuffer.position(idxDefPos);
1579       newIdx = new IndexImpl(tableBuffer, _indexDatas, format);
1580       newIdx.setName(index.getName());
1581 
1582       ////
1583       // write updated table def back to the database
1584       writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
1585                                  mutator.getNextPages());
1586       success = true;
1587 
1588     } finally {
1589       if(!success) {
1590         // need to discard modified table buffer
1591         _tableDefBufferH.invalidate();
1592       }
1593     }
1594 
1595     ////
1596     // now, update current TableImpl
1597 
1598     ++_logicalIndexCount;
1599     _indexes.add(newIdx);
1600 
1601     completeTableMutation(tableBuffer);
1602 
1603     return newIdx;
1604   }
1605 
1606   private void validateTableDefUpdate(TableUpdater mutator, ByteBuffer tableBuffer)
1607   {
1608     if(!mutator.validateUpdatedTdef(tableBuffer)) {
1609       throw new IllegalStateException(
1610           withErrorContext("Failed updating table definition (unexpected length)"));
1611     }
1612   }
1613 
1614   private void completeTableMutation(ByteBuffer tableBuffer)
1615   {
1616     // lastly, may need to clear table def buffer
1617     _tableDefBufferH.possiblyInvalidate(_tableDefPageNumber, tableBuffer);
1618 
1619     // update any foreign key enforcing
1620     _fkEnforcer.reset();
1621 
1622     // update modification count so any active RowStates can keep themselves
1623     // up-to-date
1624     ++_modCount;
1625   }
1626 
1627   /**
1628    * Skips the given number of names in the table buffer.
1629    */
1630   private static void skipNames(ByteBuffer tableBuffer, int count) {
1631     for(int i = 0; i < count; ++i) {
1632       ByteUtil.forward(tableBuffer, tableBuffer.getShort());
1633     }
1634   }
1635 
1636   private ByteBuffer loadCompleteTableDefinitionBufferForUpdate(
1637       TableUpdater mutator)
1638     throws IOException
1639   {
1640     // load complete table definition
1641     ByteBuffer tableBuffer = _tableDefBufferH.setPage(getPageChannel(),
1642                                                       _tableDefPageNumber);
1643     tableBuffer = loadCompleteTableDefinitionBuffer(
1644         tableBuffer, mutator.getNextPages());
1645 
1646     // make sure the table buffer has enough room for the new info
1647     int addedLen = mutator.getAddedTdefLen();
1648     int origTdefLen = tableBuffer.getInt(8);
1649     mutator.setOrigTdefLen(origTdefLen);
1650     int newTdefLen = origTdefLen + addedLen;
1651     while(newTdefLen > tableBuffer.capacity()) {
1652       tableBuffer = expandTableBuffer(tableBuffer);
1653       tableBuffer.flip();
1654     }
1655 
1656     tableBuffer.limit(origTdefLen);
1657 
1658     // set new tdef length
1659     tableBuffer.position(8);
1660     tableBuffer.putInt(newTdefLen);
1661 
1662     return tableBuffer;
1663   }
1664 
1665   /**
1666    * Adds some usage maps for use with this table.  This method is expected to
1667    * be called with a small-ish number of requested usage maps.
1668    */
1669   private Map.Entry<Integer,Integer> addUsageMaps(
1670       int numMaps, Integer firstUsedPage)
1671     throws IOException
1672   {
1673     JetFormat format = getFormat();
1674     PageChannel pageChannel = getPageChannel();
1675     int umapRowLength = format.OFFSET_USAGE_MAP_START +
1676       format.USAGE_MAP_TABLE_BYTE_LENGTH;
1677     int totalUmapSpaceUsage = getRowSpaceUsage(umapRowLength, format) * numMaps;
1678     int umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
1679     int firstRowNum = -1;
1680     int freeSpace = 0;
1681 
1682     // search currently known usage map buffers to find one with enough free
1683     // space (the numMaps should always be small enough to put them all on one
1684     // page).  pages will free space will probaby be newer pages (higher
1685     // numbers), so we sort in reverse order.
1686     Set<Integer> knownPages = new TreeSet<>(Collections.reverseOrder());
1687     collectUsageMapPages(knownPages);
1688 
1689     ByteBuffer umapBuf = pageChannel.createPageBuffer();
1690     for(Integer pageNum : knownPages) {
1691       pageChannel.readPage(umapBuf, pageNum);
1692       freeSpace = umapBuf.getShort(format.OFFSET_FREE_SPACE);
1693       if(freeSpace >= totalUmapSpaceUsage) {
1694         // found a page!
1695         umapPageNumber = pageNum;
1696         firstRowNum = getRowsOnDataPage(umapBuf, format);
1697         break;
1698       }
1699     }
1700 
1701     if(umapPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
1702 
1703       // didn't find any existing pages, need to create a new one
1704       umapPageNumber = pageChannel.allocateNewPage();
1705       freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE;
1706       firstRowNum = 0;
1707       umapBuf = createUsageMapDefPage(pageChannel, freeSpace);
1708     }
1709 
1710     // write the actual usage map defs
1711     int rowStart = findRowEnd(umapBuf, firstRowNum, format) - umapRowLength;
1712     int umapRowNum = firstRowNum;
1713     for(int i = 0; i < numMaps; ++i) {
1714       umapBuf.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
1715       umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
1716 
1717       int dataOffset = rowStart + 1;
1718       if(firstUsedPage != null) {
1719         // fill in the first used page of the usage map
1720         umapBuf.putInt(dataOffset, firstUsedPage);
1721         dataOffset += 4;
1722         umapBuf.put(dataOffset, (byte)1);
1723         dataOffset++;
1724       }
1725 
1726       // zero remaining row data
1727       ByteUtil.clearRange(umapBuf, dataOffset, (rowStart + umapRowLength));
1728 
1729       rowStart -= umapRowLength;
1730       ++umapRowNum;
1731     }
1732 
1733     // finish the page
1734     freeSpace -= totalUmapSpaceUsage;
1735     umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
1736     umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
1737                      (short)umapRowNum);
1738     pageChannel.writePage(umapBuf, umapPageNumber);
1739 
1740     return new AbstractMap.SimpleImmutableEntry<Integer,Integer>(
1741         umapPageNumber, firstRowNum);
1742   }
1743 
1744   void collectUsageMapPages(Collection<Integer> pages) {
1745     pages.add(_ownedPages.getTablePageNumber());
1746     pages.add(_freeSpacePages.getTablePageNumber());
1747 
1748     for(IndexData idx : _indexDatas) {
1749       idx.collectUsageMapPages(pages);
1750     }
1751 
1752     for(ColumnImpl col : _columns) {
1753       col.collectUsageMapPages(pages);
1754     }
1755   }
1756 
1757   /**
1758    * @param buffer Buffer to write to
1759    */
1760   private static void writeTableDefinitionHeader(
1761       TableCreator creator, ByteBuffer buffer, int totalTableDefSize)
1762   {
1763     List<ColumnBuilder> columns = creator.getColumns();
1764 
1765     //Start writing the tdef
1766     writeTablePageHeader(buffer);
1767     buffer.putInt(totalTableDefSize);  //Length of table def
1768     buffer.putInt(MAGIC_TABLE_NUMBER); // seemingly constant magic value
1769     buffer.putInt(0);  //Number of rows
1770     buffer.putInt(0); //Last Autonumber
1771     buffer.put((byte) 1); // this makes autonumbering work in access
1772     // 3 constant zero bytes, the next complex autonumber value, then 8 bytes
1773     // which hold whatever the page held before the definition was written
1774     for (int i = 0; i < 15; i++) {
1775       buffer.put((byte) 0);
1776     }
1777     buffer.put(TYPE_USER); //Table type
1778     buffer.putShort((short) columns.size()); //Max columns a row will have
1779     buffer.putShort(ColumnImpl.countVariableLength(columns));  //Number of variable columns in table
1780     buffer.putShort((short) columns.size()); //Number of columns in table
1781     buffer.putInt(creator.getLogicalIndexCount());  //Number of logical indexes in table
1782     buffer.putInt(creator.getIndexCount());  //Number of indexes in table
1783     buffer.put((byte) 0); //Usage map row number
1784     ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber());  //Usage map page number
1785     buffer.put((byte) 1); //Free map row number
1786     ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber());  //Free map page number
1787   }
1788 
1789   /**
1790    * Writes the page header for a table definition page
1791    * @param buffer Buffer to write to
1792    */
1793   private static void writeTablePageHeader(ByteBuffer buffer)
1794   {
1795     buffer.put(PageTypes.TABLE_DEF);  //Page type
1796     buffer.put((byte) 0x01); // constant 1 on every page type
1797     // free space on this page, which writeTableDefinitionBuffer fills in once
1798     // the length of the definition is known
1799     buffer.putShort((short) 0);
1800     buffer.putInt(0);  //Next TDEF page pointer
1801   }
1802 
1803   /**
1804    * Writes the given name into the given buffer in the format as expected by
1805    * {@link #readName}.
1806    */
1807   static void writeName(ByteBuffer buffer, String name, Charset charset)
1808   {
1809       ByteBuffer encName = ColumnImpl.encodeUncompressedText(name, charset);
1810       buffer.putShort((short) encName.remaining());
1811       buffer.put(encName);
1812   }
1813 
1814   /**
1815    * Create the usage map definition page buffer.  The "used pages" map is in
1816    * row 0, the "pages with free space" map is in row 1.  Index usage maps are
1817    * in subsequent rows.
1818    */
1819   private static void createUsageMapDefinitionBuffer(TableCreator creator)
1820     throws IOException
1821   {
1822     List<ColumnBuilder> lvalCols = creator.getLongValueColumns();
1823 
1824     // 2 table usage maps plus 1 for each index and 2 for each lval col
1825     int indexUmapEnd = 2 + creator.getIndexCount();
1826     int umapNum = indexUmapEnd + (lvalCols.size() * 2);
1827 
1828     JetFormat format = creator.getFormat();
1829     int umapRowLength = format.OFFSET_USAGE_MAP_START +
1830       format.USAGE_MAP_TABLE_BYTE_LENGTH;
1831     int umapSpaceUsage = getRowSpaceUsage(umapRowLength, format);
1832     PageChannel pageChannel = creator.getPageChannel();
1833     int umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
1834     ByteBuffer umapBuf = null;
1835     int freeSpace = 0;
1836     int rowStart = 0;
1837     int umapRowNum = 0;
1838 
1839     for(int i = 0; i < umapNum; ++i) {
1840 
1841       if(umapBuf == null) {
1842 
1843         // need new page for usage maps
1844         if(umapPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
1845           // first umap page has already been reserved
1846           umapPageNumber = creator.getUmapPageNumber();
1847         } else {
1848           // need another umap page
1849           umapPageNumber = creator.reservePageNumber();
1850         }
1851 
1852         freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE;
1853 
1854         umapBuf = createUsageMapDefPage(pageChannel, freeSpace);
1855 
1856         rowStart = findRowEnd(umapBuf, 0, format) - umapRowLength;
1857         umapRowNum = 0;
1858       }
1859 
1860       umapBuf.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
1861 
1862       if(i == 0) {
1863 
1864         // table "owned pages" map definition
1865         umapBuf.put(rowStart, UsageMap.MAP_TYPE_REFERENCE);
1866 
1867       } else if(i == 1) {
1868 
1869         // table "free space pages" map definition
1870         umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
1871 
1872       } else if(i < indexUmapEnd) {
1873 
1874         // index umap
1875         int indexIdx = i - 2;
1876         TableMutator.IndexDataState idxDataState =
1877           creator.getIndexDataStates().get(indexIdx);
1878 
1879         // allocate root page for the index
1880         int rootPageNumber = pageChannel.allocateNewPage();
1881 
1882         // stash info for later use
1883         idxDataState.setRootPageNumber(rootPageNumber);
1884         idxDataState.setUmapRowNumber((byte)umapRowNum);
1885         idxDataState.setUmapPageNumber(umapPageNumber);
1886 
1887         // index map definition, including initial root page
1888         umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
1889         umapBuf.putInt(rowStart + 1, rootPageNumber);
1890         umapBuf.put(rowStart + 5, (byte)1);
1891 
1892       } else {
1893 
1894         // long value column umaps
1895         int lvalColIdx = i - indexUmapEnd;
1896         int umapType = lvalColIdx % 2;
1897         lvalColIdx /= 2;
1898 
1899         ColumnBuilder lvalCol = lvalCols.get(lvalColIdx);
1900         TableMutator.ColumnState colState =
1901           creator.getColumnState(lvalCol);
1902 
1903         umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
1904 
1905         if((umapType == 1) &&
1906            (umapPageNumber != colState.getUmapPageNumber())) {
1907           // we want to force both usage maps for a column to be on the same
1908           // data page, so just discard the previous one we wrote
1909           --i;
1910           umapType = 0;
1911         }
1912 
1913         if(umapType == 0) {
1914           // lval column "owned pages" usage map
1915           colState.setUmapOwnedRowNumber((byte)umapRowNum);
1916           colState.setUmapPageNumber(umapPageNumber);
1917         } else {
1918           // lval column "free space pages" usage map (always on same page)
1919           colState.setUmapFreeRowNumber((byte)umapRowNum);
1920         }
1921       }
1922 
1923       rowStart -= umapRowLength;
1924       freeSpace -= umapSpaceUsage;
1925       ++umapRowNum;
1926 
1927       if((freeSpace <= umapSpaceUsage) || (i == (umapNum - 1))) {
1928         // finish current page
1929         umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
1930         umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
1931                          (short)umapRowNum);
1932         pageChannel.writePage(umapBuf, umapPageNumber);
1933         umapBuf = null;
1934       }
1935     }
1936   }
1937 
1938   private static ByteBuffer createUsageMapDefPage(
1939       PageChannel pageChannel, int freeSpace)
1940   {
1941     ByteBuffer umapBuf = pageChannel.createPageBuffer();
1942     umapBuf.put(PageTypes.DATA);
1943     umapBuf.put((byte) 0x1);  // constant 1 on every page type
1944     umapBuf.putShort((short)freeSpace);  //Free space in page
1945     umapBuf.putInt(0); //Table definition
1946     umapBuf.putInt(0); // write stamp, see newDataPage
1947     umapBuf.putShort((short)0); //Number of records on this page
1948     return umapBuf;
1949   }
1950 
1951   /**
1952    * Returns a single ByteBuffer which contains the entire table definition
1953    * (which may span multiple database pages).
1954    */
1955   private ByteBuffer loadCompleteTableDefinitionBuffer(
1956       ByteBuffer tableBuffer, List<Integer> pages)
1957     throws IOException
1958   {
1959     int nextPage = tableBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
1960     ByteBuffer nextPageBuffer = null;
1961     while (nextPage != 0) {
1962       if(pages != null) {
1963         pages.add(nextPage);
1964       }
1965       if (nextPageBuffer == null) {
1966         nextPageBuffer = getPageChannel().createPageBuffer();
1967       }
1968       getPageChannel().readPage(nextPageBuffer, nextPage);
1969       nextPage = nextPageBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
1970       tableBuffer = expandTableBuffer(tableBuffer);
1971       tableBuffer.put(nextPageBuffer.array(), 8, getFormat().PAGE_SIZE - 8);
1972       tableBuffer.flip();
1973     }
1974     return tableBuffer;
1975   }
1976 
1977   private ByteBuffer expandTableBuffer(ByteBuffer tableBuffer) {
1978       ByteBuffer newBuffer = PageChannel.createBuffer(
1979           tableBuffer.capacity() + getFormat().PAGE_SIZE - 8);
1980       newBuffer.put(tableBuffer);
1981       return newBuffer;
1982   }
1983 
1984   private void readColumnDefinitions(ByteBuffer tableBuffer, short columnCount)
1985     throws IOException
1986   {
1987     int colOffset = getFormat().OFFSET_INDEX_DEF_BLOCK +
1988         _indexCount * getFormat().SIZE_INDEX_DEFINITION;
1989 
1990     tableBuffer.position(colOffset +
1991                          (columnCount * getFormat().SIZE_COLUMN_HEADER));
1992     List<String> colNames = new ArrayList<>(columnCount);
1993     for (int i = 0; i < columnCount; i++) {
1994       colNames.add(readName(tableBuffer));
1995     }
1996 
1997     int dispIndex = 0;
1998     for (int i = 0; i < columnCount; i++) {
1999       ColumnImpl column = ColumnImpl.create(this, tableBuffer,
2000           colOffset + (i * getFormat().SIZE_COLUMN_HEADER), colNames.get(i),
2001           dispIndex++);
2002       _columns.add(column);
2003       if(column.isVariableLength()) {
2004         // also shove it in the variable columns list, which is ordered
2005         // differently from the _columns list
2006         _varColumns.add(column);
2007       }
2008     }
2009 
2010     Collections.sort(_columns, ColumnImpl.DEFAULT_ORDER_COMPARATOR);
2011     initAutoNumberColumns();
2012     initCalculatedColumns();
2013 
2014     // setup the data index for the columns
2015     int colIdx = 0;
2016     for(ColumnImpl col : _columns) {
2017       col.setColumnIndex(colIdx++);
2018     }
2019 
2020     // sort variable length columns based on their index into the variable
2021     // length offset table, because we will write the columns in this order
2022     Collections.sort(_varColumns, VAR_LEN_COLUMN_COMPARATOR);
2023   }
2024 
2025   /**
2026    * Verifies that the column counts and sizes in the table definition cover
2027    * the columns which are actually defined.  A damaged table definition can
2028    * disagree, in which case writing a row would run off the end of the null
2029    * mask, the variable length offset table or the row buffer.
2030    * <p>
2031    * Rows can still be read from such a table, so this only records the
2032    * problem, which is reported when a row write is attempted.
2033    */
2034   private void validateColumnDefs() {
2035     int maxRowSize = getFormat().MAX_ROW_SIZE;
2036 
2037     for(ColumnImpl col : _columns) {
2038 
2039       if(col.getColumnNumber() >= _maxColumnCount) {
2040         setWriteDefError("column " + col.getName() + " has number " +
2041                          col.getColumnNumber() + ", which is outside the " +
2042                          "table column count " + _maxColumnCount);
2043       } else if(col.isVariableLength()) {
2044         if(col.getVarLenTableIndex() >= _maxVarColumnCount) {
2045           setWriteDefError("variable length column " + col.getName() +
2046                            " has offset index " + col.getVarLenTableIndex() +
2047                            ", which is outside the table variable length " +
2048                            "column count " + _maxVarColumnCount);
2049         }
2050       } else if((col.getFixedDataOffset() + col.getLength()) > maxRowSize) {
2051         setWriteDefError("fixed length column " + col.getName() +
2052                          " ends at offset " +
2053                          (col.getFixedDataOffset() + col.getLength()) +
2054                          ", which is beyond the maximum row size " +
2055                          maxRowSize);
2056       }
2057     }
2058   }
2059 
2060   /**
2061    * Records the first inconsistency found by {@link #validateColumnDefs}.
2062    */
2063   private void setWriteDefError(String reason) {
2064     if(_writeDefError != null) {
2065       return;
2066     }
2067     _writeDefError = "Table definition is corrupt, " + reason;
2068     LOG.log(Logger.Level.WARNING, withErrorContext(
2069                  _writeDefError + ".  Table is read-only"));
2070   }
2071 
2072   private void readIndexDefinitions(ByteBuffer tableBuffer) throws IOException
2073   {
2074     // read index column information
2075     for (int i = 0; i < _indexCount; i++) {
2076       IndexData idxData = _indexDatas.get(i);
2077       idxData.read(tableBuffer, _columns);
2078       // keep track of all columns involved in indexes
2079       for(IndexData.ColumnDescriptor iCol : idxData.getColumns()) {
2080         _indexColumns.add(iCol.getColumn());
2081       }
2082     }
2083 
2084     // read logical index info (may be more logical indexes than index datas)
2085     for (int i = 0; i < _logicalIndexCount; i++) {
2086       _indexes.add(new IndexImpl(tableBuffer, _indexDatas, getFormat()));
2087     }
2088 
2089     // read logical index names
2090     for (int i = 0; i < _logicalIndexCount; i++) {
2091       _indexes.get(i).setName(readName(tableBuffer));
2092     }
2093 
2094     Collections.sort(_indexes, IndexImpl.DEFAULT_ORDER_COMPARATOR);
2095   }
2096 
2097   private boolean readColumnUsageMaps(ByteBuffer tableBuffer)
2098     throws IOException
2099   {
2100     short umapColNum = tableBuffer.getShort();
2101     if(umapColNum == IndexData.COLUMN_UNUSED) {
2102       return false;
2103     }
2104 
2105     int pos = tableBuffer.position();
2106     UsageMap colOwnedPages = null;
2107     UsageMap colFreeSpacePages = null;
2108     try {
2109       colOwnedPages = UsageMap.read(getDatabase(), tableBuffer);
2110       colFreeSpacePages = UsageMap.read(getDatabase(), tableBuffer);
2111     } catch(IllegalStateException e) {
2112       // ignore invalid usage map info
2113       colOwnedPages = null;
2114       colFreeSpacePages = null;
2115       tableBuffer.position(pos + 8);
2116       LOG.log(Logger.Level.WARNING, withErrorContext("Invalid column " + umapColNum +
2117                                 " usage map definition: " + e));
2118     }
2119 
2120     for(ColumnImpl col : _columns) {
2121       if(col.getColumnNumber() == umapColNum) {
2122         col.setUsageMaps(colOwnedPages, colFreeSpacePages);
2123         break;
2124       }
2125     }
2126 
2127     return true;
2128   }
2129 
2130   /**
2131    * Writes the given page data to the given page number, clears any other
2132    * relevant buffers.
2133    */
2134   private void writeDataPage(ByteBuffer pageBuffer, int pageNumber)
2135     throws IOException
2136   {
2137     // write the page data
2138     getPageChannel().writePage(pageBuffer, pageNumber);
2139 
2140     // possibly invalidate the add row buffer if a different data buffer is
2141     // being written (e.g. this happens during deleteRow)
2142     _addRowBufferH.possiblyInvalidate(pageNumber, pageBuffer);
2143 
2144     // update modification count so any active RowStates can keep themselves
2145     // up-to-date
2146     ++_modCount;
2147   }
2148 
2149   /**
2150    * Returns a name read from the buffer at the current position. The
2151    * expected name format is the name length followed by the name
2152    * encoded using the {@link JetFormat#CHARSET}
2153    */
2154   private String readName(ByteBuffer buffer) {
2155     int nameLength = readNameLength(buffer);
2156     byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength);
2157     return ColumnImpl.decodeUncompressedText(nameBytes,
2158                                          getDatabase().getCharset());
2159   }
2160 
2161   /**
2162    * Returns a name length read from the buffer at the current position.
2163    */
2164   private int readNameLength(ByteBuffer buffer) {
2165     return ByteUtil.getUnsignedVarInt(buffer, getFormat().SIZE_NAME_LENGTH);
2166   }
2167 
2168   @Override
2169   public Object[] asRow(Map<String,?> rowMap) {
2170     return asRow(rowMap, null, false);
2171   }
2172 
2173   /**
2174    * Converts a map of columnName -&gt; columnValue to an array of row values
2175    * appropriate for a call to {@link #addRow(Object...)}, where the generated
2176    * RowId will be an extra value at the end of the array.
2177    * @see ColumnImpl#RETURN_ROW_ID
2178    * @usage _intermediate_method_
2179    */
2180   public Object[] asRowWithRowId(Map<String,?> rowMap) {
2181     return asRow(rowMap, null, true);
2182   }
2183 
2184   @Override
2185   public Object[] asUpdateRow(Map<String,?> rowMap) {
2186     return asRow(rowMap, Column.KEEP_VALUE, false);
2187   }
2188 
2189   /**
2190    * @return the generated RowId added to a row of values created via {@link
2191    *         #asRowWithRowId}
2192    * @usage _intermediate_method_
2193    */
2194   public RowId getRowId(Object[] row) {
2195     return (RowId)row[_columns.size()];
2196   }
2197 
2198   /**
2199    * Converts a map of columnName -&gt; columnValue to an array of row values.
2200    */
2201   private Object[] asRow(Map<String,?> rowMap, Object defaultValue,
2202                          boolean returnRowId)
2203   {
2204     int len = _columns.size();
2205     if(returnRowId) {
2206       ++len;
2207     }
2208     Object[] row = new Object[len];
2209     if(defaultValue != null) {
2210       Arrays.fill(row, defaultValue);
2211     }
2212     if(returnRowId) {
2213       row[len - 1] = ColumnImpl.RETURN_ROW_ID;
2214     }
2215     if(rowMap == null) {
2216       return row;
2217     }
2218     for(ColumnImpl col : _columns) {
2219       if(rowMap.containsKey(col.getName())) {
2220         col.setRowValue(row, col.getRowValue(rowMap));
2221       }
2222     }
2223     return row;
2224   }
2225 
2226   @Override
2227   public Object[] addRow(Object... row) throws IOException {
2228     return addRows(Collections.singletonList(row), false).get(0);
2229   }
2230 
2231   @Override
2232   public <M extends Map<String,Object>> M addRowFromMap(M row)
2233     throws IOException
2234   {
2235     Object[] rowValues = asRow(row);
2236 
2237     addRow(rowValues);
2238 
2239     returnRowValues(row, rowValues, _columns);
2240     return row;
2241   }
2242 
2243   @Override
2244   public List<? extends Object[]> addRows(List<? extends Object[]> rows)
2245     throws IOException
2246   {
2247     return addRows(rows, true);
2248   }
2249 
2250   @Override
2251   public <M extends Map<String,Object>> List<M> addRowsFromMaps(List<M> rows)
2252     throws IOException
2253   {
2254     List<Object[]> rowValuesList = new ArrayList<>(rows.size());
2255     for(Map<String,Object> row : rows) {
2256       rowValuesList.add(asRow(row));
2257     }
2258 
2259     addRows(rowValuesList);
2260 
2261     for(int i = 0; i < rowValuesList.size(); ++i) {
2262       Map<String,Object> row = rows.get(i);
2263       Object[] rowValues = rowValuesList.get(i);
2264       returnRowValues(row, rowValues, _columns);
2265     }
2266     return rows;
2267   }
2268 
2269   private static void returnRowValues(Map<String,Object> row, Object[] rowValues,
2270                                       List<ColumnImpl> cols)
2271   {
2272     for(ColumnImpl col : cols) {
2273       col.setRowValue(row, col.getRowValue(rowValues));
2274     }
2275   }
2276 
2277   /**
2278    * Add multiple rows to this table, only writing to disk after all
2279    * rows have been written, and every time a data page is filled.
2280    * @param rows List of Object[] row values
2281    */
2282   protected List<? extends Object[]> addRows(List<? extends Object[]> rows,
2283                                              final boolean isBatchWrite)
2284     throws IOException
2285   {
2286     if(rows.isEmpty()) {
2287       return rows;
2288     }
2289 
2290     getPageChannel().startWrite();
2291     try {
2292 
2293       ByteBuffer dataPage = null;
2294       int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
2295       int updateCount = 0;
2296       int autoNumAssignCount = 0;
2297       WriteRowState writeRowState =
2298         (!_autoNumColumns.isEmpty() ? new WriteRowState() : null);
2299       try {
2300 
2301         List<Object[]> dupeRows = null;
2302         final int numCols = _columns.size();
2303         for (int i = 0; i < rows.size(); i++) {
2304 
2305           // we need to make sure the row is the right length and is an
2306           // Object[] (fill with null if too short).  note, if the row is
2307           // copied the caller will not be able to access any generated
2308           // auto-number value, but if they need that info they should use a
2309           // row array of the right size/type!
2310           Object[] row = rows.get(i);
2311           if((row.length < numCols) || (row.getClass() != Object[].class)) {
2312             row = dupeRow(row, numCols);
2313             // copy the input rows to a modifiable list so we can update the
2314             // elements
2315             if(dupeRows == null) {
2316               dupeRows = new ArrayList<>(rows);
2317               rows = dupeRows;
2318             }
2319             // we copied the row, so put the copy back into the rows list
2320             dupeRows.set(i, row);
2321           }
2322 
2323           // handle various value massaging activities
2324           for(ColumnImpl column : _columns) {
2325             if(!column.isAutoNumber()) {
2326               Object val = column.getRowValue(row);
2327               if(val == null) {
2328                 val = column.generateDefaultValue();
2329               }
2330               // pass input value through column validator
2331               column.setRowValue(row, column.validate(val));
2332             }
2333           }
2334 
2335           // fill in autonumbers
2336           handleAutoNumbersForAdd(row, writeRowState);
2337           ++autoNumAssignCount;
2338 
2339           // need to assign calculated values after all the other fields are
2340           // filled in but before final validation
2341           _calcColEval.calculate(row);
2342 
2343           // run row validation if enabled
2344           if(_rowValidator != null) {
2345             _rowValidator.validate(row);
2346           }
2347 
2348           // write the row of data to a temporary buffer
2349           ByteBuffer rowData = createRow(
2350               row, _writeRowBufferH.getPageBuffer(getPageChannel()));
2351 
2352           int rowSize = rowData.remaining();
2353           if (rowSize > getFormat().MAX_ROW_SIZE) {
2354             throw createTooLargeException(rowSize);
2355           }
2356 
2357           // get page with space
2358           dataPage = findFreeRowSpace(rowSize, dataPage, pageNumber);
2359           pageNumber = _addRowBufferH.getPageNumber();
2360 
2361           // determine where this row will end up on the page
2362           int rowNum = getRowsOnDataPage(dataPage, getFormat());
2363 
2364           RowIdImplss/impl/RowIdImpl.html#RowIdImpl">RowIdImpl rowId = new RowIdImpl(pageNumber, rowNum);
2365 
2366           // before we actually write the row data, we verify all the database
2367           // constraints.
2368           if(!_indexDatas.isEmpty()) {
2369 
2370             IndexData.PendingChange idxChange = null;
2371             try {
2372 
2373               // handle foreign keys before adding to table
2374               _fkEnforcer.addRow(row);
2375 
2376               // prepare index updates
2377               for(IndexData indexData : _indexDatas) {
2378                 idxChange = indexData.prepareAddRow(row, rowId, idxChange);
2379               }
2380 
2381               // complete index updates
2382               IndexData.commitAll(idxChange);
2383 
2384             } catch(ConstraintViolationException ce) {
2385               IndexData.rollbackAll(idxChange);
2386               throw ce;
2387             }
2388           }
2389 
2390           // we have satisfied all the constraints, write the row
2391           addDataPageRow(dataPage, rowSize, getFormat(), 0);
2392           dataPage.put(rowData);
2393 
2394           // return rowTd if desired
2395           if((row.length > numCols) &&
2396              (row[numCols] == ColumnImpl.RETURN_ROW_ID)) {
2397             row[numCols] = rowId;
2398           }
2399 
2400           ++updateCount;
2401         }
2402 
2403         writeDataPage(dataPage, pageNumber);
2404 
2405         // Update tdef page
2406         updateTableDefinition(rows.size());
2407 
2408       } catch(Exception rowWriteFailure) {
2409 
2410         boolean isWriteFailure = isWriteFailure(rowWriteFailure);
2411 
2412         if(!isWriteFailure && (autoNumAssignCount > updateCount)) {
2413           // we assigned some autonumbers which won't get written.  attempt to
2414           // recover them so we don't get ugly "holes"
2415           restoreAutoNumbersFromAdd(rows.get(autoNumAssignCount - 1));
2416         }
2417 
2418         if(!isBatchWrite) {
2419           // just re-throw the original exception
2420           if(rowWriteFailure instanceof IOException) {
2421             throw (IOException)rowWriteFailure;
2422           }
2423           throw (RuntimeException)rowWriteFailure;
2424         }
2425 
2426         // attempt to resolve a partial batch write
2427         if(isWriteFailure) {
2428 
2429           // we don't really know the status of any of the rows, so clear the
2430           // update count
2431           updateCount = 0;
2432 
2433         } else if(updateCount > 0) {
2434 
2435           // attempt to flush the rows already written to disk
2436           try {
2437 
2438             writeDataPage(dataPage, pageNumber);
2439 
2440             // Update tdef page
2441             updateTableDefinition(updateCount);
2442 
2443           } catch(Exception flushFailure) {
2444             // the flush failure is "worse" as it implies possible database
2445             // corruption (failed write vs. a row failure which was not a
2446             // write failure).  we don't know the status of any rows at this
2447             // point (and the original failure is probably irrelevant)
2448             LOG.log(Logger.Level.WARNING, withErrorContext(
2449                     "Secondary row failure which preceded the write failure"),
2450                      rowWriteFailure);
2451             updateCount = 0;
2452             rowWriteFailure = flushFailure;
2453           }
2454         }
2455 
2456         throw new BatchUpdateException(
2457             updateCount, withErrorContext("Failed adding rows"),
2458             rowWriteFailure);
2459       }
2460 
2461     } finally {
2462       getPageChannel().finishWrite();
2463     }
2464 
2465     return rows;
2466   }
2467 
2468   private static boolean isWriteFailure(Throwable t) {
2469     while(t != null) {
2470       if((t instanceof IOException) && !(t instanceof JackcessException)) {
2471         return true;
2472       }
2473       t = t.getCause();
2474     }
2475     // some other sort of exception which is not a write failure
2476     return false;
2477   }
2478 
2479   @Override
2480   public Rowf="../../../../com/healthmarketscience/jackcess/Row.html#Row">Row updateRow(Row row) throws IOException {
2481     return updateRowFromMap(
2482         getDefaultCursor().getRowState(), (RowIdImpl)row.getId(), row);
2483   }
2484 
2485   /**
2486    * Update the row with the given id.  Provided RowId must have previously
2487    * been returned from this Table.
2488    * @return the given row, updated with the current row values
2489    * @throws IllegalStateException if the given row is not valid, or deleted.
2490    * @usage _intermediate_method_
2491    */
2492   public Object[] updateRow(RowId rowId, Object... row) throws IOException {
2493     return updateRow(
2494         getDefaultCursor().getRowState(), (RowIdImpl)rowId, row);
2495   }
2496 
2497   /**
2498    * Update the given column's value for the given row id.  Provided RowId
2499    * must have previously been returned from this Table.
2500    * @throws IllegalStateException if the given row is not valid, or deleted.
2501    * @usage _intermediate_method_
2502    */
2503   public void updateValue(Column column, RowId rowId, Object value)
2504     throws IOException
2505   {
2506     Object[] row = new Object[_columns.size()];
2507     Arrays.fill(row, Column.KEEP_VALUE);
2508     column.setRowValue(row, value);
2509 
2510     updateRow(rowId, row);
2511   }
2512 
2513   public <M extends Map<String,Object>> M updateRowFromMap(
2514       RowState rowState, RowIdImpl rowId, M row)
2515      throws IOException
2516   {
2517     Object[] rowValues = updateRow(rowState, rowId, asUpdateRow(row));
2518     returnRowValues(row, rowValues, _columns);
2519     return row;
2520   }
2521 
2522   /**
2523    * Update the row for the given rowId.
2524    * @usage _advanced_method_
2525    */
2526   public Object[] updateRow(RowState rowState, RowIdImpl rowId, Object... row)
2527     throws IOException
2528   {
2529     requireValidRowId(rowId);
2530 
2531     getPageChannel().startWrite();
2532     try {
2533 
2534       // ensure that the relevant row state is up-to-date
2535       ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
2536       int oldRowSize = rowBuffer.remaining();
2537 
2538       requireNonDeletedRow(rowState, rowId);
2539 
2540       // we need to make sure the row is the right length & type (fill with
2541       // null if too short).
2542       if((row.length < _columns.size()) || (row.getClass() != Object[].class)) {
2543         row = dupeRow(row, _columns.size());
2544       }
2545 
2546       // hang on to the raw values of var length columns we are "keeping".  this
2547       // will allow us to re-use pre-written var length data, which can save
2548       // space for things like long value columns.
2549       Map<ColumnImpl,byte[]> keepRawVarValues =
2550         (!_varColumns.isEmpty() ? new HashMap<>() : null);
2551 
2552       // handle various value massaging activities
2553       for(ColumnImpl column : _columns) {
2554 
2555         if(column.isAutoNumber()) {
2556           // handle these separately (below)
2557           continue;
2558         }
2559 
2560         Object rowValue = column.getRowValue(row);
2561         if(rowValue == Column.KEEP_VALUE) {
2562 
2563           // fill in any "keep value" fields (restore old value)
2564           rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
2565                                   keepRawVarValues);
2566 
2567         } else {
2568 
2569           // set oldValue to something that could not possibly be a real value
2570           Object oldValue = Column.KEEP_VALUE;
2571           if(_indexColumns.contains(column)) {
2572             // read (old) row value to help update indexes
2573             oldValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
2574                                     null);
2575           } else {
2576             oldValue = rowState.getRowCacheValue(column.getColumnIndex());
2577           }
2578 
2579           // if the old value was passed back in, we don't need to validate
2580           if(oldValue != rowValue) {
2581             // pass input value through column validator
2582             rowValue = column.validate(rowValue);
2583           }
2584         }
2585 
2586         column.setRowValue(row, rowValue);
2587       }
2588 
2589       // fill in autonumbers
2590       handleAutoNumbersForUpdate(row, rowBuffer, rowState);
2591 
2592       // need to assign calculated values after all the other fields are
2593       // filled in but before final validation
2594       _calcColEval.calculate(row);
2595 
2596       // run row validation if enabled
2597       if(_rowValidator != null) {
2598         _rowValidator.validate(row);
2599       }
2600 
2601       // generate new row bytes
2602       ByteBuffer newRowData = createRow(
2603           row, _writeRowBufferH.getPageBuffer(getPageChannel()), oldRowSize,
2604           keepRawVarValues);
2605 
2606       if (newRowData.limit() > getFormat().MAX_ROW_SIZE) {
2607         throw createTooLargeException(newRowData.limit());
2608       }
2609 
2610       if(!_indexDatas.isEmpty()) {
2611 
2612         IndexData.PendingChange idxChange = null;
2613         try {
2614 
2615           Object[] oldRowValues = rowState.getRowCacheValues();
2616 
2617           // check foreign keys before actually updating
2618           _fkEnforcer.updateRow(oldRowValues, row);
2619 
2620           // prepare index updates
2621           for(IndexData indexData : _indexDatas) {
2622             idxChange = indexData.prepareUpdateRow(oldRowValues, rowId, row,
2623                                                    idxChange);
2624           }
2625 
2626           // complete index updates
2627           IndexData.commitAll(idxChange);
2628 
2629         } catch(ConstraintViolationException ce) {
2630           IndexData.rollbackAll(idxChange);
2631           throw ce;
2632         }
2633       }
2634 
2635       // see if we can squeeze the new row data into the existing row
2636       rowBuffer.reset();
2637       int rowSize = newRowData.remaining();
2638 
2639       ByteBuffer dataPage = null;
2640       int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
2641 
2642       if(oldRowSize >= rowSize) {
2643 
2644         // awesome, slap it in!
2645         rowBuffer.put(newRowData);
2646 
2647         // grab the page we just updated
2648         dataPage = rowState.getFinalPage();
2649         pageNumber = rowState.getFinalRowId().getPageNumber();
2650 
2651       } else {
2652 
2653         // bummer, need to find a new page for the data
2654         dataPage = findFreeRowSpace(rowSize, null,
2655                                     PageChannel.INVALID_PAGE_NUMBER);
2656         pageNumber = _addRowBufferH.getPageNumber();
2657 
2658         RowIdImpl headerRowId = rowState.getHeaderRowId();
2659         ByteBuffer headerPage = rowState.getHeaderPage();
2660         if(pageNumber == headerRowId.getPageNumber()) {
2661           // new row is on the same page as header row, share page
2662           dataPage = headerPage;
2663         }
2664 
2665         // write out the new row data (set the deleted flag on the new data row
2666         // so that it is ignored during normal table traversal)
2667         int rowNum = addDataPageRow(dataPage, rowSize, getFormat(),
2668                                     DELETED_ROW_MASK);
2669         dataPage.put(newRowData);
2670 
2671         // write the overflow info into the header row and clear out the
2672         // remaining header data
2673         rowBuffer = PageChannel.narrowBuffer(
2674             headerPage,
2675             findRowStart(headerPage, headerRowId.getRowNumber(), getFormat()),
2676             findRowEnd(headerPage, headerRowId.getRowNumber(), getFormat()));
2677         rowBuffer.put((byte)rowNum);
2678         ByteUtil.put3ByteInt(rowBuffer, pageNumber);
2679         ByteUtil.clearRemaining(rowBuffer);
2680 
2681         // set the overflow flag on the header row
2682         int headerRowIndex = getRowStartOffset(headerRowId.getRowNumber(),
2683                                                getFormat());
2684         headerPage.putShort(headerRowIndex,
2685                             (short)(headerPage.getShort(headerRowIndex)
2686                                     | OVERFLOW_ROW_MASK));
2687         if(pageNumber != headerRowId.getPageNumber()) {
2688           writeDataPage(headerPage, headerRowId.getPageNumber());
2689         }
2690       }
2691 
2692       writeDataPage(dataPage, pageNumber);
2693 
2694       updateTableDefinition(0);
2695 
2696     } finally {
2697       getPageChannel().finishWrite();
2698     }
2699 
2700     return row;
2701   }
2702 
2703   private ByteBuffer findFreeRowSpace(int rowSize, ByteBuffer dataPage,
2704                                       int pageNumber)
2705     throws IOException
2706   {
2707     // assume incoming page is modified
2708     boolean modifiedPage = true;
2709 
2710     if(dataPage == null) {
2711 
2712       // find owned page w/ free space
2713       dataPage = findFreeRowSpace(_ownedPages, _freeSpacePages,
2714                                   _addRowBufferH);
2715 
2716       if(dataPage == null) {
2717         // No data pages exist (with free space).  Create a new one.
2718         return newDataPage();
2719       }
2720 
2721       // found a page, see if it will work
2722       pageNumber = _addRowBufferH.getPageNumber();
2723       // since we just loaded this page, it is not yet modified
2724       modifiedPage = false;
2725     }
2726 
2727     if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
2728 
2729       // Last data page is full.  Write old one and create a new one.
2730       if(modifiedPage) {
2731         writeDataPage(dataPage, pageNumber);
2732       }
2733       _freeSpacePages.removePageNumber(pageNumber);
2734 
2735       dataPage = newDataPage();
2736     }
2737 
2738     return dataPage;
2739   }
2740 
2741   static ByteBuffer findFreeRowSpace(
2742       UsageMap./../../com/healthmarketscience/jackcess/impl/UsageMap.html#UsageMap">UsageMap ownedPages, UsageMap freeSpacePages,
2743       TempPageHolder rowBufferH)
2744     throws IOException
2745   {
2746     // find last data page (Not bothering to check other pages for free
2747     // space.)
2748     UsageMap.PageCursor revPageCursor = ownedPages.cursor();
2749     revPageCursor.afterLast();
2750     while(true) {
2751       int tmpPageNumber = revPageCursor.getPreviousPage();
2752       if(tmpPageNumber < 0) {
2753         break;
2754       }
2755       // only use if actually listed in free space pages
2756       if(!freeSpacePages.containsPageNumber(tmpPageNumber)) {
2757         continue;
2758       }
2759       ByteBuffer dataPage = rowBufferH.setPage(ownedPages.getPageChannel(),
2760                                                tmpPageNumber);
2761       if(dataPage.get() == PageTypes.DATA) {
2762         // found last data page with free space
2763         return dataPage;
2764       }
2765     }
2766 
2767     return null;
2768   }
2769 
2770   /**
2771    * Updates the table definition after rows are modified.
2772    */
2773   private void updateTableDefinition(int rowCountInc) throws IOException
2774   {
2775     // load table definition
2776     ByteBuffer tdefPage = _tableDefBufferH.setPage(getPageChannel(),
2777                                                    _tableDefPageNumber);
2778 
2779     // make sure rowcount and autonumber are up-to-date
2780     _rowCount += rowCountInc;
2781     tdefPage.putInt(getFormat().OFFSET_NUM_ROWS, _rowCount);
2782     tdefPage.putInt(getFormat().OFFSET_NEXT_AUTO_NUMBER, _lastLongAutoNumber);
2783     int ctypeOff = getFormat().OFFSET_NEXT_COMPLEX_AUTO_NUMBER;
2784     if(ctypeOff >= 0) {
2785       tdefPage.putInt(ctypeOff, _lastComplexTypeAutoNumber);
2786     }
2787 
2788     // write any index changes
2789     for (IndexData indexData : _indexDatas) {
2790       // write the unique entry count for the index to the table definition
2791       // page
2792       tdefPage.putInt(indexData.getUniqueEntryCountOffset(),
2793                       indexData.getUniqueEntryCount());
2794       // write the entry page for the index
2795       indexData.update();
2796     }
2797 
2798     // write modified table definition
2799     getPageChannel().writePage(tdefPage, _tableDefPageNumber);
2800   }
2801 
2802   /**
2803    * Create a new data page
2804    * @return Page number of the new page
2805    */
2806   private ByteBuffer newDataPage() throws IOException {
2807     ByteBuffer dataPage = _addRowBufferH.setNewPage(getPageChannel());
2808     dataPage.put(PageTypes.DATA); //Page type
2809     dataPage.put((byte) 1); // constant 1 on every page type
2810     dataPage.putShort((short)getFormat().DATA_PAGE_INITIAL_FREE_SPACE); //Free space in this page
2811     dataPage.putInt(_tableDefPageNumber); //Page pointer to table definition
2812     // ms access stamps GetTickCount() here when it writes the page, and
2813     // writes zero when it initializes one
2814     dataPage.putInt(0);
2815     dataPage.putShort((short)0); //Number of rows on this page
2816     int pageNumber = _addRowBufferH.getPageNumber();
2817     getPageChannel().writePage(dataPage, pageNumber);
2818     _ownedPages.addPageNumber(pageNumber);
2819     _freeSpacePages.addPageNumber(pageNumber);
2820     return dataPage;
2821   }
2822 
2823   // exposed for unit tests
2824   protected ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer)
2825     throws IOException
2826   {
2827     return createRow(rowArray, buffer, 0,
2828                      Collections.<ColumnImpl,byte[]>emptyMap());
2829   }
2830 
2831   /**
2832    * Serialize a row of Objects into a byte buffer.
2833    *
2834    * @param rowArray row data, expected to be correct length for this table
2835    * @param buffer buffer to which to write the row data
2836    * @param minRowSize min size for result row
2837    * @param rawVarValues optional, pre-written values for var length columns
2838    *                     (enables re-use of previously written values).
2839    * @return the given buffer, filled with the row data
2840    */
2841   private ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer,
2842                                int minRowSize,
2843                                Map<ColumnImpl,byte[]> rawVarValues)
2844     throws IOException
2845   {
2846     if(_writeDefError != null) {
2847       // we cannot lay out a row without a table definition which matches the
2848       // columns
2849       throw new JackcessException(withErrorContext(_writeDefError));
2850     }
2851 
2852     buffer.putShort(_maxColumnCount);
2853     NullMask/impl/NullMask.html#NullMask">NullMask nullMask = new NullMask(_maxColumnCount);
2854 
2855     //Fixed length column data comes first
2856     int fixedDataStart = buffer.position();
2857     int fixedDataEnd = fixedDataStart;
2858     for (ColumnImpl col : _columns) {
2859 
2860       if(col.isVariableLength()) {
2861         continue;
2862       }
2863 
2864       Object rowValue = col.getRowValue(rowArray);
2865 
2866       if (col.storeInNullMask()) {
2867 
2868         if(col.writeToNullMask(rowValue)) {
2869           nullMask.markNotNull(col);
2870         }
2871         rowValue = null;
2872       }
2873 
2874       if(rowValue != null) {
2875 
2876         // we have a value to write
2877         nullMask.markNotNull(col);
2878 
2879         // remainingRowLength is ignored when writing fixed length data
2880         buffer.position(fixedDataStart + col.getFixedDataOffset());
2881         buffer.put(col.write(rowValue, 0));
2882       }
2883 
2884       // always insert space for the entire fixed data column length
2885       // (including null values), access expects the row to always be at least
2886       // big enough to hold all fixed values
2887       buffer.position(fixedDataStart + col.getFixedDataOffset() +
2888                       col.getLength());
2889 
2890       // keep track of the end of fixed data
2891       if(buffer.position() > fixedDataEnd) {
2892         fixedDataEnd = buffer.position();
2893       }
2894 
2895     }
2896 
2897     // reposition at end of fixed data
2898     buffer.position(fixedDataEnd);
2899 
2900     // only need this info if this table contains any var length data
2901     if(_maxVarColumnCount > 0) {
2902 
2903       int maxRowSize = getFormat().MAX_ROW_SIZE;
2904 
2905       // figure out how much space remains for var length data.  first,
2906       // account for already written space
2907       maxRowSize -= buffer.position();
2908       // now, account for trailer space
2909       int trailerSize = (nullMask.byteSize() + 4 + (_maxVarColumnCount * 2));
2910       maxRowSize -= trailerSize;
2911 
2912       // for each non-null long value column we need to reserve a small
2913       // amount of space so that we don't end up running out of row space
2914       // later by being too greedy
2915       for (ColumnImpl varCol : _varColumns) {
2916         if((varCol.getType().isLongValue()) &&
2917            (varCol.getRowValue(rowArray) != null)) {
2918           maxRowSize -= getFormat().SIZE_LONG_VALUE_DEF;
2919         }
2920       }
2921 
2922       //Now write out variable length column data
2923       short[] varColumnOffsets = new short[_maxVarColumnCount];
2924       int varColumnOffsetsIndex = 0;
2925       for (ColumnImpl varCol : _varColumns) {
2926         short offset = (short) buffer.position();
2927         Object rowValue = varCol.getRowValue(rowArray);
2928         if (rowValue != null) {
2929           // we have a value
2930           nullMask.markNotNull(varCol);
2931 
2932           byte[] rawValue = null;
2933           ByteBuffer varDataBuf = null;
2934           if(((rawValue = rawVarValues.get(varCol)) != null) &&
2935              (rawValue.length <= maxRowSize)) {
2936             // save time and potentially db space, re-use raw value
2937             varDataBuf = ByteBuffer.wrap(rawValue);
2938           } else {
2939             // write column value
2940             varDataBuf = varCol.write(rowValue, maxRowSize);
2941           }
2942 
2943           maxRowSize -= varDataBuf.remaining();
2944           if(varCol.getType().isLongValue()) {
2945             // we already accounted for some amount of the long value data
2946             // above.  add that space back so we don't double count
2947             maxRowSize += getFormat().SIZE_LONG_VALUE_DEF;
2948           }
2949           try {
2950             buffer.put(varDataBuf);
2951           } catch(BufferOverflowException e) {
2952             // if the data is too big for the buffer, then we have gone over
2953             // the max row size
2954             throw createTooLargeException(buffer.limit());
2955           }
2956         }
2957 
2958         // we do a loop here so that we fill in offsets for deleted columns
2959         while(varColumnOffsetsIndex <= varCol.getVarLenTableIndex()) {
2960           varColumnOffsets[varColumnOffsetsIndex++] = offset;
2961         }
2962       }
2963 
2964       // fill in offsets for any remaining deleted columns
2965       while(varColumnOffsetsIndex < varColumnOffsets.length) {
2966         varColumnOffsets[varColumnOffsetsIndex++] = (short) buffer.position();
2967       }
2968 
2969       // record where we stopped writing
2970       int eod = buffer.position();
2971 
2972       if(buffer.remaining() < trailerSize) {
2973         // we don't have enough space left for required trailer
2974         throw createTooLargeException(eod + trailerSize);
2975       }
2976 
2977       // insert padding if necessary
2978       padRowBuffer(buffer, minRowSize, trailerSize);
2979 
2980       buffer.putShort((short) eod); //EOD marker
2981 
2982       //Now write out variable length offsets
2983       //Offsets are stored in reverse order
2984       for (int i = _maxVarColumnCount - 1; i >= 0; i--) {
2985         buffer.putShort(varColumnOffsets[i]);
2986       }
2987       buffer.putShort(_maxVarColumnCount);  //Number of var length columns
2988 
2989     } else {
2990 
2991       // insert padding for row w/ no var cols
2992       padRowBuffer(buffer, minRowSize, nullMask.byteSize());
2993     }
2994 
2995     nullMask.write(buffer);  //Null mask
2996     buffer.flip();
2997     return buffer;
2998   }
2999 
3000   private InvalidValueException createTooLargeException(int size) {
3001     return new InvalidValueException(
3002         withErrorContext(
3003             "Row size " + size + " is too large (max " +
3004             getFormat().MAX_ROW_SIZE + ")"));
3005   }
3006 
3007   /**
3008    * Fill in all autonumber column values for add.
3009    */
3010   private void handleAutoNumbersForAdd(Object[] row, WriteRowState writeRowState)
3011     throws IOException
3012   {
3013     if(_autoNumColumns.isEmpty()) {
3014       return;
3015     }
3016 
3017     boolean enableInsert = isAllowAutoNumberInsert();
3018     writeRowState.resetAutoNumber();
3019     for(ColumnImpl col : _autoNumColumns) {
3020 
3021       // ignore input row value, use original row value (unless explicitly
3022       // enabled)
3023       Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
3024 
3025       ColumnImpl.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator();
3026       Object rowValue = ((inRowValue == null) ?
3027                          autoNumGen.getNext(writeRowState) :
3028                          autoNumGen.handleInsert(writeRowState, inRowValue));
3029 
3030       col.setRowValue(row, rowValue);
3031     }
3032   }
3033 
3034   /**
3035    * Fill in all autonumber column values for update.
3036    */
3037   private void handleAutoNumbersForUpdate(Object[] row, ByteBuffer rowBuffer,
3038                                           RowState rowState)
3039     throws IOException
3040   {
3041     if(_autoNumColumns.isEmpty()) {
3042       return;
3043     }
3044 
3045     boolean enableInsert = isAllowAutoNumberInsert();
3046     rowState.resetAutoNumber();
3047     for(ColumnImpl col : _autoNumColumns) {
3048 
3049       // ignore input row value, use original row value (unless explicitly
3050       // enabled)
3051       Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
3052 
3053       Object rowValue =
3054         ((inRowValue == null) ?
3055          getRowColumn(getFormat(), rowBuffer, col, rowState, null) :
3056          col.getAutoNumberGenerator().handleInsert(rowState, inRowValue));
3057 
3058       col.setRowValue(row, rowValue);
3059     }
3060   }
3061 
3062   /**
3063    * Optionally get the input autonumber row value for the given column from
3064    * the given row if one was provided.
3065    */
3066   private static Object getInputAutoNumberRowValue(
3067       boolean enableInsert, ColumnImpl col, Object[] row)
3068   {
3069     if(!enableInsert) {
3070       return null;
3071     }
3072 
3073     Object inRowValue = col.getRowValue(row);
3074     if((inRowValue == Column.KEEP_VALUE) || (inRowValue == Column.AUTO_NUMBER)) {
3075       // these "special" values both behave like nothing was given
3076       inRowValue = null;
3077     }
3078     return inRowValue;
3079   }
3080 
3081   /**
3082    * Restores all autonumber column values from a failed add row.
3083    */
3084   private void restoreAutoNumbersFromAdd(Object[] row)
3085   {
3086     if(_autoNumColumns.isEmpty()) {
3087       return;
3088     }
3089 
3090     for(ColumnImpl col : _autoNumColumns) {
3091       // restore the last value from the row
3092       col.getAutoNumberGenerator().restoreLast(col.getRowValue(row));
3093     }
3094   }
3095 
3096   private static void padRowBuffer(ByteBuffer buffer, int minRowSize,
3097                                    int trailerSize)
3098   {
3099     int pos = buffer.position();
3100     if((pos + trailerSize) < minRowSize) {
3101       // pad the row to get to the min byte size
3102       int padSize = minRowSize - (pos + trailerSize);
3103       ByteUtil.clearRange(buffer, pos, pos + padSize);
3104       ByteUtil.forward(buffer, padSize);
3105     }
3106   }
3107 
3108   @Override
3109   public int getRowCount() {
3110     return _rowCount;
3111   }
3112 
3113   int getNextLongAutoNumber() {
3114     // note, the saved value is the last one handed out, so pre-increment
3115     return ++_lastLongAutoNumber;
3116   }
3117 
3118   int getLastLongAutoNumber() {
3119     // gets the last used auto number (does not modify)
3120     return _lastLongAutoNumber;
3121   }
3122 
3123   void adjustLongAutoNumber(int inLongAutoNumber) {
3124     if(inLongAutoNumber > _lastLongAutoNumber) {
3125       _lastLongAutoNumber = inLongAutoNumber;
3126     }
3127   }
3128 
3129   void restoreLastLongAutoNumber(int lastLongAutoNumber) {
3130     // restores the last used auto number
3131     _lastLongAutoNumber = lastLongAutoNumber - 1;
3132   }
3133 
3134   int getNextComplexTypeAutoNumber() {
3135     // note, the saved value is the last one handed out, so pre-increment
3136     return ++_lastComplexTypeAutoNumber;
3137   }
3138 
3139   int getLastComplexTypeAutoNumber() {
3140     // gets the last used auto number (does not modify)
3141     return _lastComplexTypeAutoNumber;
3142   }
3143 
3144   void adjustComplexTypeAutoNumber(int inComplexTypeAutoNumber) {
3145     if(inComplexTypeAutoNumber > _lastComplexTypeAutoNumber) {
3146       _lastComplexTypeAutoNumber = inComplexTypeAutoNumber;
3147     }
3148   }
3149 
3150   void restoreLastComplexTypeAutoNumber(int lastComplexTypeAutoNumber) {
3151     // restores the last used auto number
3152     _lastComplexTypeAutoNumber = lastComplexTypeAutoNumber - 1;
3153   }
3154 
3155   @Override
3156   public String toString() {
3157     return ToStringBuilder.builder(this)
3158       .append("type", (_tableType + (!isSystem() ? " (USER)" : " (SYSTEM)")))
3159       .append("name", _name)
3160       .append("rowCount", _rowCount)
3161       .append("columnCount", _columns.size())
3162       .append("indexCount(data)", _indexCount)
3163       .append("logicalIndexCount", _logicalIndexCount)
3164       .appendIfNotNull("validator", _rowValidator)
3165       .append("columns", _columns)
3166       .append("indexes", _indexes)
3167       .append("ownedPages", _ownedPages)
3168       .toString();
3169   }
3170 
3171   /**
3172    * @return A simple String representation of the entire table in
3173    *         tab-delimited format
3174    * @usage _general_method_
3175    */
3176   public String display() throws IOException {
3177     return display(Long.MAX_VALUE);
3178   }
3179 
3180   /**
3181    * @param limit Maximum number of rows to display
3182    * @return A simple String representation of the entire table in
3183    *         tab-delimited format
3184    * @usage _general_method_
3185    */
3186   public String display(long limit) throws IOException {
3187     reset();
3188     StringWriter rtn = new StringWriter();
3189     new ExportUtil.Builder(getDefaultCursor()).setDelimiter("\t").setHeader(true)
3190       .exportWriter(new BufferedWriter(rtn));
3191     return rtn.toString();
3192   }
3193 
3194   /**
3195    * Updates free space and row info for a new row of the given size in the
3196    * given data page.  Positions the page for writing the row data.
3197    * @return the row number of the new row
3198    * @usage _advanced_method_
3199    */
3200   public static int addDataPageRow(ByteBuffer dataPage,
3201                                    int rowSize,
3202                                    JetFormat format,
3203                                    int rowFlags)
3204   {
3205     int rowSpaceUsage = getRowSpaceUsage(rowSize, format);
3206 
3207     // Decrease free space record.
3208     short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
3209     dataPage.putShort(format.OFFSET_FREE_SPACE, (short) (freeSpaceInPage -
3210                                                          rowSpaceUsage));
3211 
3212     // Increment row count record.
3213     short rowCount = dataPage.getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
3214     dataPage.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
3215                       (short) (rowCount + 1));
3216 
3217     // determine row position
3218     short rowLocation = findRowEnd(dataPage, rowCount, format);
3219     rowLocation -= rowSize;
3220 
3221     // write row position
3222     dataPage.putShort(getRowStartOffset(rowCount, format),
3223                       (short)(rowLocation | rowFlags));
3224 
3225     // set position for row data
3226     dataPage.position(rowLocation);
3227 
3228     return rowCount;
3229   }
3230 
3231   /**
3232    * Returns the row count for the current page.  If the page is invalid
3233    * ({@code null}) or the page is not a DATA page, 0 is returned.
3234    */
3235   static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
3236   {
3237     int rowsOnPage = 0;
3238     if((rowBuffer != null) && (rowBuffer.get(0) == PageTypes.DATA)) {
3239       rowsOnPage = rowBuffer.getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
3240     }
3241     return rowsOnPage;
3242   }
3243 
3244   /**
3245    * @throws IllegalStateException if the given rowId is invalid
3246    */
3247   private void requireValidRowId(RowIdImpl rowId) {
3248     if(!rowId.isValid()) {
3249       throw new IllegalArgumentException(withErrorContext(
3250               "Given rowId is invalid: " + rowId));
3251     }
3252   }
3253 
3254   /**
3255    * @throws IllegalStateException if the given row is invalid or deleted
3256    */
3257   private void requireNonDeletedRow(RowState rowState, RowIdImpl rowId)
3258   {
3259     if(!rowState.isValid()) {
3260       throw new IllegalArgumentException(withErrorContext(
3261           "Given rowId is invalid for this table: " + rowId));
3262     }
3263     if(rowState.isDeleted()) {
3264       throw new IllegalStateException(withErrorContext(
3265           "Row is deleted: " + rowId));
3266     }
3267   }
3268 
3269   /**
3270    * @usage _advanced_method_
3271    */
3272   public static boolean isDeletedRow(short rowStart) {
3273     return ((rowStart & DELETED_ROW_MASK) != 0);
3274   }
3275 
3276   /**
3277    * @usage _advanced_method_
3278    */
3279   public static boolean isOverflowRow(short rowStart) {
3280     return ((rowStart & OVERFLOW_ROW_MASK) != 0);
3281   }
3282 
3283   /**
3284    * @usage _advanced_method_
3285    */
3286   public static short cleanRowStart(short rowStart) {
3287     return (short)(rowStart & OFFSET_MASK);
3288   }
3289 
3290   /**
3291    * @usage _advanced_method_
3292    */
3293   public static short findRowStart(ByteBuffer buffer, int rowNum,
3294                                    JetFormat format)
3295   {
3296     return cleanRowStart(
3297         buffer.getShort(getRowStartOffset(rowNum, format)));
3298   }
3299 
3300   /**
3301    * @usage _advanced_method_
3302    */
3303   public static int getRowStartOffset(int rowNum, JetFormat format)
3304   {
3305     return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * rowNum);
3306   }
3307 
3308   /**
3309    * @usage _advanced_method_
3310    */
3311   public static short findRowEnd(ByteBuffer buffer, int rowNum,
3312                                  JetFormat format)
3313   {
3314     return (short)((rowNum == 0) ?
3315                    format.PAGE_SIZE :
3316                    cleanRowStart(
3317                        buffer.getShort(getRowEndOffset(rowNum, format))));
3318   }
3319 
3320   /**
3321    * @usage _advanced_method_
3322    */
3323   public static int getRowEndOffset(int rowNum, JetFormat format)
3324   {
3325     return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * (rowNum - 1));
3326   }
3327 
3328   /**
3329    * @usage _advanced_method_
3330    */
3331   public static int getRowSpaceUsage(int rowSize, JetFormat format)
3332   {
3333     return rowSize + format.SIZE_ROW_LOCATION;
3334   }
3335 
3336   private void initAutoNumberColumns() {
3337     for(ColumnImpl c : _columns) {
3338       if(c.isAutoNumber()) {
3339         _autoNumColumns.add(c);
3340       }
3341     }
3342   }
3343 
3344   private void initCalculatedColumns() {
3345     for(ColumnImpl c : _columns) {
3346       if(c.isCalculated()) {
3347         _calcColEval.add(c);
3348       }
3349     }
3350   }
3351 
3352   boolean isThisTable(Identifier identifier) {
3353     String collectionName = identifier.getCollectionName();
3354     return ((collectionName == null) ||
3355             collectionName.equalsIgnoreCase(getName()));
3356   }
3357 
3358   /**
3359    * Returns {@code true} if a row of the given size will fit on the given
3360    * data page, {@code false} otherwise.
3361    * @usage _advanced_method_
3362    */
3363   public static boolean rowFitsOnDataPage(
3364       int rowLength, ByteBuffer dataPage, JetFormat format)
3365   {
3366     int rowSpaceUsage = getRowSpaceUsage(rowLength, format);
3367     short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
3368     int rowsOnPage = getRowsOnDataPage(dataPage, format);
3369     return ((rowSpaceUsage <= freeSpaceInPage) &&
3370             (rowsOnPage < format.MAX_NUM_ROWS_ON_DATA_PAGE));
3371   }
3372 
3373   /**
3374    * Duplicates and returns a row of data, optionally with a longer length
3375    * filled with {@code null}.
3376    */
3377   static Object[] dupeRow(Object[] row, int newRowLength) {
3378     Object[] copy = new Object[newRowLength];
3379     System.arraycopy(row, 0, copy, 0, Math.min(row.length, newRowLength));
3380     return copy;
3381   }
3382 
3383   String withErrorContext(String msg) {
3384     return withErrorContext(msg, getDatabase(), getName());
3385   }
3386 
3387   private static String withErrorContext(String msg, DatabaseImpl db,
3388                                          String tableName) {
3389     return msg + " (Db=" + db.getName() + ";Table=" + tableName + ")";
3390   }
3391 
3392   /** various statuses for the row data */
3393   private enum RowStatus {
3394     INIT, INVALID_PAGE, INVALID_ROW, VALID, DELETED, NORMAL, OVERFLOW;
3395   }
3396 
3397   /** the phases the RowState moves through as the data is parsed */
3398   private enum RowStateStatus {
3399     INIT, AT_HEADER, AT_FINAL;
3400   }
3401 
3402   /**
3403    * Maintains state for writing a new row of data.
3404    */
3405   protected static class WriteRowState
3406   {
3407     private int _complexAutoNumber = ColumnImpl.INVALID_AUTO_NUMBER;
3408 
3409     public int getComplexAutoNumber() {
3410       return _complexAutoNumber;
3411     }
3412 
3413     public void setComplexAutoNumber(int complexAutoNumber) {
3414       _complexAutoNumber = complexAutoNumber;
3415     }
3416 
3417     public void resetAutoNumber() {
3418       _complexAutoNumber = ColumnImpl.INVALID_AUTO_NUMBER;
3419     }
3420   }
3421 
3422   /**
3423    * Maintains the state of reading/updating a row of data.
3424    * @usage _advanced_class_
3425    */
3426   public final class RowState extends WriteRowState
3427     implements ErrorHandler.Location
3428   {
3429     /** Buffer used for reading the header row data pages */
3430     private final TempPageHolder _headerRowBufferH;
3431     /** the header rowId */
3432     private RowIdImpl _headerRowId = RowIdImpl.FIRST_ROW_ID;
3433     /** the number of rows on the header page */
3434     private int _rowsOnHeaderPage;
3435     /** the rowState status */
3436     private RowStateStatus _status = RowStateStatus.INIT;
3437     /** the row status */
3438     private RowStatus _rowStatus = RowStatus.INIT;
3439     /** buffer used for reading overflow pages */
3440     private final TempPageHolder _overflowRowBufferH =
3441       TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
3442     /** the row buffer which contains the final data (after following any
3443         overflow pointers) */
3444     private ByteBuffer _finalRowBuffer;
3445     /** the rowId which contains the final data (after following any overflow
3446         pointers) */
3447     private RowIdImpl _finalRowId = null;
3448     /** true if the row values array has data */
3449     private boolean _haveRowValues;
3450     /** values read from the last row */
3451     private Object[] _rowValues;
3452     /** null mask for the last row */
3453     private NullMask _nullMask;
3454     /** last modification count seen on the table we track this so that the
3455         rowState can detect updates to the table and re-read any buffered
3456         data */
3457     private int _lastModCount;
3458     /** optional error handler to use when row errors are encountered */
3459     private ErrorHandler _errorHandler;
3460     /** cached variable column offsets for jump-table based rows */
3461     private short[] _varColOffsets;
3462 
3463     private RowState(TempBufferHolder.Type headerType) {
3464       _headerRowBufferH = TempPageHolder.newHolder(headerType);
3465       _rowValues = new Object[TableImpl.this.getColumnCount()];
3466       _lastModCount = TableImpl.this._modCount;
3467     }
3468 
3469     @Override
3470     public TableImpl getTable() {
3471       return TableImpl.this;
3472     }
3473 
3474     public ErrorHandler getErrorHandler() {
3475       return((_errorHandler != null) ? _errorHandler :
3476              getTable().getErrorHandler());
3477     }
3478 
3479     public void setErrorHandler(ErrorHandler newErrorHandler) {
3480       _errorHandler = newErrorHandler;
3481     }
3482 
3483     public void reset() {
3484       resetAutoNumber();
3485       _finalRowId = null;
3486       _finalRowBuffer = null;
3487       _rowsOnHeaderPage = 0;
3488       _status = RowStateStatus.INIT;
3489       _rowStatus = RowStatus.INIT;
3490       _varColOffsets = null;
3491       _nullMask = null;
3492       if(_haveRowValues) {
3493         Arrays.fill(_rowValues, null);
3494         _haveRowValues = false;
3495       }
3496     }
3497 
3498     public boolean isUpToDate() {
3499       return(TableImpl.this._modCount == _lastModCount);
3500     }
3501 
3502     private void checkForModification() {
3503       if(!isUpToDate()) {
3504         reset();
3505         _headerRowBufferH.invalidate();
3506         _overflowRowBufferH.invalidate();
3507         int colCount = TableImpl.this.getColumnCount();
3508         if(colCount != _rowValues.length) {
3509           // columns added or removed from table
3510           _rowValues = new Object[colCount];
3511         }
3512         _lastModCount = TableImpl.this._modCount;
3513       }
3514     }
3515 
3516     private ByteBuffer getFinalPage()
3517       throws IOException
3518     {
3519       if(_finalRowBuffer == null) {
3520         // (re)load current page
3521         _finalRowBuffer = getHeaderPage();
3522       }
3523       return _finalRowBuffer;
3524     }
3525 
3526     public RowIdImpl getFinalRowId() {
3527       if(_finalRowId == null) {
3528         _finalRowId = getHeaderRowId();
3529       }
3530       return _finalRowId;
3531     }
3532 
3533     private void setRowStatus(RowStatus rowStatus) {
3534       _rowStatus = rowStatus;
3535     }
3536 
3537     public boolean isValid() {
3538       return(_rowStatus.ordinal() >= RowStatus.VALID.ordinal());
3539     }
3540 
3541     public boolean isDeleted() {
3542       return(_rowStatus == RowStatus.DELETED);
3543     }
3544 
3545     public boolean isOverflow() {
3546       return(_rowStatus == RowStatus.OVERFLOW);
3547     }
3548 
3549     public boolean isHeaderPageNumberValid() {
3550       return(_rowStatus.ordinal() > RowStatus.INVALID_PAGE.ordinal());
3551     }
3552 
3553     public boolean isHeaderRowNumberValid() {
3554       return(_rowStatus.ordinal() > RowStatus.INVALID_ROW.ordinal());
3555     }
3556 
3557     private void setStatus(RowStateStatus status) {
3558       _status = status;
3559     }
3560 
3561     public boolean isAtHeaderRow() {
3562       return(_status.ordinal() >= RowStateStatus.AT_HEADER.ordinal());
3563     }
3564 
3565     public boolean isAtFinalRow() {
3566       return(_status.ordinal() >= RowStateStatus.AT_FINAL.ordinal());
3567     }
3568 
3569     private Object setRowCacheValue(int idx, Object value) {
3570       _haveRowValues = true;
3571       _rowValues[idx] = value;
3572       return value;
3573     }
3574 
3575     private Object getRowCacheValue(int idx) {
3576       Object value = _rowValues[idx];
3577       // only return immutable values.  mutable values could have been
3578       // modified externally and therefore could return an incorrect value
3579       return(ColumnImpl.isImmutableValue(value) ? value : null);
3580     }
3581 
3582     public Object[] getRowCacheValues() {
3583       return dupeRow(_rowValues, _rowValues.length);
3584     }
3585 
3586     public NullMask getNullMask(ByteBuffer rowBuffer) {
3587       if(_nullMask == null) {
3588         _nullMask = getRowNullMask(rowBuffer);
3589       }
3590       return _nullMask;
3591     }
3592 
3593     private short[] getVarColOffsets() {
3594       return _varColOffsets;
3595     }
3596 
3597     private void setVarColOffsets(short[] varColOffsets) {
3598       _varColOffsets = varColOffsets;
3599     }
3600 
3601     public RowIdImpl getHeaderRowId() {
3602       return _headerRowId;
3603     }
3604 
3605     public int getRowsOnHeaderPage() {
3606       return _rowsOnHeaderPage;
3607     }
3608 
3609     private ByteBuffer getHeaderPage()
3610       throws IOException
3611     {
3612       checkForModification();
3613       return _headerRowBufferH.getPage(getPageChannel());
3614     }
3615 
3616     private ByteBuffer setHeaderRow(RowIdImpl rowId)
3617       throws IOException
3618     {
3619       checkForModification();
3620 
3621       // don't do any work if we are already positioned correctly
3622       if(isAtHeaderRow() && (getHeaderRowId().equals(rowId))) {
3623         return(isValid() ? getHeaderPage() : null);
3624       }
3625 
3626       // rejigger everything
3627       reset();
3628       _headerRowId = rowId;
3629       _finalRowId = rowId;
3630 
3631       int pageNumber = rowId.getPageNumber();
3632       int rowNumber = rowId.getRowNumber();
3633       if((pageNumber < 0) || !_ownedPages.containsPageNumber(pageNumber)) {
3634         setRowStatus(RowStatus.INVALID_PAGE);
3635         return null;
3636       }
3637 
3638       _finalRowBuffer = _headerRowBufferH.setPage(getPageChannel(),
3639                                                   pageNumber);
3640       _rowsOnHeaderPage = getRowsOnDataPage(_finalRowBuffer, getFormat());
3641 
3642       if((rowNumber < 0) || (rowNumber >= _rowsOnHeaderPage)) {
3643         setRowStatus(RowStatus.INVALID_ROW);
3644         return null;
3645       }
3646 
3647       setRowStatus(RowStatus.VALID);
3648       return _finalRowBuffer;
3649     }
3650 
3651     private ByteBuffer setOverflowRow(RowIdImpl rowId)
3652       throws IOException
3653     {
3654       // this should never see modifications because it only happens within
3655       // the positionAtRowData method
3656       if(!isUpToDate()) {
3657         throw new IllegalStateException(getTable().withErrorContext(
3658                                             "Table modified while searching?"));
3659       }
3660       if(_rowStatus != RowStatus.OVERFLOW) {
3661         throw new IllegalStateException(getTable().withErrorContext(
3662                                             "Row is not an overflow row?"));
3663       }
3664       _finalRowId = rowId;
3665       _finalRowBuffer = _overflowRowBufferH.setPage(getPageChannel(),
3666                                                     rowId.getPageNumber());
3667       return _finalRowBuffer;
3668     }
3669 
3670     private Object handleRowError(ColumnImpl column, byte[] columnData,
3671                                   Exception error)
3672       throws IOException
3673     {
3674       return getErrorHandler().handleRowError(column, columnData,
3675                                               this, error);
3676     }
3677 
3678     @Override
3679     public String toString() {
3680       return ToStringBuilder.valueBuilder(this)
3681         .append("headerRowId", _headerRowId)
3682         .append("finalRowId", _finalRowId)
3683         .toString();
3684     }
3685   }
3686 
3687   /**
3688    * Utility for managing calculated columns.  Calculated columns need to be
3689    * evaluated in dependency order.
3690    */
3691   private class CalcColEvaluator
3692   {
3693     /** List of calculated columns in this table, ordered by calculation
3694         dependency */
3695     private final List<ColumnImpl> _calcColumns = new ArrayList<>(1);
3696     private boolean _sorted;
3697 
3698     public void add(ColumnImpl col) {
3699       if(!getDatabase().isEvaluateExpressions()) {
3700         return;
3701       }
3702       _calcColumns.add(col);
3703       // whenever we add new columns, we need to re-sort
3704       _sorted = false;
3705     }
3706 
3707     public void reSort() {
3708       // mark columns for re-sort on next use
3709       _sorted = false;
3710     }
3711 
3712     public void calculate(Object[] row) throws IOException {
3713       if(!_sorted) {
3714         sortColumnsByDeps();
3715         _sorted = true;
3716       }
3717 
3718       for(ColumnImpl col : _calcColumns) {
3719         Object rowValue = col.getCalculationContext().eval(row);
3720         col.setRowValue(row, rowValue);
3721       }
3722     }
3723 
3724     private void sortColumnsByDeps() {
3725 
3726       // a topological sort sorts nodes where A -> B such that A ends up in
3727       // the list before B (assuming that we are working with a DAG).  In our
3728       // case, we return "descendent" info as Field1 -> Field2 (where Field1
3729       // uses Field2 in its calculation).  This means that in order to
3730       // correctly calculate Field1, we need to calculate Field2 first, and
3731       // hence essentially need the reverse topo sort (a list where Field2
3732       // comes before Field1).
3733       (new TopoSorter<ColumnImpl>(_calcColumns, TopoSorter.REVERSE) {
3734         @Override
3735         protected void getDescendents(ColumnImpl from,
3736                                       List<ColumnImpl> descendents) {
3737 
3738           Set<Identifier> identifiers = new LinkedHashSet<>();
3739           from.getCalculationContext().collectIdentifiers(identifiers);
3740 
3741           for(Identifier identifier : identifiers) {
3742             if(isThisTable(identifier)) {
3743               String colName = identifier.getObjectName();
3744               for(ColumnImpl calcCol : _calcColumns) {
3745                 // we only care if the identifier is another calc field
3746                 if(calcCol.getName().equalsIgnoreCase(colName)) {
3747                   descendents.add(calcCol);
3748                 }
3749               }
3750             }
3751           }
3752         }
3753       }).sort();
3754     }
3755   }
3756 }