View Javadoc

1   /*
2   Copyright (c) 2005 Health Market Science, Inc.
3   
4   This library is free software; you can redistribute it and/or
5   modify it under the terms of the GNU Lesser General Public
6   License as published by the Free Software Foundation; either
7   version 2.1 of the License, or (at your option) any later version.
8   
9   This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  Lesser General Public License for more details.
13  
14  You should have received a copy of the GNU Lesser General Public
15  License along with this library; if not, write to the Free Software
16  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
17  USA
18  
19  You can contact Health Market Science at info@healthmarketscience.com
20  or at the following address:
21  
22  Health Market Science
23  2700 Horizon Drive
24  Suite 200
25  King of Prussia, PA 19406
26  */
27  
28  package com.healthmarketscience.jackcess;
29  
30  import java.io.IOException;
31  import java.nio.ByteBuffer;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.Collections;
36  import java.util.Comparator;
37  import java.util.Iterator;
38  import java.util.LinkedHashMap;
39  import java.util.List;
40  import java.util.Map;
41  
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  
45  /**
46   * A single database table
47   * <p>
48   * Is not thread-safe.
49   * 
50   * @author Tim McCune
51   */
52  public class Table
53    implements Iterable<Map<String, Object>>
54  {
55    
56    private static final Log LOG = LogFactory.getLog(Table.class);
57  
58    private static final short OFFSET_MASK = (short)0x1FFF;
59  
60    private static final short DELETED_ROW_MASK = (short)0x8000;
61    
62    private static final short OVERFLOW_ROW_MASK = (short)0x4000;
63  
64    /** Table type code for system tables */
65    public static final byte TYPE_SYSTEM = 0x53;
66    /** Table type code for user tables */
67    public static final byte TYPE_USER = 0x4e;
68  
69    /** comparator which sorts variable length columns vased on their index into
70        the variable length offset table */
71    private static final Comparator<Column> VAR_LEN_COLUMN_COMPARATOR =
72      new Comparator<Column>() {
73        public int compare(Column c1, Column c2) {
74          return ((c1.getVarLenTableIndex() < c2.getVarLenTableIndex()) ? -1 :
75                  ((c1.getVarLenTableIndex() > c2.getVarLenTableIndex()) ? 1 :
76                   0));
77        }
78      };
79  
80    /** owning database */
81    private final Database _database;
82    /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */
83    private byte _tableType;
84    /** Number of indexes on the table */
85    private int _indexCount;
86    /** Number of index slots for the table */
87    private int _indexSlotCount;
88    /** Number of rows in the table */
89    private int _rowCount;
90    /** last auto number for the table */
91    private int _lastAutoNumber;
92    /** page number of the definition of this table */
93    private final int _tableDefPageNumber;
94    /** max Number of columns in the table (includes previous deletions) */
95    private short _maxColumnCount;
96    /** max Number of variable columns in the table */
97    private short _maxVarColumnCount;
98    /** List of columns in this table, ordered by column number */
99    private List<Column> _columns = new ArrayList<Column>();
100   /** List of variable length columns in this table, ordered by offset */
101   private List<Column> _varColumns = new ArrayList<Column>();
102   /** List of indexes on this table */
103   private List<Index> _indexes = new ArrayList<Index>();
104   /** Table name as stored in Database */
105   private final String _name;
106   /** Usage map of pages that this table owns */
107   private UsageMap _ownedPages;
108   /** Usage map of pages that this table owns with free space on them */
109   private UsageMap _freeSpacePages;
110   /** modification count for the table, keeps row-states up-to-date */
111   private int _modCount;
112   /** page buffer used to update data pages when adding rows */
113   private final TempPageHolder _addRowBufferH =
114     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
115   /** page buffer used to update the table def page */
116   private final TempPageHolder _tableDefBufferH =
117     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
118   /** buffer used to writing single rows of data */
119   private final TempBufferHolder _singleRowBufferH =
120     TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true);
121   /** "buffer" used to writing multi rows of data (will create new buffer on
122       every call) */
123   private final TempBufferHolder _multiRowBufferH =
124     TempBufferHolder.newHolder(TempBufferHolder.Type.NONE, true);
125   /** for now, "big index support" is optional */
126   private final boolean _useBigIndex;
127   
128   /** common cursor for iterating through the table, kept here for historic
129       reasons */
130   private Cursor _cursor;
131   
132   /**
133    * Only used by unit tests
134    */
135   Table(boolean testing, List<Column> columns) throws IOException {
136     if(!testing) {
137       throw new IllegalArgumentException();
138     }
139     _database = null;
140     _tableDefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
141     _name = null;
142     _useBigIndex = false;
143     setColumns(columns);
144   }
145   
146   /**
147    * @param database database which owns this table
148    * @param tableBuffer Buffer to read the table with
149    * @param pageNumber Page number of the table definition
150    * @param name Table name
151    * @param useBigIndex whether or not "big index support" should be enabled
152    *                    for the table
153    */
154   protected Table(Database database, ByteBuffer tableBuffer,
155                   int pageNumber, String name, boolean useBigIndex)
156   throws IOException
157   {
158     _database = database;
159     _tableDefPageNumber = pageNumber;
160     _name = name;
161     _useBigIndex = useBigIndex; 
162     int nextPage = tableBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
163     ByteBuffer nextPageBuffer = null;
164     while (nextPage != 0) {
165       if (nextPageBuffer == null) {
166         nextPageBuffer = getPageChannel().createPageBuffer();
167       }
168       getPageChannel().readPage(nextPageBuffer, nextPage);
169       nextPage = nextPageBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
170       ByteBuffer newBuffer = getPageChannel().createBuffer(
171           tableBuffer.capacity() + getFormat().PAGE_SIZE - 8);
172       newBuffer.put(tableBuffer);
173       newBuffer.put(nextPageBuffer.array(), 8, getFormat().PAGE_SIZE - 8);
174       tableBuffer = newBuffer;
175       tableBuffer.flip();
176     }
177     readTableDefinition(tableBuffer);
178     tableBuffer = null;
179 
180     // setup common cursor
181     _cursor = Cursor.createCursor(this);
182   }
183 
184   /**
185    * @return The name of the table
186    */
187   public String getName() {
188     return _name;
189   }
190 
191   public boolean doUseBigIndex() {
192     return _useBigIndex;
193   }
194   
195   public int getMaxColumnCount() {
196     return _maxColumnCount;
197   }
198   
199   public int getColumnCount() {
200     return _columns.size();
201   }
202   
203   public Database getDatabase() {
204     return _database;
205   }
206   
207   public JetFormat getFormat() {
208     return getDatabase().getFormat();
209   }
210 
211   public PageChannel getPageChannel() {
212     return getDatabase().getPageChannel();
213   }
214 
215   protected int getTableDefPageNumber() {
216     return _tableDefPageNumber;
217   }
218 
219   public RowState createRowState() {
220     return new RowState(TempBufferHolder.Type.HARD);
221   }
222 
223   protected UsageMap.PageCursor getOwnedPagesCursor() {
224     return _ownedPages.cursor();
225   }
226   
227   /**
228    * @return All of the columns in this table (unmodifiable List)
229    */
230   public List<Column> getColumns() {
231     return Collections.unmodifiableList(_columns);
232   }
233 
234   /**
235    * @return the column with the given name
236    */
237   public Column getColumn(String name) {
238     for(Column column : _columns) {
239       if(column.getName().equals(name)) {
240         return column;
241       }
242     }
243     throw new IllegalArgumentException("Column with name " + name +
244                                        " does not exist in this table");
245   }
246     
247   /**
248    * Only called by unit tests
249    */
250   private void setColumns(List<Column> columns) {
251     _columns = columns;
252     int colIdx = 0;
253     int varLenIdx = 0;
254     int fixedOffset = 0;
255     for(Column col : _columns) {
256       col.setColumnNumber((short)colIdx);
257       col.setColumnIndex(colIdx++);
258       if(col.isVariableLength()) {
259         col.setVarLenTableIndex(varLenIdx++);
260         _varColumns.add(col);
261       } else {
262         col.setFixedDataOffset(fixedOffset);
263         fixedOffset += col.getType().getFixedSize();
264       }
265     }
266     _maxColumnCount = (short)_columns.size();
267     _maxVarColumnCount = (short)_varColumns.size();
268   }
269   
270   /**
271    * @return All of the Indexes on this table (unmodifiable List)
272    */
273   public List<Index> getIndexes() {
274     return Collections.unmodifiableList(_indexes);
275   }
276 
277   /**
278    * @return the index with the given name
279    */
280   public Index getIndex(String name) {
281     for(Index index : _indexes) {
282       if(index.getName().equals(name)) {
283         return index;
284       }
285     }
286     throw new IllegalArgumentException("Index with name " + name +
287                                        " does not exist on this table");
288   }
289     
290   /**
291    * Only called by unit tests
292    */
293   int getIndexSlotCount() {
294     return _indexSlotCount;
295   }
296 
297   /**
298    * After calling this method, getNextRow will return the first row in the
299    * table
300    */
301   public void reset() {
302     _cursor.reset();
303   }
304   
305   /**
306    * Delete the current row (retrieved by a call to {@link #getNextRow()}).
307    */
308   public void deleteCurrentRow() throws IOException {
309     _cursor.deleteCurrentRow();
310   }
311 
312   /**
313    * Delete the row on which the given rowState is currently positioned.
314    */
315   public void deleteRow(RowState rowState, RowId rowId) throws IOException {
316     requireValidRowId(rowId);
317     
318     // ensure that the relevant row state is up-to-date
319     ByteBuffer rowBuffer = positionAtRowHeader(rowState, rowId);
320 
321     requireNonDeletedRow(rowState, rowId);
322     
323     // delete flag always gets set in the "header" row (even if data is on
324     // overflow row)
325     int pageNumber = rowState.getHeaderRowId().getPageNumber();
326     int rowNumber = rowState.getHeaderRowId().getRowNumber();
327 
328     // use any read rowValues to help update the indexes
329     Object[] rowValues = (!_indexes.isEmpty() ?
330                           rowState.getRowValues() : null);
331     
332     int rowIndex = getRowStartOffset(rowNumber, getFormat());
333     rowBuffer.putShort(rowIndex, (short)(rowBuffer.getShort(rowIndex)
334                                       | DELETED_ROW_MASK | OVERFLOW_ROW_MASK));
335     writeDataPage(rowBuffer, pageNumber);
336 
337     // update the indexes
338     for(Index index : _indexes) {
339       index.deleteRow(rowValues, rowId);
340     }
341     
342     // make sure table def gets updated
343     updateTableDefinition(-1);
344   }
345   
346   /**
347    * @return The next row in this table (Column name -> Column value)
348    */
349   public Map<String, Object> getNextRow() throws IOException {
350     return getNextRow(null);
351   }
352 
353   /**
354    * @param columnNames Only column names in this collection will be returned
355    * @return The next row in this table (Column name -> Column value)
356    */
357   public Map<String, Object> getNextRow(Collection<String> columnNames) 
358     throws IOException
359   {
360     return _cursor.getNextRow(columnNames);
361   }
362   
363   /**
364    * Reads a single column from the given row.
365    */
366   public Object getRowValue(RowState rowState, RowId rowId, Column column)
367     throws IOException
368   {
369     if(this != column.getTable()) {
370       throw new IllegalArgumentException(
371           "Given column " + column + " is not from this table");
372     }
373     requireValidRowId(rowId);
374     
375     // position at correct row
376     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
377     requireNonDeletedRow(rowState, rowId);
378     
379     Object value = getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column);
380 
381     // cache the row values in order to be able to update the index on row
382     // deletion.  note, most of the returned values are immutable, except
383     // for binary data (returned as byte[]), but binary data shouldn't be
384     // indexed anyway.
385     rowState.setRowValue(column.getColumnIndex(), value);
386 
387     return value;
388   }
389 
390   /**
391    * Reads some columns from the given row.
392    * @param columnNames Only column names in this collection will be returned
393    */
394   public Map<String, Object> getRow(
395       RowState rowState, RowId rowId, Collection<String> columnNames)
396     throws IOException
397   {
398     requireValidRowId(rowId);
399 
400     // position at correct row
401     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
402     requireNonDeletedRow(rowState, rowId);
403 
404     return getRow(rowState, rowBuffer, getRowNullMask(rowBuffer), _columns,
405                   columnNames);
406   }
407 
408   /**
409    * Reads the row data from the given row buffer.  Leaves limit unchanged.
410    * Saves parsed row values to the given rowState.
411    */
412   private static Map<String, Object> getRow(
413       RowState rowState,
414       ByteBuffer rowBuffer,
415       NullMask nullMask,
416       Collection<Column> columns,
417       Collection<String> columnNames)
418     throws IOException
419   {
420     Map<String, Object> rtn = new LinkedHashMap<String, Object>(
421         columns.size());
422     for(Column column : columns) {
423 
424       if((columnNames == null) || (columnNames.contains(column.getName()))) {
425         // Add the value to the row data
426         Object value = getRowColumn(rowBuffer, nullMask, column);
427         rtn.put(column.getName(), value);
428 
429         // cache the row values in order to be able to update the index on row
430         // deletion.  note, most of the returned values are immutable, except
431         // for binary data (returned as byte[]), but binary data shouldn't be
432         // indexed anyway.
433         rowState.setRowValue(column.getColumnIndex(), value);
434       }
435     }
436     return rtn;
437   }
438   
439   /**
440    * Reads the column data from the given row buffer.  Leaves limit unchanged.
441    */
442   private static Object getRowColumn(ByteBuffer rowBuffer,
443                                      NullMask nullMask,
444                                      Column column)
445     throws IOException
446   {
447     boolean isNull = nullMask.isNull(column);
448     if(column.getType() == DataType.BOOLEAN) {
449       return Boolean.valueOf(!isNull);  //Boolean values are stored in the null mask
450     } else if(isNull) {
451       // well, that's easy!
452       return null;
453     }
454 
455     // reset position to row start
456     rowBuffer.reset();
457     
458     // locate the column data bytes
459     int rowStart = rowBuffer.position();
460     int colDataPos = 0;
461     int colDataLen = 0;
462     if(!column.isVariableLength()) {
463 
464       // read fixed length value (non-boolean at this point)
465       int dataStart = rowStart + 2;
466       colDataPos = dataStart + column.getFixedDataOffset();
467       colDataLen = column.getType().getFixedSize();
468       
469     } else {
470 
471       // read var length value
472       int varColumnOffsetPos =
473         (rowBuffer.limit() - nullMask.byteSize() - 4) -
474         (column.getVarLenTableIndex() * 2);
475 
476       short varDataStart = rowBuffer.getShort(varColumnOffsetPos);
477       short varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);
478       colDataPos = rowStart + varDataStart;
479       colDataLen = varDataEnd - varDataStart;
480     }
481 
482     // grab the column data
483     byte[] columnData = new byte[colDataLen];
484     rowBuffer.position(colDataPos);
485     rowBuffer.get(columnData);
486 
487     // parse the column data
488     return column.read(columnData);
489   }
490 
491   /**
492    * Reads the null mask from the given row buffer.  Leaves limit unchanged.
493    */
494   private static NullMask getRowNullMask(ByteBuffer rowBuffer)
495     throws IOException
496   {
497     // reset position to row start
498     rowBuffer.reset();
499     
500     short columnCount = rowBuffer.getShort(); // Number of columns in this row
501     
502     // read null mask
503     NullMask nullMask = new NullMask(columnCount);
504     rowBuffer.position(rowBuffer.limit() - nullMask.byteSize());  //Null mask at end
505     nullMask.read(rowBuffer);
506 
507     return nullMask;
508   }
509 
510   /**
511    * Sets a new buffer to the correct row header page using the given rowState
512    * according to the given rowId.  Deleted state is
513    * determined, but overflow row pointers are not followed.
514    * 
515    * @return a ByteBuffer of the relevant page, or null if row was invalid
516    */
517   public static ByteBuffer positionAtRowHeader(RowState rowState,
518                                                RowId rowId)
519     throws IOException
520   {
521     ByteBuffer rowBuffer = rowState.setHeaderRow(rowId);
522 
523     if(rowState.isAtHeaderRow()) {
524       // this task has already been accomplished
525       return rowBuffer;
526     }
527     
528     if(!rowState.isValid()) {
529       // this was an invalid page/row
530       rowState.setStatus(RowStateStatus.AT_HEADER);
531       return null;
532     }
533 
534     // note, we don't use findRowStart here cause we need the unmasked value
535     short rowStart = rowBuffer.getShort(
536         getRowStartOffset(rowId.getRowNumber(),
537                           rowState.getTable().getFormat()));
538 
539     // check the deleted, overflow flags for the row (the "real" flags are
540     // always set on the header row)
541     RowStatus rowStatus = RowStatus.NORMAL;
542     if(isDeletedRow(rowStart)) {
543       rowStatus = RowStatus.DELETED;
544     } else if(isOverflowRow(rowStart)) {
545       rowStatus = RowStatus.OVERFLOW;
546     }
547 
548     rowState.setRowStatus(rowStatus);
549     rowState.setStatus(RowStateStatus.AT_HEADER);
550     return rowBuffer;
551   }
552   
553   /**
554    * Sets the position and limit in a new buffer using the given rowState
555    * according to the given row number and row end, following overflow row
556    * pointers as necessary.
557    * 
558    * @return a ByteBuffer narrowed to the actual row data, or null if row was
559    *         invalid or deleted
560    */
561   public static ByteBuffer positionAtRowData(RowState rowState,
562                                              RowId rowId)
563     throws IOException
564   {
565     positionAtRowHeader(rowState, rowId);
566     if(!rowState.isValid() || rowState.isDeleted()) {
567       // row is invalid or deleted
568       rowState.setStatus(RowStateStatus.AT_FINAL);
569       return null;
570     }
571 
572     ByteBuffer rowBuffer = rowState.getFinalPage();
573     int rowNum = rowState.getFinalRowId().getRowNumber();
574     JetFormat format = rowState.getTable().getFormat();
575     
576     if(rowState.isAtFinalRow()) {
577       // we've already found the final row data
578       return PageChannel.narrowBuffer(
579           rowBuffer,
580           findRowStart(rowBuffer, rowNum, format),
581           findRowEnd(rowBuffer, rowNum, format));
582     }
583     
584     while(true) {
585       
586       // note, we don't use findRowStart here cause we need the unmasked value
587       short rowStart = rowBuffer.getShort(getRowStartOffset(rowNum, format));
588       short rowEnd = findRowEnd(rowBuffer, rowNum, format);
589 
590       // note, at this point we know the row is not deleted, so ignore any
591       // subsequent deleted flags (as overflow rows are always marked deleted
592       // anyway)
593       boolean overflowRow = isOverflowRow(rowStart);
594 
595       // now, strip flags from rowStart offset
596       rowStart = (short)(rowStart & OFFSET_MASK);
597 
598       if (overflowRow) {
599 
600         if((rowEnd - rowStart) < 4) {
601           throw new IOException("invalid overflow row info");
602         }
603       
604         // Overflow page.  the "row" data in the current page points to
605         // another page/row
606         int overflowRowNum = ByteUtil.getUnsignedByte(rowBuffer, rowStart);
607         int overflowPageNum = ByteUtil.get3ByteInt(rowBuffer, rowStart + 1);
608         rowBuffer = rowState.setOverflowRow(
609             new RowId(overflowPageNum, overflowRowNum));
610         rowNum = overflowRowNum;
611       
612       } else {
613 
614         rowState.setStatus(RowStateStatus.AT_FINAL);
615         return PageChannel.narrowBuffer(rowBuffer, rowStart, rowEnd);
616       }
617     }
618   }
619 
620   
621   /**
622    * Calls <code>reset</code> on this table and returns an unmodifiable
623    * Iterator which will iterate through all the rows of this table.  Use of
624    * the Iterator follows the same restrictions as a call to
625    * <code>getNextRow</code>.
626    * @throws IllegalStateException if an IOException is thrown by one of the
627    *         operations, the actual exception will be contained within
628    */
629   public Iterator<Map<String, Object>> iterator()
630   {
631     return iterator(null);
632   }
633   
634   /**
635    * Calls <code>reset</code> on this table and returns an unmodifiable
636    * Iterator which will iterate through all the rows of this table, returning
637    * only the given columns.  Use of the Iterator follows the same
638    * restrictions as a call to <code>getNextRow</code>.
639    * @throws IllegalStateException if an IOException is thrown by one of the
640    *         operations, the actual exception will be contained within
641    */
642   public Iterator<Map<String, Object>> iterator(Collection<String> columnNames)
643   {
644     reset();
645     return _cursor.iterator(columnNames);
646   }
647 
648   /**
649    * Writes a new table defined by the given columns to the database.
650    * @return the first page of the new table's definition
651    */
652   public static int writeTableDefinition(
653       List<Column> columns, PageChannel pageChannel, JetFormat format)
654     throws IOException
655   {
656     // first, create the usage map page
657     int usageMapPageNumber = pageChannel.writeNewPage(
658         createUsageMapDefinitionBuffer(pageChannel, format));
659 
660     // next, determine how big the table def will be (in case it will be more
661     // than one page)
662     int totalTableDefSize = format.SIZE_TDEF_HEADER +
663       (format.SIZE_COLUMN_DEF_BLOCK * columns.size()) +
664       format.SIZE_TDEF_TRAILER;
665     for(Column col : columns) {
666       // we add the number of bytes for the column name and 2 bytes for the
667       // length of the column name
668       int nameByteLen = (col.getName().length() *
669                          JetFormat.TEXT_FIELD_UNIT_SIZE);
670       totalTableDefSize += nameByteLen + 2;
671     }
672     
673     // now, create the table definition
674     ByteBuffer buffer = pageChannel.createBuffer(Math.max(totalTableDefSize,
675                                                           format.PAGE_SIZE));
676     writeTableDefinitionHeader(buffer, columns, usageMapPageNumber,
677                                totalTableDefSize, format);
678     writeColumnDefinitions(buffer, columns, format); 
679     
680     //End of tabledef
681     buffer.put((byte) 0xff);
682     buffer.put((byte) 0xff);
683 
684     // write table buffer to database
685     int tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
686     if(totalTableDefSize <= format.PAGE_SIZE) {
687       
688       // easy case, fits on one page
689       buffer.putShort(format.OFFSET_FREE_SPACE,
690                       (short)(buffer.remaining() - 8)); // overwrite page free space
691       // Write the tdef page to disk.
692       tdefPageNumber = pageChannel.writeNewPage(buffer);
693       
694     } else {
695 
696       // need to split across multiple pages
697       ByteBuffer partialTdef = pageChannel.createPageBuffer();
698       buffer.rewind();
699       int nextTdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
700       while(buffer.hasRemaining()) {
701 
702         // reset for next write
703         partialTdef.clear();
704         
705         if(tdefPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
706           
707           // this is the first page.  note, the first page already has the
708           // page header, so no need to write it here
709           tdefPageNumber = pageChannel.allocateNewPage();
710           nextTdefPageNumber = tdefPageNumber;
711           
712         } else {
713 
714           // write page header
715           writeTablePageHeader(partialTdef);
716         }
717 
718         // copy the next page of tdef bytes
719         int curTdefPageNumber = nextTdefPageNumber;
720         int writeLen = Math.min(partialTdef.remaining(), buffer.remaining());
721         partialTdef.put(buffer.array(), buffer.position(), writeLen);
722         buffer.position(buffer.position() + writeLen);
723 
724         if(buffer.hasRemaining()) {
725           // need a next page
726           nextTdefPageNumber = pageChannel.allocateNewPage();
727           partialTdef.putInt(format.OFFSET_NEXT_TABLE_DEF_PAGE,
728                              nextTdefPageNumber);
729         }
730 
731         // update page free space
732         partialTdef.putShort(format.OFFSET_FREE_SPACE,
733                              (short)(partialTdef.remaining() - 8)); // overwrite page free space
734 
735         // write partial page to disk
736         pageChannel.writePage(partialTdef, curTdefPageNumber);
737       }
738         
739     }
740        
741     return tdefPageNumber;
742   }
743 
744   /**
745    * @param buffer Buffer to write to
746    * @param columns List of Columns in the table
747    */
748   private static void writeTableDefinitionHeader(
749       ByteBuffer buffer, List<Column> columns,
750       int usageMapPageNumber, int totalTableDefSize, JetFormat format)
751     throws IOException
752   {
753     //Start writing the tdef
754     writeTablePageHeader(buffer);
755     buffer.putInt(totalTableDefSize);  //Length of table def
756     buffer.put((byte) 0x59);  //Unknown
757     buffer.put((byte) 0x06);  //Unknown
758     buffer.putShort((short) 0); //Unknown
759     buffer.putInt(0);  //Number of rows
760     buffer.putInt(0); //Last Autonumber
761     if(countAutoNumberColumns(columns) > 0) {
762       buffer.put((byte) 1);
763     } else {
764       buffer.put((byte) 0);
765     }
766     for (int i = 0; i < 15; i++) {  //Unknown
767       buffer.put((byte) 0);
768     }
769     buffer.put(Table.TYPE_USER); //Table type
770     buffer.putShort((short) columns.size()); //Max columns a row will have
771     buffer.putShort(Column.countVariableLength(columns));  //Number of variable columns in table
772     buffer.putShort((short) columns.size()); //Number of columns in table
773     buffer.putInt(0);  //Number of indexes in table
774     buffer.putInt(0);  //Number of indexes in table
775     buffer.put((byte) 0); //Usage map row number
776     ByteUtil.put3ByteInt(buffer, usageMapPageNumber);  //Usage map page number
777     buffer.put((byte) 1); //Free map row number
778     ByteUtil.put3ByteInt(buffer, usageMapPageNumber);  //Free map page number
779     if (LOG.isDebugEnabled()) {
780       int position = buffer.position();
781       buffer.rewind();
782       LOG.debug("Creating new table def block:\n" + ByteUtil.toHexString(
783           buffer, format.SIZE_TDEF_HEADER));
784       buffer.position(position);
785     }
786   }
787 
788   /**
789    * Writes the page header for a table definition page
790    * @param buffer Buffer to write to
791    */
792   private static void writeTablePageHeader(ByteBuffer buffer)
793   {
794     buffer.put(PageTypes.TABLE_DEF);  //Page type
795     buffer.put((byte) 0x01); //Unknown
796     buffer.put((byte) 0); //Unknown
797     buffer.put((byte) 0); //Unknown
798     buffer.putInt(0);  //Next TDEF page pointer
799   }
800   
801   /**
802    * @param buffer Buffer to write to
803    * @param columns List of Columns to write definitions for
804    */
805   private static void writeColumnDefinitions(
806       ByteBuffer buffer, List<Column> columns, JetFormat format)
807     throws IOException
808   {
809     short columnNumber = (short) 0;
810     short fixedOffset = (short) 0;
811     short variableOffset = (short) 0;
812     // we specifically put the "long variable" values after the normal
813     // variable length values so that we have a better chance of fitting it
814     // all (because "long variable" values can go in separate pages)
815     short longVariableOffset =
816       Column.countNonLongVariableLength(columns);
817     for (Column col : columns) {
818       int position = buffer.position();
819       buffer.put(col.getType().getValue());
820       buffer.put((byte) 0x59);  //Unknown
821       buffer.put((byte) 0x06);  //Unknown
822       buffer.putShort((short) 0); //Unknown
823       buffer.putShort(columnNumber);  //Column Number
824       if (col.isVariableLength()) {
825         if(!col.getType().isLongValue()) {
826           buffer.putShort(variableOffset++);
827         } else {
828           buffer.putShort(longVariableOffset++);
829         }          
830       } else {
831         buffer.putShort((short) 0);
832       }
833       buffer.putShort(columnNumber); //Column Number again
834       if(col.getType().getHasScalePrecision()) {
835         buffer.put(col.getPrecision());  // numeric precision
836         buffer.put(col.getScale());  // numeric scale
837       } else {
838         buffer.put((byte) 0x00); //unused
839         buffer.put((byte) 0x00); //unused
840       }
841       buffer.putShort((short) 0); //Unknown
842       buffer.put(getColumnBitFlags(col)); // misc col flags
843       if (col.isCompressedUnicode()) {  //Compressed
844         buffer.put((byte) 1);
845       } else {
846         buffer.put((byte) 0);
847       }
848       buffer.putInt(0); //Unknown, but always 0.
849       //Offset for fixed length columns
850       if (col.isVariableLength()) {
851         buffer.putShort((short) 0);
852       } else {
853         buffer.putShort(fixedOffset);
854         fixedOffset += col.getType().getFixedSize();
855       }
856       if(!col.getType().isLongValue()) {
857         buffer.putShort(col.getLength()); //Column length
858       } else {
859         buffer.putShort((short)0x0000); // unused
860       }
861       columnNumber++;
862       if (LOG.isDebugEnabled()) {
863         LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
864             buffer, position, format.SIZE_COLUMN_DEF_BLOCK));
865       }
866     }
867     for (Column col : columns) {
868       writeName(buffer, col.getName(), format);
869     }
870   }
871 
872   /**
873    * Writes the given name into the given buffer in the format as expected by
874    * {@link #readName}.
875    */
876   private static void writeName(ByteBuffer buffer, String name,
877                                 JetFormat format)
878   {
879       ByteBuffer encName = Column.encodeUncompressedText(
880           name, format);
881       buffer.putShort((short) encName.remaining());
882       buffer.put(encName);
883   }
884 
885   /**
886    * Constructs a byte containing the flags for the given column.
887    */
888   private static byte getColumnBitFlags(Column col) {
889     byte flags = Column.UNKNOWN_FLAG_MASK;
890     if(!col.isVariableLength()) {
891       flags |= Column.FIXED_LEN_FLAG_MASK;
892     }
893     if(col.isAutoNumber()) {
894       flags |= Column.AUTO_NUMBER_FLAG_MASK;
895     }
896     return flags;
897   }
898   
899   /**
900    * Create the usage map definition page buffer.  The "used pages" map is in
901    * row 0, the "pages with free space" map is in row 1.
902    */
903   private static ByteBuffer createUsageMapDefinitionBuffer(
904       PageChannel pageChannel, JetFormat format)
905     throws IOException
906   {
907     int usageMapRowLength = format.OFFSET_USAGE_MAP_START +
908       format.USAGE_MAP_TABLE_BYTE_LENGTH;
909     int freeSpace = format.PAGE_INITIAL_FREE_SPACE
910       - (2 * getRowSpaceUsage(usageMapRowLength, format));
911     
912     ByteBuffer rtn = pageChannel.createPageBuffer();
913     rtn.put(PageTypes.DATA);
914     rtn.put((byte) 0x1);  //Unknown
915     rtn.putShort((short)freeSpace);  //Free space in page
916     rtn.putInt(0); //Table definition
917     rtn.putInt(0); //Unknown
918     rtn.putShort((short) 2); //Number of records on this page
919 
920     // write two rows of usage map definitions
921     int rowStart = findRowEnd(rtn, 0, format) - usageMapRowLength;
922     for(int i = 0; i < 2; ++i) {
923       rtn.putShort(getRowStartOffset(i, format), (short)rowStart);
924       if(i == 0) {
925         // initial "usage pages" map definition
926         rtn.put(rowStart, UsageMap.MAP_TYPE_REFERENCE);
927       } else {
928         // initial "pages with free space" map definition
929         rtn.put(rowStart, UsageMap.MAP_TYPE_INLINE);
930       }
931       rowStart -= usageMapRowLength;
932     }
933         
934     return rtn;
935   }
936     
937   /**
938    * Read the table definition
939    */
940   private void readTableDefinition(ByteBuffer tableBuffer) throws IOException
941   {
942     if (LOG.isDebugEnabled()) {
943       tableBuffer.rewind();
944       LOG.debug("Table def block:\n" + ByteUtil.toHexString(tableBuffer,
945           getFormat().SIZE_TDEF_HEADER));
946     }
947     _rowCount = tableBuffer.getInt(getFormat().OFFSET_NUM_ROWS);
948     _lastAutoNumber = tableBuffer.getInt(getFormat().OFFSET_NEXT_AUTO_NUMBER);
949     _tableType = tableBuffer.get(getFormat().OFFSET_TABLE_TYPE);
950     _maxColumnCount = tableBuffer.getShort(getFormat().OFFSET_MAX_COLS);
951     _maxVarColumnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_VAR_COLS);
952     short columnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_COLS);
953     _indexSlotCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEX_SLOTS);
954     _indexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEXES);
955 
956     int rowNum = ByteUtil.getUnsignedByte(
957         tableBuffer, getFormat().OFFSET_OWNED_PAGES);
958     int pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_OWNED_PAGES + 1);
959     _ownedPages = UsageMap.read(getDatabase(), pageNum, rowNum, false);
960     rowNum = ByteUtil.getUnsignedByte(
961         tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES);
962     pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES + 1);
963     _freeSpacePages = UsageMap.read(getDatabase(), pageNum, rowNum, false);
964     
965     for (int i = 0; i < _indexCount; i++) {
966       int uniqueEntryCountOffset =
967         (getFormat().OFFSET_INDEX_DEF_BLOCK +
968          (i * getFormat().SIZE_INDEX_DEFINITION) + 4);
969       int uniqueEntryCount = tableBuffer.getInt(uniqueEntryCountOffset);
970       _indexes.add(createIndex(uniqueEntryCount, uniqueEntryCountOffset));
971     }
972     
973     int colOffset = getFormat().OFFSET_INDEX_DEF_BLOCK +
974         _indexCount * getFormat().SIZE_INDEX_DEFINITION;
975     for (int i = 0; i < columnCount; i++) {
976       Column column = new Column(this, tableBuffer,
977           colOffset + (i * getFormat().SIZE_COLUMN_HEADER));
978       _columns.add(column);
979       if(column.isVariableLength()) {
980         // also shove it in the variable columns list, which is ordered
981         // differently from the _columns list
982         _varColumns.add(column);
983       }
984     }
985     tableBuffer.position(colOffset +
986                          (columnCount * getFormat().SIZE_COLUMN_HEADER));
987     for (int i = 0; i < columnCount; i++) {
988       Column column = _columns.get(i);
989       column.setName(readName(tableBuffer));
990     }    
991     Collections.sort(_columns);
992 
993     // setup the data index for the columns
994     int colIdx = 0;
995     for(Column col : _columns) {
996       col.setColumnIndex(colIdx++);
997     }
998 
999     // sort variable length columns based on their index into the variable
1000     // length offset table, because we will write the columns in this order
1001     Collections.sort(_varColumns, VAR_LEN_COLUMN_COMPARATOR);
1002     
1003     int idxOffset = tableBuffer.position();
1004     tableBuffer.position(idxOffset +
1005                      (getFormat().OFFSET_INDEX_NUMBER_BLOCK * _indexCount));
1006 
1007     // if there are more index slots than indexes, the initial slots are
1008     // always empty/invalid, so we skip that data
1009     int firstRealIdx = (_indexSlotCount - _indexCount);
1010     
1011     for (int i = 0; i < _indexSlotCount; i++) {
1012 
1013       tableBuffer.getInt(); //Forward past Unknown
1014       tableBuffer.getInt(); //Forward past alternate index number
1015       int indexNumber = tableBuffer.getInt();
1016       tableBuffer.position(tableBuffer.position() + 11);
1017       byte indexType = tableBuffer.get();
1018       tableBuffer.position(tableBuffer.position() + 4);
1019 
1020       if(i < firstRealIdx) {
1021         // ignore this info
1022         continue;
1023       }
1024 
1025       Index index = _indexes.get(i - firstRealIdx);
1026       index.setIndexNumber(indexNumber);
1027       index.setIndexType(indexType);
1028     }
1029 
1030     // read actual index names
1031     for (int i = 0; i < _indexSlotCount; i++) {
1032       if(i < firstRealIdx) {
1033         // for each empty index slot, there is some weird sort of name, skip
1034         // it
1035         skipName(tableBuffer);
1036         continue;
1037       }
1038         
1039       _indexes.get(i - firstRealIdx)
1040         .setName(readName(tableBuffer));
1041     }
1042     int idxEndOffset = tableBuffer.position();
1043     
1044     Collections.sort(_indexes);
1045 
1046     // go back to index column info after sorting
1047     tableBuffer.position(idxOffset);
1048     for (int i = 0; i < _indexCount; i++) {
1049       tableBuffer.getInt(); //Forward past Unknown
1050       _indexes.get(i).read(tableBuffer, _columns);
1051     }
1052 
1053     // reset to end of index info
1054     tableBuffer.position(idxEndOffset);
1055   }
1056 
1057   /**
1058    * Creates an index with the given initial info.
1059    */
1060   private Index createIndex(int uniqueEntryCount, int uniqueEntryCountOffset)
1061   {
1062     return(_useBigIndex ?
1063            new BigIndex(this, uniqueEntryCount, uniqueEntryCountOffset) :
1064            new SimpleIndex(this, uniqueEntryCount, uniqueEntryCountOffset));
1065   }
1066   
1067   /**
1068    * Writes the given page data to the given page number, clears any other
1069    * relevant buffers.
1070    */
1071   private void writeDataPage(ByteBuffer pageBuffer, int pageNumber)
1072     throws IOException
1073   {
1074     // write the page data
1075     getPageChannel().writePage(pageBuffer, pageNumber);
1076 
1077     // possibly invalidate the add row buffer if a different data buffer is
1078     // being written (e.g. this happens during deleteRow)
1079     _addRowBufferH.possiblyInvalidate(pageNumber, pageBuffer);
1080     
1081     // update modification count so any active RowStates can keep themselves
1082     // up-to-date
1083     ++_modCount;
1084   }
1085 
1086   /**
1087    * Returns a name read from the buffer at the current position.  The
1088    * expected name format is the name length as a short followed by (length *
1089    * 2) bytes encoded using the {@link JetFormat#CHARSET}
1090    */
1091   private String readName(ByteBuffer buffer) {
1092     int nameLength = ByteUtil.getUnsignedShort(buffer);
1093     byte[] nameBytes = new byte[nameLength];
1094     buffer.get(nameBytes);
1095     return Column.decodeUncompressedText(nameBytes, getFormat());
1096   }
1097   
1098   /**
1099    * Skips past a name int the buffer at the current position.  The
1100    * expected name format is the same as that for {@link #readName}.
1101    */
1102   private void skipName(ByteBuffer buffer) {
1103     int nameLength = ByteUtil.getUnsignedShort(buffer);
1104     buffer.position(buffer.position() + nameLength);
1105   }
1106   
1107   /**
1108    * Converts a map of columnName -> columnValue to an array of row values
1109    * appropriate for a call to {@link #addRow(Object...)}.
1110    */
1111   public Object[] asRow(Map<String,Object> rowMap) {
1112     Object[] row = new Object[_columns.size()];
1113     if(rowMap == null) {
1114       return row;
1115     }
1116     for(Column col : _columns) {
1117       row[col.getColumnIndex()] = rowMap.get(col.getName());
1118     }
1119     return row;
1120   }
1121   
1122   /**
1123    * Add a single row to this table and write it to disk
1124    * <p>
1125    * Note, if this table has an auto-number column, the value written will be
1126    * put back into the given row array.
1127    *
1128    * @param row row values for a single row.  the row will be modified if
1129    *            this table contains an auto-number column, otherwise it
1130    *            will not be modified.
1131    */
1132   public void addRow(Object... row) throws IOException {
1133     addRows(Collections.singletonList(row), _singleRowBufferH);
1134   }
1135   
1136   /**
1137    * Add multiple rows to this table, only writing to disk after all
1138    * rows have been written, and every time a data page is filled.  This
1139    * is much more efficient than calling <code>addRow</code> multiple times.
1140    * <p>
1141    * Note, if this table has an auto-number column, the values written will be
1142    * put back into the given row arrays.
1143    * 
1144    * @param rows List of Object[] row values.  the rows will be modified if
1145    *             this table contains an auto-number column, otherwise they
1146    *             will not be modified.
1147    */
1148   public void addRows(List<? extends Object[]> rows) throws IOException {
1149     addRows(rows, _multiRowBufferH);
1150   }
1151   
1152   /**
1153    * Add multiple rows to this table, only writing to disk after all
1154    * rows have been written, and every time a data page is filled.
1155    * @param inRows List of Object[] row values
1156    * @param writeRowBufferH TempBufferHolder used to generate buffers for
1157    *                        writing the row data
1158    */
1159   private void addRows(List<? extends Object[]> inRows,
1160                        TempBufferHolder writeRowBufferH)
1161     throws IOException
1162   {
1163     // copy the input rows to a modifiable list so we can update the elements
1164     List<Object[]> rows = new ArrayList<Object[]>(inRows);
1165     ByteBuffer[] rowData = new ByteBuffer[rows.size()];
1166     for (int i = 0; i < rows.size(); i++) {
1167 
1168       // we need to make sure the row is the right length (fill with null).
1169       // note, if the row is copied the caller will not be able to access any
1170       // generated auto-number value, but if they need that info they should
1171       // use a row array of the right size!
1172       Object[] row = rows.get(i);
1173       if(row.length < _columns.size()) {
1174         row = dupeRow(row, _columns.size());
1175         // we copied the row, so put the copy back into the rows list
1176         rows.set(i, row);
1177       }
1178 
1179       // write the row of data to a temporary buffer
1180       rowData[i] = createRow(row, getFormat().MAX_ROW_SIZE,
1181                              writeRowBufferH.getPageBuffer(getPageChannel()));
1182       
1183       if (rowData[i].limit() > getFormat().MAX_ROW_SIZE) {
1184         throw new IOException("Row size " + rowData[i].limit() +
1185                               " is too large");
1186       }
1187     }
1188     
1189     ByteBuffer dataPage = null;
1190     int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
1191 
1192     // find last data page (Not bothering to check other pages for free
1193     // space.)
1194     UsageMap.PageCursor revPageCursor = _ownedPages.cursor();
1195     revPageCursor.afterLast();
1196     while(true) {
1197       int tmpPageNumber = revPageCursor.getPreviousPage();
1198       if(tmpPageNumber < 0) {
1199         break;
1200       }
1201       dataPage = _addRowBufferH.setPage(getPageChannel(), tmpPageNumber);
1202       if(dataPage.get() == PageTypes.DATA) {
1203         // found last data page, only use if actually listed in free space
1204         // pages
1205         if(_freeSpacePages.containsPageNumber(tmpPageNumber)) {
1206           pageNumber = tmpPageNumber;
1207         }
1208         break;
1209       }
1210     }
1211 
1212     if(pageNumber == PageChannel.INVALID_PAGE_NUMBER) {
1213       // No data pages exist (with free space).  Create a new one.
1214       dataPage = newDataPage();
1215       pageNumber = _addRowBufferH.getPageNumber();
1216     }
1217     
1218     for (int i = 0; i < rowData.length; i++) {
1219       int rowSize = rowData[i].remaining();
1220       int rowSpaceUsage = getRowSpaceUsage(rowSize, getFormat());
1221       short freeSpaceInPage = dataPage.getShort(getFormat().OFFSET_FREE_SPACE);
1222       int rowsOnPage = getRowsOnDataPage(dataPage, getFormat());
1223       if((freeSpaceInPage < rowSpaceUsage) ||
1224          (rowsOnPage >= getFormat().MAX_NUM_ROWS_ON_DATA_PAGE)) {
1225 
1226         // Last data page is full.  Create a new one.
1227         writeDataPage(dataPage, pageNumber);
1228         _freeSpacePages.removePageNumber(pageNumber);
1229 
1230         dataPage = newDataPage();
1231         pageNumber = _addRowBufferH.getPageNumber();
1232         
1233         freeSpaceInPage = dataPage.getShort(getFormat().OFFSET_FREE_SPACE);
1234       }
1235 
1236       // write out the row data
1237       int rowNum = addDataPageRow(dataPage, rowSize, getFormat());
1238       dataPage.put(rowData[i]);
1239 
1240       // update the indexes
1241       for(Index index : _indexes) {
1242         index.addRow(rows.get(i), new RowId(pageNumber, rowNum));
1243       }
1244     }
1245     writeDataPage(dataPage, pageNumber);
1246     
1247     // Update tdef page
1248     updateTableDefinition(rows.size());
1249   }
1250 
1251   /**
1252    * Updates the table definition after rows are modified.
1253    */
1254   private void updateTableDefinition(int rowCountInc) throws IOException
1255   {
1256     // load table definition
1257     ByteBuffer tdefPage = _tableDefBufferH.setPage(getPageChannel(),
1258                                                    _tableDefPageNumber);
1259     
1260     // make sure rowcount and autonumber are up-to-date
1261     _rowCount += rowCountInc;
1262     tdefPage.putInt(getFormat().OFFSET_NUM_ROWS, _rowCount);
1263     tdefPage.putInt(getFormat().OFFSET_NEXT_AUTO_NUMBER, _lastAutoNumber);
1264 
1265     // write any index changes
1266     Iterator<Index> indIter = _indexes.iterator();
1267     for (int i = 0; i < _indexes.size(); i++) {
1268       Index index = indIter.next();
1269       // write the unique entry count for the index to the table definition
1270       // page
1271       tdefPage.putInt(index.getUniqueEntryCountOffset(),
1272                       index.getUniqueEntryCount());
1273       // write the entry page for the index
1274       index.update();
1275     }
1276 
1277     // write modified table definition
1278     getPageChannel().writePage(tdefPage, _tableDefPageNumber);
1279   }
1280   
1281   /**
1282    * Create a new data page
1283    * @return Page number of the new page
1284    */
1285   private ByteBuffer newDataPage() throws IOException {
1286     if (LOG.isDebugEnabled()) {
1287       LOG.debug("Creating new data page");
1288     }
1289     ByteBuffer dataPage = _addRowBufferH.setNewPage(getPageChannel());
1290     dataPage.put(PageTypes.DATA); //Page type
1291     dataPage.put((byte) 1); //Unknown
1292     dataPage.putShort((short)getFormat().PAGE_INITIAL_FREE_SPACE); //Free space in this page
1293     dataPage.putInt(_tableDefPageNumber); //Page pointer to table definition
1294     dataPage.putInt(0); //Unknown
1295     dataPage.putShort((short)0); //Number of rows on this page
1296     int pageNumber = _addRowBufferH.getPageNumber();
1297     getPageChannel().writePage(dataPage, pageNumber);
1298     _ownedPages.addPageNumber(pageNumber);
1299     _freeSpacePages.addPageNumber(pageNumber);
1300     return dataPage;
1301   }
1302   
1303   /**
1304    * Serialize a row of Objects into a byte buffer.
1305    * <p>
1306    * Note, if this table has an auto-number column, the value written will be
1307    * put back into the given row array.
1308    * 
1309    * @param rowArray row data, expected to be correct length for this table
1310    * @param maxRowSize max size the data can be for this row
1311    * @param buffer buffer to which to write the row data
1312    * @return the given buffer, filled with the row data
1313    */
1314   ByteBuffer createRow(Object[] rowArray, int maxRowSize, ByteBuffer buffer)
1315     throws IOException
1316   {
1317     buffer.putShort(_maxColumnCount);
1318     NullMask nullMask = new NullMask(_maxColumnCount);
1319     
1320     //Fixed length column data comes first
1321     int fixedDataStart = buffer.position();
1322     int fixedDataEnd = fixedDataStart;
1323     for (Column col : _columns) {
1324 
1325       if(!col.isVariableLength()) {
1326         
1327         Object rowValue = rowArray[col.getColumnIndex()];
1328 
1329         if (col.getType() == DataType.BOOLEAN) {
1330         
1331           if(Column.toBooleanValue(rowValue)) {