final DOMFilePageHeader pageHeader = rec.getPage().getPageHeader();
//Copy the old data up to the split point into a new array
final int oldDataLen = pageHeader.getDataLength();
final byte[] oldData = rec.getPage().data;
if (isTransactional && transaction != null) {
final Loggable loggable = new SplitPageLoggable(transaction,
rec.getPage().getPageNum(), rec.offset, oldData, oldDataLen);
writeToLog(loggable, rec.getPage().page);
}
rec.getPage().data = new byte[fileHeader.getWorkSize()];
System.arraycopy(oldData, 0, rec.getPage().data, 0, rec.offset);
//The old rec.page now contains a copy of the data up to the split point
rec.getPage().len = rec.offset;
pageHeader.setDataLength(rec.getPage().len);
rec.getPage().setDirty(true);
//Create a first split page
DOMPage firstSplitPage = new DOMPage();
if (isTransactional && transaction != null) {
final Loggable loggable = new CreatePageLoggable(transaction,
rec.getPage().getPageNum(), firstSplitPage.getPageNum(),
Page.NO_PAGE, pageHeader.getCurrentTupleID());
writeToLog(loggable, firstSplitPage.page);
}
DOMPage nextSplitPage = firstSplitPage;
nextSplitPage.getPageHeader().setNextTupleID(pageHeader.getCurrentTupleID());
long backLink;
short splitRecordCount = 0;
LOG.debug("Splitting " + rec.getPage().getPageNum() + " at " + rec.offset
+ ": New page: " + nextSplitPage.getPageNum() +
"; Next page: " + pageHeader.getNextDataPage());
//Start copying records from rec.offset to the new split pages
for (int pos = rec.offset; pos < oldDataLen; splitRecordCount++) {
//Read the current id
final short tupleID = ByteConversion.byteToShort(oldData, pos);
pos += LENGTH_TID;
//This is already a link, so we just copy it
if (ItemId.isLink(tupleID)) {
/* No room in the old page, append a new one */
if (rec.getPage().len + LENGTH_TID + LENGTH_FORWARD_LOCATION > fileHeader.getWorkSize()) {
final DOMPage newPage = new DOMPage();
final DOMFilePageHeader newPageHeader = newPage.getPageHeader();
if (isTransactional && transaction != null) {
Loggable loggable = new CreatePageLoggable(transaction,
rec.getPage().getPageNum(), newPage.getPageNum(),
pageHeader.getNextDataPage(), pageHeader.getCurrentTupleID());
writeToLog(loggable, firstSplitPage.page);
loggable = new UpdateHeaderLoggable(transaction,
pageHeader.getPreviousDataPage(), rec.getPage().getPageNum(),
newPage.getPageNum(), pageHeader.getPreviousDataPage(),
pageHeader.getNextDataPage());
writeToLog(loggable, nextSplitPage.page);
}
newPageHeader.setNextTupleID(pageHeader.getCurrentTupleID());
newPageHeader.setPrevDataPage(rec.getPage().getPageNum());
newPageHeader.setNextDataPage(pageHeader.getNextDataPage());
LOG.debug("Appending page after split: " + newPage.getPageNum());
pageHeader.setNextDataPage(newPage.getPageNum());
pageHeader.setDataLength(rec.getPage().len);
pageHeader.setRecordCount(countRecordsInPage(rec.getPage()));
rec.getPage().cleanUp();
rec.getPage().setDirty(true);
dataCache.add(rec.getPage());
//Switch record to new page...
rec.setPage(newPage);
rec.getPage().len = 0;
dataCache.add(newPage);
}
if (isTransactional && transaction != null) {
final long oldLink = ByteConversion.byteToLong(oldData, pos);
final Loggable loggable = new AddLinkLoggable(transaction,
rec.getPage().getPageNum(), ItemId.getId(tupleID), oldLink);
writeToLog(loggable, rec.getPage().page);
}
ByteConversion.shortToByte(tupleID, rec.getPage().data, rec.getPage().len);
rec.getPage().len += LENGTH_TID;
System.arraycopy(oldData, pos, rec.getPage().data, rec.getPage().len,
LENGTH_FORWARD_LOCATION);
rec.getPage().len += LENGTH_FORWARD_LOCATION;
pos += LENGTH_FORWARD_LOCATION;
continue;
}
//Read data length
final short vlen = ByteConversion.byteToShort(oldData, pos);
pos += LENGTH_DATA_LENGTH;
//If this is an overflow page, the real data length is always
//LENGTH_LINK byte for the page number of the overflow page
final short realLen = (vlen == OVERFLOW ? LENGTH_OVERFLOW_LOCATION : vlen);
//Check if we have room in the current split page
if (nextSplitPage.len + LENGTH_TID + LENGTH_DATA_LENGTH +
LENGTH_ORIGINAL_LOCATION + realLen > fileHeader.getWorkSize()) {
//Not enough room in the split page: append a new page
final DOMPage newPage = new DOMPage();
final DOMFilePageHeader newPageHeader = newPage.getPageHeader();
if (isTransactional && transaction != null) {
Loggable loggable = new CreatePageLoggable(transaction,
nextSplitPage.getPageNum(), newPage.getPageNum(),
Page.NO_PAGE, pageHeader.getCurrentTupleID());
writeToLog(loggable, firstSplitPage.page);
loggable = new UpdateHeaderLoggable(transaction,
nextSplitPage.getPageHeader().getPreviousDataPage(),
nextSplitPage.getPageNum(), newPage.getPageNum(),
nextSplitPage.getPageHeader().getPreviousDataPage(),
nextSplitPage.getPageHeader().getNextDataPage());
writeToLog(loggable, nextSplitPage.page);
}
newPageHeader.setNextTupleID(pageHeader.getCurrentTupleID());
newPageHeader.setPrevDataPage(nextSplitPage.getPageNum());
//No next page ? Well... we might want to enforce the value -pb
LOG.debug("Creating new split page: " + newPage.getPageNum());
nextSplitPage.getPageHeader().setNextDataPage(newPage.getPageNum());
nextSplitPage.getPageHeader().setDataLength(nextSplitPage.len);
nextSplitPage.getPageHeader().setRecordCount(splitRecordCount);
nextSplitPage.cleanUp();
nextSplitPage.setDirty(true);
dataCache.add(nextSplitPage);
dataCache.add(newPage);
nextSplitPage = newPage;
splitRecordCount = 0;
}
/*
* If the record has already been relocated,
* read the original storage address and update the link there.
*/
if (ItemId.isRelocated(tupleID)) {
backLink = ByteConversion.byteToLong(oldData, pos);
pos += LENGTH_ORIGINAL_LOCATION;
final RecordPos originalRecordPos = findRecord(backLink, false);
final long oldLink = ByteConversion.byteToLong(originalRecordPos.getPage().data,
originalRecordPos.offset);
final long forwardLink = StorageAddress.createPointer((int)
nextSplitPage.getPageNum(), ItemId.getId(tupleID));
if (isTransactional && transaction != null) {
final Loggable loggable = new UpdateLinkLoggable(transaction,
originalRecordPos.getPage().getPageNum(), originalRecordPos.offset,
forwardLink, oldLink);
writeToLog(loggable, originalRecordPos.getPage().page);
}
ByteConversion.longToByte(forwardLink, originalRecordPos.getPage().data,
originalRecordPos.offset);
originalRecordPos.getPage().setDirty(true);
dataCache.add(originalRecordPos.getPage());
} else {
backLink = StorageAddress.createPointer((int) rec.getPage().getPageNum(),
ItemId.getId(tupleID));
}
/*
* Save the record to the split page:
*/
if (isTransactional && transaction != null) {
//What does this "log" mean really ? Original ? -pb
final byte[] logData = new byte[realLen];
System.arraycopy(oldData, pos, logData, 0, realLen);
final Loggable loggable = new AddMovedValueLoggable(transaction,
nextSplitPage.getPageNum(), tupleID, logData, backLink);
writeToLog(loggable, nextSplitPage.page);
}
//Set the relocated flag and save the item id
ByteConversion.shortToByte(ItemId.setIsRelocated(tupleID), nextSplitPage.data,
nextSplitPage.len);
nextSplitPage.len += LENGTH_TID;
//Save length field
ByteConversion.shortToByte(vlen, nextSplitPage.data, nextSplitPage.len);
nextSplitPage.len += LENGTH_DATA_LENGTH;
//Save link to the original page
ByteConversion.longToByte(backLink, nextSplitPage.data, nextSplitPage.len);
nextSplitPage.len += LENGTH_ORIGINAL_LOCATION;
//Now save the data
try {
System.arraycopy(oldData, pos, nextSplitPage.data, nextSplitPage.len, realLen);
} catch (final ArrayIndexOutOfBoundsException e) {
SanityCheck.TRACE("pos = " + pos + "; len = " + nextSplitPage.len +
"; currentLen = " + realLen + "; tupleID = " + tupleID +
"; page = " + rec.getPage().getPageNum());
throw e;
}
nextSplitPage.len += realLen;
pos += realLen;
// save a link pointer in the original page if the record has not
// been relocated before.
if (!ItemId.isRelocated(tupleID)) {
// the link doesn't fit into the old page. Append a new page
if (rec.getPage().len + LENGTH_TID + LENGTH_FORWARD_LOCATION > fileHeader.getWorkSize()) {
final DOMPage newPage = new DOMPage();
final DOMFilePageHeader newPageHeader = newPage.getPageHeader();
if (isTransactional && transaction != null) {
Loggable loggable = new CreatePageLoggable(transaction,
rec.getPage().getPageNum(), newPage.getPageNum(),
pageHeader.getNextDataPage(), pageHeader.getCurrentTupleID());
writeToLog(loggable, firstSplitPage.page);
loggable = new UpdateHeaderLoggable(transaction,
pageHeader.getPreviousDataPage(),
rec.getPage().getPageNum(), newPage.getPageNum(),
pageHeader.getPreviousDataPage(), pageHeader.getNextDataPage());
writeToLog(loggable, nextSplitPage.page);
}
newPageHeader.setNextTupleID(pageHeader.getCurrentTupleID());
newPageHeader.setPrevDataPage(rec.getPage().getPageNum());
newPageHeader.setNextDataPage(pageHeader.getNextDataPage());
LOG.debug("Creating new page after split: " + newPage.getPageNum());
pageHeader.setNextDataPage(newPage.getPageNum());
pageHeader.setDataLength(rec.getPage().len);
pageHeader.setRecordCount(countRecordsInPage(rec.getPage()));
rec.getPage().cleanUp();
rec.getPage().setDirty(true);
dataCache.add(rec.getPage());
//switch record to new page...
rec.setPage(newPage);
rec.getPage().len = 0;
dataCache.add(newPage);
}
final long forwardLink = StorageAddress.createPointer(
(int) nextSplitPage.getPageNum(), ItemId.getId(tupleID));
if (isTransactional && transaction != null) {
final Loggable loggable = new AddLinkLoggable(transaction,
rec.getPage().getPageNum(), tupleID, forwardLink);
writeToLog(loggable, rec.getPage().page);
}
ByteConversion.shortToByte(ItemId.setIsLink(tupleID), rec.getPage().data, rec.getPage().len);
rec.getPage().len += LENGTH_TID;
ByteConversion.longToByte(forwardLink, rec.getPage().data, rec.getPage().len);
rec.getPage().len += LENGTH_FORWARD_LOCATION;
}
} //End of for loop: finished copying data
//Link the split pages to the original page
if (nextSplitPage.len == 0) {
LOG.warn("Page " + nextSplitPage.getPageNum() + " is empty. Remove it");
//If nothing has been copied to the last split page, remove it
if (nextSplitPage == firstSplitPage)
{firstSplitPage = null;}
try {
unlinkPages(nextSplitPage.page);
} catch (final IOException e) {
LOG.warn("Failed to remove empty split page: " + e.getMessage(), e);
}
nextSplitPage.setDirty(true);
dataCache.remove(nextSplitPage);
nextSplitPage = null;
} else {
if (isTransactional && transaction != null) {
final Loggable loggable = new UpdateHeaderLoggable(transaction,
nextSplitPage.getPageHeader().getPreviousDataPage(), nextSplitPage.getPageNum(),
pageHeader.getNextDataPage(), nextSplitPage.getPageHeader().getPreviousDataPage(),
nextSplitPage.getPageHeader().getNextDataPage());
writeToLog(loggable, nextSplitPage.page);
}
nextSplitPage.getPageHeader().setDataLength(nextSplitPage.len);
nextSplitPage.getPageHeader().setNextDataPage(pageHeader.getNextDataPage());
nextSplitPage.getPageHeader().setRecordCount(splitRecordCount);
nextSplitPage.cleanUp();
nextSplitPage.setDirty(true);
dataCache.add(nextSplitPage);
if (isTransactional && transaction != null) {
final DOMFilePageHeader fisrtPageHeader = firstSplitPage.getPageHeader();
final Loggable loggable = new UpdateHeaderLoggable(transaction,
rec.getPage().getPageNum(), firstSplitPage.getPageNum(),
fisrtPageHeader.getNextDataPage(), fisrtPageHeader.getPreviousDataPage(),
fisrtPageHeader.getNextDataPage());
writeToLog(loggable, nextSplitPage.page);
}
firstSplitPage.getPageHeader().setPrevDataPage(rec.getPage().getPageNum());
if (nextSplitPage != firstSplitPage) {
firstSplitPage.setDirty(true);
dataCache.add(firstSplitPage);
}
}
final long nextPageNum = pageHeader.getNextDataPage();
if (Page.NO_PAGE != nextPageNum) {
final DOMPage nextPage = getDOMPage(nextPageNum);
if (isTransactional && transaction != null) {
final Loggable loggable = new UpdateHeaderLoggable(transaction,
nextSplitPage.getPageNum(), nextPage.getPageNum(),
Page.NO_PAGE, nextPage.getPageHeader().getPreviousDataPage(),
nextPage.getPageHeader().getNextDataPage());
writeToLog(loggable, nextPage.page);
}
nextPage.getPageHeader().setPrevDataPage(nextSplitPage.getPageNum());
nextPage.setDirty(true);
dataCache.add(nextPage);
}
rec.setPage(getDOMPage(rec.getPage().getPageNum()));
if (firstSplitPage != null) {
if (isTransactional && transaction != null) {
final Loggable loggable = new UpdateHeaderLoggable(transaction,
pageHeader.getPreviousDataPage(), rec.getPage().getPageNum(),
firstSplitPage.getPageNum(), pageHeader.getPreviousDataPage(),
pageHeader.getNextDataPage());
writeToLog(loggable, rec.getPage().page);
}