]> source.dussan.org Git - jackcess.git/commitdiff
implement range-based, index cursors
authorJames Ahlborn <jtahlborn@yahoo.com>
Sun, 2 Dec 2007 04:29:59 +0000 (04:29 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Sun, 2 Dec 2007 04:29:59 +0000 (04:29 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@194 f203690c-595d-4dc9-a70b-905162fa7fd2

src/java/com/healthmarketscience/jackcess/Cursor.java
src/java/com/healthmarketscience/jackcess/Index.java
src/java/com/healthmarketscience/jackcess/Table.java
src/java/com/healthmarketscience/jackcess/UsageMap.java
test/src/java/com/healthmarketscience/jackcess/CursorTest.java
test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java

index 3bba37e319862c7f533707a8b383725702e6f576..520481a5086c2ebddfa06fe6ab3d84b21255f4c1 100644 (file)
@@ -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,26 +511,37 @@ 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.
@@ -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
index 7aed3452ac241d602799da4ff2af7661158557d0..fe128328e2a0c4e49b3a918864d99d661b71a652 100644 (file)
@@ -596,9 +596,47 @@ 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();
     }
 
@@ -1425,6 +1480,20 @@ public class Index implements Comparable<Index> {
       return Index.this;
     }
     
+    /**
+     * 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
      */
@@ -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;
       }
     }
   }
index 08c7d4a90bdafbd0ade678d021405d1c04e20c42..830b17c27e3c717890a9609c798da128c419e952 100644 (file)
@@ -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) {
index 231267adccd5f3eff4054251597529b8e2b45471..79e772e142f29bab3116d19642f8aabf251365f9 100644 (file)
@@ -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;
     }
 
     /**
index f6b829d4f9fc6c39a8eaa7acaa702fbe1273ecf0..6b6789226ac278b7586fca7c7363d232b2c34967 100644 (file)
@@ -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();
+    }    
+  }
+
   
 }
index a962c46a27b58ec01de336c1ff547ec1e4c7fa93..83930af5f893752936be56c35b02ac348841edfb 100644 (file)
@@ -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]);