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