/*
* Copyright (C) 2006 http://www.chaidb.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
package org.chaidb.db.index.btree;
import org.apache.log4j.Logger;
import org.chaidb.db.Db;
import org.chaidb.db.KernelContext;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.helper.ByteTool;
import org.chaidb.db.index.IDBIndex;
import org.chaidb.db.index.btree.bufmgr.PageNumber;
import org.chaidb.db.log.logrecord.BTreeAddRemoveLogRecord;
import org.chaidb.db.log.logrecord.BTreeReplLogRecord;
/**
* <P>Each data node has the following format:
* <pre>
* Size Description
* 4 Key Size
* 4 Data Size
* 1 Flags: 0x01 for overflow data, 0x02 for overflow key
* 2 Space allocated for this node within the page
* n Key/Data
* </pre>
*/
public class DataNode {
private static final Logger logger = Logger.getLogger(AbstractBTree.class);
/**
* Origin page of this node
*/
private DataPage page;
/**
* Location of this node on that page
*/
private int nodeOffset;
/**
* space allocated for this node within the page, two cases are:
* 1. when overflow: this is DATA_NODE_HEADER_SIZE + 4
* 2. if not overflow: this is DATA_NODE_HEADER_SIZE + keySize + dataSize
*/
private short nodeSpace;
/**
* Length of data
*/
private int dataSize;
/**
* 1 if data is on an overflow page, 2 for overflow key
*/
private byte flags;
boolean isOverflow() {
return (flags & 0x01) != 0;
}
public int getNodeOffset() {
return nodeOffset;
}
public byte getFlags() {
return flags;
}
public boolean isDupData() {
return ((flags & BTreeSpec.DATA_NODE_DUP) == BTreeSpec.DATA_NODE_DUP);
}
public boolean isDupNext() {
return ((flags & BTreeSpec.DATA_NODE_DUP_NEXT) == BTreeSpec.DATA_NODE_DUP_NEXT);
}
int getBtreeId() {
return page.btreeSpec.btree.getBtreeId();
}
int getNodeSize() {
return dataSize + BTreeSpec.DATA_NODE_HEADER_SIZE;
}
int getDataSize() {
return dataSize;
}
public DataPage getPage() {
return page;
}
public void setFlags(byte flags) {
this.flags = flags;
}
void setDataSize(int dataSize) {
this.dataSize = dataSize;
}
void setNodeSpace(short nodeSpace) {
this.nodeSpace = nodeSpace;
}
public short getNodeSpace() {
return nodeSpace;
}
/**
* create DataNode by Datapage and offset in the datapage,
*
* @param page the page where this node stays
* @param offset the offset of the node in the page
*/
public DataNode(DataPage page, int offset) {
this.page = page;
byte[] rawPage = this.page.getPage();
nodeOffset = offset;
dataSize = ByteTool.bytesToInt(rawPage,
// nodeOffset + BTreeSpec.DATA_NODE_OFF_PAGENUMBER, this.page.btreeSpec.msbFirst);
nodeOffset + BTreeSpec.DATA_NODE_OFF_DATASIZE, this.page.btreeSpec.isMsbFirst());
flags = rawPage[nodeOffset + BTreeSpec.DATA_NODE_OFF_FLAGS];
nodeSpace = ByteTool.bytesToShort(rawPage, nodeOffset + BTreeSpec.DATA_NODE_OFF_ALLOCATED_SPACE, this.page.btreeSpec.isMsbFirst());
}
/**
* Build a data node, get all parameters needed
*/
public DataNode(DataPage page, int offset, byte[] data) {
this(page, offset, data, (byte) 0); // normal node
}
/**
* Build a data node, get all parameters needed
*/
public DataNode(DataPage page, int offset, byte[] data, byte flags) {
this.page = page;
this.nodeOffset = offset;
dataSize = data.length;
this.flags = flags;
}
/**
* return the node header in byte[]
*/
public byte[] getHeader() {
byte[] ret = new byte[BTreeSpec.DATA_NODE_HEADER_SIZE];
System.arraycopy(ByteTool.intToBytes(dataSize), 0, ret, BTreeSpec.DATA_NODE_OFF_DATASIZE, 4);
// ret,BTreeSpec.DATA_NODE_OFF_PAGENUMBER,4);
ret[BTreeSpec.DATA_NODE_OFF_FLAGS] = flags;
System.arraycopy(ByteTool.shortToBytes(nodeSpace), 0, ret, BTreeSpec.DATA_NODE_OFF_ALLOCATED_SPACE, 2);
return ret;
}
/**
* Fetch data from a leaf page to a new copy; guaranteed to be data.
*
* @return A new copy of data in this node
*/
// did NOT fix / unfix the current page ###
public byte[] getData() throws ChaiDBException {
byte[] data = new byte[dataSize];
if (!isOverflow()) {
System.arraycopy(page.getPage(), nodeOffset + BTreeSpec.DATA_NODE_HEADER_SIZE, data, 0, dataSize);
} else {
// handle overflow data
PageNumber overflowPageNumber = new PageNumber(ByteTool.bytesToInt(page.getPage(), nodeOffset + BTreeSpec.DATA_NODE_HEADER_SIZE, page.btreeSpec.isMsbFirst()));
DataPage overflowPage = new DataPage(page.btreeSpec.btree.getBtreeId(), overflowPageNumber, page.btreeSpec, page.buffer);
//=======>>>> 1. try to find the first overflow page containing data
int freeSpace = page.btreeSpec.getPageSize() - BTreeSpec.PAGE_HEADER_SIZE;
int dataLeft = dataSize;
int dataStartPos = 0;
int dataLength = (dataLeft < freeSpace ? dataLeft : freeSpace);
while (dataLeft > 0 && dataLength > 0) {
System.arraycopy(overflowPage.getPage(), overflowPage.upperBound, data, dataStartPos, dataLength);
// release / unfix the overflow page
page.buffer.releasePage(overflowPage.pageNumber.getTreeId(), overflowPage.pageNumber, false);
dataStartPos += dataLength;
dataLeft -= dataLength;
if (dataLeft > 0 && overflowPage.nextPage.getPageNumber() > 0) {
overflowPage = new DataPage(page.btreeSpec.btree.getBtreeId(), overflowPage.nextPage, page.btreeSpec, page.buffer);
dataLength = (dataLeft < freeSpace) ? dataLeft : freeSpace;
} else if (dataLeft > 0 && overflowPage.nextPage.getPageNumber() <= 0) {
logger.error("dataSize=" + dataSize + " dataLeft" + dataLeft + " node offset=" + getNodeOffset() + " node pageNumber=" + page.pageNumber.toHexString() + " overflowpage=" + overflowPage.pageNumber.toHexString() + " of " + page.buffer.getBTreeName(overflowPage.pageNumber.getTreeId()));
/**
* Debug info for getXMLNode failed.
*/
if (Debug.DEBUG_XMLNODEFAILED) {
new Throwable().printStackTrace(System.err);
Debug.dumpPageToLog(page.getPage());
Debug.dumpPageToLog(overflowPage.getPage());
page.buffer.closeAllBTrees();
page.buffer.dump();
Db.getLogManager().flush();
System.exit(-100);
}
// details -ranjeet
String details = "Non-match key length in the overflow page.";
throw new ChaiDBException(ErrorCode.KEY_LENGTH_NOT_MATCH, details);
}
}
}
return data;
}
/**
* First time set up. guaranteed to be data; need to know whether overflow
* or not ahead.
*/
// did NOT unfix / fix the current page ###
public void storeNode(byte[] data, byte[] oldData, KernelContext kContext) throws ChaiDBException {
int txnId = kContext.getLocker();
boolean needLog = kContext.getNeedLog();
if (!isOverflow()) {
if (needLog) {
int newPageNo = page.pageNumber.getPageNumber();
byte[] newData = ByteTool.append(this.getHeader(), data);
if (oldData != null && (oldData != newData)) {
/* replace old data with new data*/
BTreeReplLogRecord logRec = new BTreeReplLogRecord(page.getPageNumber().getTreeId(), newPageNo, txnId, nodeOffset, oldData, newData, page.btreeSpec.btree.getType());
logRec.log();
} else {
if (page.btreeSpec.btree.getType() == IDBIndex.HYPER_BTREE) {
BTreeAddRemoveLogRecord logRec = new BTreeAddRemoveLogRecord(page.getPageNumber().getTreeId(), newPageNo, txnId, BTreeAddRemoveLogRecord.ADD_FLAG, nodeOffset, newData, page.btreeSpec.btree.getType());
logRec.log();
}
}
}
// add the new node
System.arraycopy(this.getHeader(), 0, page.getPage(), nodeOffset, BTreeSpec.DATA_NODE_HEADER_SIZE);
System.arraycopy(data, 0, page.getPage(), nodeOffset + BTreeSpec.DATA_NODE_HEADER_SIZE, dataSize);
} else {
//=====>>> Build overflow pages
// make it large enough
int freeSpace = page.btreeSpec.getPageSize() - BTreeSpec.PAGE_HEADER_SIZE;
int overflows = data.length / freeSpace + (data.length % freeSpace > 0 ? 1 : 0);
int dataStartPos = 0;
int dataLeft = data.length;
int requireSpace;
int left;
int dataLength = 0;
DataPage last = page;
PageNumber firstOverflowPageNo = null;
for (int i = 0; i < overflows; i++) {
left = dataLeft;
if (left <= 0) break;
requireSpace = (left > freeSpace) ? freeSpace : left;
dataLength = (dataLeft >= freeSpace) ? freeSpace : dataLeft;
DataPage overflowPage = DataPage.newPage(page.btreeSpec, page.buffer, true, kContext, 0);
overflowPage.setLogInfo(txnId, false);
if (needLog) {
int pgno = overflowPage.getPageNumber().getPageNumber();
byte[] oldV = ByteTool.copyByteArray(overflowPage.getPage(), 0, BTreeSpec.PAGE_HEADER_SIZE);
byte[] newV = new byte[oldV.length];
System.arraycopy(oldV, 0, newV, 0, oldV.length);
System.arraycopy(ByteTool.intToBytes(4), 0, newV, BTreeSpec.OFF_FLAGS, 4);
System.arraycopy(ByteTool.shortToBytes((short) (overflowPage.upperBound - requireSpace)), 0, newV, BTreeSpec.OFF_UPPERBOUND, 2);
System.arraycopy(ByteTool.intToBytes(last.pageNumber.getPageNumber(), overflowPage.btreeSpec.getMsbFirst()), 0, newV, BTreeSpec.OFF_PREVPAGE, 4);
if (newV != oldV) {
BTreeReplLogRecord lr = new BTreeReplLogRecord(overflowPage.getPageNumber().getTreeId(), pgno, txnId, 0, oldV, newV, overflowPage.btreeSpec.btree.getType());
lr.log();
}
}
overflowPage.setUpperBound((short) (overflowPage.upperBound - requireSpace));
overflowPage.setPrevPage(last.pageNumber);
overflowPage.setLogInfo(txnId, needLog);
if (i == 0) firstOverflowPageNo = overflowPage.pageNumber;
else last.setNextPage(overflowPage.pageNumber);
//add the new node
if (needLog) {
int newPageNo = overflowPage.getPageNumber().getPageNumber();
byte[] addData = ByteTool.subByteArray(data, dataStartPos, dataLength);
BTreeAddRemoveLogRecord logRec = new BTreeAddRemoveLogRecord(overflowPage.getPageNumber().getTreeId(), newPageNo, txnId, BTreeAddRemoveLogRecord.ADD_FLAG, overflowPage.upperBound, addData, overflowPage.btreeSpec.btree.getType());
logRec.log();
}
// only data left
System.arraycopy(data, dataStartPos, overflowPage.page, overflowPage.upperBound, dataLength);
dataStartPos += dataLength;
dataLeft -= dataLength;
//Dont release the owner nonoverflow page of these overflow pages.
if (i > 0) page.buffer.releasePage(last.pageNumber.getTreeId(), last.pageNumber, true);
last = overflowPage;
}
//unfix the last overflow page
page.buffer.releasePage(last.pageNumber.getTreeId(), last.pageNumber, true);
setOverflowPage(firstOverflowPageNo, oldData);
}
}
private void setOverflowPage(PageNumber pn, byte[] oldData) throws ChaiDBException {
if (page.needLog) {
int newPageNo = page.getPageNumber().getPageNumber();
byte[] newData = ByteTool.append(this.getHeader(), ByteTool.intToBytes(pn.getPageNumber()));
if (oldData != null && (newData != oldData)) {
BTreeReplLogRecord logRec = new BTreeReplLogRecord(page.getPageNumber().getTreeId(), newPageNo, page.txnId, nodeOffset, oldData, newData, page.btreeSpec.btree.getType());
logRec.log();
} else {
BTreeAddRemoveLogRecord logRec = new BTreeAddRemoveLogRecord(page.getPageNumber().getTreeId(), newPageNo, page.txnId, BTreeAddRemoveLogRecord.ADD_FLAG, nodeOffset, newData, page.btreeSpec.btree.getType());
logRec.log();
}
}
// add the new node to the current page
System.arraycopy(this.getHeader(), 0, page.getPage(), nodeOffset, BTreeSpec.DATA_NODE_HEADER_SIZE);
// last pageNumber is the one we need here!!
System.arraycopy(ByteTool.intToBytes(pn.getPageNumber()), 0, page.getPage(), nodeOffset + BTreeSpec.DATA_NODE_HEADER_SIZE, 4);
}
/**
* Debug method
*/
public String toString() {
StringBuffer str = new StringBuffer("Node: " + nodeOffset + "{\n");
str.append(this.page.pageNumber.toHexString());
str.append(" dataSize: " + dataSize);
str.append("}\n");
return str.toString();
}
}