Browse Source

Fix edge case which can cause table to be considered corrupt. Fixes #156

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1385 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-4.0.3
James Ahlborn 1 year ago
parent
commit
1ac3fbe1c9

+ 5
- 0
src/changes/changes.xml View File

<author email="javajedi@users.sf.net">Tim McCune</author> <author email="javajedi@users.sf.net">Tim McCune</author>
</properties> </properties>
<body> <body>
<release version="4.0.3" date="TBD">
<action dev="jahlborn" type="fix" system="SourceForge2" issue="156">
Fix edge case which can cause table to be considered corrupt.
</action>
</release>
<release version="4.0.2" date="2022-08-26"> <release version="4.0.2" date="2022-08-26">
<action dev="jahlborn" type="update"> <action dev="jahlborn" type="update">
Add Table methods to get the creation and last modified dates. Add Table methods to get the creation and last modified dates.

+ 87
- 78
src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java View File



/** bit index value for an invalid page number */ /** bit index value for an invalid page number */
private static final int INVALID_BIT_INDEX = -1; private static final int INVALID_BIT_INDEX = -1;
/** owning database */ /** owning database */
private final DatabaseImpl _database; private final DatabaseImpl _database;
/** Page number of the map table declaration */ /** Page number of the map table declaration */
public DatabaseImpl getDatabase() { public DatabaseImpl getDatabase() {
return _database; return _database;
} }
public JetFormat getFormat() { public JetFormat getFormat() {
return getDatabase().getFormat(); return getDatabase().getFormat();
} }
int umapPageNum = ByteUtil.get3ByteInt(buf); int umapPageNum = ByteUtil.get3ByteInt(buf);
return read(database, umapPageNum, umapRowNum, false); return read(database, umapPageNum, umapRowNum, false);
} }
/** /**
* @param database database that contains this usage map * @param database database that contains this usage map
* @param pageNum Page number that this usage map is contained in * @param pageNum Page number that this usage map is contained in
pageChannel.readPage(tableBuffer, pageNum); pageChannel.readPage(tableBuffer, pageNum);
short rowStart = TableImpl.findRowStart(tableBuffer, rowNum, format); short rowStart = TableImpl.findRowStart(tableBuffer, rowNum, format);
int rowEnd = TableImpl.findRowEnd(tableBuffer, rowNum, format); int rowEnd = TableImpl.findRowEnd(tableBuffer, rowNum, format);
tableBuffer.limit(rowEnd);
tableBuffer.limit(rowEnd);
byte mapType = tableBuffer.get(rowStart); byte mapType = tableBuffer.get(rowStart);
UsageMap rtn = new UsageMap(database, tableBuffer, pageNum, rowStart); UsageMap rtn = new UsageMap(database, tableBuffer, pageNum, rowStart);
rtn.initHandler(mapType, isGlobal); rtn.initHandler(mapType, isGlobal);
throws IOException throws IOException
{ {
if (mapType == MAP_TYPE_INLINE) { if (mapType == MAP_TYPE_INLINE) {
_handler = (isGlobal ? new GlobalInlineHandler() :
_handler = (isGlobal ? new GlobalInlineHandler() :
new InlineHandler()); new InlineHandler());
} else if (mapType == MAP_TYPE_REFERENCE) { } else if (mapType == MAP_TYPE_REFERENCE) {
_handler = (isGlobal ? new GlobalReferenceHandler() :
_handler = (isGlobal ? new GlobalReferenceHandler() :
new ReferenceHandler()); new ReferenceHandler());
} else { } else {
throw new IOException(MSG_PREFIX_UNRECOGNIZED_MAP + mapType); throw new IOException(MSG_PREFIX_UNRECOGNIZED_MAP + mapType);
} }
} }
public PageCursor cursor() { public PageCursor cursor() {
return new PageCursor(); return new PageCursor();
} }
public int getPageCount() { public int getPageCount() {
return _pageNumbers.cardinality(); return _pageNumbers.cardinality();
} }
protected short getRowStart() { protected short getRowStart() {
return _rowStart; return _rowStart;
} }
protected void setStartOffset(int startOffset) { protected void setStartOffset(int startOffset) {
_startOffset = startOffset; _startOffset = startOffset;
} }
protected int getStartOffset() { protected int getStartOffset() {
return _startOffset; return _startOffset;
} }
protected ByteBuffer getTableBuffer() { protected ByteBuffer getTableBuffer() {
return _tableBuffer; return _tableBuffer;
} }
protected int getTablePageNumber() { protected int getTablePageNumber() {
return _tablePageNum; return _tablePageNum;
} }
protected int getStartPage() { protected int getStartPage() {
return _startPage; return _startPage;
} }
protected int getEndPage() { protected int getEndPage() {
return _endPage; return _endPage;
} }
protected BitSet getPageNumbers() { protected BitSet getPageNumbers() {
return _pageNumbers; return _pageNumbers;
} }
} }


protected int getFirstPageNumber() { protected int getFirstPageNumber() {
return bitIndexToPageNumber(getNextBitIndex(-1),
return bitIndexToPageNumber(getNextBitIndex(-1),
RowIdImpl.LAST_PAGE_NUMBER); RowIdImpl.LAST_PAGE_NUMBER);
} }


return bitIndexToPageNumber( return bitIndexToPageNumber(
getNextBitIndex(pageNumberToBitIndex(curPage)), getNextBitIndex(pageNumberToBitIndex(curPage)),
RowIdImpl.LAST_PAGE_NUMBER); RowIdImpl.LAST_PAGE_NUMBER);
}
}
protected int getNextBitIndex(int curIndex) { protected int getNextBitIndex(int curIndex) {
return _pageNumbers.nextSetBit(curIndex + 1); return _pageNumbers.nextSetBit(curIndex + 1);
}
}
protected int getLastPageNumber() { protected int getLastPageNumber() {
return bitIndexToPageNumber(getPrevBitIndex(_pageNumbers.length()), return bitIndexToPageNumber(getPrevBitIndex(_pageNumbers.length()),
RowIdImpl.FIRST_PAGE_NUMBER); RowIdImpl.FIRST_PAGE_NUMBER);
return bitIndexToPageNumber( return bitIndexToPageNumber(
getPrevBitIndex(pageNumberToBitIndex(curPage)), getPrevBitIndex(pageNumberToBitIndex(curPage)),
RowIdImpl.FIRST_PAGE_NUMBER); RowIdImpl.FIRST_PAGE_NUMBER);
}
}
protected int getPrevBitIndex(int curIndex) { protected int getPrevBitIndex(int curIndex) {
--curIndex; --curIndex;
while((curIndex >= 0) && !_pageNumbers.get(curIndex)) { while((curIndex >= 0) && !_pageNumbers.get(curIndex)) {
--curIndex; --curIndex;
} }
return curIndex; return curIndex;
}
}
protected int bitIndexToPageNumber(int bitIndex, protected int bitIndexToPageNumber(int bitIndex,
int invalidPageNumber) { int invalidPageNumber) {
return((bitIndex >= 0) ? (_startPage + bitIndex) : invalidPageNumber); return((bitIndex >= 0) ? (_startPage + bitIndex) : invalidPageNumber);
_startPage = 0; _startPage = 0;
_endPage = 0; _endPage = 0;
++_modCount; ++_modCount;
// clear out the table data (everything except map type) // clear out the table data (everything except map type)
int tableStart = getRowStart() + 1; int tableStart = getRowStart() + 1;
int tableEnd = getRowEnd(); int tableEnd = getRowEnd();
ByteUtil.clearRange(_tableBuffer, tableStart, tableEnd); ByteUtil.clearRange(_tableBuffer, tableStart, tableEnd);
} }
protected void writeTable() protected void writeTable()
throws IOException throws IOException
{ {
// note, we only want to write the row data with which we are working // note, we only want to write the row data with which we are working
getPageChannel().writePage(_tableBuffer, _tablePageNum, _rowStart); getPageChannel().writePage(_tableBuffer, _tablePageNum, _rowStart);
} }
/** /**
* Read in the page numbers in this inline map * Read in the page numbers in this inline map
*/ */
public boolean containsPageNumber(int pageNumber) { public boolean containsPageNumber(int pageNumber) {
return _handler.containsPageNumber(pageNumber); return _handler.containsPageNumber(pageNumber);
} }
/** /**
* Add a page number to this usage map * Add a page number to this usage map
*/ */
++_modCount; ++_modCount;
_handler.addOrRemovePageNumber(pageNumber, true, false); _handler.addOrRemovePageNumber(pageNumber, true, false);
} }
/** /**
* Remove a page number from this usage map * Remove a page number from this usage map
*/ */
public void removePageNumber(int pageNumber)
throws IOException
public void removePageNumber(int pageNumber)
throws IOException
{ {
removePageNumber(pageNumber, true); removePageNumber(pageNumber, true);
} }
private void removePageNumber(int pageNumber, boolean force)
throws IOException
private void removePageNumber(int pageNumber, boolean force)
throws IOException
{ {
++_modCount; ++_modCount;
_handler.addOrRemovePageNumber(pageNumber, false, force); _handler.addOrRemovePageNumber(pageNumber, false, force);
} }
protected void updateMap(int absolutePageNumber, protected void updateMap(int absolutePageNumber,
int bufferRelativePageNumber, int bufferRelativePageNumber,
ByteBuffer buffer, boolean add, boolean force) ByteBuffer buffer, boolean add, boolean force)
" usage map, expected range " + " usage map, expected range " +
_startPage + " to " + _endPage); _startPage + " to " + _endPage);
} }
//Apply the bitmask //Apply the bitmask
if (add) { if (add) {
b |= bitmask; b |= bitmask;


// clear out the main table (inline usage map data and start page) // clear out the main table (inline usage map data and start page)
clearTableAndPages(); clearTableAndPages();
// set the new map type // set the new map type
_tableBuffer.put(getRowStart(), MAP_TYPE_REFERENCE); _tableBuffer.put(getRowStart(), MAP_TYPE_REFERENCE);


// write the new table data // write the new table data
writeTable(); writeTable();
// set new handler // set new handler
_handler = new ReferenceHandler(); _handler = new ReferenceHandler();


ranges.add(String.valueOf(rangeStart)); ranges.add(String.valueOf(rangeStart));
} }
} }

