Coverage Report - com.healthmarketscience.jackcess.Table
 
Classes in this File Line Coverage Branch Coverage Complexity
Table
92%
563/613
83%
189/228
0
Table$1
100%
2/2
75%
3/4
0
Table$RowState
92%
72/78
79%
33/42
0
Table$RowStateStatus
100%
2/2
N/A
0
Table$RowStatus
100%
2/2
N/A
0
 
 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  234309
 public class Table
 53  
   implements Iterable<Map<String, Object>>
 54  
 {
 55  
   
 56  1
   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  1
   private static final Comparator<Column> VAR_LEN_COLUMN_COMPARATOR =
 72  
     new Comparator<Column>() {
 73  2299
       public int compare(Column c1, Column c2) {
 74  2298
         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  271
   private List<Column> _columns = new ArrayList<Column>();
 100  
   /** List of variable length columns in this table, ordered by offset */
 101  271
   private List<Column> _varColumns = new ArrayList<Column>();
 102  
   /** List of indexes on this table */
 103  271
   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  271
   private final TempPageHolder _addRowBufferH =
 114  
     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
 115  
   /** page buffer used to update the table def page */
 116  271
   private final TempPageHolder _tableDefBufferH =
 117  
     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
 118  
   /** buffer used to writing single rows of data */
 119  271
   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  271
   private final TempBufferHolder _multiRowBufferH =
 124  
     TempBufferHolder.newHolder(TempBufferHolder.Type.NONE, true);
 125  
   /** page buffer used to write out-of-line "long value" data */
 126  271
   private final TempPageHolder _longValueBufferH =
 127  
     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
 128  
   /** for now, "big index support" is optional */
 129  
   private final boolean _useBigIndex;
 130  
   
 131  
   /** common cursor for iterating through the table, kept here for historic
 132  
       reasons */
 133  
   private Cursor _cursor;
 134  
   
 135  
   /**
 136  
    * Only used by unit tests
 137  
    */
 138  2
   Table(boolean testing, List<Column> columns) throws IOException {
 139  2
     if(!testing) {
 140  0
       throw new IllegalArgumentException();
 141  
     }
 142  2
     _database = null;
 143  2
     _tableDefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
 144  2
     _name = null;
 145  2
     _useBigIndex = false;
 146  2
     setColumns(columns);
 147  2
   }
 148  
   
 149  
   /**
 150  
    * @param database database which owns this table
 151  
    * @param tableBuffer Buffer to read the table with
 152  
    * @param pageNumber Page number of the table definition
 153  
    * @param name Table name
 154  
    * @param useBigIndex whether or not "big index support" should be enabled
 155  
    *                    for the table
 156  
    */
 157  
   protected Table(Database database, ByteBuffer tableBuffer,
 158  
                   int pageNumber, String name, boolean useBigIndex)
 159  
   throws IOException
 160  269
   {
 161  269
     _database = database;
 162  269
     _tableDefPageNumber = pageNumber;
 163  269
     _name = name;
 164  269
     _useBigIndex = useBigIndex; 
 165  269
     int nextPage = tableBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
 166  269
     ByteBuffer nextPageBuffer = null;
 167  273
     while (nextPage != 0) {
 168  4
       if (nextPageBuffer == null) {
 169  4
         nextPageBuffer = getPageChannel().createPageBuffer();
 170  
       }
 171  4
       getPageChannel().readPage(nextPageBuffer, nextPage);
 172  4
       nextPage = nextPageBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
 173  4
       ByteBuffer newBuffer = getPageChannel().createBuffer(
 174  
           tableBuffer.capacity() + getFormat().PAGE_SIZE - 8);
 175  4
       newBuffer.put(tableBuffer);
 176  4
       newBuffer.put(nextPageBuffer.array(), 8, getFormat().PAGE_SIZE - 8);
 177  4
       tableBuffer = newBuffer;
 178  4
       tableBuffer.flip();
 179  4
     }
 180  269
     readTableDefinition(tableBuffer);
 181  269
     tableBuffer = null;
 182  
 
 183  
     // setup common cursor
 184  269
     _cursor = Cursor.createCursor(this);
 185  269
   }
 186  
 
 187  
   /**
 188  
    * @return The name of the table
 189  
    */
 190  
   public String getName() {
 191  2407
     return _name;
 192  
   }
 193  
 
 194  
   public boolean doUseBigIndex() {
 195  0
     return _useBigIndex;
 196  
   }
 197  
   
 198  
   public int getMaxColumnCount() {
 199  0
     return _maxColumnCount;
 200  
   }
 201  
   
 202  
   public int getColumnCount() {
 203  4346
     return _columns.size();
 204  
   }
 205  
   
 206  
   public Database getDatabase() {
 207  530494
     return _database;
 208  
   }
 209  
   
 210  
   public JetFormat getFormat() {
 211  307519
     return getDatabase().getFormat();
 212  
   }
 213  
 
 214  
   public PageChannel getPageChannel() {
 215  222437
     return getDatabase().getPageChannel();
 216  
   }
 217  
 
 218  
   protected int getTableDefPageNumber() {
 219  6668
     return _tableDefPageNumber;
 220  
   }
 221  
 
 222  
   public RowState createRowState() {
 223  2324
     return new RowState(TempBufferHolder.Type.HARD);
 224  
   }
 225  
 
 226  
   protected UsageMap.PageCursor getOwnedPagesCursor() {
 227  383
     return _ownedPages.cursor();
 228  
   }
 229  
   
 230  
   protected TempPageHolder getLongValueBuffer() {
 231  241
     return _longValueBufferH;
 232  
   }
 233  
 
 234  
   /**
 235  
    * @return All of the columns in this table (unmodifiable List)
 236  
    */
 237  
   public List<Column> getColumns() {
 238  27
     return Collections.unmodifiableList(_columns);
 239  
   }
 240  
 
 241  
   /**
 242  
    * @return the column with the given name
 243  
    */
 244  
   public Column getColumn(String name) {
 245  193
     for(Column column : _columns) {
 246  434
       if(column.getName().equals(name)) {
 247  193
         return column;
 248  
       }
 249  
     }
 250  0
     throw new IllegalArgumentException("Column with name " + name +
 251  
                                        " does not exist in this table");
 252  
   }
 253  
     
 254  
   /**
 255  
    * Only called by unit tests
 256  
    */
 257  
   private void setColumns(List<Column> columns) {
 258  2
     _columns = columns;
 259  2
     int colIdx = 0;
 260  2
     int varLenIdx = 0;
 261  2
     int fixedOffset = 0;
 262  2
     for(Column col : _columns) {
 263  5
       col.setColumnNumber((short)colIdx);
 264  5
       col.setColumnIndex(colIdx++);
 265  5
       if(col.isVariableLength()) {
 266  4
         col.setVarLenTableIndex(varLenIdx++);
 267  4
         _varColumns.add(col);
 268  
       } else {
 269  1
         col.setFixedDataOffset(fixedOffset);
 270  1
         fixedOffset += col.getType().getFixedSize();
 271  
       }
 272  
     }
 273  2
     _maxColumnCount = (short)_columns.size();
 274  2
     _maxVarColumnCount = (short)_varColumns.size();
 275  2
   }
 276  
   
 277  
   /**
 278  
    * @return All of the Indexes on this table (unmodifiable List)
 279  
    */
 280  
   public List<Index> getIndexes() {
 281  100
     return Collections.unmodifiableList(_indexes);
 282  
   }
 283  
 
 284  
   /**
 285  
    * @return the index with the given name
 286  
    */
 287  
   public Index getIndex(String name) {
 288  16
     for(Index index : _indexes) {
 289  22
       if(index.getName().equals(name)) {
 290  15
         return index;
 291  
       }
 292  
     }
 293  1
     throw new IllegalArgumentException("Index with name " + name +
 294  
                                        " does not exist on this table");
 295  
   }
 296  
     
 297  
   /**
 298  
    * Only called by unit tests
 299  
    */
 300  
   int getIndexSlotCount() {
 301  3
     return _indexSlotCount;
 302  
   }
 303  
 
 304  
   /**
 305  
    * After calling this method, getNextRow will return the first row in the
 306  
    * table
 307  
    */
 308  
   public void reset() {
 309  27
     _cursor.reset();
 310  27
   }
 311  
   
 312  
   /**
 313  
    * Delete the current row (retrieved by a call to {@link #getNextRow()}).
 314  
    */
 315  
   public void deleteCurrentRow() throws IOException {
 316  9
     _cursor.deleteCurrentRow();
 317  9
   }
 318  
 
 319  
   /**
 320  
    * Delete the row on which the given rowState is currently positioned.
 321  
    */
 322  
   public void deleteRow(RowState rowState, RowId rowId) throws IOException {
 323  2092
     requireValidRowId(rowId);
 324  
     
 325  
     // ensure that the relevant row state is up-to-date
 326  2092
     ByteBuffer rowBuffer = positionAtRowHeader(rowState, rowId);
 327  
 
 328  2092
     requireNonDeletedRow(rowState, rowId);
 329  
     
 330  
     // delete flag always gets set in the "header" row (even if data is on
 331  
     // overflow row)
 332  2092
     int pageNumber = rowState.getHeaderRowId().getPageNumber();
 333  2092
     int rowNumber = rowState.getHeaderRowId().getRowNumber();
 334  
 
 335  
     // use any read rowValues to help update the indexes
 336  2092
     Object[] rowValues = (!_indexes.isEmpty() ?
 337  
                           rowState.getRowValues() : null);
 338  
     
 339  2092
     int rowIndex = getRowStartOffset(rowNumber, getFormat());
 340  2092
     rowBuffer.putShort(rowIndex, (short)(rowBuffer.getShort(rowIndex)
 341  
                                       | DELETED_ROW_MASK | OVERFLOW_ROW_MASK));
 342  2092
     writeDataPage(rowBuffer, pageNumber);
 343  
 
 344  
     // update the indexes
 345  2092
     for(Index index : _indexes) {
 346  2082
       index.deleteRow(rowValues, rowId);
 347  
     }
 348  
     
 349  
     // make sure table def gets updated
 350  2092
     updateTableDefinition(-1);
 351  2092
   }
 352  
   
 353  
   /**
 354  
    * @return The next row in this table (Column name -> Column value)
 355  
    */
 356  
   public Map<String, Object> getNextRow() throws IOException {
 357  2061
     return getNextRow(null);
 358  
   }
 359  
 
 360  
   /**
 361  
    * @param columnNames Only column names in this collection will be returned
 362  
    * @return The next row in this table (Column name -> Column value)
 363  
    */
 364  
   public Map<String, Object> getNextRow(Collection<String> columnNames) 
 365  
     throws IOException
 366  
   {
 367  2061
     return _cursor.getNextRow(columnNames);
 368  
   }
 369  
   
 370  
   /**
 371  
    * Reads a single column from the given row.
 372  
    */
 373  
   public Object getRowValue(RowState rowState, RowId rowId, Column column)
 374  
     throws IOException
 375  
   {
 376  161
     if(this != column.getTable()) {
 377  0
       throw new IllegalArgumentException(
 378  
           "Given column " + column + " is not from this table");
 379  
     }
 380  161
     requireValidRowId(rowId);
 381  
     
 382  
     // position at correct row
 383  161
     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
 384  161
     requireNonDeletedRow(rowState, rowId);
 385  
     
 386  161
     Object value = getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column);
 387  
 
 388  
     // cache the row values in order to be able to update the index on row
 389  
     // deletion.  note, most of the returned values are immutable, except
 390  
     // for binary data (returned as byte[]), but binary data shouldn't be
 391  
     // indexed anyway.
 392  161
     rowState.setRowValue(column.getColumnIndex(), value);
 393  
 
 394  161
     return value;
 395  
   }
 396  
 
 397  
   /**
 398  
    * Reads some columns from the given row.
 399  
    * @param columnNames Only column names in this collection will be returned
 400  
    */
 401  
   public Map<String, Object> getRow(
 402  
       RowState rowState, RowId rowId, Collection<String> columnNames)
 403  
     throws IOException
 404  
   {
 405  22484
     requireValidRowId(rowId);
 406  
 
 407  
     // position at correct row
 408  22484
     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
 409  22484
     requireNonDeletedRow(rowState, rowId);
 410  
 
 411  22484
     return getRow(rowState, rowBuffer, getRowNullMask(rowBuffer), _columns,
 412  
                   columnNames);
 413  
   }
 414  
 
 415  
   /**
 416  
    * Reads the row data from the given row buffer.  Leaves limit unchanged.
 417  
    * Saves parsed row values to the given rowState.
 418  
    */
 419  
   private static Map<String, Object> getRow(
 420  
       RowState rowState,
 421  
       ByteBuffer rowBuffer,
 422  
       NullMask nullMask,
 423  
       Collection<Column> columns,
 424  
       Collection<String> columnNames)
 425  
     throws IOException
 426  
   {
 427  22484
     Map<String, Object> rtn = new LinkedHashMap<String, Object>(
 428  
         columns.size());
 429  22484
     for(Column column : columns) {
 430  
 
 431  90046
       if((columnNames == null) || (columnNames.contains(column.getName()))) {
 432  
         // Add the value to the row data
 433  71413
         Object value = getRowColumn(rowBuffer, nullMask, column);
 434  71413
         rtn.put(column.getName(), value);
 435  
 
 436  
         // cache the row values in order to be able to update the index on row
 437  
         // deletion.  note, most of the returned values are immutable, except
 438  
         // for binary data (returned as byte[]), but binary data shouldn't be
 439  
         // indexed anyway.
 440  71413
         rowState.setRowValue(column.getColumnIndex(), value);
 441  90046
       }
 442  
     }
 443  22484
     return rtn;
 444  
   }
 445  
   
 446  
   /**
 447  
    * Reads the column data from the given row buffer.  Leaves limit unchanged.
 448  
    */
 449  
   private static Object getRowColumn(ByteBuffer rowBuffer,
 450  
                                      NullMask nullMask,
 451  
                                      Column column)
 452  
     throws IOException
 453  
   {
 454  71574
     boolean isNull = nullMask.isNull(column);
 455  71574
     if(column.getType() == DataType.BOOLEAN) {
 456  193
       return Boolean.valueOf(!isNull);  //Boolean values are stored in the null mask
 457  71381
     } else if(isNull) {
 458  
       // well, that's easy!
 459  1449
       return null;
 460  
     }
 461  
 
 462  
     // reset position to row start
 463  69932
     rowBuffer.reset();
 464  
     
 465  
     // locate the column data bytes
 466  69932
     int rowStart = rowBuffer.position();
 467  69932
     int colDataPos = 0;
 468  69932
     int colDataLen = 0;
 469  69932
     if(!column.isVariableLength()) {
 470  
 
 471  
       // read fixed length value (non-boolean at this point)
 472  31616
       int dataStart = rowStart + 2;
 473  31616
       colDataPos = dataStart + column.getFixedDataOffset();
 474  31616
       colDataLen = column.getType().getFixedSize();
 475  
       
 476  31616
     } else {
 477  
 
 478  
       // read var length value
 479  38316
       int varColumnOffsetPos =
 480  
         (rowBuffer.limit() - nullMask.byteSize() - 4) -
 481  
         (column.getVarLenTableIndex() * 2);
 482  
 
 483  38316
       short varDataStart = rowBuffer.getShort(varColumnOffsetPos);
 484  38316
       short varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);
 485  38316
       colDataPos = rowStart + varDataStart;
 486  38316
       colDataLen = varDataEnd - varDataStart;
 487  
     }
 488  
 
 489  
     // grab the column data
 490  69932
     byte[] columnData = new byte[colDataLen];
 491  69932
     rowBuffer.position(colDataPos);
 492  69932
     rowBuffer.get(columnData);
 493  
 
 494  
     // parse the column data
 495  69932
     return column.read(columnData);
 496  
   }
 497  
 
 498  
   /**
 499  
    * Reads the null mask from the given row buffer.  Leaves limit unchanged.
 500  
    */
 501  
   private static NullMask getRowNullMask(ByteBuffer rowBuffer)
 502  
     throws IOException
 503  
   {
 504  
     // reset position to row start
 505  22645
     rowBuffer.reset();
 506  
     
 507  22645
     short columnCount = rowBuffer.getShort(); // Number of columns in this row
 508  
     
 509  
     // read null mask
 510  22645
     NullMask nullMask = new NullMask(columnCount);
 511  22645
     rowBuffer.position(rowBuffer.limit() - nullMask.byteSize());  //Null mask at end
 512  22645
     nullMask.read(rowBuffer);
 513  
 
 514  22645
     return nullMask;
 515  
   }
 516  
 
 517  
   /**
 518  
    * Sets a new buffer to the correct row header page using the given rowState
 519  
    * according to the given rowId.  Deleted state is
 520  
    * determined, but overflow row pointers are not followed.
 521  
    * 
 522  
    * @return a ByteBuffer of the relevant page, or null if row was invalid
 523  
    */
 524  
   public static ByteBuffer positionAtRowHeader(RowState rowState,
 525  
                                                RowId rowId)
 526  
     throws IOException
 527  
   {
 528  90799
     ByteBuffer rowBuffer = rowState.setHeaderRow(rowId);
 529  
 
 530  90799
     if(rowState.isAtHeaderRow()) {
 531  
       // this task has already been accomplished
 532  41410
       return rowBuffer;
 533  
     }
 534  
     
 535  49389
     if(!rowState.isValid()) {
 536  
       // this was an invalid page/row
 537  2942
       rowState.setStatus(RowStateStatus.AT_HEADER);
 538  2942
       return null;
 539  
     }
 540  
 
 541  
     // note, we don't use findRowStart here cause we need the unmasked value
 542  46447
     short rowStart = rowBuffer.getShort(
 543  
         getRowStartOffset(rowId.getRowNumber(),
 544  
                           rowState.getTable().getFormat()));
 545  
 
 546  
     // check the deleted, overflow flags for the row (the "real" flags are
 547  
     // always set on the header row)
 548  46447
     RowStatus rowStatus = RowStatus.NORMAL;
 549  46447
     if(isDeletedRow(rowStart)) {
 550  4088
       rowStatus = RowStatus.DELETED;
 551  42359
     } else if(isOverflowRow(rowStart)) {
 552  13
       rowStatus = RowStatus.OVERFLOW;
 553  
     }
 554  
 
 555  46447
     rowState.setRowStatus(rowStatus);
 556  46447
     rowState.setStatus(RowStateStatus.AT_HEADER);
 557  46447
     return rowBuffer;
 558  
   }
 559  
   
 560  
   /**
 561  
    * Sets the position and limit in a new buffer using the given rowState
 562  
    * according to the given row number and row end, following overflow row
 563  
    * pointers as necessary.
 564  
    * 
 565  
    * @return a ByteBuffer narrowed to the actual row data, or null if row was
 566  
    *         invalid or deleted
 567  
    */
 568  
   public static ByteBuffer positionAtRowData(RowState rowState,
 569  
                                              RowId rowId)
 570  
     throws IOException
 571  
   {
 572  22677
     positionAtRowHeader(rowState, rowId);
 573  22677
     if(!rowState.isValid() || rowState.isDeleted()) {
 574  
       // row is invalid or deleted
 575  24
       rowState.setStatus(RowStateStatus.AT_FINAL);
 576  24
       return null;
 577  
     }
 578  
 
 579  22653
     ByteBuffer rowBuffer = rowState.getFinalPage();
 580  22653
     int rowNum = rowState.getFinalRowId().getRowNumber();
 581  22653
     JetFormat format = rowState.getTable().getFormat();
 582  
     
 583  22653
     if(rowState.isAtFinalRow()) {
 584  
       // we've already found the final row data
 585  44
       return PageChannel.narrowBuffer(
 586  
           rowBuffer,
 587  
           findRowStart(rowBuffer, rowNum, format),
 588  
           findRowEnd(rowBuffer, rowNum, format));
 589  
     }
 590  
     
 591  
     while(true) {
 592  
       
 593  
       // note, we don't use findRowStart here cause we need the unmasked value
 594  22618
       short rowStart = rowBuffer.getShort(getRowStartOffset(rowNum, format));
 595  22618
       short rowEnd = findRowEnd(rowBuffer, rowNum, format);
 596  
 
 597  
       // note, at this point we know the row is not deleted, so ignore any
 598  
       // subsequent deleted flags (as overflow rows are always marked deleted
 599  
       // anyway)
 600  22618
       boolean overflowRow = isOverflowRow(rowStart);
 601  
 
 602  
       // now, strip flags from rowStart offset
 603  22618
       rowStart = (short)(rowStart & OFFSET_MASK);
 604  
 
 605  22618
       if (overflowRow) {
 606  
 
 607  9
         if((rowEnd - rowStart) < 4) {
 608  0
           throw new IOException("invalid overflow row info");
 609  
         }
 610  
       
 611  
         // Overflow page.  the "row" data in the current page points to
 612  
         // another page/row
 613  9
         int overflowRowNum = ByteUtil.getUnsignedByte(rowBuffer, rowStart);
 614  9
         int overflowPageNum = ByteUtil.get3ByteInt(rowBuffer, rowStart + 1);
 615  9
         rowBuffer = rowState.setOverflowRow(
 616  
             new RowId(overflowPageNum, overflowRowNum));
 617  9
         rowNum = overflowRowNum;
 618  
       
 619  9
       } else {
 620  
 
 621  22609
         rowState.setStatus(RowStateStatus.AT_FINAL);
 622  22609
         return PageChannel.narrowBuffer(rowBuffer, rowStart, rowEnd);
 623  
       }
 624  9
     }
 625  
   }
 626  
 
 627  
   
 628  
   /**
 629  
    * Calls <code>reset</code> on this table and returns an unmodifiable
 630  
    * Iterator which will iterate through all the rows of this table.  Use of
 631  
    * the Iterator follows the same restrictions as a call to
 632  
    * <code>getNextRow</code>.
 633  
    * @throws IllegalStateException if an IOException is thrown by one of the
 634  
    *         operations, the actual exception will be contained within
 635  
    */
 636  
   public Iterator<Map<String, Object>> iterator()
 637  
   {
 638  5
     return iterator(null);
 639  
   }
 640  
   
 641  
   /**
 642  
    * Calls <code>reset</code> on this table and returns an unmodifiable
 643  
    * Iterator which will iterate through all the rows of this table, returning
 644  
    * only the given columns.  Use of the Iterator follows the same
 645  
    * restrictions as a call to <code>getNextRow</code>.
 646  
    * @throws IllegalStateException if an IOException is thrown by one of the
 647  
    *         operations, the actual exception will be contained within
 648