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