From 27f7855db113e4556e9e957b139dc50e5e06e275 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Tue, 22 Apr 2008 18:12:25 +0000 Subject: [PATCH] implement page deallocation; fix some issues in global usage map handling git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@340 f203690c-595d-4dc9-a70b-905162fa7fd2 --- TODO.txt | 4 +- .../jackcess/PageChannel.java | 73 ++++++++++++++++--- .../jackcess/UsageMap.java | 28 +++---- 3 files changed, 77 insertions(+), 28 deletions(-) diff --git a/TODO.txt b/TODO.txt index 45a3921..dcd75b5 100644 --- 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) diff --git a/src/java/com/healthmarketscience/jackcess/PageChannel.java b/src/java/com/healthmarketscience/jackcess/PageChannel.java index 7931f3e..8adf74b 100644 --- a/src/java/com/healthmarketscience/jackcess/PageChannel.java +++ b/src/java/com/healthmarketscience/jackcess/PageChannel.java @@ -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 } /** diff --git a/src/java/com/healthmarketscience/jackcess/UsageMap.java b/src/java/com/healthmarketscience/jackcess/UsageMap.java index 8e40f4d..b0ca415 100644 --- a/src/java/com/healthmarketscience/jackcess/UsageMap.java +++ b/src/java/com/healthmarketscience/jackcess/UsageMap.java @@ -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); } } -- 2.39.5