summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2008-04-22 18:12:25 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2008-04-22 18:12:25 +0000
commit27f7855db113e4556e9e957b139dc50e5e06e275 (patch)
treecda81eaca0f891844018446134ef065c5177ac41
parentd2d14fe7ee6dad07eab83cc37c1bb5c0fa42aad6 (diff)
downloadjackcess-27f7855db113e4556e9e957b139dc50e5e06e275.tar.gz
jackcess-27f7855db113e4556e9e957b139dc50e5e06e275.zip
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
-rw-r--r--TODO.txt4
-rw-r--r--src/java/com/healthmarketscience/jackcess/PageChannel.java73
-rw-r--r--src/java/com/healthmarketscience/jackcess/UsageMap.java28
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);
}
}