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 createTooLargeException(rowSize);
2301           }
2302 
2303           // get page with space
2304           dataPage = findFreeRowSpace(rowSize, dataPage, pageNumber);
2305           pageNumber = _addRowBufferH.getPageNumber();
2306 
2307           // determine where this row will end up on the page
2308           int rowNum = getRowsOnDataPage(dataPage, getFormat());
2309 
2310           RowIdImplss/impl/RowIdImpl.html#RowIdImpl">RowIdImpl rowId = new RowIdImpl(pageNumber, rowNum);
2311 
2312           // before we actually write the row data, we verify all the database
2313           // constraints.
2314           if(!_indexDatas.isEmpty()) {
2315 
2316             IndexData.PendingChange idxChange = null;
2317             try {
2318 
2319               // handle foreign keys before adding to table
2320               _fkEnforcer.addRow(row);
2321 
2322               // prepare index updates
2323               for(IndexData indexData : _indexDatas) {
2324                 idxChange = indexData.prepareAddRow(row, rowId, idxChange);
2325               }
2326 
2327               // complete index updates
2328               IndexData.commitAll(idxChange);
2329 
2330             } catch(ConstraintViolationException ce) {
2331               IndexData.rollbackAll(idxChange);
2332               throw ce;
2333             }
2334           }
2335 
2336           // we have satisfied all the constraints, write the row
2337           addDataPageRow(dataPage, rowSize, getFormat(), 0);
2338           dataPage.put(rowData);
2339 
2340           // return rowTd if desired
2341           if((row.length > numCols) &&
2342              (row[numCols] == ColumnImpl.RETURN_ROW_ID)) {
2343             row[numCols] = rowId;
2344           }
2345 
2346           ++updateCount;
2347         }
2348 
2349         writeDataPage(dataPage, pageNumber);
2350 
2351         // Update tdef page
2352         updateTableDefinition(rows.size());
2353 
2354       } catch(Exception rowWriteFailure) {
2355 
2356         boolean isWriteFailure = isWriteFailure(rowWriteFailure);
2357 
2358         if(!isWriteFailure && (autoNumAssignCount > updateCount)) {
2359           // we assigned some autonumbers which won't get written.  attempt to
2360           // recover them so we don't get ugly "holes"
2361           restoreAutoNumbersFromAdd(rows.get(autoNumAssignCount - 1));
2362         }
2363 
2364         if(!isBatchWrite) {
2365           // just re-throw the original exception
2366           if(rowWriteFailure instanceof IOException) {
2367             throw (IOException)rowWriteFailure;
2368           }
2369           throw (RuntimeException)rowWriteFailure;
2370         }
2371 
2372         // attempt to resolve a partial batch write
2373         if(isWriteFailure) {
2374 
2375           // we don't really know the status of any of the rows, so clear the
2376           // update count
2377           updateCount = 0;
2378 
2379         } else if(updateCount > 0) {
2380 
2381           // attempt to flush the rows already written to disk
2382           try {
2383 
2384             writeDataPage(dataPage, pageNumber);
2385 
2386             // Update tdef page
2387             updateTableDefinition(updateCount);
2388 
2389           } catch(Exception flushFailure) {
2390             // the flush failure is "worse" as it implies possible database
2391             // corruption (failed write vs. a row failure which was not a
2392             // write failure).  we don't know the status of any rows at this
2393             // point (and the original failure is probably irrelevant)
2394             LOG.warn(withErrorContext(
2395                     "Secondary row failure which preceded the write failure"),
2396                      rowWriteFailure);
2397             updateCount = 0;
2398             rowWriteFailure = flushFailure;
2399           }
2400         }
2401 
2402         throw new BatchUpdateException(
2403             updateCount, withErrorContext("Failed adding rows"),
2404             rowWriteFailure);
2405       }
2406 
2407     } finally {
2408       getPageChannel().finishWrite();
2409     }
2410 
2411     return rows;
2412   }
2413 
2414   private static boolean isWriteFailure(Throwable t) {
2415     while(t != null) {
2416       if((t instanceof IOException) && !(t instanceof JackcessException)) {
2417         return true;
2418       }
2419       t = t.getCause();
2420     }
2421     // some other sort of exception which is not a write failure
2422     return false;
2423   }
2424 
2425   @Override
2426   public Rowf="../../../../com/healthmarketscience/jackcess/Row.html#Row">Row updateRow(Row row) throws IOException {
2427     return updateRowFromMap(
2428         getDefaultCursor().getRowState(), (RowIdImpl)row.getId(), row);
2429   }
2430 
2431   /**
2432    * Update the row with the given id.  Provided RowId must have previously
2433    * been returned from this Table.
2434    * @return the given row, updated with the current row values
2435    * @throws IllegalStateException if the given row is not valid, or deleted.
2436    * @usage _intermediate_method_
2437    */
2438   public Object[] updateRow(RowId rowId, Object... row) throws IOException {
2439     return updateRow(
2440         getDefaultCursor().getRowState(), (RowIdImpl)rowId, row);
2441   }
2442 
2443   /**
2444    * Update the given column's value for the given row id.  Provided RowId
2445    * must have previously been returned from this Table.
2446    * @throws IllegalStateException if the given row is not valid, or deleted.
2447    * @usage _intermediate_method_
2448    */
2449   public void updateValue(Column column, RowId rowId, Object value)
2450     throws IOException
2451   {
2452     Object[] row = new Object[_columns.size()];
2453     Arrays.fill(row, Column.KEEP_VALUE);
2454     column.setRowValue(row, value);
2455 
2456     updateRow(rowId, row);
2457   }
2458 
2459   public <M extends Map<String,Object>> M updateRowFromMap(
2460       RowState rowState, RowIdImpl rowId, M row)
2461      throws IOException
2462   {
2463     Object[] rowValues = updateRow(rowState, rowId, asUpdateRow(row));
2464     returnRowValues(row, rowValues, _columns);
2465     return row;
2466   }
2467 
2468   /**
2469    * Update the row for the given rowId.
2470    * @usage _advanced_method_
2471    */
2472   public Object[] updateRow(RowState rowState, RowIdImpl rowId, Object... row)
2473     throws IOException
2474   {
2475     requireValidRowId(rowId);
2476 
2477     getPageChannel().startWrite();
2478     try {
2479 
2480       // ensure that the relevant row state is up-to-date
2481       ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
2482       int oldRowSize = rowBuffer.remaining();
2483 
2484       requireNonDeletedRow(rowState, rowId);
2485 
2486       // we need to make sure the row is the right length & type (fill with
2487       // null if too short).
2488       if((row.length < _columns.size()) || (row.getClass() != Object[].class)) {
2489         row = dupeRow(row, _columns.size());
2490       }
2491 
2492       // hang on to the raw values of var length columns we are "keeping".  this
2493       // will allow us to re-use pre-written var length data, which can save
2494       // space for things like long value columns.
2495       Map<ColumnImpl,byte[]> keepRawVarValues =
2496         (!_varColumns.isEmpty() ? new HashMap<ColumnImpl,byte[]>() : null);
2497 
2498       // handle various value massaging activities
2499       for(ColumnImpl column : _columns) {
2500 
2501         if(column.isAutoNumber()) {
2502           // handle these separately (below)
2503           continue;
2504         }
2505 
2506         Object rowValue = column.getRowValue(row);
2507         if(rowValue == Column.KEEP_VALUE) {
2508 
2509           // fill in any "keep value" fields (restore old value)
2510           rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
2511                                   keepRawVarValues);
2512 
2513         } else {
2514 
2515           // set oldValue to something that could not possibly be a real value
2516           Object oldValue = Column.KEEP_VALUE;
2517           if(_indexColumns.contains(column)) {
2518             // read (old) row value to help update indexes
2519             oldValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
2520                                     null);
2521           } else {
2522             oldValue = rowState.getRowCacheValue(column.getColumnIndex());
2523           }
2524 
2525           // if the old value was passed back in, we don't need to validate
2526           if(oldValue != rowValue) {
2527             // pass input value through column validator
2528             rowValue = column.validate(rowValue);
2529           }
2530         }
2531 
2532         column.setRowValue(row, rowValue);
2533       }
2534 
2535       // fill in autonumbers
2536       handleAutoNumbersForUpdate(row, rowBuffer, rowState);
2537 
2538       // need to assign calculated values after all the other fields are
2539       // filled in but before final validation
2540       _calcColEval.calculate(row);
2541 
2542       // run row validation if enabled
2543       if(_rowValidator != null) {
2544         _rowValidator.validate(row);
2545       }
2546 
2547       // generate new row bytes
2548       ByteBuffer newRowData = createRow(
2549           row, _writeRowBufferH.getPageBuffer(getPageChannel()), oldRowSize,
2550           keepRawVarValues);
2551 
2552       if (newRowData.limit() > getFormat().MAX_ROW_SIZE) {
2553         throw createTooLargeException(newRowData.limit());
2554       }
2555 
2556       if(!_indexDatas.isEmpty()) {
2557 
2558         IndexData.PendingChange idxChange = null;
2559         try {
2560 
2561           Object[] oldRowValues = rowState.getRowCacheValues();
2562 
2563           // check foreign keys before actually updating
2564           _fkEnforcer.updateRow(oldRowValues, row);
2565 
2566           // prepare index updates
2567           for(IndexData indexData : _indexDatas) {
2568             idxChange = indexData.prepareUpdateRow(oldRowValues, rowId, row,
2569                                                    idxChange);
2570           }
2571 
2572           // complete index updates
2573           IndexData.commitAll(idxChange);
2574 
2575         } catch(ConstraintViolationException ce) {
2576           IndexData.rollbackAll(idxChange);
2577           throw ce;
2578         }
2579       }
2580 
2581       // see if we can squeeze the new row data into the existing row
2582       rowBuffer.reset();
2583       int rowSize = newRowData.remaining();
2584 
2585       ByteBuffer dataPage = null;
2586       int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
2587 
2588       if(oldRowSize >= rowSize) {
2589 
2590         // awesome, slap it in!
2591         rowBuffer.put(newRowData);
2592 
2593         // grab the page we just updated
2594         dataPage = rowState.getFinalPage();
2595         pageNumber = rowState.getFinalRowId().getPageNumber();
2596 
2597       } else {
2598 
2599         // bummer, need to find a new page for the data
2600         dataPage = findFreeRowSpace(rowSize, null,
2601                                     PageChannel.INVALID_PAGE_NUMBER);
2602         pageNumber = _addRowBufferH.getPageNumber();
2603 
2604         RowIdImpl headerRowId = rowState.getHeaderRowId();
2605         ByteBuffer headerPage = rowState.getHeaderPage();
2606         if(pageNumber == headerRowId.getPageNumber()) {
2607           // new row is on the same page as header row, share page
2608           dataPage = headerPage;
2609         }
2610 
2611         // write out the new row data (set the deleted flag on the new data row
2612         // so that it is ignored during normal table traversal)
2613         int rowNum = addDataPageRow(dataPage, rowSize, getFormat(),
2614                                     DELETED_ROW_MASK);
2615         dataPage.put(newRowData);
2616 
2617         // write the overflow info into the header row and clear out the
2618         // remaining header data
2619         rowBuffer = PageChannel.narrowBuffer(
2620             headerPage,
2621             findRowStart(headerPage, headerRowId.getRowNumber(), getFormat()),
2622             findRowEnd(headerPage, headerRowId.getRowNumber(), getFormat()));
2623         rowBuffer.put((byte)rowNum);
2624         ByteUtil.put3ByteInt(rowBuffer, pageNumber);
2625         ByteUtil.clearRemaining(rowBuffer);
2626 
2627         // set the overflow flag on the header row
2628         int headerRowIndex = getRowStartOffset(headerRowId.getRowNumber(),
2629                                                getFormat());
2630         headerPage.putShort(headerRowIndex,
2631                             (short)(headerPage.getShort(headerRowIndex)
2632                                     | OVERFLOW_ROW_MASK));
2633         if(pageNumber != headerRowId.getPageNumber()) {
2634           writeDataPage(headerPage, headerRowId.getPageNumber());
2635         }
2636       }
2637 
2638       writeDataPage(dataPage, pageNumber);
2639 
2640       updateTableDefinition(0);
2641 
2642     } finally {
2643       getPageChannel().finishWrite();
2644     }
2645 
2646     return row;
2647   }
2648 
2649   private ByteBuffer findFreeRowSpace(int rowSize, ByteBuffer dataPage,
2650                                       int pageNumber)
2651     throws IOException
2652   {
2653     // assume incoming page is modified
2654     boolean modifiedPage = true;
2655 
2656     if(dataPage == null) {
2657 
2658       // find owned page w/ free space
2659       dataPage = findFreeRowSpace(_ownedPages, _freeSpacePages,
2660                                   _addRowBufferH);
2661 
2662       if(dataPage == null) {
2663         // No data pages exist (with free space).  Create a new one.
2664         return newDataPage();
2665       }
2666 
2667       // found a page, see if it will work
2668       pageNumber = _addRowBufferH.getPageNumber();
2669       // since we just loaded this page, it is not yet modified
2670       modifiedPage = false;
2671     }
2672 
2673     if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
2674 
2675       // Last data page is full.  Write old one and create a new one.
2676       if(modifiedPage) {
2677         writeDataPage(dataPage, pageNumber);
2678       }
2679       _freeSpacePages.removePageNumber(pageNumber);
2680 
2681       dataPage = newDataPage();
2682     }
2683 
2684     return dataPage;
2685   }
2686 
2687   static ByteBuffer findFreeRowSpace(
2688       UsageMap./../../com/healthmarketscience/jackcess/impl/UsageMap.html#UsageMap">UsageMap ownedPages, UsageMap freeSpacePages,
2689       TempPageHolder rowBufferH)
2690     throws IOException
2691   {
2692     // find last data page (Not bothering to check other pages for free
2693     // space.)
2694     UsageMap.PageCursor revPageCursor = ownedPages.cursor();
2695     revPageCursor.afterLast();
2696     while(true) {
2697       int tmpPageNumber = revPageCursor.getPreviousPage();
2698       if(tmpPageNumber < 0) {
2699         break;
2700       }
2701       // only use if actually listed in free space pages
2702       if(!freeSpacePages.containsPageNumber(tmpPageNumber)) {
2703         continue;
2704       }
2705       ByteBuffer dataPage = rowBufferH.setPage(ownedPages.getPageChannel(),
2706                                                tmpPageNumber);
2707       if(dataPage.get() == PageTypes.DATA) {
2708         // found last data page with free space
2709         return dataPage;
2710       }
2711     }
2712 
2713     return null;
2714   }
2715 
2716   /**
2717    * Updates the table definition after rows are modified.
2718    */
2719   private void updateTableDefinition(int rowCountInc) throws IOException
2720   {
2721     // load table definition
2722     ByteBuffer tdefPage = _tableDefBufferH.setPage(getPageChannel(),
2723                                                    _tableDefPageNumber);
2724 
2725     // make sure rowcount and autonumber are up-to-date
2726     _rowCount += rowCountInc;
2727     tdefPage.putInt(getFormat().OFFSET_NUM_ROWS, _rowCount);
2728     tdefPage.putInt(getFormat().OFFSET_NEXT_AUTO_NUMBER, _lastLongAutoNumber);
2729     int ctypeOff = getFormat().OFFSET_NEXT_COMPLEX_AUTO_NUMBER;
2730     if(ctypeOff >= 0) {
2731       tdefPage.putInt(ctypeOff, _lastComplexTypeAutoNumber);
2732     }
2733 
2734     // write any index changes
2735     for (IndexData indexData : _indexDatas) {
2736       // write the unique entry count for the index to the table definition
2737       // page
2738       tdefPage.putInt(indexData.getUniqueEntryCountOffset(),
2739                       indexData.getUniqueEntryCount());
2740       // write the entry page for the index
2741       indexData.update();
2742     }
2743 
2744     // write modified table definition
2745     getPageChannel().writePage(tdefPage, _tableDefPageNumber);
2746   }
2747 
2748   /**
2749    * Create a new data page
2750    * @return Page number of the new page
2751    */
2752   private ByteBuffer newDataPage() throws IOException {
2753     ByteBuffer dataPage = _addRowBufferH.setNewPage(getPageChannel());
2754     dataPage.put(PageTypes.DATA); //Page type
2755     dataPage.put((byte) 1); //Unknown
2756     dataPage.putShort((short)getFormat().DATA_PAGE_INITIAL_FREE_SPACE); //Free space in this page
2757     dataPage.putInt(_tableDefPageNumber); //Page pointer to table definition
2758     dataPage.putInt(0); //Unknown
2759     dataPage.putShort((short)0); //Number of rows on this page
2760     int pageNumber = _addRowBufferH.getPageNumber();
2761     getPageChannel().writePage(dataPage, pageNumber);
2762     _ownedPages.addPageNumber(pageNumber);
2763     _freeSpacePages.addPageNumber(pageNumber);
2764     return dataPage;
2765   }
2766 
2767   // exposed for unit tests
2768   protected ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer)
2769     throws IOException
2770   {
2771     return createRow(rowArray, buffer, 0,
2772                      Collections.<ColumnImpl,byte[]>emptyMap());
2773   }
2774 
2775   /**
2776    * Serialize a row of Objects into a byte buffer.
2777    *
2778    * @param rowArray row data, expected to be correct length for this table
2779    * @param buffer buffer to which to write the row data
2780    * @param minRowSize min size for result row
2781    * @param rawVarValues optional, pre-written values for var length columns
2782    *                     (enables re-use of previously written values).
2783    * @return the given buffer, filled with the row data
2784    */
2785   private ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer,
2786                                int minRowSize,
2787                                Map<ColumnImpl,byte[]> rawVarValues)
2788     throws IOException
2789   {
2790     buffer.putShort(_maxColumnCount);
2791     NullMask/impl/NullMask.html#NullMask">NullMask nullMask = new NullMask(_maxColumnCount);
2792 
2793     //Fixed length column data comes first
2794     int fixedDataStart = buffer.position();
2795     int fixedDataEnd = fixedDataStart;
2796     for (ColumnImpl col : _columns) {
2797 
2798       if(col.isVariableLength()) {
2799         continue;
2800       }
2801 
2802       Object rowValue = col.getRowValue(rowArray);
2803 
2804       if (col.storeInNullMask()) {
2805 
2806         if(col.writeToNullMask(rowValue)) {
2807           nullMask.markNotNull(col);
2808         }
2809         rowValue = null;
2810       }
2811 
2812       if(rowValue != null) {
2813 
2814         // we have a value to write
2815         nullMask.markNotNull(col);
2816 
2817         // remainingRowLength is ignored when writing fixed length data
2818         buffer.position(fixedDataStart + col.getFixedDataOffset());
2819         buffer.put(col.write(rowValue, 0));
2820       }
2821 
2822       // always insert space for the entire fixed data column length
2823       // (including null values), access expects the row to always be at least
2824       // big enough to hold all fixed values
2825       buffer.position(fixedDataStart + col.getFixedDataOffset() +
2826                       col.getLength());
2827 
2828       // keep track of the end of fixed data
2829       if(buffer.position() > fixedDataEnd) {
2830         fixedDataEnd = buffer.position();
2831       }
2832 
2833     }
2834 
2835     // reposition at end of fixed data
2836     buffer.position(fixedDataEnd);
2837 
2838     // only need this info if this table contains any var length data
2839     if(_maxVarColumnCount > 0) {
2840 
2841       int maxRowSize = getFormat().MAX_ROW_SIZE;
2842 
2843       // figure out how much space remains for var length data.  first,
2844       // account for already written space
2845       maxRowSize -= buffer.position();
2846       // now, account for trailer space
2847       int trailerSize = (nullMask.byteSize() + 4 + (_maxVarColumnCount * 2));
2848       maxRowSize -= trailerSize;
2849 
2850       // for each non-null long value column we need to reserve a small
2851       // amount of space so that we don't end up running out of row space
2852       // later by being too greedy
2853       for (ColumnImpl varCol : _varColumns) {
2854         if((varCol.getType().isLongValue()) &&
2855            (varCol.getRowValue(rowArray) != null)) {
2856           maxRowSize -= getFormat().SIZE_LONG_VALUE_DEF;
2857         }
2858       }
2859 
2860       //Now write out variable length column data
2861       short[] varColumnOffsets = new short[_maxVarColumnCount];
2862       int varColumnOffsetsIndex = 0;
2863       for (ColumnImpl varCol : _varColumns) {
2864         short offset = (short) buffer.position();
2865         Object rowValue = varCol.getRowValue(rowArray);
2866         if (rowValue != null) {
2867           // we have a value
2868           nullMask.markNotNull(varCol);
2869 
2870           byte[] rawValue = null;
2871           ByteBuffer varDataBuf = null;
2872           if(((rawValue = rawVarValues.get(varCol)) != null) &&
2873              (rawValue.length <= maxRowSize)) {
2874             // save time and potentially db space, re-use raw value
2875             varDataBuf = ByteBuffer.wrap(rawValue);
2876           } else {
2877             // write column value
2878             varDataBuf = varCol.write(rowValue, maxRowSize);
2879           }
2880 
2881           maxRowSize -= varDataBuf.remaining();
2882           if(varCol.getType().isLongValue()) {
2883             // we already accounted for some amount of the long value data
2884             // above.  add that space back so we don't double count
2885             maxRowSize += getFormat().SIZE_LONG_VALUE_DEF;
2886           }
2887           try {
2888             buffer.put(varDataBuf);
2889           } catch(BufferOverflowException e) {
2890             // if the data is too big for the buffer, then we have gone over
2891             // the max row size
2892             throw createTooLargeException(buffer.limit());
2893           }
2894         }
2895 
2896         // we do a loop here so that we fill in offsets for deleted columns
2897         while(varColumnOffsetsIndex <= varCol.getVarLenTableIndex()) {
2898           varColumnOffsets[varColumnOffsetsIndex++] = offset;
2899         }
2900       }
2901 
2902       // fill in offsets for any remaining deleted columns
2903       while(varColumnOffsetsIndex < varColumnOffsets.length) {
2904         varColumnOffsets[varColumnOffsetsIndex++] = (short) buffer.position();
2905       }
2906 
2907       // record where we stopped writing
2908       int eod = buffer.position();
2909 
2910       if(buffer.remaining() < trailerSize) {
2911         // we don't have enough space left for required trailer
2912         throw createTooLargeException(eod + trailerSize);
2913       }
2914 
2915       // insert padding if necessary
2916       padRowBuffer(buffer, minRowSize, trailerSize);
2917 
2918       buffer.putShort((short) eod); //EOD marker
2919 
2920       //Now write out variable length offsets
2921       //Offsets are stored in reverse order
2922       for (int i = _maxVarColumnCount - 1; i >= 0; i--) {
2923         buffer.putShort(varColumnOffsets[i]);
2924       }
2925       buffer.putShort(_maxVarColumnCount);  //Number of var length columns
2926 
2927     } else {
2928 
2929       // insert padding for row w/ no var cols
2930       padRowBuffer(buffer, minRowSize, nullMask.byteSize());
2931     }
2932 
2933     nullMask.write(buffer);  //Null mask
2934     buffer.flip();
2935     return buffer;
2936   }
2937 
2938   private InvalidValueException createTooLargeException(int size) {
2939     return new InvalidValueException(
2940         withErrorContext(
2941             "Row size " + size + " is too large (max " +
2942             getFormat().MAX_ROW_SIZE + ")"));
2943   }
2944 
2945   /**
2946    * Fill in all autonumber column values for add.
2947    */
2948   private void handleAutoNumbersForAdd(Object[] row, WriteRowState writeRowState)
2949     throws IOException
2950   {
2951     if(_autoNumColumns.isEmpty()) {
2952       return;
2953     }
2954 
2955     boolean enableInsert = isAllowAutoNumberInsert();
2956     writeRowState.resetAutoNumber();
2957     for(ColumnImpl col : _autoNumColumns) {
2958 
2959       // ignore input row value, use original row value (unless explicitly
2960       // enabled)
2961       Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
2962 
2963       ColumnImpl.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator();
2964       Object rowValue = ((inRowValue == null) ?
2965                          autoNumGen.getNext(writeRowState) :
2966                          autoNumGen.handleInsert(writeRowState, inRowValue));
2967 
2968       col.setRowValue(row, rowValue);
2969     }
2970   }
2971 
2972   /**
2973    * Fill in all autonumber column values for update.
2974    */
2975   private void handleAutoNumbersForUpdate(Object[] row, ByteBuffer rowBuffer,
2976                                           RowState rowState)
2977     throws IOException
2978   {
2979     if(_autoNumColumns.isEmpty()) {
2980       return;
2981     }
2982 
2983     boolean enableInsert = isAllowAutoNumberInsert();
2984     rowState.resetAutoNumber();
2985     for(ColumnImpl col : _autoNumColumns) {
2986 
2987       // ignore input row value, use original row value (unless explicitly
2988       // enabled)
2989       Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
2990 
2991       Object rowValue =
2992         ((inRowValue == null) ?
2993          getRowColumn(getFormat(), rowBuffer, col, rowState, null) :
2994          col.getAutoNumberGenerator().handleInsert(rowState, inRowValue));
2995 
2996       col.setRowValue(row, rowValue);
2997     }
2998   }
2999 
3000   /**
3001    * Optionally get the input autonumber row value for the given column from
3002    * the given row if one was provided.
3003    */
3004   private static Object getInputAutoNumberRowValue(
3005       boolean enableInsert, ColumnImpl col, Object[] row)
3006   {
3007     if(!enableInsert) {
3008       return null;
3009     }
3010 
3011     Object inRowValue = col.getRowValue(row);
3012     if((inRowValue == Column.KEEP_VALUE) || (inRowValue == Column.AUTO_NUMBER)) {
3013       // these "special" values both behave like nothing was given
3014       inRowValue = null;
3015     }
3016     return inRowValue;
3017   }
3018 
3019   /**
3020    * Restores all autonumber column values from a failed add row.
3021    */
3022   private void restoreAutoNumbersFromAdd(Object[] row)
3023   {
3024     if(_autoNumColumns.isEmpty()) {
3025       return;
3026     }
3027 
3028     for(ColumnImpl col : _autoNumColumns) {
3029       // restore the last value from the row
3030       col.getAutoNumberGenerator().restoreLast(col.getRowValue(row));
3031     }
3032   }
3033 
3034   private static void padRowBuffer(ByteBuffer buffer, int minRowSize,
3035                                    int trailerSize)
3036   {
3037     int pos = buffer.position();
3038     if((pos + trailerSize) < minRowSize) {
3039       // pad the row to get to the min byte size
3040       int padSize = minRowSize - (pos + trailerSize);
3041       ByteUtil.clearRange(buffer, pos, pos + padSize);
3042       ByteUtil.forward(buffer, padSize);
3043     }
3044   }
3045 
3046   @Override
3047   public int getRowCount() {
3048     return _rowCount;
3049   }
3050 
3051   int getNextLongAutoNumber() {
3052     // note, the saved value is the last one handed out, so pre-increment
3053     return ++_lastLongAutoNumber;
3054   }
3055 
3056   int getLastLongAutoNumber() {
3057     // gets the last used auto number (does not modify)
3058     return _lastLongAutoNumber;
3059   }
3060 
3061   void adjustLongAutoNumber(int inLongAutoNumber) {
3062     if(inLongAutoNumber > _lastLongAutoNumber) {
3063       _lastLongAutoNumber = inLongAutoNumber;
3064     }
3065   }
3066 
3067   void restoreLastLongAutoNumber(int lastLongAutoNumber) {
3068     // restores the last used auto number
3069     _lastLongAutoNumber = lastLongAutoNumber - 1;
3070   }
3071 
3072   int getNextComplexTypeAutoNumber() {
3073     // note, the saved value is the last one handed out, so pre-increment
3074     return ++_lastComplexTypeAutoNumber;
3075   }
3076 
3077   int getLastComplexTypeAutoNumber() {
3078     // gets the last used auto number (does not modify)
3079     return _lastComplexTypeAutoNumber;
3080   }
3081 
3082   void adjustComplexTypeAutoNumber(int inComplexTypeAutoNumber) {
3083     if(inComplexTypeAutoNumber > _lastComplexTypeAutoNumber) {
3084       _lastComplexTypeAutoNumber = inComplexTypeAutoNumber;
3085     }
3086   }
3087 
3088   void restoreLastComplexTypeAutoNumber(int lastComplexTypeAutoNumber) {
3089     // restores the last used auto number
3090     _lastComplexTypeAutoNumber = lastComplexTypeAutoNumber - 1;
3091   }
3092 
3093   @Override
3094   public String toString() {
3095     return CustomToStringStyle.builder(this)
3096       .append("type", (_tableType + (!isSystem() ? " (USER)" : " (SYSTEM)")))
3097       .append("name", _name)
3098       .append("rowCount", _rowCount)
3099       .append("columnCount", _columns.size())
3100       .append("indexCount(data)", _indexCount)
3101       .append("logicalIndexCount", _logicalIndexCount)
3102       .append("validator", CustomToStringStyle.ignoreNull(_rowValidator))
3103       .append("columns", _columns)
3104       .append("indexes", _indexes)
3105       .append("ownedPages", _ownedPages)
3106       .toString();
3107   }
3108 
3109   /**
3110    * @return A simple String representation of the entire table in
3111    *         tab-delimited format
3112    * @usage _general_method_
3113    */
3114   public String display() throws IOException {
3115     return display(Long.MAX_VALUE);
3116   }
3117 
3118   /**
3119    * @param limit Maximum number of rows to display
3120    * @return A simple String representation of the entire table in
3121    *         tab-delimited format
3122    * @usage _general_method_
3123    */
3124   public String display(long limit) throws IOException {
3125     reset();
3126     StringWriter rtn = new StringWriter();
3127     new ExportUtil.Builder(getDefaultCursor()).setDelimiter("\t").setHeader(true)
3128       .exportWriter(new BufferedWriter(rtn));
3129     return rtn.toString();
3130   }
3131 
3132   /**
3133    * Updates free space and row info for a new row of the given size in the
3134    * given data page.  Positions the page for writing the row data.
3135    * @return the row number of the new row
3136    * @usage _advanced_method_
3137    */
3138   public static int addDataPageRow(ByteBuffer dataPage,
3139                                    int rowSize,
3140                                    JetFormat format,
3141                                    int rowFlags)
3142   {
3143     int rowSpaceUsage = getRowSpaceUsage(rowSize, format);
3144 
3145     // Decrease free space record.
3146     short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
3147     dataPage.putShort(format.OFFSET_FREE_SPACE, (short) (freeSpaceInPage -
3148                                                          rowSpaceUsage));
3149 
3150     // Increment row count record.
3151     short rowCount = dataPage.getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
3152     dataPage.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
3153                       (short) (rowCount + 1));
3154 
3155     // determine row position
3156     short rowLocation = findRowEnd(dataPage, rowCount, format);
3157     rowLocation -= rowSize;
3158 
3159     // write row position
3160     dataPage.putShort(getRowStartOffset(rowCount, format),
3161                       (short)(rowLocation | rowFlags));
3162 
3163     // set position for row data
3164     dataPage.position(rowLocation);
3165 
3166     return rowCount;
3167   }
3168 
3169   /**
3170    * Returns the row count for the current page.  If the page is invalid
3171    * ({@code null}) or the page is not a DATA page, 0 is returned.
3172    */
3173   static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
3174   {
3175     int rowsOnPage = 0;
3176     if((rowBuffer != null) && (rowBuffer.get(0) == PageTypes.DATA)) {
3177       rowsOnPage = rowBuffer.getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
3178     }
3179     return rowsOnPage;
3180   }
3181 
3182   /**
3183    * @throws IllegalStateException if the given rowId is invalid
3184    */
3185   private void requireValidRowId(RowIdImpl rowId) {
3186     if(!rowId.isValid()) {
3187       throw new IllegalArgumentException(withErrorContext(
3188               "Given rowId is invalid: " + rowId));
3189     }
3190   }
3191 
3192   /**
3193    * @throws IllegalStateException if the given row is invalid or deleted
3194    */
3195   private void requireNonDeletedRow(RowState rowState, RowIdImpl rowId)
3196   {
3197     if(!rowState.isValid()) {
3198       throw new IllegalArgumentException(withErrorContext(
3199           "Given rowId is invalid for this table: " + rowId));
3200     }
3201     if(rowState.isDeleted()) {
3202       throw new IllegalStateException(withErrorContext(
3203           "Row is deleted: " + rowId));
3204     }
3205   }
3206 
3207   /**
3208    * @usage _advanced_method_
3209    */
3210   public static boolean isDeletedRow(short rowStart) {
3211     return ((rowStart & DELETED_ROW_MASK) != 0);
3212   }
3213 
3214   /**
3215    * @usage _advanced_method_
3216    */
3217   public static boolean isOverflowRow(short rowStart) {
3218     return ((rowStart & OVERFLOW_ROW_MASK) != 0);
3219   }
3220 
3221   /**
3222    * @usage _advanced_method_
3223    */
3224   public static short cleanRowStart(short rowStart) {
3225     return (short)(rowStart & OFFSET_MASK);
3226   }
3227 
3228   /**
3229    * @usage _advanced_method_
3230    */
3231   public static short findRowStart(ByteBuffer buffer, int rowNum,
3232                                    JetFormat format)
3233   {
3234     return cleanRowStart(
3235         buffer.getShort(getRowStartOffset(rowNum, format)));
3236   }
3237 
3238   /**
3239    * @usage _advanced_method_
3240    */
3241   public static int getRowStartOffset(int rowNum, JetFormat format)
3242   {
3243     return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * rowNum);
3244   }
3245 
3246   /**
3247    * @usage _advanced_method_
3248    */
3249   public static short findRowEnd(ByteBuffer buffer, int rowNum,
3250                                  JetFormat format)
3251   {
3252     return (short)((rowNum == 0) ?
3253                    format.PAGE_SIZE :
3254                    cleanRowStart(
3255                        buffer.getShort(getRowEndOffset(rowNum, format))));
3256   }
3257 
3258   /**
3259    * @usage _advanced_method_
3260    */
3261   public static int getRowEndOffset(int rowNum, JetFormat format)
3262   {
3263     return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * (rowNum - 1));
3264   }
3265 
3266   /**
3267    * @usage _advanced_method_
3268    */
3269   public static int getRowSpaceUsage(int rowSize, JetFormat format)
3270   {
3271     return rowSize + format.SIZE_ROW_LOCATION;
3272   }
3273 
3274   private void initAutoNumberColumns() {
3275     for(ColumnImpl c : _columns) {
3276       if(c.isAutoNumber()) {
3277         _autoNumColumns.add(c);
3278       }
3279     }
3280   }
3281 
3282   private void initCalculatedColumns() {
3283     for(ColumnImpl c : _columns) {
3284       if(c.isCalculated()) {
3285         _calcColEval.add(c);
3286       }
3287     }
3288   }
3289 
3290   boolean isThisTable(Identifier identifier) {
3291     String collectionName = identifier.getCollectionName();
3292     return ((collectionName == null) ||
3293             collectionName.equalsIgnoreCase(getName()));
3294   }
3295 
3296   /**
3297    * Returns {@code true} if a row of the given size will fit on the given
3298    * data page, {@code false} otherwise.
3299    * @usage _advanced_method_
3300    */
3301   public static boolean rowFitsOnDataPage(
3302       int rowLength, ByteBuffer dataPage, JetFormat format)
3303   {
3304     int rowSpaceUsage = getRowSpaceUsage(rowLength, format);
3305     short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
3306     int rowsOnPage = getRowsOnDataPage(dataPage, format);
3307     return ((rowSpaceUsage <= freeSpaceInPage) &&
3308             (rowsOnPage < format.MAX_NUM_ROWS_ON_DATA_PAGE));
3309   }
3310 
3311   /**
3312    * Duplicates and returns a row of data, optionally with a longer length
3313    * filled with {@code null}.
3314    */
3315   static Object[] dupeRow(Object[] row, int newRowLength) {
3316     Object[] copy = new Object[newRowLength];
3317     System.arraycopy(row, 0, copy, 0, Math.min(row.length, newRowLength));
3318     return copy;
3319   }
3320 
3321   String withErrorContext(String msg) {
3322     return withErrorContext(msg, getDatabase(), getName());
3323   }
3324 
3325   private static String withErrorContext(String msg, DatabaseImpl db,
3326                                          String tableName) {
3327     return msg + " (Db=" + db.getName() + ";Table=" + tableName + ")";
3328   }
3329 
3330   /** various statuses for the row data */
3331   private enum RowStatus {
3332     INIT, INVALID_PAGE, INVALID_ROW, VALID, DELETED, NORMAL, OVERFLOW;
3333   }
3334 
3335   /** the phases the RowState moves through as the data is parsed */
3336   private enum RowStateStatus {
3337     INIT, AT_HEADER, AT_FINAL;
3338   }
3339 
3340   /**
3341    * Maintains state for writing a new row of data.
3342    */
3343   protected static class WriteRowState
3344   {
3345     private int _complexAutoNumber = ColumnImpl.INVALID_AUTO_NUMBER;
3346 
3347     public int getComplexAutoNumber() {
3348       return _complexAutoNumber;
3349     }
3350 
3351     public void setComplexAutoNumber(int complexAutoNumber) {
3352       _complexAutoNumber = complexAutoNumber;
3353     }
3354 
3355     public void resetAutoNumber() {
3356       _complexAutoNumber = ColumnImpl.INVALID_AUTO_NUMBER;
3357     }
3358   }
3359 
3360   /**
3361    * Maintains the state of reading/updating a row of data.
3362    * @usage _advanced_class_
3363    */
3364   public final class RowState extends WriteRowState
3365     implements ErrorHandler.Location
3366   {
3367     /** Buffer used for reading the header row data pages */
3368     private final TempPageHolder _headerRowBufferH;
3369     /** the header rowId */
3370     private RowIdImpl _headerRowId = RowIdImpl.FIRST_ROW_ID;
3371     /** the number of rows on the header page */
3372     private int _rowsOnHeaderPage;
3373     /** the rowState status */
3374     private RowStateStatus _status = RowStateStatus.INIT;
3375     /** the row status */
3376     private RowStatus _rowStatus = RowStatus.INIT;
3377     /** buffer used for reading overflow pages */
3378     private final TempPageHolder _overflowRowBufferH =
3379       TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
3380     /** the row buffer which contains the final data (after following any
3381         overflow pointers) */
3382     private ByteBuffer _finalRowBuffer;
3383     /** the rowId which contains the final data (after following any overflow
3384         pointers) */
3385     private RowIdImpl _finalRowId = null;
3386     /** true if the row values array has data */
3387     private boolean _haveRowValues;
3388     /** values read from the last row */
3389     private Object[] _rowValues;
3390     /** null mask for the last row */
3391     private NullMask _nullMask;
3392     /** last modification count seen on the table we track this so that the
3393         rowState can detect updates to the table and re-read any buffered
3394         data */
3395     private int _lastModCount;
3396     /** optional error handler to use when row errors are encountered */
3397     private ErrorHandler _errorHandler;
3398     /** cached variable column offsets for jump-table based rows */
3399     private short[] _varColOffsets;
3400 
3401     private RowState(TempBufferHolder.Type headerType) {
3402       _headerRowBufferH = TempPageHolder.newHolder(headerType);
3403       _rowValues = new Object[TableImpl.this.getColumnCount()];
3404       _lastModCount = TableImpl.this._modCount;
3405     }
3406 
3407     @Override
3408     public TableImpl getTable() {
3409       return TableImpl.this;
3410     }
3411 
3412     public ErrorHandler getErrorHandler() {
3413       return((_errorHandler != null) ? _errorHandler :
3414              getTable().getErrorHandler());
3415     }
3416 
3417     public void setErrorHandler(ErrorHandler newErrorHandler) {
3418       _errorHandler = newErrorHandler;
3419     }
3420 
3421     public void reset() {
3422       resetAutoNumber();
3423       _finalRowId = null;
3424       _finalRowBuffer = null;
3425       _rowsOnHeaderPage = 0;
3426       _status = RowStateStatus.INIT;
3427       _rowStatus = RowStatus.INIT;
3428       _varColOffsets = null;
3429       _nullMask = null;
3430       if(_haveRowValues) {
3431         Arrays.fill(_rowValues, null);
3432         _haveRowValues = false;
3433       }
3434     }
3435 
3436     public boolean isUpToDate() {
3437       return(TableImpl.this._modCount == _lastModCount);
3438     }
3439 
3440     private void checkForModification() {
3441       if(!isUpToDate()) {
3442         reset();
3443         _headerRowBufferH.invalidate();
3444         _overflowRowBufferH.invalidate();
3445         int colCount = TableImpl.this.getColumnCount();
3446         if(colCount != _rowValues.length) {
3447           // columns added or removed from table
3448           _rowValues = new Object[colCount];
3449         }
3450         _lastModCount = TableImpl.this._modCount;
3451       }
3452     }
3453 
3454     private ByteBuffer getFinalPage()
3455       throws IOException
3456     {
3457       if(_finalRowBuffer == null) {
3458         // (re)load current page
3459         _finalRowBuffer = getHeaderPage();
3460       }
3461       return _finalRowBuffer;
3462     }
3463 
3464     public RowIdImpl getFinalRowId() {
3465       if(_finalRowId == null) {
3466         _finalRowId = getHeaderRowId();
3467       }
3468       return _finalRowId;
3469     }
3470 
3471     private void setRowStatus(RowStatus rowStatus) {
3472       _rowStatus = rowStatus;
3473     }
3474 
3475     public boolean isValid() {
3476       return(_rowStatus.ordinal() >= RowStatus.VALID.ordinal());
3477     }
3478 
3479     public boolean isDeleted() {
3480       return(_rowStatus == RowStatus.DELETED);
3481     }
3482 
3483     public boolean isOverflow() {
3484       return(_rowStatus == RowStatus.OVERFLOW);
3485     }
3486 
3487     public boolean isHeaderPageNumberValid() {
3488       return(_rowStatus.ordinal() > RowStatus.INVALID_PAGE.ordinal());
3489     }
3490 
3491     public boolean isHeaderRowNumberValid() {
3492       return(_rowStatus.ordinal() > RowStatus.INVALID_ROW.ordinal());
3493     }
3494 
3495     private void setStatus(RowStateStatus status) {
3496       _status = status;
3497     }
3498 
3499     public boolean isAtHeaderRow() {
3500       return(_status.ordinal() >= RowStateStatus.AT_HEADER.ordinal());
3501     }
3502 
3503     public boolean isAtFinalRow() {
3504       return(_status.ordinal() >= RowStateStatus.AT_FINAL.ordinal());
3505     }
3506 
3507     private Object setRowCacheValue(int idx, Object value) {
3508       _haveRowValues = true;
3509       _rowValues[idx] = value;
3510       return value;
3511     }
3512 
3513     private Object getRowCacheValue(int idx) {
3514       Object value = _rowValues[idx];
3515       // only return immutable values.  mutable values could have been
3516       // modified externally and therefore could return an incorrect value
3517       return(ColumnImpl.isImmutableValue(value) ? value : null);
3518     }
3519 
3520     public Object[] getRowCacheValues() {
3521       return dupeRow(_rowValues, _rowValues.length);
3522     }
3523 
3524     public NullMask getNullMask(ByteBuffer rowBuffer) {
3525       if(_nullMask == null) {
3526         _nullMask = getRowNullMask(rowBuffer);
3527       }
3528       return _nullMask;
3529     }
3530 
3531     private short[] getVarColOffsets() {
3532       return _varColOffsets;
3533     }
3534 
3535     private void setVarColOffsets(short[] varColOffsets) {
3536       _varColOffsets = varColOffsets;
3537     }
3538 
3539     public RowIdImpl getHeaderRowId() {
3540       return _headerRowId;
3541     }
3542 
3543     public int getRowsOnHeaderPage() {
3544       return _rowsOnHeaderPage;
3545     }
3546 
3547     private ByteBuffer getHeaderPage()
3548       throws IOException
3549     {
3550       checkForModification();
3551       return _headerRowBufferH.getPage(getPageChannel());
3552     }
3553 
3554     private ByteBuffer setHeaderRow(RowIdImpl rowId)
3555       throws IOException
3556     {
3557       checkForModification();
3558 
3559       // don't do any work if we are already positioned correctly
3560       if(isAtHeaderRow() && (getHeaderRowId().equals(rowId))) {
3561         return(isValid() ? getHeaderPage() : null);
3562       }
3563 
3564       // rejigger everything
3565       reset();
3566       _headerRowId = rowId;
3567       _finalRowId = rowId;
3568 
3569       int pageNumber = rowId.getPageNumber();
3570       int rowNumber = rowId.getRowNumber();
3571       if((pageNumber < 0) || !_ownedPages.containsPageNumber(pageNumber)) {
3572         setRowStatus(RowStatus.INVALID_PAGE);
3573         return null;
3574       }
3575 
3576       _finalRowBuffer = _headerRowBufferH.setPage(getPageChannel(),
3577                                                   pageNumber);
3578       _rowsOnHeaderPage = getRowsOnDataPage(_finalRowBuffer, getFormat());
3579 
3580       if((rowNumber < 0) || (rowNumber >= _rowsOnHeaderPage)) {
3581         setRowStatus(RowStatus.INVALID_ROW);
3582         return null;
3583       }
3584 
3585       setRowStatus(RowStatus.VALID);
3586       return _finalRowBuffer;
3587     }
3588 
3589     private ByteBuffer setOverflowRow(RowIdImpl rowId)
3590       throws IOException
3591     {
3592       // this should never see modifications because it only happens within
3593       // the positionAtRowData method
3594       if(!isUpToDate()) {
3595         throw new IllegalStateException(getTable().withErrorContext(
3596                                             "Table modified while searching?"));
3597       }
3598       if(_rowStatus != RowStatus.OVERFLOW) {
3599         throw new IllegalStateException(getTable().withErrorContext(
3600                                             "Row is not an overflow row?"));
3601       }
3602       _finalRowId = rowId;
3603       _finalRowBuffer = _overflowRowBufferH.setPage(getPageChannel(),
3604                                                     rowId.getPageNumber());
3605       return _finalRowBuffer;
3606     }
3607 
3608     private Object handleRowError(ColumnImpl column, byte[] columnData,
3609                                   Exception error)
3610       throws IOException
3611     {
3612       return getErrorHandler().handleRowError(column, columnData,
3613                                               this, error);
3614     }
3615 
3616     @Override
3617     public String toString() {
3618       return CustomToStringStyle.valueBuilder(this)
3619         .append("headerRowId", _headerRowId)
3620         .append("finalRowId", _finalRowId)
3621         .toString();
3622     }
3623   }
3624 
3625   /**
3626    * Utility for managing calculated columns.  Calculated columns need to be
3627    * evaluated in dependency order.
3628    */
3629   private class CalcColEvaluator
3630   {
3631     /** List of calculated columns in this table, ordered by calculation
3632         dependency */
3633     private final List<ColumnImpl> _calcColumns = new ArrayList<ColumnImpl>(1);
3634     private boolean _sorted;
3635 
3636     public void add(ColumnImpl col) {
3637       if(!getDatabase().isEvaluateExpressions()) {
3638         return;
3639       }
3640       _calcColumns.add(col);
3641       // whenever we add new columns, we need to re-sort
3642       _sorted = false;
3643     }
3644 
3645     public void reSort() {
3646       // mark columns for re-sort on next use
3647       _sorted = false;
3648     }
3649 
3650     public void calculate(Object[] row) throws IOException {
3651       if(!_sorted) {
3652         sortColumnsByDeps();
3653         _sorted = true;
3654       }
3655 
3656       for(ColumnImpl col : _calcColumns) {
3657         Object rowValue = col.getCalculationContext().eval(row);
3658         col.setRowValue(row, rowValue);
3659       }
3660     }
3661 
3662     private void sortColumnsByDeps() {
3663 
3664       // a topological sort sorts nodes where A -> B such that A ends up in
3665       // the list before B (assuming that we are working with a DAG).  In our
3666       // case, we return "descendent" info as Field1 -> Field2 (where Field1
3667       // uses Field2 in its calculation).  This means that in order to
3668       // correctly calculate Field1, we need to calculate Field2 first, and
3669       // hence essentially need the reverse topo sort (a list where Field2
3670       // comes before Field1).
3671       (new TopoSorter<ColumnImpl>(_calcColumns, TopoSorter.REVERSE) {
3672         @Override
3673         protected void getDescendents(ColumnImpl from,
3674                                       List<ColumnImpl> descendents) {
3675 
3676           Set<Identifier> identifiers = new LinkedHashSet<Identifier>();
3677           from.getCalculationContext().collectIdentifiers(identifiers);
3678 
3679           for(Identifier identifier : identifiers) {
3680             if(isThisTable(identifier)) {
3681               String colName = identifier.getObjectName();
3682               for(ColumnImpl calcCol : _calcColumns) {
3683                 // we only care if the identifier is another calc field
3684                 if(calcCol.getName().equalsIgnoreCase(colName)) {
3685                   descendents.add(calcCol);
3686                 }
3687               }
3688             }
3689           }
3690         }
3691       }).sort();
3692     }
3693   }
3694 }