diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2007-12-02 04:29:59 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2007-12-02 04:29:59 +0000 |
commit | bca9319ef7c3fba0ee234d2ba8152de6fe0d1329 (patch) | |
tree | 2a8b859f6a325b1febeeb8e09367875f08d0d202 | |
parent | 5cd19292eb5178343233bd6e5ef2ce8367b443ff (diff) | |
download | jackcess-bca9319ef7c3fba0ee234d2ba8152de6fe0d1329.tar.gz jackcess-bca9319ef7c3fba0ee234d2ba8152de6fe0d1329.zip |
implement range-based, index cursors
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@194 f203690c-595d-4dc9-a70b-905162fa7fd2
6 files changed, 466 insertions, 161 deletions
diff --git a/src/java/com/healthmarketscience/jackcess/Cursor.java b/src/java/com/healthmarketscience/jackcess/Cursor.java index 3bba37e..520481a 100644 --- a/src/java/com/healthmarketscience/jackcess/Cursor.java +++ b/src/java/com/healthmarketscience/jackcess/Cursor.java @@ -30,27 +30,20 @@ public abstract class Cursor implements Iterable<Map<String, Object>> { private static final Log LOG = LogFactory.getLog(Cursor.class); - /** normal first position for the TableScanCursor */ + /** first position for the TableScanCursor */ private static final ScanPosition FIRST_SCAN_POSITION = new ScanPosition(RowId.FIRST_ROW_ID); - /** normal last position for the TableScanCursor */ + /** last position for the TableScanCursor */ private static final ScanPosition LAST_SCAN_POSITION = new ScanPosition(RowId.LAST_ROW_ID); - /** normal first position for the IndexCursor */ - private static final IndexPosition FIRST_INDEX_POSITION = - new IndexPosition(Index.FIRST_ENTRY); - /** normal last position for the IndexCursor */ - private static final IndexPosition LAST_INDEX_POSITION = - new IndexPosition(Index.LAST_ENTRY); - /** owning table */ private final Table _table; /** State used for reading the table rows */ private final RowState _rowState; - /** the first (exclusive) row id for this iterator */ + /** the first (exclusive) row id for this cursor */ private final Position _firstPos; - /** the last (exclusive) row id for this iterator */ + /** the last (exclusive) row id for this cursor */ private final Position _lastPos; /** the previous row */ private Position _prevPos; @@ -84,7 +77,54 @@ public abstract class Cursor implements Iterable<Map<String, Object>> public static Cursor createIndexCursor(Table table, Index index) throws IOException { - return new IndexCursor(table, index); + return createIndexCursor(table, index, null, null); + } + + /** + * Creates an indexed cursor for the given table, narrowed to the given + * range. + * @param table the table over which this cursor will traverse + * @param index index for the table which will define traversal order as + * well as enhance certain lookups + * @param startRow the first row of data for the cursor (inclusive), or + * {@code null} for the first entry + * @param endRow the last row of data for the cursor (inclusive), or + * {@code null} for the last entry + */ + public static Cursor createIndexCursor(Table table, Index index, + Object[] startRow, Object[] endRow) + throws IOException + { + return createIndexCursor(table, index, startRow, true, endRow, true); + } + + /** + * Creates an indexed cursor for the given table, narrowed to the given + * range. + * @param table the table over which this cursor will traverse + * @param index index for the table which will define traversal order as + * well as enhance certain lookups + * @param startRow the first row of data for the cursor, or {@code null} for + * the first entry + * @param startInclusive whether or not startRow is inclusive or exclusive + * @param endRow the last row of data for the cursor, or {@code null} for + * the last entry + * @param endInclusive whether or not endRow is inclusive or exclusive + */ + public static Cursor createIndexCursor(Table table, Index index, + Object[] startRow, + boolean startInclusive, + Object[] endRow, + boolean endInclusive) + throws IOException + { + if(table != index.getTable()) { + throw new IllegalArgumentException( + "Given index is not for given table: " + index + ", " + table); + } + return new IndexCursor(table, index, + index.cursor(startRow, startInclusive, + endRow, endInclusive)); } /** @@ -209,11 +249,11 @@ public abstract class Cursor implements Iterable<Map<String, Object>> * Moves the cursor to a savepoint previously returned from * {@link #getSavepoint}. */ - public void restoreSavepoint(Savepoint savepoint) + public Cursor restoreSavepoint(Savepoint savepoint) throws IOException { - restorePosition(savepoint.getCurrentPosition(), - savepoint.getPreviousPosition()); + return restorePosition(savepoint.getCurrentPosition(), + savepoint.getPreviousPosition()); } /** @@ -231,26 +271,26 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } /** - * Resets this cursor for forward iteration. Calls {@link #beforeFirst}. + * Resets this cursor for forward traversal. Calls {@link #beforeFirst}. */ - public void reset() { - beforeFirst(); + public Cursor reset() { + return beforeFirst(); } /** - * Resets this cursor for forward iteration (sets cursor to before the first + * Resets this cursor for forward traversal (sets cursor to before the first * row). */ - public void beforeFirst() { - reset(true); + public Cursor beforeFirst() { + return reset(true); } /** - * Resets this cursor for reverse iteration (sets cursor to after the last + * Resets this cursor for reverse traversal (sets cursor to after the last * row). */ - public void afterLast() { - reset(false); + public Cursor afterLast() { + return reset(false); } /** @@ -293,12 +333,13 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } /** - * Resets this cursor for iterating the given direction. + * Resets this cursor for traversing the given direction. */ - protected void reset(boolean moveForward) { + protected Cursor reset(boolean moveForward) { _curPos = getDirHandler(moveForward).getBeginningPosition(); _prevPos = _curPos; _rowState.reset(); + return this; } /** @@ -470,27 +511,38 @@ public abstract class Cursor implements Iterable<Map<String, Object>> * Restores a current position for the cursor (current position becomes * previous position). */ - protected void restorePosition(Position curPos) + protected Cursor restorePosition(Position curPos) throws IOException { - restorePosition(curPos, _curPos); + return restorePosition(curPos, _curPos); } /** - * Restores a current and previous position for the cursor. + * Restores a current and previous position for the cursor if the given + * positions are different from the current positions. */ - protected void restorePosition(Position curPos, Position prevPos) + protected final Cursor restorePosition(Position curPos, Position prevPos) throws IOException { if(!curPos.equals(_curPos) || !prevPos.equals(_prevPos)) { - // make the current position previous, and the new position current - _prevPos = _curPos; - _curPos = curPos; - _rowState.reset(); + restorePositionImpl(curPos, prevPos); } + return this; } /** + * Restores a current and previous position for the cursor. + */ + protected void restorePositionImpl(Position curPos, Position prevPos) + throws IOException + { + // make the current position previous, and the new position current + _prevPos = _curPos; + _curPos = curPos; + _rowState.reset(); + } + + /** * Rechecks the current position if the underlying data structures have been * modified. * @return {@code true} if the cursor ended up in a new position, @@ -815,13 +867,13 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } @Override - protected void reset(boolean moveForward) { + protected Cursor reset(boolean moveForward) { _ownedPagesCursor.reset(moveForward); - super.reset(moveForward); + return super.reset(moveForward); } @Override - protected void restorePosition(Position curPos, Position prevPos) + protected void restorePositionImpl(Position curPos, Position prevPos) throws IOException { if(!(curPos instanceof ScanPosition) || @@ -829,9 +881,9 @@ public abstract class Cursor implements Iterable<Map<String, Object>> throw new IllegalArgumentException( "Restored positions must be scan positions"); } - super.restorePosition(curPos, prevPos); _ownedPagesCursor.restorePosition(curPos.getRowId().getPageNumber(), prevPos.getRowId().getPageNumber()); + super.restorePositionImpl(curPos, prevPos); } @Override @@ -957,15 +1009,14 @@ public abstract class Cursor implements Iterable<Map<String, Object>> /** Cursor over the entries of the relvant index */ private final Index.EntryCursor _entryCursor; - private IndexCursor(Table table, Index index) + private IndexCursor(Table table, Index index, + Index.EntryCursor entryCursor) throws IOException { - super(table, FIRST_INDEX_POSITION, LAST_INDEX_POSITION); - if(table != index.getTable()) { - throw new IllegalArgumentException( - "Given index is not for given table: " + index + ", " + table); - } - _entryCursor = index.cursor(); + super(table, + new IndexPosition(entryCursor.getFirstEntry()), + new IndexPosition(entryCursor.getLastEntry())); + _entryCursor = entryCursor; } @Override @@ -979,13 +1030,13 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } @Override - protected void reset(boolean moveForward) { + protected Cursor reset(boolean moveForward) { _entryCursor.reset(moveForward); - super.reset(moveForward); + return super.reset(moveForward); } @Override - protected void restorePosition(Position curPos, Position prevPos) + protected void restorePositionImpl(Position curPos, Position prevPos) throws IOException { if(!(curPos instanceof IndexPosition) || @@ -993,9 +1044,9 @@ public abstract class Cursor implements Iterable<Map<String, Object>> throw new IllegalArgumentException( "Restored positions must be index positions"); } - super.restorePosition(curPos, prevPos); _entryCursor.restorePosition(((IndexPosition)curPos).getEntry(), ((IndexPosition)prevPos).getEntry()); + super.restorePositionImpl(curPos, prevPos); } @Override diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index 7aed345..fe12832 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -597,8 +597,46 @@ public class Index implements Comparable<Index> { public EntryCursor cursor() throws IOException { + return cursor(null, true, null, true); + } + + /** + * Gets a new cursor for this index, narrowed to the range defined by the + * given startRow and endRow. + * <p> + * Forces index initialization. + * + * @param startRow the first row of data for the cursor, or {@code null} for + * the first entry + * @param startInclusive whether or not startRow is inclusive or exclusive + * @param endRow the last row of data for the cursor, or {@code null} for + * the last entry + * @param endInclusive whether or not endRow is inclusive or exclusive + */ + public EntryCursor cursor(Object[] startRow, + boolean startInclusive, + Object[] endRow, + boolean endInclusive) + throws IOException + { initialize(); - return new EntryCursor(); + Position startPos = FIRST_POSITION; + if(startRow != null) { + Entry startEntry = new Entry(startRow, + (startInclusive ? + RowId.FIRST_ROW_ID : RowId.LAST_ROW_ID), + _columns); + startPos = new Position(FIRST_ENTRY_IDX, startEntry); + } + Position endPos = LAST_POSITION; + if(endRow != null) { + Entry endEntry = new Entry(endRow, + (endInclusive ? + RowId.LAST_ROW_ID : RowId.FIRST_ROW_ID), + _columns); + endPos = new Position(LAST_ENTRY_IDX, endEntry); + } + return new EntryCursor(startPos, endPos); } /** @@ -1413,11 +1451,28 @@ public class Index implements Comparable<Index> { private final DirHandler _forwardDirHandler = new ForwardDirHandler(); /** handler for moving the page cursor backward */ private final DirHandler _reverseDirHandler = new ReverseDirHandler(); + /** the first (exclusive) row id for this cursor */ + private final Position _firstPos; + /** the last (exclusive) row id for this cursor */ + private final Position _lastPos; + /** the first valid index for this cursor */ + private int _minIndex; + /** the last valid index for this cursor */ + private int _maxIndex; + /** the current entry */ private Position _curPos; + /** the previous entry */ private Position _prevPos; + /** the last read modification count on the Index. we track this so that + the cursor can detect updates to the index while traversing and act + accordingly */ private int _lastModCount; - private EntryCursor() { + private EntryCursor(Position firstPos, Position lastPos) { + _firstPos = firstPos; + _lastPos = lastPos; + // force bounds to be updated + _lastModCount = Index.this._modCount - 1; reset(); } @@ -1426,6 +1481,20 @@ public class Index implements Comparable<Index> { } /** + * Returns the first entry (exclusive) as defined by this cursor. + */ + public Entry getFirstEntry() { + return _firstPos.getEntry(); + } + + /** + * Returns the last entry (exclusive) as defined by this cursor. + */ + public Entry getLastEntry() { + return _lastPos.getEntry(); + } + + /** * Returns the DirHandler for the given direction */ private DirHandler getDirHandler(boolean moveForward) { @@ -1440,55 +1509,60 @@ public class Index implements Comparable<Index> { return(Index.this._modCount == _lastModCount); } - public void reset() { - beforeFirst(); + public EntryCursor reset() { + return beforeFirst(); } - public void beforeFirst() { - reset(true); + public EntryCursor beforeFirst() { + return reset(true); } - public void afterLast() { - reset(false); + public EntryCursor afterLast() { + return reset(false); } - protected void reset(boolean moveForward) { + protected EntryCursor reset(boolean moveForward) { _curPos = getDirHandler(moveForward).getBeginningPosition(); _prevPos = _curPos; - _lastModCount = Index.this._modCount; + if(!isUpToDate()) { + // update bounds + updateBounds(); + _lastModCount = Index.this._modCount; + } + return this; } /** * Repositions the cursor so that the next row will be the first entry * >= the given row. */ - public void beforeEntry(Object[] row) + public EntryCursor beforeEntry(Object[] row) throws IOException { - restorePosition(new Entry(row, RowId.FIRST_ROW_ID, _columns)); + return restorePosition(new Entry(row, RowId.FIRST_ROW_ID, _columns)); } /** * Repositions the cursor so that the previous row will be the first * entry <= the given row. */ - public void afterEntry(Object[] row) + public EntryCursor afterEntry(Object[] row) throws IOException { - restorePosition(new Entry(row, RowId.LAST_ROW_ID, _columns)); + return restorePosition(new Entry(row, RowId.LAST_ROW_ID, _columns)); } /** - * @return valid entry if there was entry, {@link Index#LAST_ENTRY} - * otherwise + * @return valid entry if there was a next entry, + * {@code #getLastEntry} otherwise */ public Entry getNextEntry() { return getAnotherEntry(true); } /** - * @return valid entry if there was entry, {@link Index#FIRST_ENTRY} - * otherwise + * @return valid entry if there was a next entry, + * {@code #getFirstEntry} otherwise */ public Entry getPreviousEntry() { return getAnotherEntry(false); @@ -1498,63 +1572,90 @@ public class Index implements Comparable<Index> { * Restores a current position for the cursor (current position becomes * previous position). */ - private void restorePosition(Entry curEntry) { - restorePosition(curEntry, _curPos.getEntry()); + private EntryCursor restorePosition(Entry curEntry) { + return restorePosition(curEntry, _curPos.getEntry()); } /** * Restores a current and previous position for the cursor. */ - protected void restorePosition(Entry curEntry, Entry prevEntry) + protected EntryCursor restorePosition(Entry curEntry, Entry prevEntry) { if(!curEntry.equals(_curPos.getEntry()) || !prevEntry.equals(_prevPos.getEntry())) { _prevPos = updatePosition(prevEntry); _curPos = updatePosition(curEntry); - _lastModCount = Index.this._modCount; + if(!isUpToDate()) { + updateBounds(); + _lastModCount = Index.this._modCount; + } } else { checkForModification(); } + return this; } /** - * Checks the index for modifications an updates state accordingly. + * Checks the index for modifications and updates state accordingly. */ private void checkForModification() { if(!isUpToDate()) { _prevPos = updatePosition(_prevPos.getEntry()); _curPos = updatePosition(_curPos.getEntry()); + updateBounds(); _lastModCount = Index.this._modCount; } } + private void updateBounds() { + int idx = findEntry(_firstPos.getEntry()); + if(idx < 0) { + idx = missingIndexToInsertionPoint(idx); + } + _minIndex = idx; + + idx = findEntry(_lastPos.getEntry()); + if(idx < 0) { + idx = missingIndexToInsertionPoint(idx) - 1; + } + _maxIndex = idx; + } + /** * Gets an up-to-date position for the given entry. */ private Position updatePosition(Entry entry) { - int curIdx = FIRST_ENTRY_IDX; - boolean between = false; if(entry.isValid()) { + // find the new position for this entry - int idx = findEntry(entry); - if(idx >= 0) { - curIdx = idx; - } else { + int curIdx = findEntry(entry); + boolean between = false; + if(curIdx < 0) { // given entry was not found exactly. our current position is now // really between two indexes, but we cannot support that as an // integer value so we set a flag instead - curIdx = missingIndexToInsertionPoint(idx); + curIdx = missingIndexToInsertionPoint(curIdx); between = true; } - } else if(entry.equals(FIRST_ENTRY)) { - curIdx = FIRST_ENTRY_IDX; - } else if(entry.equals(LAST_ENTRY)) { - curIdx = LAST_ENTRY_IDX; + + if(curIdx < _minIndex) { + curIdx = _minIndex; + between = true; + } else if(curIdx > _maxIndex) { + curIdx = _maxIndex + 1; + between = true; + } + + return new Position(curIdx, entry, between); + + } else if(entry.equals(_firstPos.getEntry())) { + return _firstPos; + } else if(entry.equals(_lastPos.getEntry())) { + return _lastPos; } else { throw new IllegalArgumentException("Invalid entry given: " + entry); } - return new Position(curIdx, entry, between); } /** @@ -1598,12 +1699,12 @@ public class Index implements Comparable<Index> { return new Position(curIdx, _entries.get(curIdx)); } protected final Position newForwardPosition(int curIdx) { - return((curIdx < _entries.size()) ? - newPosition(curIdx) : LAST_POSITION); + return((curIdx <= _maxIndex) ? + newPosition(curIdx) : _lastPos); } protected final Position newReversePosition(int curIdx) { - return ((curIdx >= 0) ? - newPosition(curIdx) : FIRST_POSITION); + return ((curIdx >= _minIndex) ? + newPosition(curIdx) : _firstPos); } } @@ -1617,17 +1718,17 @@ public class Index implements Comparable<Index> { // between position if(!between) { curIdx = ((curIdx == getBeginningPosition().getIndex()) ? - 0 : (curIdx + 1)); + _minIndex : (curIdx + 1)); } return newForwardPosition(curIdx); } @Override public Position getBeginningPosition() { - return FIRST_POSITION; + return _firstPos; } @Override public Position getEndPosition() { - return LAST_POSITION; + return _lastPos; } } @@ -1641,16 +1742,16 @@ public class Index implements Comparable<Index> { // pointing at the correct next index in either the between or // non-between case curIdx = ((curIdx == getBeginningPosition().getIndex()) ? - (_entries.size() - 1) : (curIdx - 1)); + _maxIndex : (curIdx - 1)); return newReversePosition(curIdx); } @Override public Position getBeginningPosition() { - return LAST_POSITION; + return _lastPos; } @Override public Position getEndPosition() { - return FIRST_POSITION; + return _firstPos; } } } diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java index 08c7d4a..830b17c 100644 --- a/src/java/com/healthmarketscience/jackcess/Table.java +++ b/src/java/com/healthmarketscience/jackcess/Table.java @@ -39,7 +39,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import com.healthmarketscience.jackcess.Table.RowState; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -1048,7 +1047,7 @@ public class Table // find last data page (Not bothering to check other pages for free // space.) - UsageMap.PageCursor revPageCursor = _ownedPages.cursorAtEnd(); + UsageMap.PageCursor revPageCursor = _ownedPages.cursor().afterLast(); while(true) { int tmpPageNumber = revPageCursor.getPreviousPage(); if(tmpPageNumber < 0) { @@ -1486,7 +1485,9 @@ public class Table private boolean _haveRowValues; /** values read from the last row */ private final Object[] _rowValues; - /** last modification count seen on the table */ + /** last modification count seen on the table we track this so that the + rowState can detect updates to the table and re-read any buffered + data */ private int _lastModCount; private RowState(boolean hardRowBuffer) { diff --git a/src/java/com/healthmarketscience/jackcess/UsageMap.java b/src/java/com/healthmarketscience/jackcess/UsageMap.java index 231267a..79e772e 100644 --- a/src/java/com/healthmarketscience/jackcess/UsageMap.java +++ b/src/java/com/healthmarketscience/jackcess/UsageMap.java @@ -147,12 +147,6 @@ public class UsageMap return new PageCursor(); } - public PageCursor cursorAtEnd() { - PageCursor cursor = new PageCursor(); - cursor.afterLast(); - return cursor; - } - protected short getRowStart() { return _rowStart; } @@ -784,48 +778,50 @@ public class UsageMap * After calling this method, getNextPage will return the first page in * the map */ - public void reset() { - beforeFirst(); + public PageCursor reset() { + return beforeFirst(); } /** * After calling this method, {@link #getNextPage} will return the first * page in the map */ - public void beforeFirst() { - reset(true); + public PageCursor beforeFirst() { + return reset(true); } /** * After calling this method, {@link #getPreviousPage} will return the * last page in the map */ - public void afterLast() { - reset(false); + public PageCursor afterLast() { + return reset(false); } /** * Resets this page cursor for traversing the given direction. */ - protected void reset(boolean moveForward) { + protected PageCursor reset(boolean moveForward) { _curPageNumber = getDirHandler(moveForward).getBeginningPageNumber(); _prevPageNumber = _curPageNumber; _lastModCount = UsageMap.this._modCount; + return this; } /** * Restores a current position for the cursor (current position becomes * previous position). */ - private void restorePosition(int curPageNumber) + private PageCursor restorePosition(int curPageNumber) { - restorePosition(curPageNumber, _curPageNumber); + return restorePosition(curPageNumber, _curPageNumber); } /** * Restores a current and previous position for the cursor. */ - protected void restorePosition(int curPageNumber, int prevPageNumber) { + protected PageCursor restorePosition(int curPageNumber, int prevPageNumber) + { if((curPageNumber != _curPageNumber) || (prevPageNumber != _prevPageNumber)) { @@ -835,6 +831,7 @@ public class UsageMap } else { checkForModification(); } + return this; } /** diff --git a/test/src/java/com/healthmarketscience/jackcess/CursorTest.java b/test/src/java/com/healthmarketscience/jackcess/CursorTest.java index f6b829d..6b67892 100644 --- a/test/src/java/com/healthmarketscience/jackcess/CursorTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/CursorTest.java @@ -33,6 +33,17 @@ public class CursorTest extends TestCase { } return expectedRows; } + + private static List<Map<String,Object>> createTestTableData( + int startIdx, + int endIdx) + throws Exception + { + List<Map<String,Object>> expectedRows = createTestTableData(); + expectedRows.subList(endIdx, expectedRows.size()).clear(); + expectedRows.subList(0, startIdx).clear(); + return expectedRows; + } private static Database createTestTable() throws Exception { Database db = create(); @@ -85,6 +96,17 @@ public class CursorTest extends TestCase { return db; } + private static Cursor createIndexSubRangeCursor(Table table, + Index idx, + int type) + throws Exception + { + return Cursor.createIndexCursor( + table, idx, + idx.constructIndexRow(3 - type), (type == 0), + idx.constructIndexRow(8 + type), (type == 0)); + } + public void testRowId() throws Exception { // test special cases RowId rowId1 = new RowId(1, 2); @@ -105,12 +127,17 @@ public class CursorTest extends TestCase { Table table = db.getTable("test"); Cursor cursor = Cursor.createCursor(table); - doTestSimple(table, cursor); + doTestSimple(table, cursor, null); db.close(); } - private void doTestSimple(Table table, Cursor cursor) throws Exception { - List<Map<String,Object>> expectedRows = createTestTableData(); + private void doTestSimple(Table table, Cursor cursor, + List<Map<String,Object>> expectedRows) + throws Exception + { + if(expectedRows == null) { + expectedRows = createTestTableData(); + } List<Map<String, Object>> foundRows = new ArrayList<Map<String, Object>>(); @@ -125,13 +152,18 @@ public class CursorTest extends TestCase { Table table = db.getTable("test"); Cursor cursor = Cursor.createCursor(table); - doTestMove(table, cursor); + doTestMove(table, cursor, null); db.close(); } - private void doTestMove(Table table, Cursor cursor) throws Exception { - List<Map<String,Object>> expectedRows = createTestTableData(); + private void doTestMove(Table table, Cursor cursor, + List<Map<String,Object>> expectedRows) + throws Exception + { + if(expectedRows == null) { + expectedRows = createTestTableData(); + } expectedRows.subList(1, 4).clear(); List<Map<String, Object>> foundRows = @@ -177,12 +209,13 @@ public class CursorTest extends TestCase { Table table = db.getTable("test"); Cursor cursor = Cursor.createCursor(table); - doTestSearch(table, cursor, null); + doTestSearch(table, cursor, null, 42, -13); db.close(); } - private void doTestSearch(Table table, Cursor cursor, Index index) + private void doTestSearch(Table table, Cursor cursor, Index index, + Integer... outOfRangeValues) throws Exception { assertTrue(cursor.findRow(table.getColumn("id"), 3)); @@ -211,28 +244,38 @@ public class CursorTest extends TestCase { "value", "data" + 7), cursor.getCurrentRow()); - assertTrue(cursor.findRow(table.getColumn("value"), "data" + 2)); - assertEquals(createExpectedRow("id", 2, - "value", "data" + 2), + assertTrue(cursor.findRow(table.getColumn("value"), "data" + 4)); + assertEquals(createExpectedRow("id", 4, + "value", "data" + 4), cursor.getCurrentRow()); + + for(Integer outOfRangeValue : outOfRangeValues) { + assertFalse(cursor.findRow(table.getColumn("id"), + outOfRangeValue)); + assertFalse(cursor.findRow(table.getColumn("value"), + "data" + outOfRangeValue)); + assertFalse(cursor.findRow(createExpectedRow( + "id", outOfRangeValue, + "value", "data" + outOfRangeValue))); + } - assertEquals("data" + 9, + assertEquals("data" + 5, Cursor.findValue(table, table.getColumn("value"), - table.getColumn("id"), 9)); - assertEquals(createExpectedRow("id", 9, - "value", "data" + 9), + table.getColumn("id"), 5)); + assertEquals(createExpectedRow("id", 5, + "value", "data" + 5), Cursor.findRow(table, - createExpectedRow("id", 9))); + createExpectedRow("id", 5))); if(index != null) { - assertEquals("data" + 9, + assertEquals("data" + 5, Cursor.findValue(table, index, table.getColumn("value"), - table.getColumn("id"), 9)); - assertEquals(createExpectedRow("id", 9, - "value", "data" + 9), + table.getColumn("id"), 5)); + assertEquals(createExpectedRow("id", 5, + "value", "data" + 5), Cursor.findRow(table, index, - createExpectedRow("id", 9))); + createExpectedRow("id", 5))); } } @@ -241,13 +284,18 @@ public class CursorTest extends TestCase { Table table = db.getTable("test"); Cursor cursor = Cursor.createCursor(table); - doTestReverse(table, cursor); + doTestReverse(table, cursor, null); db.close(); } - private void doTestReverse(Table table, Cursor cursor) throws Exception { - List<Map<String,Object>> expectedRows = createTestTableData(); + private void doTestReverse(Table table, Cursor cursor, + List<Map<String,Object>> expectedRows) + throws Exception + { + if(expectedRows == null) { + expectedRows = createTestTableData(); + } Collections.reverse(expectedRows); List<Map<String, Object>> foundRows = @@ -265,14 +313,15 @@ public class CursorTest extends TestCase { Cursor cursor1 = Cursor.createCursor(table); Cursor cursor2 = Cursor.createCursor(table); - doTestLiveAddition(table, cursor1, cursor2); + doTestLiveAddition(table, cursor1, cursor2, 11); db.close(); } private void doTestLiveAddition(Table table, Cursor cursor1, - Cursor cursor2) throws Exception + Cursor cursor2, + Integer newRowNum) throws Exception { cursor1.moveNextRows(11); cursor2.moveNextRows(11); @@ -280,7 +329,6 @@ public class CursorTest extends TestCase { assertTrue(cursor1.isAfterLast()); assertTrue(cursor2.isAfterLast()); - int newRowNum = 11; table.addRow(newRowNum, "data" + newRowNum); Map<String,Object> expectedRow = createExpectedRow("id", newRowNum, "value", "data" + newRowNum); @@ -306,7 +354,7 @@ public class CursorTest extends TestCase { Cursor cursor2 = Cursor.createCursor(table); Cursor cursor3 = Cursor.createCursor(table); Cursor cursor4 = Cursor.createCursor(table); - doTestLiveDeletion(table, cursor1, cursor2, cursor3, cursor4); + doTestLiveDeletion(table, cursor1, cursor2, cursor3, cursor4, 1); db.close(); } @@ -315,20 +363,23 @@ public class CursorTest extends TestCase { Cursor cursor1, Cursor cursor2, Cursor cursor3, - Cursor cursor4) throws Exception + Cursor cursor4, + int firstValue) throws Exception { - cursor1.moveNextRows(2); - cursor2.moveNextRows(3); - cursor3.moveNextRows(3); - cursor4.moveNextRows(4); + assertEquals(2, cursor1.moveNextRows(2)); + assertEquals(3, cursor2.moveNextRows(3)); + assertEquals(3, cursor3.moveNextRows(3)); + assertEquals(4, cursor4.moveNextRows(4)); Map<String,Object> expectedPrevRow = - createExpectedRow("id", 1, "value", "data" + 1); + createExpectedRow("id", firstValue, "value", "data" + firstValue); + ++firstValue; Map<String,Object> expectedDeletedRow = - createExpectedRow("id", 2, "value", "data" + 2); + createExpectedRow("id", firstValue, "value", "data" + firstValue); + ++firstValue; Map<String,Object> expectedNextRow = - createExpectedRow("id", 3, "value", "data" + 3); - + createExpectedRow("id", firstValue, "value", "data" + firstValue); + assertEquals(expectedDeletedRow, cursor2.getCurrentRow()); assertEquals(expectedDeletedRow, cursor3.getCurrentRow()); @@ -356,7 +407,7 @@ public class CursorTest extends TestCase { assertTable(createUnorderedTestTableData(), table); Cursor cursor = Cursor.createIndexCursor(table, idx); - doTestSimple(table, cursor); + doTestSimple(table, cursor, null); db.close(); } @@ -367,7 +418,7 @@ public class CursorTest extends TestCase { Table table = db.getTable("test"); Index idx = table.getIndexes().get(0); Cursor cursor = Cursor.createIndexCursor(table, idx); - doTestMove(table, cursor); + doTestMove(table, cursor, null); db.close(); } @@ -378,7 +429,7 @@ public class CursorTest extends TestCase { Table table = db.getTable("test"); Index idx = table.getIndexes().get(0); Cursor cursor = Cursor.createIndexCursor(table, idx); - doTestReverse(table, cursor); + doTestReverse(table, cursor, null); db.close(); } @@ -389,7 +440,7 @@ public class CursorTest extends TestCase { Table table = db.getTable("test"); Index idx = table.getIndexes().get(0); Cursor cursor = Cursor.createIndexCursor(table, idx); - doTestSearch(table, cursor, idx); + doTestSearch(table, cursor, idx, 42, -13); db.close(); } @@ -402,7 +453,7 @@ public class CursorTest extends TestCase { Cursor cursor1 = Cursor.createIndexCursor(table, idx); Cursor cursor2 = Cursor.createIndexCursor(table, idx); - doTestLiveAddition(table, cursor1, cursor2); + doTestLiveAddition(table, cursor1, cursor2, 11); db.close(); } @@ -417,9 +468,113 @@ public class CursorTest extends TestCase { Cursor cursor2 = Cursor.createIndexCursor(table, idx); Cursor cursor3 = Cursor.createIndexCursor(table, idx); Cursor cursor4 = Cursor.createIndexCursor(table, idx); - doTestLiveDeletion(table, cursor1, cursor2, cursor3, cursor4); + doTestLiveDeletion(table, cursor1, cursor2, cursor3, cursor4, 1); db.close(); } + + public void testSimpleIndexSubRange() throws Exception { + for(int i = 0; i < 2; ++i) { + Database db = createTestIndexTable(); + + Table table = db.getTable("test"); + Index idx = table.getIndexes().get(0); + + Cursor cursor = createIndexSubRangeCursor(table, idx, i); + + List<Map<String,Object>> expectedRows = + createTestTableData(3, 9); + + doTestSimple(table, cursor, expectedRows); + + db.close(); + } + } + + public void testMoveIndexSubRange() throws Exception { + for(int i = 0; i < 2; ++i) { + Database db = createTestIndexTable(); + + Table table = db.getTable("test"); + Index idx = table.getIndexes().get(0); + + Cursor cursor = createIndexSubRangeCursor(table, idx, i); + + List<Map<String,Object>> expectedRows = + createTestTableData(3, 9); + + doTestMove(table, cursor, expectedRows); + + db.close(); + } + } + + public void testSearchIndexSubRange() throws Exception { + for(int i = 0; i < 2; ++i) { + Database db = createTestIndexTable(); + + Table table = db.getTable("test"); + Index idx = table.getIndexes().get(0); + + Cursor cursor = createIndexSubRangeCursor(table, idx, i); + + doTestSearch(table, cursor, idx, 2, 9); + + db.close(); + } + } + + public void testReverseIndexSubRange() throws Exception { + for(int i = 0; i < 2; ++i) { + Database db = createTestIndexTable(); + + Table table = db.getTable("test"); + Index idx = table.getIndexes().get(0); + + Cursor cursor = createIndexSubRangeCursor(table, idx, i); + + List<Map<String,Object>> expectedRows = + createTestTableData(3, 9); + + doTestReverse(table, cursor, expectedRows); + + db.close(); + } + } + + public void testLiveAdditionIndexSubRange() throws Exception { + for(int i = 0; i < 2; ++i) { + Database db = createTestIndexTable(); + + Table table = db.getTable("test"); + Index idx = table.getIndexes().get(0); + + Cursor cursor1 = createIndexSubRangeCursor(table, idx, i); + Cursor cursor2 = createIndexSubRangeCursor(table, idx, i); + + doTestLiveAddition(table, cursor1, cursor2, 8); + + db.close(); + } + } + + public void testLiveDeletionIndexSubRange() throws Exception { + for(int i = 0; i < 2; ++i) { + Database db = createTestIndexTable(); + + Table table = db.getTable("test"); + Index idx = table.getIndexes().get(0); + + Cursor cursor1 = createIndexSubRangeCursor(table, idx, i); + Cursor cursor2 = createIndexSubRangeCursor(table, idx, i); + Cursor cursor3 = createIndexSubRangeCursor(table, idx, i); + Cursor cursor4 = createIndexSubRangeCursor(table, idx, i); + + doTestLiveDeletion(table, cursor1, cursor2, cursor3, cursor4, 4); + + db.close(); + } + } + } diff --git a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java index a962c46..83930af 100644 --- a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java @@ -18,8 +18,8 @@ import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -368,13 +368,13 @@ public class DatabaseTest extends TestCase { public void testReadWithDeletedCols() throws Exception { Table table = Database.open(new File("test/data/delColTest.mdb")).getTable("Table1"); - Map<String, Object> expectedRow0 = new HashMap<String, Object>(); + Map<String, Object> expectedRow0 = new LinkedHashMap<String, Object>(); expectedRow0.put("id", 0); expectedRow0.put("id2", 2); expectedRow0.put("data", "foo"); expectedRow0.put("data2", "foo2"); - Map<String, Object> expectedRow1 = new HashMap<String, Object>(); + Map<String, Object> expectedRow1 = new LinkedHashMap<String, Object>(); expectedRow1.put("id", 3); expectedRow1.put("id2", 5); expectedRow1.put("data", "bar"); @@ -687,7 +687,7 @@ public class DatabaseTest extends TestCase { Table t = db.getTable("test"); List<String> row = new ArrayList<String>(); - Map<String,Object> expectedRowData = new HashMap<String, Object>(); + Map<String,Object> expectedRowData = new LinkedHashMap<String, Object>(); for(int i = 0; i < numColumns; ++i) { String value = "" + i + " some row data"; row.add(value); @@ -838,7 +838,7 @@ public class DatabaseTest extends TestCase { } static Map<String, Object> createExpectedRow(Object... rowElements) { - Map<String, Object> row = new HashMap<String, Object>(); + Map<String, Object> row = new LinkedHashMap<String, Object>(); for(int i = 0; i < rowElements.length; i += 2) { row.put((String)rowElements[i], rowElements[i + 1]); |