<action dev="jahlborn" type="fix">
Fix creation of tables with auto-number columns.
</action>
+ <action dev="jahlborn" type="fix">
+ Completely fix problems with sporadic usage map corruption.
+ </action>
+ <action dev="jahlborn" type="update">
+ Add some soft buffer caching in various places to avoid excessive
+ buffer reallocation.
+ </action>
</release>
<release version="1.1.12" date="2008-02-27">
<action dev="jahlborn" type="fix">
private boolean _initialized;
/** modification count for the table, keeps cursors up-to-date */
private int _modCount;
+ /** temp buffer used to writing the index */
+ private final TempBufferHolder _indexBufferH =
+ TempBufferHolder.newHolder(false, true);
/** FIXME, for now, we can't write multi-page indexes or indexes using the funky primary key compression scheme */
boolean _readOnly;
* Write this index out to a buffer
*/
private ByteBuffer write() throws IOException {
- ByteBuffer buffer = getPageChannel().createPageBuffer();
+ ByteBuffer buffer = _indexBufferH.getPageBuffer(getPageChannel());
buffer.put((byte) 0x04); //Page type
buffer.put((byte) 0x01); //Unknown
buffer.putShort((short) 0); //Free space
int lastStart = 0;
byte[] valuePrefix = null;
boolean firstEntry = true;
- ByteBuffer tmpEntryBuffer = null;
+ TempBufferHolder tmpEntryBufferH =
+ TempBufferHolder.newHolder(true, true, ByteOrder.BIG_ENDIAN);
for (int i = 0; i < entryMaskLength; i++) {
byte entryMask = indexPage.get(entryMaskPos + i);
ByteBuffer curEntryBuffer = indexPage;
int curEntryLen = length;
if(valuePrefix != null) {
- tmpEntryBuffer = getTempEntryBuffer(
- indexPage, length, valuePrefix, tmpEntryBuffer);
- curEntryBuffer = tmpEntryBuffer;
+ curEntryBuffer = getTempEntryBuffer(
+ indexPage, length, valuePrefix, tmpEntryBufferH);
curEntryLen += valuePrefix.length;
}
*/
private ByteBuffer getTempEntryBuffer(
ByteBuffer indexPage, int entryLen, byte[] valuePrefix,
- ByteBuffer tmpEntryBuffer)
+ TempBufferHolder tmpEntryBufferH)
{
- int totalLen = entryLen + valuePrefix.length;
- if((tmpEntryBuffer == null) || (tmpEntryBuffer.capacity() < totalLen)) {
- tmpEntryBuffer = ByteBuffer.allocate(totalLen);
- } else {
- tmpEntryBuffer.clear();
- }
+ ByteBuffer tmpEntryBuffer = tmpEntryBufferH.getBuffer(
+ getPageChannel(), valuePrefix.length + entryLen);
// combine valuePrefix and rest of entry from indexPage, then prep for
// reading
public void read(ByteBuffer buffer) {
buffer.get(_mask);
}
-
- public ByteBuffer wrap() {
- return ByteBuffer.wrap(_mask);
+
+ /**
+ * Write a mask to a buffer
+ */
+ public void write(ByteBuffer buffer) {
+ buffer.put(_mask);
}
/**
static final int INVALID_PAGE_NUMBER = -1;
+ 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);
/**
* @param buffer Buffer to read the page into
* @param pageNumber Number of the page to read in (starting at 0)
- * @return True if the page was successfully read into the buffer, false if
- * that page doesn't exist.
*/
- public boolean readPage(ByteBuffer buffer, int pageNumber)
+ public void readPage(ByteBuffer buffer, int pageNumber)
throws IOException
{
if(pageNumber == INVALID_PAGE_NUMBER) {
LOG.debug("Reading in page " + Integer.toHexString(pageNumber));
}
buffer.clear();
- boolean rtn = _channel.read(buffer, (long) pageNumber * (long) getFormat().PAGE_SIZE) != -1;
+ int bytesRead = _channel.read(
+ buffer, (long) pageNumber * (long) getFormat().PAGE_SIZE);
buffer.flip();
- return rtn;
+ if(bytesRead != getFormat().PAGE_SIZE) {
+ throw new IOException("Failed attempting to read " +
+ getFormat().PAGE_SIZE + " bytes from page " +
+ pageNumber + ", only read " + bytesRead);
+ }
}
/**
private UsageMap _freeSpacePages;
/** modification count for the table, keeps row-states up-to-date */
private int _modCount;
+ /** page buffer used to update data pages when adding rows */
+ private final TempPageHolder _addRowBufferH =
+ TempPageHolder.newHolder(false);
+ /** page buffer used to update the table def page */
+ private final TempPageHolder _tableDefBufferH =
+ TempPageHolder.newHolder(false);
+ /** buffer used to writing single rows of data */
+ private final TempBufferHolder _singleRowBufferH =
+ TempBufferHolder.newHolder(false, true);
/** common cursor for iterating through the table, kept here for historic
reasons */
// write the page data
getPageChannel().writePage(pageBuffer, pageNumber);
+ // possibly invalidate the add row buffer if a different data buffer is
+ // being written (e.g. this happens during deleteRow)
+ _addRowBufferH.possiblyInvalidate(pageNumber, pageBuffer);
+
// update modification count so any active RowStates can keep themselves
// up-to-date
++_modCount;
* @param rows List of Object[] row values
*/
public void addRows(List<? extends Object[]> rows) throws IOException {
- ByteBuffer dataPage = getPageChannel().createPageBuffer();
ByteBuffer[] rowData = new ByteBuffer[rows.size()];
Iterator<? extends Object[]> iter = rows.iterator();
for (int i = 0; iter.hasNext(); i++) {
- rowData[i] = createRow(iter.next(), getFormat().MAX_ROW_SIZE);
+ // note, use the cached _singleRowBufferH for the first row. this will
+ // speed up single row writes
+ rowData[i] = createRow(
+ iter.next(), getFormat().MAX_ROW_SIZE,
+ ((i == 0) ? _singleRowBufferH.getPageBuffer(getPageChannel()) :
+ getPageChannel().createPageBuffer()));
if (rowData[i].limit() > getFormat().MAX_ROW_SIZE) {
throw new IOException("Row size " + rowData[i].limit() +
" is too large");
// find last data page (Not bothering to check other pages for free
// space.)
+ ByteBuffer dataPage = null;
UsageMap.PageCursor revPageCursor = _ownedPages.cursor();
revPageCursor.afterLast();
while(true) {
if(tmpPageNumber < 0) {
break;
}
- getPageChannel().readPage(dataPage, tmpPageNumber);
+ dataPage = _addRowBufferH.setPage(getPageChannel(), tmpPageNumber);
if(dataPage.get() == PageTypes.DATA) {
// found last data page, only use if actually listed in free space
// pages
if(pageNumber == PageChannel.INVALID_PAGE_NUMBER) {
// No data pages exist (with free space). Create a new one.
- pageNumber = newDataPage(dataPage);
+ dataPage = newDataPage();
+ pageNumber = _addRowBufferH.getPageNumber();
}
for (int i = 0; i < rowData.length; i++) {
writeDataPage(dataPage, pageNumber);
_freeSpacePages.removePageNumber(pageNumber);
- dataPage.clear();
- pageNumber = newDataPage(dataPage);
+ dataPage = newDataPage();
+ pageNumber = _addRowBufferH.getPageNumber();
freeSpaceInPage = dataPage.getShort(getFormat().OFFSET_FREE_SPACE);
}
private void updateTableDefinition(int rowCountInc) throws IOException
{
// load table definition
- ByteBuffer tdefPage = getPageChannel().createPageBuffer();
- getPageChannel().readPage(tdefPage, _tableDefPageNumber);
+ ByteBuffer tdefPage = _tableDefBufferH.setPage(getPageChannel(),
+ _tableDefPageNumber);
// make sure rowcount and autonumber are up-to-date
_rowCount += rowCountInc;
* Create a new data page
* @return Page number of the new page
*/
- private int newDataPage(ByteBuffer dataPage) throws IOException {
+ private ByteBuffer newDataPage() throws IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Creating new data page");
}
+ ByteBuffer dataPage = _addRowBufferH.setNewPage(getPageChannel());
dataPage.put(PageTypes.DATA); //Page type
dataPage.put((byte) 1); //Unknown
dataPage.putShort((short)getRowSpaceUsage(getFormat().MAX_ROW_SIZE,
dataPage.putInt(_tableDefPageNumber); //Page pointer to table definition
dataPage.putInt(0); //Unknown
dataPage.putInt(0); //Number of records on this page
- int pageNumber = getPageChannel().writeNewPage(dataPage);
+ int pageNumber = _addRowBufferH.getPageNumber();
+ getPageChannel().writePage(dataPage, pageNumber);
_ownedPages.addPageNumber(pageNumber);
_freeSpacePages.addPageNumber(pageNumber);
- return pageNumber;
+ return dataPage;
}
/**
* Serialize a row of Objects into a byte buffer
*/
- ByteBuffer createRow(Object[] rowArray, int maxRowSize) throws IOException {
- ByteBuffer buffer = getPageChannel().createPageBuffer();
+ ByteBuffer createRow(Object[] rowArray, int maxRowSize, ByteBuffer buffer)
+ throws IOException
+ {
buffer.putShort(_maxColumnCount);
NullMask nullMask = new NullMask(_maxColumnCount);
}
buffer.putShort(_maxVarColumnCount); //Number of var length columns
}
-
- buffer.put(nullMask.wrap()); //Null mask
+
+ nullMask.write(buffer); //Null mask
buffer.limit(buffer.position());
buffer.flip();
if (LOG.isDebugEnabled()) {
--- /dev/null
+// Copyright (c) 2008 Health Market Science, Inc.
+
+package com.healthmarketscience.jackcess;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Manages a reference to a ByteBuffer.
+ *
+ * @author James Ahlborn
+ */
+public abstract class TempBufferHolder {
+
+ private static final Reference<ByteBuffer> EMPTY_BUFFER_REF =
+ new SoftReference<ByteBuffer>(null);
+
+ /** whether or not every get automatically rewinds the buffer */
+ private final boolean _autoRewind;
+ /** ByteOrder for all allocated buffers */
+ private final ByteOrder _order;
+ /** the mod count of the current buffer (changes on every realloc) */
+ private int _modCount;
+
+ protected TempBufferHolder(boolean autoRewind, ByteOrder order) {
+ _autoRewind = autoRewind;
+ _order = order;
+ }
+
+ /**
+ * @return the modification count of the current buffer (this count is
+ * changed every time the buffer is reallocated)
+ */
+ public int getModCount() {
+ return _modCount;
+ }
+
+ /**
+ * Creates a new TempBufferHolder.
+ * @param hard iff true, the TempBufferHolder will maintain a hard reference
+ * to the current buffer, otherwise will maintain a
+ * SoftReference.
+ * @param autoRewind whether or not every get automatically rewinds the
+ * buffer
+ */
+ public static TempBufferHolder newHolder(boolean hard, boolean autoRewind) {
+ return newHolder(hard, autoRewind, PageChannel.DEFAULT_BYTE_ORDER);
+ }
+
+ /**
+ * Creates a new TempBufferHolder.
+ * @param hard iff true, the TempBufferHolder will maintain a hard reference
+ * to the current buffer, otherwise will maintain a
+ * SoftReference.
+ * @param autoRewind whether or not every get automatically rewinds the
+ * buffer
+ * @param order byte order for all allocated buffers
+ */
+ public static TempBufferHolder newHolder(boolean hard, boolean autoRewind,
+ ByteOrder order)
+ {
+ if(hard) {
+ return new HardTempBufferHolder(autoRewind, order);
+ }
+ return new SoftTempBufferHolder(autoRewind, order);
+ }
+
+ /**
+ * Returns a ByteBuffer of at least the defined page size, with the limit
+ * set to the page size, and the predefined byteOrder. Will be rewound iff
+ * autoRewind is enabled for this buffer.
+ */
+ public final ByteBuffer getPageBuffer(PageChannel pageChannel) {
+ return getBuffer(pageChannel, pageChannel.getFormat().PAGE_SIZE);
+ }
+
+ /**
+ * Returns a ByteBuffer of at least the given size, with the limit set to
+ * the given size, and the predefined byteOrder. Will be rewound iff
+ * autoRewind is enabled for this buffer.
+ */
+ public final ByteBuffer getBuffer(PageChannel pageChannel, int size) {
+ ByteBuffer buffer = getExistingBuffer();
+ if((buffer == null) || (buffer.capacity() < size)) {
+ buffer = pageChannel.createBuffer(size, _order);
+ ++_modCount;
+ setNewBuffer(buffer);
+ } else {
+ buffer.limit(size);
+ }
+ if(_autoRewind) {
+ buffer.rewind();
+ }
+ return buffer;
+ }
+
+ /**
+ * @returns the currently referenced buffer, {@code null} if none
+ */
+ public abstract ByteBuffer getExistingBuffer();
+
+ /**
+ * Releases any referenced memory.
+ */
+ public abstract void clear();
+
+ /**
+ * Sets a new buffer for this holder.
+ */
+ protected abstract void setNewBuffer(ByteBuffer newBuffer);
+
+ /**
+ * TempBufferHolder which has a hard reference to the buffer buffer.
+ */
+ private static final class HardTempBufferHolder extends TempBufferHolder
+ {
+ private ByteBuffer _buffer;
+
+ private HardTempBufferHolder(boolean autoRewind, ByteOrder order) {
+ super(autoRewind, order);
+ }
+
+ @Override
+ public ByteBuffer getExistingBuffer() {
+ return _buffer;
+ }
+
+ @Override
+ protected void setNewBuffer(ByteBuffer newBuffer) {
+ _buffer = newBuffer;
+ }
+
+ @Override
+ public void clear() {
+ _buffer = null;
+ }
+ }
+
+ /**
+ * TempBufferHolder which has a soft reference to the buffer buffer.
+ */
+ private static final class SoftTempBufferHolder extends TempBufferHolder
+ {
+ private Reference<ByteBuffer> _buffer = EMPTY_BUFFER_REF;
+
+ private SoftTempBufferHolder(boolean autoRewind, ByteOrder order) {
+ super(autoRewind, order);
+ }
+
+ @Override
+ public ByteBuffer getExistingBuffer() {
+ return _buffer.get();
+ }
+
+ @Override
+ protected void setNewBuffer(ByteBuffer newBuffer) {
+ _buffer.clear();
+ _buffer = new SoftReference<ByteBuffer>(newBuffer);
+// // FIXME, enable for testing (make this automatic)
+// _buffer = new PhantomReference<ByteBuffer>(newBuffer, null);
+ }
+
+ @Override
+ public void clear() {
+ _buffer.clear();
+ }
+ }
+
+
+}
package com.healthmarketscience.jackcess;
import java.io.IOException;
-import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
/**
*
* @author James Ahlborn
*/
-public abstract class TempPageHolder {
-
- private static final SoftReference<ByteBuffer> EMPTY_BUFFER_REF =
- new SoftReference<ByteBuffer>(null);
-
- protected int _pageNumber = PageChannel.INVALID_PAGE_NUMBER;
+public final class TempPageHolder {
+
+ private int _pageNumber = PageChannel.INVALID_PAGE_NUMBER;
+ private final TempBufferHolder _buffer;
+ /** the last "modification" count of the buffer that this holder observed.
+ this is tracked so that the page data can be re-read if the underlying
+ buffer has been discarded since the last page read */
+ private int _bufferModCount;
- protected TempPageHolder() {
+ private TempPageHolder(boolean hard) {
+ _buffer = TempBufferHolder.newHolder(hard, false);
+ _bufferModCount = _buffer.getModCount();
}
/**
* SoftReference.
*/
public static TempPageHolder newHolder(boolean hard) {
- if(hard) {
- return new HardTempPageHolder();
- }
- return new SoftTempPageHolder();
+ return new TempPageHolder(hard);
}
/**
boolean rewind)
throws IOException
{
- ByteBuffer buffer = getBuffer(pageChannel);
- if(pageNumber != _pageNumber) {
+ ByteBuffer buffer = _buffer.getPageBuffer(pageChannel);
+ int modCount = _buffer.getModCount();
+ if((pageNumber != _pageNumber) || (_bufferModCount != modCount)) {
_pageNumber = pageNumber;
+ _bufferModCount = modCount;
pageChannel.readPage(buffer, _pageNumber);
} else if(rewind) {
buffer.rewind();
// allocate a new page in the database
_pageNumber = pageChannel.allocateNewPage();
// return a new buffer
- return getBuffer(pageChannel);
+ return _buffer.getPageBuffer(pageChannel);
}
/**
*/
public void possiblyInvalidate(int modifiedPageNumber,
ByteBuffer modifiedBuffer) {
- if(modifiedBuffer == getExistingBuffer()) {
+ if(modifiedBuffer == _buffer.getExistingBuffer()) {
// no worries, our buffer was the one modified (or is null, either way
// we'll need to reload)
return;
*/
public void clear() {
invalidate();
+ _buffer.clear();
}
- protected abstract ByteBuffer getExistingBuffer();
-
- protected abstract ByteBuffer getBuffer(PageChannel pageChannel);
-
- /**
- * TempPageHolder which has a hard reference to the page buffer.
- */
- private static class HardTempPageHolder extends TempPageHolder
- {
- private ByteBuffer _buffer;
-
- @Override
- protected ByteBuffer getExistingBuffer() {
- return _buffer;
- }
-
- @Override
- protected ByteBuffer getBuffer(PageChannel pageChannel) {
- if(_buffer == null) {
- _buffer = pageChannel.createPageBuffer();
- }
- return _buffer;
- }
-
- @Override
- public void clear() {
- super.clear();
- _buffer = null;
- }
- }
-
- /**
- * TempPageHolder which has a soft reference to the page buffer.
- */
- private static class SoftTempPageHolder extends TempPageHolder
- {
- private SoftReference<ByteBuffer> _buffer = EMPTY_BUFFER_REF;
-
- @Override
- protected ByteBuffer getExistingBuffer() {
- return _buffer.get();
- }
-
- @Override
- protected ByteBuffer getBuffer(PageChannel pageChannel) {
- ByteBuffer buffer = _buffer.get();
- if(buffer == null) {
- buffer = pageChannel.createPageBuffer();
- _buffer = new SoftReference<ByteBuffer>(buffer);
- }
- return buffer;
- }
-
- @Override
- public void clear() {
- super.clear();
- _buffer.clear();
- }
- }
-
-
}
mapPageBuffer.putShort((short) 0); //Unknown
int mapPageNum = _mapPageHolder.getPageNumber();
getTableBuffer().putInt(calculateMapPagePointerOffset(pageIndex),
- mapPageNum);
+ mapPageNum);
writeTable();
return mapPageBuffer;
}
row[0] = new Short((short) 9);
row[1] = "Tim";
row[2] = "McCune";
- ByteBuffer buffer = table.createRow(row, format.MAX_ROW_SIZE);
+ ByteBuffer buffer = table.createRow(row, format.MAX_ROW_SIZE,
+ pageChannel.createPageBuffer());
assertEquals((short) colCount, buffer.getShort());
assertEquals((short) 9, buffer.getShort());
assertEquals((byte) 'T', buffer.get());