<author email="javajedi@users.sf.net">Tim McCune</author>
</properties>
<body>
+ <release version="1.2.14" date="TBD">
+ <action dev="jahlborn" type="fix" system="SourceForge2" issue="95">
+ Implement handling of usagemaps for long value (MEMO/OLE) columns.
+ </action>
+ </release>
<release version="1.2.13" date="2013-06-18">
<action dev="jahlborn" type="fix">
Fix partial page updates when using CodecHandlers which can only do
private ComplexColumnInfo<? extends ComplexValue> _complexInfo;
/** properties for this column, if any */
private PropertyMap _props;
+ /** Holds additional info for writing long values */
+ private LongValueBufferHolder _lvalBufferH;
/**
* @usage _general_method_
}
}
+ /**
+ * Sets the usage maps for this column.
+ */
+ void setUsageMaps(UsageMap ownedPages, UsageMap freeSpacePages) {
+ _lvalBufferH = new UmapLongValueBufferHolder(ownedPages, freeSpacePages);
+ }
+
/**
* Secondary column initialization after the table is fully loaded.
*/
void postTableLoadInit() throws IOException {
+ if(getType().isLongValue() && (_lvalBufferH == null)) {
+ _lvalBufferH = new LegacyLongValueBufferHolder();
+ }
if(_complexInfo != null) {
_complexInfo.postTableLoadInit();
}
return getDatabase().getCalendar();
}
+ /**
+ * Returns the number of database pages owned by this column.
+ * @usage _intermediate_method_
+ */
+ public int getOwnedPageCount() {
+ return ((_lvalBufferH == null) ? 0 : _lvalBufferH.getOwnedPageCount());
+ }
+
/**
* Whether or not this column is "append only" (its history is tracked by a
* separate version history column).
def.put(value);
} else {
- TempPageHolder lvalBufferH = getTable().getLongValueBuffer();
ByteBuffer lvalPage = null;
int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER;
byte firstLvalRow = 0;
// write other page(s)
switch(type) {
case LONG_VALUE_TYPE_OTHER_PAGE:
- lvalPage = getLongValuePage(value.length, lvalBufferH);
- firstLvalPageNum = lvalBufferH.getPageNumber();
+ lvalPage = _lvalBufferH.getLongValuePage(value.length);
+ firstLvalPageNum = _lvalBufferH.getPageNumber();
firstLvalRow = (byte)Table.addDataPageRow(lvalPage, value.length,
getFormat(), 0);
lvalPage.put(value);
ByteBuffer buffer = ByteBuffer.wrap(value);
int remainingLen = buffer.remaining();
buffer.limit(0);
- lvalPage = getLongValuePage(getFormat().MAX_LONG_VALUE_ROW_SIZE,
- lvalBufferH);
- firstLvalPageNum = lvalBufferH.getPageNumber();
+ lvalPage = _lvalBufferH.getLongValuePage(remainingLen);
+ firstLvalPageNum = _lvalBufferH.getPageNumber();
+ firstLvalRow = (byte)Table.getRowsOnDataPage(lvalPage, getFormat());
int lvalPageNum = firstLvalPageNum;
ByteBuffer nextLvalPage = null;
int nextLvalPageNum = 0;
+ int nextLvalRowNum = 0;
while(remainingLen > 0) {
lvalPage.clear();
// figure out if we will need another page, and if so, allocate it
if(chunkLength < remainingLen) {
- // force a new page to be allocated
- lvalBufferH.clear();
- nextLvalPage = getLongValuePage(
- getFormat().MAX_LONG_VALUE_ROW_SIZE, lvalBufferH);
- nextLvalPageNum = lvalBufferH.getPageNumber();
+ // force a new page to be allocated for the chunk after this
+ _lvalBufferH.clear();
+ nextLvalPage = _lvalBufferH.getLongValuePage(
+ (remainingLen - chunkLength) + 4);
+ nextLvalPageNum = _lvalBufferH.getPageNumber();
+ nextLvalRowNum = Table.getRowsOnDataPage(nextLvalPage,
+ getFormat());
} else {
nextLvalPage = null;
nextLvalPageNum = 0;
+ nextLvalRowNum = 0;
}
// add row to this page
byte lvalRow = (byte)Table.addDataPageRow(lvalPage, chunkLength + 4,
getFormat(), 0);
- // write next page info (we'll always be writing into row 0 for
- // newly created pages)
- lvalPage.put((byte)0); // row number
+ // write next page info
+ lvalPage.put((byte)nextLvalRowNum); // row number
ByteUtil.put3ByteInt(lvalPage, nextLvalPageNum); // page number
// write this page's chunk of data
// write new page to database
getPageChannel().writePage(lvalPage, lvalPageNum);
-
- if(lvalPageNum == firstLvalPageNum) {
- // save initial row info
- firstLvalRow = lvalRow;
- } else {
- // check assertion that we wrote to row 0 for all subsequent pages
- if(lvalRow != (byte)0) {
- throw new IllegalStateException("Expected row 0, but was " +
- lvalRow);
- }
- }
// move to next page
lvalPage = nextLvalPage;
lvalPage.putShort((short)0); // num rows in page
}
- /**
- * Returns a long value data page with space for data of the given length.
- */
- private ByteBuffer getLongValuePage(int dataLength,
- TempPageHolder lvalBufferH)
- throws IOException
- {
- ByteBuffer lvalPage = null;
- if(lvalBufferH.getPageNumber() != PageChannel.INVALID_PAGE_NUMBER) {
- lvalPage = lvalBufferH.getPage(getPageChannel());
- if(Table.rowFitsOnDataPage(dataLength, lvalPage, getFormat())) {
- // the current page has space
- return lvalPage;
- }
- }
-
- // need new page
- lvalPage = lvalBufferH.setNewPage(getPageChannel());
- writeLongValueHeader(lvalPage);
- return lvalPage;
- }
-
/**
* Serialize an Object into a raw byte value for this column in little
* endian order
throws IOException
{
List<Column> columns = creator.getColumns();
- short columnNumber = (short) 0;
short fixedOffset = (short) 0;
short variableOffset = (short) 0;
// we specifically put the "long variable" values after the normal
// all (because "long variable" values can go in separate pages)
short longVariableOffset = countNonLongVariableLength(columns);
for (Column col : columns) {
- // record this for later use when writing indexes
- col.setColumnNumber(columnNumber);
int position = buffer.position();
buffer.put(col.getType().getValue());
buffer.putInt(Table.MAGIC_TABLE_NUMBER); //constant magic number
- buffer.putShort(columnNumber); //Column Number
+ buffer.putShort(col.getColumnNumber()); //Column Number
if (col.isVariableLength()) {
if(!col.getType().isLongValue()) {
buffer.putShort(variableOffset++);
} else {
buffer.putShort((short) 0);
}
- buffer.putShort(columnNumber); //Column Number again
+ buffer.putShort(col.getColumnNumber()); //Column Number again
if(col.getType().isTextual()) {
// this will write 4 bytes (note we don't support writing dbs which
// use the text code page)
} else {
buffer.putShort((short)0x0000); // unused
}
- columnNumber++;
if (LOG.isDebugEnabled()) {
LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
buffer, position, creator.getFormat().SIZE_COLUMN_DEF_BLOCK));
of type MEMO) */
private boolean _hyperlink;
}
+
+ /**
+ * Manages secondary page buffers for long value writing.
+ */
+ private abstract class LongValueBufferHolder
+ {
+ /**
+ * Returns a long value data page with space for data of the given length.
+ */
+ public ByteBuffer getLongValuePage(int dataLength) throws IOException {
+
+ TempPageHolder lvalBufferH = getBufferHolder();
+ dataLength = Math.min(dataLength, getFormat().MAX_LONG_VALUE_ROW_SIZE);
+
+ ByteBuffer lvalPage = null;
+ if(lvalBufferH.getPageNumber() != PageChannel.INVALID_PAGE_NUMBER) {
+ lvalPage = lvalBufferH.getPage(getPageChannel());
+ if(Table.rowFitsOnDataPage(dataLength, lvalPage, getFormat())) {
+ // the current page has space
+ return lvalPage;
+ }
+ }
+
+ // need new page
+ return findNewPage(dataLength);
+ }
+
+ protected ByteBuffer findNewPage(int dataLength) throws IOException {
+ ByteBuffer lvalPage = getBufferHolder().setNewPage(getPageChannel());
+ writeLongValueHeader(lvalPage);
+ return lvalPage;
+ }
+
+ public int getOwnedPageCount() {
+ return 0;
+ }
+
+ /**
+ * Returns the page number of the current long value data page.
+ */
+ public int getPageNumber() {
+ return getBufferHolder().getPageNumber();
+ }
+
+ /**
+ * Discards the current the current long value data page.
+ */
+ public void clear() throws IOException {
+ getBufferHolder().clear();
+ }
+
+ protected abstract TempPageHolder getBufferHolder();
+ }
+
+ /**
+ * Manages a common, shared extra page for long values. This is legacy
+ * behavior from before it was understood that there were additional usage
+ * maps for each columns.
+ */
+ private final class LegacyLongValueBufferHolder extends LongValueBufferHolder
+ {
+ @Override
+ protected TempPageHolder getBufferHolder() {
+ return getTable().getLongValueBuffer();
+ }
+ }
+
+ /**
+ * Manages the column usage maps for long values.
+ */
+ private final class UmapLongValueBufferHolder extends LongValueBufferHolder
+ {
+ /** Usage map of pages that this column owns */
+ private final UsageMap _ownedPages;
+ /** Usage map of pages that this column owns with free space on them */
+ private final UsageMap _freeSpacePages;
+ /** page buffer used to write "long value" data */
+ private final TempPageHolder _longValueBufferH =
+ TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
+
+ private UmapLongValueBufferHolder(UsageMap ownedPages,
+ UsageMap freeSpacePages) {
+ _ownedPages = ownedPages;
+ _freeSpacePages = freeSpacePages;
+ }
+
+ @Override
+ protected TempPageHolder getBufferHolder() {
+ return _longValueBufferH;
+ }
+
+ @Override
+ public int getOwnedPageCount() {
+ return _ownedPages.getPageCount();
+ }
+
+ @Override
+ protected ByteBuffer findNewPage(int dataLength) throws IOException {
+
+ // grab last owned page and check for free space.
+ ByteBuffer newPage = Table.findFreeRowSpace(
+ _ownedPages, _freeSpacePages, _longValueBufferH);
+
+ if(newPage != null) {
+ if(Table.rowFitsOnDataPage(dataLength, newPage, getFormat())) {
+ return newPage;
+ }
+ // discard this page and allocate a new one
+ clear();
+ }
+
+ // nothing found on current pages, need new page
+ newPage = super.findNewPage(dataLength);
+ int pageNumber = getPageNumber();
+ _ownedPages.addPageNumber(pageNumber);
+ _freeSpacePages.addPageNumber(pageNumber);
+ return newPage;
+ }
+
+ @Override
+ public void clear() throws IOException {
+ int pageNumber = getPageNumber();
+ if(pageNumber != PageChannel.INVALID_PAGE_NUMBER) {
+ _freeSpacePages.removePageNumber(pageNumber, true);
+ }
+ super.clear();
+ }
+ }
}
protected static final byte[] EMPTY_PREFIX = new byte[0];
- private static final short COLUMN_UNUSED = -1;
+ static final short COLUMN_UNUSED = -1;
static final byte ASCENDING_COLUMN_FLAG = (byte)0x01;
/**
* Returns the number of database pages owned by this index data.
+ * @usage _intermediate_method_
*/
public int getOwnedPageCount() {
return _ownedPages.getPageCount();
}
}
- int umapRowNum = tableBuffer.get();
- int umapPageNum = ByteUtil.get3ByteInt(tableBuffer);
- _ownedPages = UsageMap.read(getTable().getDatabase(), umapPageNum,
- umapRowNum, false);
+ _ownedPages = UsageMap.read(getTable().getDatabase(), tableBuffer, false);
_rootPageNumber = tableBuffer.getInt();
every call) */
private final TempBufferHolder _multiRowBufferH =
TempBufferHolder.newHolder(TempBufferHolder.Type.NONE, true);
- /** page buffer used to write out-of-line "long value" data */
+ /** page buffer used to write out-of-row "long value" data */
private final TempPageHolder _longValueBufferH =
TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
/** "big index support" is optional */
* @usage _intermediate_method_
*/
public int getApproximateOwnedPageCount() {
+
// add a page for the table def (although that might actually be more than
// one page)
int count = _ownedPages.getPageCount() + 1;
+
+ for(Column col : _columns) {
+ count += col.getOwnedPageCount();
+ }
+
// note, we count owned pages from _physical_ indexes, not logical indexes
// (otherwise we could double count pages)
for(IndexData indexData : _indexDatas) {
count += indexData.getOwnedPageCount();
}
+
return count;
}
(format.SIZE_INDEX_DEFINITION +
format.SIZE_INDEX_COLUMN_BLOCK)) +
(creator.getLogicalIndexCount() * format.SIZE_INDEX_INFO_BLOCK);
+ int colUmapLen = creator.getLongValueColumns().size() * 10;
int totalTableDefSize = format.SIZE_TDEF_HEADER +
(format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) +
- idxDataLen + format.SIZE_TDEF_TRAILER;
+ idxDataLen + colUmapLen + format.SIZE_TDEF_TRAILER;
// total up the amount of space used by the column and index names (2
// bytes per char + 2 bytes for the length)
Index.writeDefinitions(creator, buffer);
}
+ // write long value column usage map references
+ for(Column lvalCol : creator.getLongValueColumns()) {
+ buffer.putShort(lvalCol.getColumnNumber());
+ TableCreator.ColumnState colState =
+ creator.getColumnState(lvalCol);
+
+ // owned pages umap (both are on same page)
+ buffer.put(colState.getUmapOwnedRowNumber());
+ ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber());
+ // free space pages umap
+ buffer.put(colState.getUmapFreeRowNumber());
+ ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber());
+ }
+
//End of tabledef
buffer.put((byte) 0xff);
buffer.put((byte) 0xff);
private static void createUsageMapDefinitionBuffer(TableCreator creator)
throws IOException
{
- // 2 table usage maps plus 1 for each index
- int umapNum = 2 + creator.getIndexCount();
+ List<Column> lvalCols = creator.getLongValueColumns();
+
+ // 2 table usage maps plus 1 for each index and 2 for each lval col
+ int indexUmapEnd = 2 + creator.getIndexCount();
+ int umapNum = indexUmapEnd + (lvalCols.size() * 2);
JetFormat format = creator.getFormat();
- int usageMapRowLength = format.OFFSET_USAGE_MAP_START +
+ int umapRowLength = format.OFFSET_USAGE_MAP_START +
format.USAGE_MAP_TABLE_BYTE_LENGTH;
- int freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE
- - (umapNum * getRowSpaceUsage(usageMapRowLength, format));
-
- // for now, don't handle writing that many indexes
- if(freeSpace < 0) {
- throw new IOException("FIXME attempting to write too many indexes");
- }
+ int umapSpaceUsage = getRowSpaceUsage(umapRowLength, format);
+ PageChannel pageChannel = creator.getPageChannel();
+ int umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
+ ByteBuffer umapBuf = null;
+ int freeSpace = 0;
+ int rowStart = 0;
+ int umapRowNum = 0;
- int umapPageNumber = creator.getUmapPageNumber();
+ for(int i = 0; i < umapNum; ++i) {
- PageChannel pageChannel = creator.getPageChannel();
- ByteBuffer rtn = pageChannel.createPageBuffer();
- rtn.put(PageTypes.DATA);
- rtn.put((byte) 0x1); //Unknown
- rtn.putShort((short)freeSpace); //Free space in page
- rtn.putInt(0); //Table definition
- rtn.putInt(0); //Unknown
- rtn.putShort((short) umapNum); //Number of records on this page
-
- // write two rows of usage map definitions for the table
- int rowStart = findRowEnd(rtn, 0, format) - usageMapRowLength;
- for(int i = 0; i < 2; ++i) {
- rtn.putShort(getRowStartOffset(i, format), (short)rowStart);
- if(i == 0) {
- // initial "usage pages" map definition
- rtn.put(rowStart, UsageMap.MAP_TYPE_REFERENCE);
- } else {
- // initial "pages with free space" map definition
- rtn.put(rowStart, UsageMap.MAP_TYPE_INLINE);
+ if(umapBuf == null) {
+
+ // need new page for usage maps
+ if(umapPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
+ // first umap page has already been reserved
+ umapPageNumber = creator.getUmapPageNumber();
+ } else {
+ // need another umap page
+ umapPageNumber = creator.reservePageNumber();
+ }
+
+ freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE;
+
+ umapBuf = pageChannel.createPageBuffer();
+ umapBuf.put(PageTypes.DATA);
+ umapBuf.put((byte) 0x1); //Unknown
+ umapBuf.putShort((short)freeSpace); //Free space in page
+ umapBuf.putInt(0); //Table definition
+ umapBuf.putInt(0); //Unknown
+ umapBuf.putShort((short)0); //Number of records on this page
+
+ rowStart = findRowEnd(umapBuf, 0, format) - umapRowLength;
+ umapRowNum = 0;
}
- rowStart -= usageMapRowLength;
- }
- if(creator.hasIndexes()) {
-
- for(int i = 0; i < creator.getIndexes().size(); ++i) {
- IndexBuilder idx = creator.getIndexes().get(i);
+ umapBuf.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
+
+ if(i == 0) {
+
+ // table "owned pages" map definition
+ umapBuf.put(rowStart, UsageMap.MAP_TYPE_REFERENCE);
+
+ } else if(i == 1) {
+
+ // table "free space pages" map definition
+ umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
+
+ } else if(i < indexUmapEnd) {
+ // index umap
+ int indexIdx = i - 2;
+ IndexBuilder idx = creator.getIndexes().get(indexIdx);
+
// allocate root page for the index
int rootPageNumber = pageChannel.allocateNewPage();
- int umapRowNum = i + 2;
// stash info for later use
TableCreator.IndexState idxState = creator.getIndexState(idx);
idxState.setUmapPageNumber(umapPageNumber);
// index map definition, including initial root page
- rtn.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
- rtn.put(rowStart, UsageMap.MAP_TYPE_INLINE);
- rtn.putInt(rowStart + 1, rootPageNumber);
- rtn.put(rowStart + 5, (byte)1);
+ umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
+ umapBuf.putInt(rowStart + 1, rootPageNumber);
+ umapBuf.put(rowStart + 5, (byte)1);
+
+ } else {
+
+ // long value column umaps
+ int lvalColIdx = i - indexUmapEnd;
+ int umapType = lvalColIdx % 2;
+ lvalColIdx /= 2;
- rowStart -= usageMapRowLength;
+ Column lvalCol = lvalCols.get(lvalColIdx);
+ TableCreator.ColumnState colState =
+ creator.getColumnState(lvalCol);
+
+ umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
+
+ if((umapType == 1) &&
+ (umapPageNumber != colState.getUmapPageNumber())) {
+ // we want to force both usage maps for a column to be on the same
+ // data page, so just discard the previous one we wrote
+ --i;
+ umapType = 0;
+ }
+
+ if(umapType == 0) {
+ // lval column "owned pages" usage map
+ colState.setUmapOwnedRowNumber((byte)umapRowNum);
+ colState.setUmapPageNumber(umapPageNumber);
+ } else {
+ // lval column "free space pages" usage map (always on same page)
+ colState.setUmapFreeRowNumber((byte)umapRowNum);
+ }
}
- }
- pageChannel.writePage(rtn, umapPageNumber);
+ rowStart -= umapRowLength;
+ freeSpace -= umapSpaceUsage;
+ ++umapRowNum;
+
+ if((freeSpace <= umapSpaceUsage) || (i == (umapNum - 1))) {
+ // finish current page
+ umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
+ umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
+ (short)umapRowNum);
+ pageChannel.writePage(umapBuf, umapPageNumber);
+ umapBuf = null;
+ }
+ }
}
/**
_logicalIndexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEX_SLOTS);
_indexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEXES);
- int rowNum = ByteUtil.getUnsignedByte(
- tableBuffer, getFormat().OFFSET_OWNED_PAGES);
- int pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_OWNED_PAGES + 1);
- _ownedPages = UsageMap.read(getDatabase(), pageNum, rowNum, false);
- rowNum = ByteUtil.getUnsignedByte(
- tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES);
- pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES + 1);
- _freeSpacePages = UsageMap.read(getDatabase(), pageNum, rowNum, false);
+ tableBuffer.position(getFormat().OFFSET_OWNED_PAGES);
+ _ownedPages = UsageMap.read(getDatabase(), tableBuffer, false);
+ tableBuffer.position(getFormat().OFFSET_FREE_SPACE_PAGES);
+ _freeSpacePages = UsageMap.read(getDatabase(), tableBuffer, false);
for (int i = 0; i < _indexCount; i++) {
_indexDatas.add(IndexData.create(this, tableBuffer, i, getFormat()));
Collections.sort(_indexes);
+ // read column usage map info
+ while(tableBuffer.remaining() >= 2) {
+
+ short umapColNum = tableBuffer.getShort();
+ if(umapColNum == IndexData.COLUMN_UNUSED) {
+ break;
+ }
+
+ UsageMap colOwnedPages = UsageMap.read(
+ getDatabase(), tableBuffer, false);
+ UsageMap colFreeSpacePages = UsageMap.read(
+ getDatabase(), tableBuffer, false);
+
+ for(Column col : _columns) {
+ if(col.getColumnNumber() == umapColNum) {
+ col.setUsageMaps(colOwnedPages, colFreeSpacePages);
+ break;
+ }
+ }
+ }
+
// re-sort columns if necessary
if(getDatabase().getColumnOrder() != ColumnOrder.DATA) {
Collections.sort(_columns, DISPLAY_ORDER_COMPARATOR);
int pageNumber)
throws IOException
{
+ // assume incoming page is modified
+ boolean modifiedPage = true;
+
if(dataPage == null) {
- // find last data page (Not bothering to check other pages for free
- // space.)
- UsageMap.PageCursor revPageCursor = _ownedPages.cursor();
- revPageCursor.afterLast();
- while(true) {
- int tmpPageNumber = revPageCursor.getPreviousPage();
- if(tmpPageNumber < 0) {
- break;
- }
- dataPage = _addRowBufferH.setPage(getPageChannel(), tmpPageNumber);
- if(dataPage.get() == PageTypes.DATA) {
- // found last data page, only use if actually listed in free space
- // pages
- if(_freeSpacePages.containsPageNumber(tmpPageNumber)) {
- pageNumber = tmpPageNumber;
- }
- break;
- }
- }
+ // find owned page w/ free space
+ dataPage = findFreeRowSpace(_ownedPages, _freeSpacePages,
+ _addRowBufferH);
- if(pageNumber == PageChannel.INVALID_PAGE_NUMBER) {
+ if(dataPage == null) {
// No data pages exist (with free space). Create a new one.
return newDataPage();
}
-
+
+ // found a page, see if it will work
+ pageNumber = _addRowBufferH.getPageNumber();
+ // since we just loaded this page, it is not yet modified
+ modifiedPage = false;
}
if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
- // Last data page is full. Create a new one.
- writeDataPage(dataPage, pageNumber);
- _freeSpacePages.removePageNumber(pageNumber);
+ // Last data page is full. Write old one and create a new one.
+ if(modifiedPage) {
+ writeDataPage(dataPage, pageNumber);
+ }
+ _freeSpacePages.removePageNumber(pageNumber, true);
dataPage = newDataPage();
}
return dataPage;
}
+
+ static ByteBuffer findFreeRowSpace(
+ UsageMap ownedPages, UsageMap freeSpacePages,
+ TempPageHolder rowBufferH)
+ throws IOException
+ {
+ // find last data page (Not bothering to check other pages for free
+ // space.)
+ UsageMap.PageCursor revPageCursor = ownedPages.cursor();
+ revPageCursor.afterLast();
+ while(true) {
+ int tmpPageNumber = revPageCursor.getPreviousPage();
+ if(tmpPageNumber < 0) {
+ break;
+ }
+ ByteBuffer dataPage = rowBufferH.setPage(ownedPages.getPageChannel(),
+ tmpPageNumber);
+ if(dataPage.get() == PageTypes.DATA) {
+ // found last data page, only use if actually listed in free space
+ // pages
+ if(freeSpacePages.containsPageNumber(tmpPageNumber)) {
+ return dataPage;
+ }
+ }
+ }
+
+ return null;
+ }
/**
* Updates the table definition after rows are modified.
* Returns the row count for the current page. If the page is invalid
* ({@code null}) or the page is not a DATA page, 0 is returned.
*/
- private static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
+ static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
throws IOException
{
int rowsOnPage = 0;
import java.io.IOException;
import java.nio.charset.Charset;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
private final List<Column> _columns;
private final List<IndexBuilder> _indexes;
private final Map<IndexBuilder,IndexState> _indexStates =
- new HashMap<IndexBuilder,IndexState>();
+ new IdentityHashMap<IndexBuilder,IndexState>();
+ private final Map<Column,ColumnState> _columnStates =
+ new IdentityHashMap<Column,ColumnState>();
+ private final List<Column> _lvalCols = new ArrayList<Column>();
private int _tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
private int _umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
private int _indexCount;
return getPageChannel().allocateNewPage();
}
+ public ColumnState getColumnState(Column col) {
+ return _columnStates.get(col);
+ }
+
+ public List<Column> getLongValueColumns() {
+ return _lvalCols;
+ }
+
/**
* Creates the table in the database.
* @usage _advanced_method_
validate();
+ // assign column numbers and do some assorted column bookkeeping
+ short columnNumber = (short) 0;
+ for(Column col : _columns) {
+ col.setColumnNumber(columnNumber++);
+ if(col.getType().isLongValue()) {
+ _lvalCols.add(col);
+ // only lval columns need extra state
+ _columnStates.put(col, new ColumnState());
+ }
+ }
+
if(hasIndexes()) {
// sort out index numbers. for now, these values will always match
// (until we support writing foreign key indexes)
public void setRootPageNumber(int newRootPageNumber) {
_rootPageNumber = newRootPageNumber;
+ }
+ }
+
+ /**
+ * Maintains additional state used during column creation.
+ * @usage _advanced_class_
+ */
+ static final class ColumnState
+ {
+ private byte _umapOwnedRowNumber;
+ private byte _umapFreeRowNumber;
+ // we always put both usage maps on the same page
+ private int _umapPageNumber;
+
+ public byte getUmapOwnedRowNumber() {
+ return _umapOwnedRowNumber;
+ }
+
+ public void setUmapOwnedRowNumber(byte newUmapOwnedRowNumber) {
+ _umapOwnedRowNumber = newUmapOwnedRowNumber;
+ }
+
+ public byte getUmapFreeRowNumber() {
+ return _umapFreeRowNumber;
+ }
+
+ public void setUmapFreeRowNumber(byte newUmapFreeRowNumber) {
+ _umapFreeRowNumber = newUmapFreeRowNumber;
+ }
+
+ public int getUmapPageNumber() {
+ return _umapPageNumber;
+ }
+
+ public void setUmapPageNumber(int newUmapPageNumber) {
+ _umapPageNumber = newUmapPageNumber;
}
-
}
}
public PageChannel getPageChannel() {
return getDatabase().getPageChannel();
}
+
+ /**
+ * @param database database that contains this usage map
+ * @param buf buffer which contains the usage map row info
+ * @return Either an InlineUsageMap or a ReferenceUsageMap, depending on
+ * which type of map is found
+ */
+ public static UsageMap read(Database database, ByteBuffer buf,
+ boolean assumeOutOfRangeBitsOn)
+ throws IOException
+ {
+ int umapRowNum = buf.get();
+ int umapPageNum = ByteUtil.get3ByteInt(buf);
+ return read(database, umapPageNum, umapRowNum, false);
+ }
/**
* @param database database that contains this usage map