diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2007-11-20 21:03:11 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2007-11-20 21:03:11 +0000 |
commit | e39f2d1d3d4d752a276daff7710a9d91a590930b (patch) | |
tree | 7326649b9b93101db2aed2eb4ee9e1e6d4d8497a /src | |
parent | b77f5f9e97ac1e27368c3b32e8cfa9a9d2f77014 (diff) | |
download | jackcess-e39f2d1d3d4d752a276daff7710a9d91a590930b.tar.gz jackcess-e39f2d1d3d4d752a276daff7710a9d91a590930b.zip |
Move table iteration out of Table and into Cursor. First stage in
offering more complicated table access.
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@178 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src')
-rw-r--r-- | src/changes/changes.xml | 4 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Cursor.java | 409 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Index.java | 49 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/RowId.java | 6 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Table.java | 288 |
5 files changed, 554 insertions, 202 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index b92065b..e027364 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,6 +6,10 @@ </properties> <body> <release version="1.1.10" date="TBD"> + <action dev="jahlborn" type="update"> + Move table iteration out of Table and into Cursor. First stage in + offering more complicated table access. + </action> <action dev="jahlborn" type="fix" issue="1681954"> Update table row count correctly on row deletion or bulk row addition, bug #1681954. diff --git a/src/java/com/healthmarketscience/jackcess/Cursor.java b/src/java/com/healthmarketscience/jackcess/Cursor.java index 3a161db..e0ae0a7 100644 --- a/src/java/com/healthmarketscience/jackcess/Cursor.java +++ b/src/java/com/healthmarketscience/jackcess/Cursor.java @@ -2,31 +2,408 @@ package com.healthmarketscience.jackcess; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +import com.healthmarketscience.jackcess.Table.RowState; +import org.apache.commons.lang.ObjectUtils; + +import static com.healthmarketscience.jackcess.PageChannel.INVALID_PAGE_NUMBER; +import static com.healthmarketscience.jackcess.RowId.INVALID_ROW_NUMBER; + + /** - * Describe class Cursor here. - * + * Manages iteration for a Table. Different cursors provide different methods + * of traversing a table. Cursors should be fairly robust in the face of + * table modification during traversal (although depending on how the table is + * traversed, row updates may or may not be seen). Multiple cursors may + * traverse the same table simultaneously. + * <p> + * Is not thread-safe. * * @author james */ -public class Cursor { +public abstract class Cursor implements Iterable<Map<String, Object>> +{ + private static final int FIRST_PAGE_NUMBER = INVALID_PAGE_NUMBER; + private static final int LAST_PAGE_NUMBER = Integer.MAX_VALUE; + + public static final RowId FIRST_ROW_ID = new RowId( + FIRST_PAGE_NUMBER, INVALID_ROW_NUMBER); + + public static final RowId LAST_ROW_ID = new RowId( + LAST_PAGE_NUMBER, INVALID_ROW_NUMBER); + /** owning table */ - private final Table _table; -// /** Number of the current row in a data page */ -// private int _currentRowInPage = INVALID_ROW_NUMBER; -// /** Number of rows left to be read on the current page */ -// private short _rowsLeftOnPage = 0; -// /** State used for reading the table rows */ -// private RowState _rowState; -// /** Iterator over the pages that this table owns */ -// private UsageMap.PageIterator _ownedPagesIterator; + protected final Table _table; + /** State used for reading the table rows */ + protected final RowState _rowState; + /** the first (exclusive) row id for this iterator */ + protected final RowId _firstRowId; + /** the last (exclusive) row id for this iterator */ + protected final RowId _lastRowId; + /** the current row */ + protected RowId _currentRowId; + + protected Cursor(Table table, RowId firstRowId, RowId lastRowId) { + _table = table; + _rowState = _table.createRowState(); + _firstRowId = firstRowId; + _lastRowId = lastRowId; + _currentRowId = firstRowId; + } + /** - * Creates a new <code>Cursor</code> instance. - * + * Creates a normal, un-indexed cursor for the given table. */ - public Cursor(Table table) { - _table = table; + public static Cursor createCursor(Table table) { + return new TableScanCursor(table); + } + + public Table getTable() { + return _table; + } + + public JetFormat getFormat() { + return getTable().getFormat(); + } + + public PageChannel getPageChannel() { + return getTable().getPageChannel(); + } + + + /** + * Returns the first row id (exclusive) as defined by this cursor. + */ + protected RowId getFirstRowId() { + return _firstRowId; + } + + /** + * Returns the last row id (exclusive) as defined by this cursor. + */ + protected RowId getLastRowId() { + return _lastRowId; + } + + public void reset() { + _currentRowId = getFirstRowId(); + _rowState.reset(); + } + + /** + * Calls <code>reset</code> on this table and returns a modifiable Iterator + * which will iterate through all the rows of this table. Use of the + * Iterator follows the same restrictions as a call to + * <code>getNextRow</code>. + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterator<Map<String, Object>> iterator() + { + return iterator(null); + } + + /** + * Calls <code>reset</code> on this table and returns a modifiable Iterator + * which will iterate through all the rows of this table, returning only the + * given columns. Use of the Iterator follows the same restrictions as a + * call to <code>getNextRow</code>. + * @throws IllegalStateException if an IOException is thrown by one of the + * operations, the actual exception will be contained within + */ + public Iterator<Map<String, Object>> iterator(Collection<String> columnNames) + { + return new RowIterator(columnNames); + } + + /** + * Delete the current row (retrieved by a call to {@link #getNextRow}). + */ + public void deleteCurrentRow() throws IOException { + _table.deleteRow(_rowState, _currentRowId); + } + + /** + * @return The next row in this table (Column name -> Column value) + */ + public Map<String, Object> getNextRow() throws IOException { + return getNextRow(null); + } + + /** + * @param columnNames Only column names in this collection will be returned + * @return The next row in this table (Column name -> Column value) + */ + public Map<String, Object> getNextRow(Collection<String> columnNames) + throws IOException + { + if(moveToNextRow()) { + return getCurrentRow(columnNames); + } + return null; + } + + /** + * Moves to the next row as defined by this cursor. + * @return {@code true} if a valid next row was found, {@code false} + * otherwise + */ + public boolean moveToNextRow() + throws IOException + { + if(_currentRowId.equals(getLastRowId())) { + // already at end + return false; + } + + _rowState.reset(); + _currentRowId = findNextRowId(_currentRowId); + return(!_currentRowId.equals(getLastRowId())); } + /** + * Moves to the first row (as defined by the cursor) where the given column + * has the given value. This may be more efficient on some cursors than + * others. + * + * @return {@code true} if a valid row was found with the given value, + * {@code false} if no row was found (and the cursor is now pointing + * past the end of the table) + */ + public boolean moveToRow(Column column, Object value) + throws IOException + { + while(moveToNextRow()) { + if(ObjectUtils.equals(value, getCurrentRowSingleColumn(column))) { + return true; + } + } + return false; + } + + /** + * Moves to the first row (as defined by the cursor) where the given columns + * have the given values. This may be more efficient on some cursors than + * others. + * + * @return {@code true} if a valid row was found with the given values, + * {@code false} if no row was found (and the cursor is now pointing + * past the end of the table) + */ + public boolean moveToRow(Map<String,Object> row) + throws IOException + { + while(moveToNextRow()) { + if(ObjectUtils.equals(row, getCurrentRow(row.keySet()))) { + return true; + } + } + return false; + } + + /** + * Skips as many rows as possible up to the given number of rows. + * @return the number of rows skipped. + */ + public int skipRows(int numRows) + throws IOException + { + int numSkippedRows = 0; + while((numSkippedRows < numRows) && moveToNextRow()) { + ++numSkippedRows; + } + return numSkippedRows; + } + + /** + * Returns the current row in this cursor (Column name -> Column value). + * @param columnNames Only column names in this collection will be returned + */ + public Map<String, Object> getCurrentRow() + throws IOException + { + return getCurrentRow(null); + } + + /** + * Returns the current row in this cursor (Column name -> Column value). + * @param columnNames Only column names in this collection will be returned + */ + public Map<String, Object> getCurrentRow(Collection<String> columnNames) + throws IOException + { + return _table.getRow(_rowState, columnNames); + } + + /** + * Returns the given column from the current row. + */ + public Object getCurrentRowSingleColumn(Column column) + throws IOException + { + return _table.getRowSingleColumn(_rowState, column); + } + + /** + * Returns {@code true} if the row is marked as deleted, {@code false} + * otherwise. This method will not modify the rowState (it only looks at + * the "main" row, which is where the deleted flag is located). + */ + protected final boolean isCurrentRowDeleted() + throws IOException + { + ByteBuffer rowBuffer = _rowState.getFinalPage(); + int rowNum = _rowState.getFinalRowNumber(); + + // note, we don't use findRowStart here cause we need the unmasked value + return Table.isDeletedRow( + rowBuffer.getShort(Table.getRowStartOffset(rowNum, getFormat()))); + } + + /** + * Returns the row count for the current page. If the page number is + * invalid or the page is not a DATA page, 0 is returned. + */ + protected final int getRowsOnCurrentDataPage(ByteBuffer rowBuffer) + throws IOException + { + int rowsOnPage = 0; + if((rowBuffer != null) && (rowBuffer.get(0) == PageTypes.DATA)) { + rowsOnPage = + rowBuffer.getShort(getFormat().OFFSET_NUM_ROWS_ON_DATA_PAGE); + } + return rowsOnPage; + } + + /** + * Finds the next non-deleted row after the given row as defined by this + * cursor and returns the id of the row. If there are no more rows, the + * returned rowId should equal the value returned by {@link #getLastRowId}. + */ + protected abstract RowId findNextRowId(RowId currentRowId) + throws IOException; + + /** + * Row iterator for this table, supports modification. + */ + private final class RowIterator implements Iterator<Map<String, Object>> + { + private Collection<String> _columnNames; + private boolean _hasNext = false; + + private RowIterator(Collection<String> columnNames) + { + try { + reset(); + _columnNames = columnNames; + _hasNext = moveToNextRow(); + } catch(IOException e) { + throw new IllegalStateException(e); + } + } + + public boolean hasNext() { return _hasNext; } + + public void remove() { + try { + deleteCurrentRow(); + } catch(IOException e) { + throw new IllegalStateException(e); + } + } + + public Map<String, Object> next() { + if(!hasNext()) { + throw new NoSuchElementException(); + } + try { + Map<String, Object> rtn = getCurrentRow(_columnNames); + _hasNext = moveToNextRow(); + return rtn; + } catch(IOException e) { + throw new IllegalStateException(e); + } + } + + } + + /** + * Simple un-indexed cursor. + */ + private static class TableScanCursor extends Cursor + { + /** Iterator over the pages that this table owns */ + private final UsageMap.PageIterator _ownedPagesIterator; + + private TableScanCursor(Table table) { + super(table, FIRST_ROW_ID, LAST_ROW_ID); + _ownedPagesIterator = table.getOwnedPagesIterator(); + } + + @Override + public void reset() { + _ownedPagesIterator.reset(); + super.reset(); + } + + /** + * Position the buffer at the next row in the table + * @return a ByteBuffer narrowed to the next row, or null if none + */ + @Override + protected RowId findNextRowId(RowId currentRowId) + throws IOException + { + + // prepare to read next row + _rowState.reset(); + int currentPageNumber = currentRowId.getPageNumber(); + int currentRowNumber = currentRowId.getRowNumber(); + + int rowsOnPage = getRowsOnCurrentDataPage( + _rowState.setRow(currentPageNumber, currentRowNumber)); + + // loop until we find the next valid row or run out of pages + while(true) { + + currentRowNumber++; + if(currentRowNumber < rowsOnPage) { + _rowState.setRow(currentPageNumber, currentRowNumber); + } else { + + // load next page + currentRowNumber = INVALID_ROW_NUMBER; + currentPageNumber = _ownedPagesIterator.getNextPage(); + + ByteBuffer rowBuffer = _rowState.setRow( + currentPageNumber, currentRowNumber); + if(rowBuffer == null) { + //No more owned pages. No more rows. + return getLastRowId(); + } + + // update row count + rowsOnPage = getRowsOnCurrentDataPage(rowBuffer); + + // start again from the top + continue; + } + + if(!isCurrentRowDeleted()) { + // we found a non-deleted row, return it + return new RowId(currentPageNumber, currentRowNumber); + } + } + } + + } + } diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index 3dba5ef..4e57813 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -519,14 +519,14 @@ public class Index implements Comparable<Index> { * @param pageNumber Page number on which the row is stored * @param rowNumber Row number at which the row is stored */ - public void addRow(Object[] row, int pageNumber, byte rowNumber) + public void addRow(Object[] row, RowId rowId) throws IOException { // make sure we've parsed the entries initialize(); ++_rowCount; - _entries.add(new Entry(row, pageNumber, rowNumber)); + _entries.add(new Entry(row, rowId)); } /** @@ -538,22 +538,21 @@ public class Index implements Comparable<Index> { * @param pageNumber Page number on which the row is removed * @param rowNumber Row number at which the row is removed */ - public void deleteRow(Object[] row, int pageNumber, byte rowNumber) + public void deleteRow(Object[] row, RowId rowId) throws IOException { // make sure we've parsed the entries initialize(); --_rowCount; - Entry oldEntry = new Entry(row, pageNumber, rowNumber); + Entry oldEntry = new Entry(row, rowId); if(!_entries.remove(oldEntry)) { // the caller may have only read some of the row data, if this is the // case, just search for the page/row numbers boolean removed = false; for(Iterator<Entry> iter = _entries.iterator(); iter.hasNext(); ) { Entry entry = iter.next(); - if((entry.getPage() == pageNumber) && - (entry.getRow() == rowNumber)) { + if(entry.getRowId().equals(rowId)) { iter.remove(); removed = true; break; @@ -718,10 +717,8 @@ public class Index implements Comparable<Index> { */ private class Entry implements Comparable<Entry> { - /** Page number on which the row is stored */ - private int _page; - /** Row number at which the row is stored */ - private byte _row; + /** page/row on which this row is stored */ + private final RowId _rowId; /** Columns that are indexed */ private List<EntryColumn> _entryColumns = new ArrayList<EntryColumn>(); @@ -731,10 +728,9 @@ public class Index implements Comparable<Index> { * @param page Page number on which the row is stored * @param rowNumber Row number at which the row is stored */ - public Entry(Object[] values, int page, byte rowNumber) throws IOException + public Entry(Object[] values, RowId rowId) throws IOException { - _page = page; - _row = rowNumber; + _rowId = rowId; for(Map.Entry<Column, Byte> entry : _columns.entrySet()) { Column col = entry.getKey(); Byte flags = entry.getValue(); @@ -755,8 +751,9 @@ public class Index implements Comparable<Index> { _entryColumns.add(newEntryColumn(col) .initFromBuffer(buffer, flags, valuePrefix)); } - _page = ByteUtil.get3ByteInt(buffer, ByteOrder.BIG_ENDIAN); - _row = buffer.get(); + int page = ByteUtil.get3ByteInt(buffer, ByteOrder.BIG_ENDIAN); + int row = buffer.get(); + _rowId = new RowId(page, row); } /** @@ -773,13 +770,17 @@ public class Index implements Comparable<Index> { public List<EntryColumn> getEntryColumns() { return _entryColumns; } + + public RowId getRowId() { + return _rowId; + } public int getPage() { - return _page; + return getRowId().getPageNumber(); } public byte getRow() { - return _row; + return (byte)getRowId().getRowNumber(); } public int size() { @@ -797,15 +798,16 @@ public class Index implements Comparable<Index> { for(EntryColumn entryCol : _entryColumns) { entryCol.write(buffer); } - buffer.put((byte) (_page >>> 16)); - buffer.put((byte) (_page >>> 8)); - buffer.put((byte) _page); - buffer.put(_row); + int page = getPage(); + buffer.put((byte) (page >>> 16)); + buffer.put((byte) (page >>> 8)); + buffer.put((byte) page); + buffer.put(getRow()); } @Override public String toString() { - return ("Page = " + _page + ", Row = " + _row + ", Columns = " + _entryColumns + "\n"); + return ("RowId = " + _rowId + ", Columns = " + _entryColumns + "\n"); } public int compareTo(Entry other) { @@ -826,8 +828,7 @@ public class Index implements Comparable<Index> { return i; } } - return new CompareToBuilder().append(_page, other.getPage()) - .append(_row, other.getRow()).toComparison(); + return _rowId.compareTo(other.getRowId()); } diff --git a/src/java/com/healthmarketscience/jackcess/RowId.java b/src/java/com/healthmarketscience/jackcess/RowId.java index 6ef7bc3..a125759 100644 --- a/src/java/com/healthmarketscience/jackcess/RowId.java +++ b/src/java/com/healthmarketscience/jackcess/RowId.java @@ -12,6 +12,8 @@ import org.apache.commons.lang.builder.CompareToBuilder; */ public class RowId implements Comparable<RowId> { + public static final int INVALID_ROW_NUMBER = -1; + private final int _pageNumber; private final int _rowNumber; @@ -32,6 +34,10 @@ public class RowId implements Comparable<RowId> return _rowNumber; } + public boolean isValidRow() { + return(getRowNumber() != INVALID_ROW_NUMBER); + } + public int compareTo(RowId other) { return new CompareToBuilder() .append(getPageNumber(), other.getPageNumber()) diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java index 08d7a2b..dc064cb 100644 --- a/src/java/com/healthmarketscience/jackcess/Table.java +++ b/src/java/com/healthmarketscience/jackcess/Table.java @@ -45,6 +45,9 @@ import org.apache.commons.logging.LogFactory; /** * A single database table + * <p> + * Is not thread-safe. + * * @author Tim McCune */ public class Table @@ -53,8 +56,6 @@ public class Table private static final Log LOG = LogFactory.getLog(Table.class); - private static final int INVALID_ROW_NUMBER = -1; - private static final short OFFSET_MASK = (short)0x1FFF; private static final short DELETED_ROW_MASK = (short)0x8000; @@ -79,8 +80,6 @@ public class Table /** owning database */ private final Database _database; - /** State used for reading the table rows */ - private RowState _rowState; /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */ private byte _tableType; /** Number of indexes on the table */ @@ -93,8 +92,6 @@ public class Table private int _lastAutoNumber; /** page number of the definition of this table */ private final int _tableDefPageNumber; - /** Number of rows left to be read on the current page */ - private short _rowsLeftOnPage = 0; /** max Number of columns in the table (includes previous deletions) */ private short _maxColumnCount; /** max Number of variable columns in the table */ @@ -109,10 +106,14 @@ public class Table private final String _name; /** Usage map of pages that this table owns */ private UsageMap _ownedPages; - /** Iterator over the pages that this table owns */ - private UsageMap.PageIterator _ownedPagesIterator; /** Usage map of pages that this table owns with free space on them */ private UsageMap _freeSpacePages; + /** modification count for the table, keeps row-states up-to-date */ + private int _modCount; + + /** common cursor for iterating through the table, kept here for historic + reasons */ + private Cursor _cursor; /** * Only used by unit tests @@ -159,7 +160,8 @@ public class Table readTableDefinition(tableBuffer); tableBuffer = null; - _rowState = new RowState(true, _maxColumnCount); + // setup common cursor + _cursor = Cursor.createCursor(this); } /** @@ -184,6 +186,18 @@ public class Table protected int getTableDefPageNumber() { return _tableDefPageNumber; } + + public RowState createRowState() { + return new RowState(true); + } + + protected UsageMap.PageIterator getOwnedPagesIterator() { + return _ownedPages.iterator(); + } + + protected UsageMap.PageIterator getOwnedPagesReverseIterator() { + return _ownedPages.reverseIterator(); + } /** * @return All of the columns in this table (unmodifiable List) @@ -191,7 +205,20 @@ public class Table public List<Column> getColumns() { return Collections.unmodifiableList(_columns); } - + + /** + * @return the column with the given name + */ + public Column getColumn(String name) { + for(Column column : _columns) { + if(column.getName().equals(name)) { + return column; + } + } + throw new IllegalArgumentException("Column with name " + name + + " does not exist in this table"); + } + /** * Only called by unit tests */ @@ -234,39 +261,40 @@ public class Table * table */ public void reset() { - _rowsLeftOnPage = 0; - _ownedPagesIterator.reset(); - _rowState.reset(); + _cursor.reset(); } /** * Delete the current row (retrieved by a call to {@link #getNextRow}). */ public void deleteCurrentRow() throws IOException { - if (_rowState.getRowNumber() == INVALID_ROW_NUMBER) { - throw new IllegalStateException("Must call getNextRow first"); + _cursor.deleteCurrentRow(); + } + + /** + * Delete the current row (retrieved by a call to {@link #getNextRow}). + */ + public void deleteRow(RowState rowState, RowId rowId) throws IOException { + if (!rowId.isValidRow()) { + throw new IllegalStateException("Given row is not valid: " + rowId); } - // FIXME, want to make this static, but writeDataPage is not static, also, this may screw up other rowstates... - // see if row was already deleted - if(_rowState.isDeleted()) { + if(rowState.isDeleted()) { throw new IllegalStateException("Deleting already deleted row"); } // delete flag always gets set in the "root" page (even if overflow row) - ByteBuffer rowBuffer = _rowState.getPage(getPageChannel()); - int pageNumber = _rowState.getPageNumber(); - int rowNumber = _rowState.getRowNumber(); - int rowIndex = getRowStartOffset(rowNumber, getFormat()); + ByteBuffer rowBuffer = rowState.getPage(); + int rowIndex = getRowStartOffset(rowId.getRowNumber(), getFormat()); rowBuffer.putShort(rowIndex, (short)(rowBuffer.getShort(rowIndex) | DELETED_ROW_MASK | OVERFLOW_ROW_MASK)); - writeDataPage(rowBuffer, pageNumber); - _rowState.setDeleted(true); + writeDataPage(rowBuffer, rowId.getPageNumber()); + rowState.setDeleted(true); // update the indexes for(Index index : _indexes) { - index.deleteRow(_rowState.getRowValues(), pageNumber, (byte)rowNumber); + index.deleteRow(rowState.getRowValues(), rowId); } // make sure table def gets updated @@ -288,30 +316,23 @@ public class Table public Map<String, Object> getNextRow(Collection<String> columnNames) throws IOException { - // find next row - ByteBuffer rowBuffer = positionAtNextRow(); - if (rowBuffer == null) { - return null; - } - - return getRow(_rowState, rowBuffer, getRowNullMask(rowBuffer), _columns, - columnNames); + return _cursor.getNextRow(columnNames); } /** * Reads a single column from the given row. */ - public static Object getRowSingleColumn( - RowState rowState, int pageNumber, int rowNum, - Column column, PageChannel pageChannel, JetFormat format) + public Object getRowSingleColumn(RowState rowState, Column column) throws IOException { - // set row state to correct page - rowState.reset(); - rowState.setPage(pageChannel, pageNumber, rowNum); - + if(this != column.getTable()) { + throw new IllegalArgumentException( + "Given column " + column + " is not from this table"); + } + // position at correct row - ByteBuffer rowBuffer = positionAtRow(rowState, pageChannel, format); + ByteBuffer rowBuffer = positionAtRowData(rowState, getPageChannel(), + getFormat()); if(rowBuffer == null) { // note, row state will indicate that row was deleted return null; @@ -319,29 +340,24 @@ public class Table return getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column); } - + /** * Reads some columns from the given row. * @param columnNames Only column names in this collection will be returned */ - public static Map<String, Object> getRow( - RowState rowState, int pageNumber, int rowNum, - Collection<Column> columns, PageChannel pageChannel, JetFormat format, - Collection<String> columnNames) + public Map<String, Object> getRow( + RowState rowState, Collection<String> columnNames) throws IOException { - // set row state to correct page - rowState.reset(); - rowState.setPage(pageChannel, pageNumber, rowNum); - // position at correct row - ByteBuffer rowBuffer = positionAtRow(rowState, pageChannel, format); + ByteBuffer rowBuffer = positionAtRowData(rowState, getPageChannel(), + getFormat()); if(rowBuffer == null) { // note, row state will indicate that row was deleted return null; } - return getRow(rowState, rowBuffer, getRowNullMask(rowBuffer), columns, + return getRow(rowState, rowBuffer, getRowNullMask(rowBuffer), _columns, columnNames); } @@ -358,6 +374,7 @@ public class Table { Map<String, Object> rtn = new LinkedHashMap<String, Object>( columns.size()); + Object[] rowValues = rowState.getRowValues(); for(Column column : columns) { Object value = null; if((columnNames == null) || (columnNames.contains(column.getName()))) { @@ -370,7 +387,7 @@ public class Table // deletion. note, most of the returned values are immutable, except // for binary data (returned as byte[]), but binary data shouldn't be // indexed anyway. - rowState._rowValues[column.getColumnNumber()] = value; + rowValues[column.getColumnNumber()] = value; } return rtn; @@ -448,54 +465,6 @@ public class Table } /** - * Position the buffer at the next row in the table - * @return a ByteBuffer narrowed to the next row, or null if none - */ - private ByteBuffer positionAtNextRow() throws IOException { - - // prepare to read next row - _rowState.reset(); - - // loop until we find the next valid row or run out of pages - while(true) { - - if (_rowsLeftOnPage == 0) { - - // load next page - ByteBuffer rowBuffer = _rowState.setPage( - getPageChannel(), _ownedPagesIterator.getNextPage(), - INVALID_ROW_NUMBER); - if(rowBuffer == null) { - //No more owned pages. No more rows. - return null; - } - if(rowBuffer.get() != PageTypes.DATA) { - //Only interested in data pages - continue; - } - - _rowsLeftOnPage = rowBuffer.getShort(getFormat().OFFSET_NUM_ROWS_ON_DATA_PAGE); - if(_rowsLeftOnPage == 0) { - // no rows on this page? - continue; - } - - } - - // move to next row - _rowState.nextRowInPage(); - _rowsLeftOnPage--; - - ByteBuffer rowBuffer = - positionAtRow(_rowState, getPageChannel(), getFormat()); - if(rowBuffer != null) { - // we found a non-deleted row, return it - return rowBuffer; - } - } - } - - /** * Sets the position and limit in a new buffer using the given rowState * according to the given row number and row end, following overflow row * pointers as necessary. @@ -503,16 +472,13 @@ public class Table * @return a ByteBuffer narrowed to the actual row data, or null if row was * deleted */ - private static ByteBuffer positionAtRow(RowState rowState, - PageChannel pageChannel, - JetFormat format) + private static ByteBuffer positionAtRowData(RowState rowState, + PageChannel pageChannel, + JetFormat format) throws IOException { - // reset row state - rowState.resetDuringSearch(); - while(true) { - ByteBuffer rowBuffer = rowState.getFinalPage(pageChannel); + ByteBuffer rowBuffer = rowState.getFinalPage(); int rowNum = rowState.getFinalRowNumber(); // note, we don't use findRowStart here cause we need the unmasked value @@ -555,8 +521,7 @@ public class Table // page/row int overflowRowNum = rowBuffer.get(rowStart); int overflowPageNum = ByteUtil.get3ByteInt(rowBuffer, rowStart + 1); - rowState.setOverflowPage(pageChannel, overflowPageNum, - overflowRowNum); + rowState.setOverflowRow(overflowPageNum, overflowRowNum); } else { @@ -589,7 +554,7 @@ public class Table */ public Iterator<Map<String, Object>> iterator(Collection<String> columnNames) { - return new RowIterator(columnNames); + return _cursor.iterator(columnNames); } /** @@ -888,7 +853,6 @@ public class Table byte rowNum = tableBuffer.get(getFormat().OFFSET_OWNED_PAGES); int pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_OWNED_PAGES + 1); _ownedPages = UsageMap.read(getDatabase(), pageNum, rowNum, false); - _ownedPagesIterator = _ownedPages.iterator(); rowNum = tableBuffer.get(getFormat().OFFSET_FREE_SPACE_PAGES); pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES + 1); _freeSpacePages = UsageMap.read(getDatabase(), pageNum, rowNum, false); @@ -998,8 +962,9 @@ public class Table // write the page data getPageChannel().writePage(pageBuffer, pageNumber); - // if the overflow buffer is this page, invalidate it - _rowState.possiblyInvalidate(pageNumber, pageBuffer); + // update modification count so any active RowStates can keep themselves + // up-to-date + ++_modCount; } /** @@ -1070,7 +1035,7 @@ public class Table // update the indexes for(Index index : _indexes) { - index.addRow(rows.get(i), pageNumber, (byte)rowNum); + index.addRow(rows.get(i), new RowId(pageNumber, rowNum)); } } writeDataPage(dataPage, pageNumber); @@ -1350,12 +1315,24 @@ public class Table return rowCount; } + + public static boolean isDeletedRow(short rowStart) { + return ((rowStart & DELETED_ROW_MASK) != 0); + } + + public static boolean isOverflowRow(short rowStart) { + return ((rowStart & OVERFLOW_ROW_MASK) != 0); + } + + public static short cleanRowStart(short rowStart) { + return (short)(rowStart & OFFSET_MASK); + } public static short findRowStart(ByteBuffer buffer, int rowNum, JetFormat format) { - return (short)(buffer.getShort(getRowStartOffset(rowNum, format)) - & OFFSET_MASK); + return cleanRowStart( + buffer.getShort(getRowStartOffset(rowNum, format))); } public static int getRowStartOffset(int rowNum, JetFormat format) @@ -1368,8 +1345,8 @@ public class Table { return (short)((rowNum == 0) ? format.PAGE_SIZE : - (buffer.getShort(getRowEndOffset(rowNum, format)) - & OFFSET_MASK)); + cleanRowStart( + buffer.getShort(getRowEndOffset(rowNum, format)))); } public static int getRowEndOffset(int rowNum, JetFormat format) @@ -1429,11 +1406,11 @@ public class Table /** * Maintains the state of reading a row of data. */ - public static class RowState + public class RowState { /** Buffer used for reading the row data pages */ private TempPageHolder _rowBufferH; - /** row number of the main row */ + /** the row number on the main page */ private int _rowNumber; /** true if the current row is an overflow row */ private boolean _overflow; @@ -1450,10 +1427,13 @@ public class Table private int _finalRowNumber; /** values read from the last row */ private Object[] _rowValues; + /** last modification count seen on the table */ + private int _lastModCount; - public RowState(boolean hardRowBuffer, int colCount) { + private RowState(boolean hardRowBuffer) { _rowBufferH = TempPageHolder.newHolder(hardRowBuffer); - _rowValues = new Object[colCount]; + _rowValues = new Object[Table.this._maxColumnCount]; + _lastModCount = Table.this._modCount; } public void reset() { @@ -1462,40 +1442,32 @@ public class Table } public void resetDuringSearch() { - resetNewPage(); - resetNewRow(); - } - - private void resetNewRow() { - _finalRowNumber = INVALID_ROW_NUMBER; - } - - private void resetNewPage() { + _finalRowNumber = RowId.INVALID_ROW_NUMBER; _finalRowBuffer = null; _deleted = false; _overflow = false; - } - - public int getPageNumber() { - return _rowBufferH.getPageNumber(); } - public int getRowNumber() { - return _rowNumber; + private void checkForModification() { + if(Table.this._modCount != _lastModCount) { + _rowBufferH.invalidate(); + _overflowRowBufferH.invalidate(); + _lastModCount = Table.this._modCount; + } } - public ByteBuffer getFinalPage(PageChannel pageChannel) + public ByteBuffer getFinalPage() throws IOException { if(_finalRowBuffer == null) { // (re)load current page - _finalRowBuffer = getPage(pageChannel); + _finalRowBuffer = getPage(); } return _finalRowBuffer; } public int getFinalRowNumber() { - if(_finalRowNumber == INVALID_ROW_NUMBER) { + if(_finalRowNumber == RowId.INVALID_ROW_NUMBER) { _finalRowNumber = _rowNumber; } return _finalRowNumber; @@ -1525,43 +1497,35 @@ public class Table modifiedBuffer); } - public ByteBuffer getPage(PageChannel pageChannel) + public ByteBuffer getPage() throws IOException { - return _rowBufferH.getPage(pageChannel); - } - - public void nextRowInPage() { - setRowNumber(_rowNumber + 1); + checkForModification(); + return _rowBufferH.getPage(getPageChannel()); } - public void setRowNumber(int rowNumber) { - resetNewRow(); - _rowNumber = rowNumber; - _finalRowNumber = rowNumber; - } - - public ByteBuffer setPage(PageChannel pageChannel, int pageNumber, - int rowNumber) + public ByteBuffer setRow(int pageNumber, int rowNumber) throws IOException { - resetNewPage(); - setRowNumber(rowNumber); + resetDuringSearch(); + checkForModification(); + _rowNumber = rowNumber; + _finalRowNumber = rowNumber; if(pageNumber == PageChannel.INVALID_PAGE_NUMBER) { - _rowBufferH.invalidate(); return null; } - _finalRowBuffer = _rowBufferH.setPage(pageChannel, pageNumber); + _finalRowBuffer = _rowBufferH.setPage(getPageChannel(), pageNumber); return _finalRowBuffer; } - public ByteBuffer setOverflowPage(PageChannel pageChannel, int pageNumber, - int rowNumber) + public ByteBuffer setOverflowRow(int pageNumber, int rowNumber) throws IOException { + checkForModification(); _overflow = true; - _finalRowBuffer = _overflowRowBufferH.setPage(pageChannel, pageNumber); _finalRowNumber = rowNumber; + _finalRowBuffer = _overflowRowBufferH.setPage(getPageChannel(), + pageNumber); return _finalRowBuffer; } |