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