private static int toValidStartPage(int startPage) {
// start page must be a multiple of 8
return ((startPage / 8) * 8);
}

private abstract class Handler private abstract class Handler
{ {
protected Handler() { protected Handler() {
return(isPageWithinRange(pageNumber) && return(isPageWithinRange(pageNumber) &&
getPageNumbers().get(pageNumberToBitIndex(pageNumber))); getPageNumbers().get(pageNumberToBitIndex(pageNumber)));
} }
/** /**
* @param pageNumber Page number to add or remove from this map * @param pageNumber Page number to add or remove from this map
* @param add True to add it, false to remove it * @param add True to add it, false to remove it
private class InlineHandler extends Handler private class InlineHandler extends Handler
{ {
private final int _maxInlinePages; private final int _maxInlinePages;
protected InlineHandler() throws IOException protected InlineHandler() throws IOException
{ {
_maxInlinePages = (getInlineDataEnd() - getInlineDataStart()) * 8; _maxInlinePages = (getInlineDataEnd() - getInlineDataStart()) * 8;
protected final int getInlineDataEnd() { protected final int getInlineDataEnd() {
return getRowEnd(); return getRowEnd();
} }
/** /**
* Sets the page range for an inline usage map starting from the given * Sets the page range for an inline usage map starting from the given
* page. * page.
*/ */
private void setInlinePageRange(int startPage) { private void setInlinePageRange(int startPage) {
setPageRange(startPage, startPage + getMaxInlinePages()); setPageRange(startPage, startPage + getMaxInlinePages());
}
}
@Override @Override
public void addOrRemovePageNumber(int pageNumber, boolean add, public void addOrRemovePageNumber(int pageNumber, boolean add,
boolean force) boolean force)
force); force);
// Write the updated map back to disk // Write the updated map back to disk
writeTable(); writeTable();
} else { } else {


// uh-oh, we've split our britches. what now? // uh-oh, we've split our britches. what now?
throws IOException throws IOException
{ {
// determine what our status is before taking action // determine what our status is before taking action
if(add) { if(add) {


int firstPage = getFirstPageNumber(); int firstPage = getFirstPageNumber();
} else { } else {
firstPage = pageNumber; firstPage = pageNumber;
} }

firstPage = toValidStartPage(firstPage);

if((lastPage - firstPage + 1) < getMaxInlinePages()) { if((lastPage - firstPage + 1) < getMaxInlinePages()) {


// we can still fit within an inline map // we can still fit within an inline map
moveToNewStartPage(firstPage, pageNumber); moveToNewStartPage(firstPage, pageNumber);
} else { } else {
// not going to happen, need to promote the usage map to a // not going to happen, need to promote the usage map to a
// reference map // reference map
// since we assuming out-of-range bits are "on". Note, we are leaving // since we assuming out-of-range bits are "on". Note, we are leaving
// small holes in the database here (leaving behind some free pages), // small holes in the database here (leaving behind some free pages),
// but it's not the end of the world. // but it's not the end of the world.
if(!add) { if(!add) {


int firstPage = getFirstPageNumber(); int firstPage = getFirstPageNumber();
throws IOException throws IOException
{ {
int oldEndPage = getEndPage(); int oldEndPage = getEndPage();
int newStartPage =
((firstPage <= PageChannel.INVALID_PAGE_NUMBER) ? newPageNumber :
// just shift a little and discard any initial unused pages.
(newPageNumber - (getMaxInlinePages() / 2)));
int newStartPage =
toValidStartPage(
((firstPage <= PageChannel.INVALID_PAGE_NUMBER) ? newPageNumber :
// just shift a little and discard any initial unused pages.
(newPageNumber - (getMaxInlinePages() / 2))));


// move the current data // move the current data
moveToNewStartPage(newStartPage, PageChannel.INVALID_PAGE_NUMBER); moveToNewStartPage(newStartPage, PageChannel.INVALID_PAGE_NUMBER);
*/ */
private class ReferenceHandler extends Handler private class ReferenceHandler extends Handler
{ {
/** Buffer that contains the current reference map page */
/** Buffer that contains the current reference map page */
private final TempPageHolder _mapPageHolder = private final TempPageHolder _mapPageHolder =
TempPageHolder.newHolder(TempBufferHolder.Type.SOFT); TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
private final int _maxPagesPerUsageMapPage; private final int _maxPagesPerUsageMapPage;
private ReferenceHandler() throws IOException private ReferenceHandler() throws IOException
{ {
_maxPagesPerUsageMapPage = ((getFormat().PAGE_SIZE -
_maxPagesPerUsageMapPage = ((getFormat().PAGE_SIZE -
getFormat().OFFSET_USAGE_MAP_PAGE_DATA) * 8); getFormat().OFFSET_USAGE_MAP_PAGE_DATA) * 8);
int numUsagePages = (getRowEnd() - getRowStart() - 1) / 4; int numUsagePages = (getRowEnd() - getRowStart() - 1) / 4;
setStartOffset(getFormat().OFFSET_USAGE_MAP_PAGE_DATA); setStartOffset(getFormat().OFFSET_USAGE_MAP_PAGE_DATA);
setPageRange(0, (numUsagePages * _maxPagesPerUsageMapPage)); setPageRange(0, (numUsagePages * _maxPagesPerUsageMapPage));
// there is no "start page" for a reference usage map, so we get an // there is no "start page" for a reference usage map, so we get an
// extra page reference on top of the number of page references that fit // extra page reference on top of the number of page references that fit
// in the table // in the table
protected final int getMaxPagesPerUsagePage() { protected final int getMaxPagesPerUsagePage() {
return _maxPagesPerUsageMapPage; return _maxPagesPerUsageMapPage;
} }
@Override @Override
public void addOrRemovePageNumber(int pageNumber, boolean add, public void addOrRemovePageNumber(int pageNumber, boolean add,
boolean force) boolean force)
mapPageBuffer, add, force); mapPageBuffer, add, force);
getPageChannel().writePage(mapPageBuffer, mapPageNum); getPageChannel().writePage(mapPageBuffer, mapPageNum);
} }
/** /**
* Create a new usage map page and update the map declaration with a * Create a new usage map page and update the map declaration with a
* pointer to it. * pointer to it.
* @param pageIndex Index of the page reference within the map declaration
* @param pageIndex Index of the page reference within the map declaration
*/ */
private ByteBuffer createNewUsageMapPage(int pageIndex) throws IOException private ByteBuffer createNewUsageMapPage(int pageIndex) throws IOException
{ {
writeTable(); writeTable();
return mapPageBuffer; return mapPageBuffer;
} }
private int calculateMapPagePointerOffset(int pageIndex) { private int calculateMapPagePointerOffset(int pageIndex) {
return getRowStart() + getFormat().OFFSET_REFERENCE_MAP_PAGE_NUMBERS + return getRowStart() + getFormat().OFFSET_REFERENCE_MAP_PAGE_NUMBERS +
(pageIndex * 4); (pageIndex * 4);
} }


protected ByteBuffer allocateNewUsageMapPage(int pageIndex)
throws IOException
protected ByteBuffer allocateNewUsageMapPage(int pageIndex)
throws IOException
{ {
ByteBuffer mapPageBuffer = _mapPageHolder.setNewPage(getPageChannel()); ByteBuffer mapPageBuffer = _mapPageHolder.setNewPage(getPageChannel());
mapPageBuffer.put(PageTypes.USAGE_MAP); mapPageBuffer.put(PageTypes.USAGE_MAP);
super.addOrRemovePageNumber(pageNumber, add, force); super.addOrRemovePageNumber(pageNumber, add, force);


while(_pendingPage != null) { while(_pendingPage != null) {
// while updating our usage map, we needed to allocate a new page (and // while updating our usage map, we needed to allocate a new page (and
// thus mark a new page as used). we delayed that marking so that we // thus mark a new page as used). we delayed that marking so that we
// didn't get into an infinite loop. now that we completed the // didn't get into an infinite loop. now that we completed the
_pendingPage = null; _pendingPage = null;


super.addOrRemovePageNumber(removedPageNumber, false, true); super.addOrRemovePageNumber(removedPageNumber, false, true);
}
}
}
}


@Override @Override
protected ByteBuffer allocateNewUsageMapPage(int pageIndex) protected ByteBuffer allocateNewUsageMapPage(int pageIndex)
throws IOException
throws IOException
{ {
try { try {
// keep track of the fact that we are actively allocating a page for our // keep track of the fact that we are actively allocating a page for our
// load this map, which is fine because we should only add pages which // load this map, which is fine because we should only add pages which
// represent the size of the database currently in use). // represent the size of the database currently in use).
int dataStart = getFormat().OFFSET_USAGE_MAP_PAGE_DATA; int dataStart = getFormat().OFFSET_USAGE_MAP_PAGE_DATA;
ByteUtil.fillRange(mapPageBuffer, dataStart,
ByteUtil.fillRange(mapPageBuffer, dataStart,
getFormat().PAGE_SIZE - dataStart); getFormat().PAGE_SIZE - dataStart);


int maxPagesPerUmapPage = getMaxPagesPerUsagePage(); int maxPagesPerUmapPage = getMaxPagesPerUsagePage();
public UsageMap getUsageMap() { public UsageMap getUsageMap() {
return UsageMap.this; return UsageMap.this;
} }
/** /**
* Returns the DirHandler for the given direction * Returns the DirHandler for the given direction
*/ */
*/ */
public boolean isUpToDate() { public boolean isUpToDate() {
return(UsageMap.this._modCount == _lastModCount); return(UsageMap.this._modCount == _lastModCount);
}
}


/** /**
* @return valid page number if there was another page to read, * @return valid page number if there was another page to read,
} }


checkForModification(); checkForModification();
_prevPageNumber = _curPageNumber; _prevPageNumber = _curPageNumber;
_curPageNumber = handler.getAnotherPageNumber(_curPageNumber); _curPageNumber = handler.getAnotherPageNumber(_curPageNumber);
return _curPageNumber; return _curPageNumber;
{ {
restorePosition(curPageNumber, _curPageNumber); restorePosition(curPageNumber, _curPageNumber);
} }
/** /**
* Restores a current and previous position for the cursor. * Restores a current and previous position for the cursor.
*/ */
} }
return pageNumber; return pageNumber;
} }
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + " CurPosition " + _curPageNumber + return getClass().getSimpleName() + " CurPosition " + _curPageNumber +
", PrevPosition " + _prevPageNumber; ", PrevPosition " + _prevPageNumber;
} }
/** /**
* Handles moving the cursor in a given direction. Separates cursor * Handles moving the cursor in a given direction. Separates cursor
* logic from value storage. * logic from value storage.
public abstract int getBeginningPageNumber(); public abstract int getBeginningPageNumber();
public abstract int getEndPageNumber(); public abstract int getEndPageNumber();
} }
/** /**
* Handles moving the cursor forward. * Handles moving the cursor forward.
*/ */
return RowIdImpl.LAST_PAGE_NUMBER; return RowIdImpl.LAST_PAGE_NUMBER;
} }
} }
/** /**
* Handles moving the cursor backward. * Handles moving the cursor backward.
*/ */
return RowIdImpl.FIRST_PAGE_NUMBER; return RowIdImpl.FIRST_PAGE_NUMBER;
} }
} }
}
}
} }

Loading…
Cancel
Save