]> source.dussan.org Git - jackcess.git/commitdiff
implement page deallocation; fix some issues in global usage map handling
authorJames Ahlborn <jtahlborn@yahoo.com>
Tue, 22 Apr 2008 18:12:25 +0000 (18:12 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Tue, 22 Apr 2008 18:12:25 +0000 (18:12 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@340 f203690c-595d-4dc9-a70b-905162fa7fd2

TODO.txt
src/java/com/healthmarketscience/jackcess/PageChannel.java
src/java/com/healthmarketscience/jackcess/UsageMap.java

index 45a3921c6a1f3a8fe8f1b5d94940a092a92c6056..dcd75b503bfd8d30f16bbde70806ae58c6fdd44c 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,12 +1,12 @@
 Missing pieces:
 
-- large index update support (can read large indexes, but not update them)
-  * HARD (all the knowledge exists, but lots of coding remains)
 - handle more text index entry types (extended charsets, alternate encodings)
   * MEDIUM
 - determine when/when not to inline memo/ole data for a row of data depending
   on how much will be written in the current row
   * MEDIUM
+- handle row "updates": write row data back to current row, handling overflow
+  * MEDIUM
 - implement index creation
   * VERY HARD (still missing knowledge about all the magic bits in index meta
     data)
index 7931f3eb66d55b25b76f0fc773754b26a75fb5fa..8adf74bef9ac0106e1d8f0650205f0370cc5b65e 100644 (file)
@@ -49,8 +49,11 @@ public class PageChannel implements Channel, Flushable {
 
   static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
   
-  /** dummy buffer used when allocating new pages */
-  private static final ByteBuffer FORCE_BYTES = ByteBuffer.allocate(1);
+  /** invalid page header, used when deallocating old pages.  data pages
+      generally have 4 interesting bytes at the beginning which we want to
+      reset. */
+  private static final byte[] INVALID_PAGE_BYTE_HEADER =
+    new byte[]{PageTypes.INVALID, (byte)0, (byte)0, (byte)0};
   
   /** Global usage map always lives on page 1 */
   private static final int PAGE_GLOBAL_USAGE_MAP = 1;
@@ -63,6 +66,12 @@ public class PageChannel implements Channel, Flushable {
   private final JetFormat _format;
   /** whether or not to force all writes to disk immediately */
   private final  boolean _autoSync;
+  /** buffer used when deallocating old pages.  data pages generally have 4
+      interesting bytes at the beginning which we want to reset. */
+  private final ByteBuffer _invalidPageBytes =
+    ByteBuffer.wrap(INVALID_PAGE_BYTE_HEADER);
+  /** dummy buffer used when allocating new pages */
+  private final ByteBuffer _forceBytes = ByteBuffer.allocate(1);
   /** Tracks free pages in the database. */
   private UsageMap _globalUsageMap;
   
@@ -105,6 +114,32 @@ public class PageChannel implements Channel, Flushable {
   public JetFormat getFormat() {
     return _format;
   }
+
+  /**
+   * Returns the next page number based on the given file size.
+   */
+  private int getNextPageNumber(long size) {
+    return (int)(size / getFormat().PAGE_SIZE);
+  }
+
+  /**
+   * Returns the offset for a page within the file.
+   */
+  private long getPageOffset(int pageNumber) {
+    return((long) pageNumber * (long) getFormat().PAGE_SIZE);
+  }
+  
+  /**
+   * Validates that the given pageNumber is valid for this database.
+   */
+  private void validatePageNumber(int pageNumber)
+    throws IOException
+  {
+    int nextPageNumber = getNextPageNumber(_channel.size());
+    if((pageNumber <= INVALID_PAGE_NUMBER) || (pageNumber >= nextPageNumber)) {
+      throw new IllegalStateException("invalid page number " + pageNumber);
+    }
+  }
   
   /**
    * @param buffer Buffer to read the page into
@@ -113,9 +148,7 @@ public class PageChannel implements Channel, Flushable {
   public void readPage(ByteBuffer buffer, int pageNumber)
     throws IOException
   {
-    if(pageNumber == INVALID_PAGE_NUMBER) {
-      throw new IllegalStateException("invalid page number");
-    }
+    validatePageNumber(pageNumber);
     if (LOG.isDebugEnabled()) {
       LOG.debug("Reading in page " + Integer.toHexString(pageNumber));
     }
@@ -150,10 +183,17 @@ public class PageChannel implements Channel, Flushable {
                         int pageOffset)
     throws IOException
   {
+    validatePageNumber(pageNumber);
+    
     page.rewind();
+
+    if((page.remaining() - pageOffset) > getFormat().PAGE_SIZE) {
+      throw new IllegalArgumentException(
+          "Page buffer is too large, size " + (page.remaining() - pageOffset));
+    }
+    
     page.position(pageOffset);
-    _channel.write(page, (((long) pageNumber * (long) getFormat().PAGE_SIZE) +
-                          pageOffset));
+    _channel.write(page, (getPageOffset(pageNumber) + pageOffset));
     if(_autoSync) {
       flush();
     }
@@ -178,12 +218,18 @@ public class PageChannel implements Channel, Flushable {
     }
     
     page.rewind();
+
+    if(page.remaining() > getFormat().PAGE_SIZE) {
+      throw new IllegalArgumentException("Page buffer is too large, size " +
+                                         page.remaining());
+    }
+    
     // push the buffer to the end of the page, so that a full page's worth of
     // data is written regardless of the incoming buffer size (we use a tiny
     // buffer in allocateNewPage)
     long offset = size + (getFormat().PAGE_SIZE - page.remaining());
     _channel.write(page, offset);
-    int pageNumber = (int) (size / getFormat().PAGE_SIZE);
+    int pageNumber = getNextPageNumber(size);
     _globalUsageMap.removePageNumber(pageNumber);  //force is done here
     return pageNumber;
   }
@@ -194,14 +240,21 @@ public class PageChannel implements Channel, Flushable {
    */
   public int allocateNewPage() throws IOException {
     // this will force the file to be extended with mostly undefined bytes
-    return writeNewPage(FORCE_BYTES);
+    return writeNewPage(_forceBytes);
   }
 
   /**
    * Deallocate a previously used page in the database.
    */
   public void deallocatePage(int pageNumber) throws IOException {
-    // FIXME, writeme
+    validatePageNumber(pageNumber);
+    
+    // don't write the whole page, just wipe out the header (which should be
+    // enough to let us know if we accidentally try to use an invalid page)
+    _invalidPageBytes.rewind();
+    _channel.write(_invalidPageBytes, getPageOffset(pageNumber));
+    
+    _globalUsageMap.addPageNumber(pageNumber);  //force is done here
   }
   
   /**
index 8e40f4db76e2c535332ccba3b7ff528b2d6feda5..b0ca415a93accb0cad1cf56fe14389643a5505a4 100644 (file)
@@ -376,7 +376,7 @@ public class UsageMap
       addPageNumber(oldStartPage + i);
     }
 
-    if(newPageNumber != PageChannel.INVALID_PAGE_NUMBER) {
+    if(newPageNumber > PageChannel.INVALID_PAGE_NUMBER) {
       // and then add the new page
       addPageNumber(newPageNumber);
     }
@@ -494,7 +494,7 @@ public class UsageMap
           if(!_assumeOutOfRangeBitsOn) {
             
             // we are adding, can we shift the bits and stay inline?
-            if(firstPage == PageChannel.INVALID_PAGE_NUMBER) {
+            if(firstPage <= PageChannel.INVALID_PAGE_NUMBER) {
               // no pages currently
               firstPage = pageNumber;
               lastPage = pageNumber;
@@ -523,7 +523,7 @@ public class UsageMap
             // within the current range is "on".  so, if we attempt to set a
             // bit which is before the current page, ignore it, we are not
             // going back for it.
-            if((firstPage == PageChannel.INVALID_PAGE_NUMBER) ||
+            if((firstPage <= PageChannel.INVALID_PAGE_NUMBER) ||
                (pageNumber > lastPage)) {
 
               // move to new start page, filling in as we move
@@ -589,21 +589,17 @@ public class UsageMap
                                              int newPageNumber)
       throws IOException
     {
-      int newStartPage = firstPage;
-      if(newStartPage == PageChannel.INVALID_PAGE_NUMBER) {
-        newStartPage = newPageNumber;
-      } else if((newPageNumber - newStartPage + 1) >=
-                getMaxInlinePages()) {
-        // this will not move us far enough to hold the new page.  just
-        // discard any initial unused pages
-        newStartPage += (newPageNumber - getMaxInlinePages() + 1);
-      }
-      
+      int oldEndPage = getEndPage();
+      int newStartPage = 
+        ((firstPage <= PageChannel.INVALID_PAGE_NUMBER) ? newPageNumber :
+         // just shift a little and discard any initial unused pages.
+         (newPageNumber - (getMaxInlinePages() / 2)));
+
       // move the current data
       moveToNewStartPage(newStartPage, PageChannel.INVALID_PAGE_NUMBER);
 
-      if(firstPage == PageChannel.INVALID_PAGE_NUMBER) {
-        
+      if(firstPage <= PageChannel.INVALID_PAGE_NUMBER) {
+
         // this is the common case where we left everything behind
         ByteUtil.fillRange(_tableBuffer, getInlineDataStart(),
                            getInlineDataEnd());
@@ -617,7 +613,7 @@ public class UsageMap
       } else {
 
         // add every new page manually
-        for(int i = (lastPage + 1); i < getEndPage(); ++i) {
+        for(int i = oldEndPage; i < getEndPage(); ++i) {
           addPageNumber(i);
         }
       }