/*
* 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.KernelContext;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.EncodingException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.helper.ByteTool;
import org.chaidb.db.index.IDBIndex;
import org.chaidb.db.index.ISingleKeyBTree;
import org.chaidb.db.index.Key;
import org.chaidb.db.index.btree.bufmgr.PageNumber;
import org.chaidb.db.lock.LockManager;
import org.chaidb.db.log.logrecord.BTreeFreeOverflowPageLogRecord;
import org.chaidb.db.log.logrecord.BTreeFreePageWithDataLogRecord;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
// Todo:
// 1) cursor
// 2) hashing
// 3) writing
/**
* This class implements Berkeley DB database format.
* <p/>
* There are 3 classes:</P>
* <DL>
* <DT>BTree<DD> Maintains the actual file handle, and
* answers requests for pages. Auxiliary classes are
* instantiated with a reference to this class so that they
* can access the file.
* <DT>BTreePage<DD> Most database
* manipulations are handled here. For example, to search, the
* top-level BTree object fetches the root page (always page 1)
* from the page file and asks it to
* search itself. It will go back to the page file to fetch additional
* pages as needed.
* <DT>BTreeNode<DD>A fairly simple class that encapsulates
* the key/data pairs stored in pages.
* </DD>
*/
public class BTree extends AbstractBTree implements ISingleKeyBTree {
private static final Logger logger = Logger.getLogger(BTree.class);
public static int DOCID_SIZE = 3;
protected AbstractBTree GenerateClonedBTree() throws ChaiDBException {
return new BTree();
}
protected void copyData(AbstractBTree newBTree, KernelContext kContext) throws ChaiDBException {
try {
this.acquire(kContext, LockManager.LOCK_READ);
newBTree.acquire(kContext, LockManager.LOCK_WRITE);
Enumeration keys = keys(kContext);
if (keys == null) return;
while (keys.hasMoreElements()) {
Key key = (Key) keys.nextElement();
newBTree.store(key, lookup(key, kContext), IDBIndex.STORE_REPLACE, kContext);
}
} finally {
newBTree.release(kContext);
this.release(kContext);
}
}
/**
* returns the values associated with an index
*
* @return A list of values associated with the given key,
* if not found, return null
* @throws ChaiDBException Exception while looking up the key
*/
public Object lookup(Key key, KernelContext kContext) throws ChaiDBException {
kContext.checkLock(getBTreeName());
if (key == null) return null;
byte[] bytes = null;
for (int i = 0; i < 3; i++) {
try {
PageNumber root = getTopRoot();
bytes = lookup(key, root, kContext);
} catch (ChaiDBException e) {
if (i == 2) {
throw e;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
;
}
continue;
}
} finally {
doFinalJob();
}
break;
}//end for
return decodeFromByteArray(bytes, key);
}
//Before going out, release all BTree level locks
protected byte[] lookup(Key key, PageNumber root, KernelContext kContext) throws ChaiDBException {
BTreePage rootPage = new BTreePage(id, root, btreeSpec, getBuffer());
if (rootPage.getPage() == null) {
return null;
}
BTreeNode n = rootPage.search(key, kContext);
// unfix root and leaf page.
getBuffer().releasePage(id, rootPage.pageNumber, false);
if (n == null) {
return null;
}
byte[] value = n.getData(kContext);
//if found,release it.
getBuffer().releasePage(id, n.getBTreePage().pageNumber, false);
return value;
}
public ArrayList rangeLookup(Key minKey, Key maxKey, boolean includeMinKey, boolean includeMaxKey, KernelContext kContext) throws ChaiDBException {
kContext.checkLock(getBTreeName());
ArrayList values = new ArrayList();
for (int i = 0; i < 3; i++) {
try {
// if (islockTree) lm.getLock(MR1WLock.READ);
PageNumber root = getTopRoot();
rangeLookup(minKey, maxKey, includeMinKey, includeMaxKey, root, values, kContext);
} catch (ChaiDBException e) {
if (i == 2) {
throw e;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
;
}
continue;
}
} finally {
doFinalJob();
}
break;
}//end for
try {
if (converter != null) {
ArrayList newList = new ArrayList(values.size());
for (int i = 0; i < values.size(); i++) {
Object obj = values.get(i);
newList.add(converter.decodeFromByteArray(minKey, (byte[]) obj));
}
return newList;
} else return values;
} catch (EncodingException ee) {
logger.debug(ee);
String details = "Converter failed to decode : " + ee.toString() + ".";
throw new ChaiDBException(ErrorCode.CONVERTER_DECODING_ERROR, details);
}
}
//here convert is not performed
protected void rangeLookup(Key minKey, Key maxKey, boolean includeMinKey, boolean includeMaxKey, PageNumber root, ArrayList values, KernelContext kContext) throws ChaiDBException {
if (!checkKeyPair(minKey, maxKey)) return;
BTreePage page;
if (minKey == null) {
page = findLeftMostLeaf(root);
} else {
page = new BTreePage(id, root, btreeSpec, getBuffer());
if (page.getPage() != null) {
page = page.getLeaf(minKey, kContext, BTreePage.SEARCH);
getBuffer().releasePage(id, root, false);
}
}
if (page.getPage() != null) {
page.search(minKey, maxKey, includeMinKey, includeMaxKey, kContext, values);
// unfix root and leaf page.
getBuffer().releasePage(id, page.pageNumber, false);
}
}
/**
* returns either enumeration or set of keys.
* If DBM and BTREE, return enumeration;
* if HashMap or TreeMap, return Set.
*
* @return Enumeration of keys
*/
public Enumeration keys(KernelContext kContext) {
BTreeEnumerator btreeEnum = null;
for (int i = 0; i < 3; i++) {
try {
kContext.checkLock(getBTreeName());
btreeEnum = new BTreeEnumerator(this, btreeSpec, getBuffer(), kContext);
} catch (ChaiDBException e) {
if (i == 2) {
logger.debug(e);
return null;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
;
}
continue;
}
}
break;
}//end for
return btreeEnum;
}
/**
* returns the index type of this index
*
* @return A short integer representing the index type
*/
public short getType() {
return IDBIndex.BTREE;
}
/**
* @param root
* @param dataPageMixed ONLY ture, for non-id2node.idb, if data pages dont belongs to one doc.
* @param kContext
* @throws ChaiDBException
*/
protected void delSubTreeCore(PageNumber root, boolean dataPageMixed, KernelContext kContext) throws ChaiDBException {
BTreePage rootPage = new BTreePage(id, root, btreeSpec, getBuffer());
if (rootPage.getPage() != null) {
ArrayList pageList = new ArrayList(rootPage.getCurrNodeNumbers() + 1);
Hashtable dataPages = new Hashtable();
BTreePage tmpPage = rootPage;
do {
freePages(tmpPage, pageList, dataPageMixed, dataPages, kContext);
if (!pageList.isEmpty()) {
tmpPage = new BTreePage(id, (PageNumber) pageList.remove(0), btreeSpec, getBuffer());
} else break;
} while (true);
if (dataPageMixed) return;
//for id2node, datapages are not freed yet.
Enumeration pagenumbers = dataPages.keys();
PageNumber ppn = null;
while (pagenumbers.hasMoreElements()) {
ppn = (PageNumber) pagenumbers.nextElement();
DataPage dPage = new DataPage(id, ppn, btreeSpec, getBuffer());
if (Debug.DEBUG_TRAVERAL) {
logger.fatal("saved data page is " + ppn.toHexString() + " ");
}
freeAPage(kContext, dPage);
}
if (Debug.DEBUG_TRAVERAL) {
logger.fatal("end loopup");
}
}
}
/**
* Get all used information of passed page, and then free it.
*
* @param page
* @param pageList
* @param dataPages
* @param kContext
* @throws ChaiDBException
*/
private void freePages(BTreePage page, ArrayList pageList, boolean dataPageMixed, Hashtable dataPages, KernelContext kContext) throws ChaiDBException {
if (!page.isLeaf() && page.nextPage.getPageInFile() > BTreeSpec.INVALID_PAGENO) pageList.add(page.nextPage);
for (int i = 0; i < page.getCurrNodeNumbers(); i++) {
BTreeNode node = page.getNode(i);
PageNumber p = node.getPageNumber();
if (!page.isLeaf()) //add btree pages
pageList.add(p);
else {
if (!dataPageMixed && !dataPages.containsKey(p)) {
PageNumber tmpPage = new PageNumber(p);
dataPages.put(tmpPage, tmpPage); //add data pages
if (Debug.DEBUG_TRAVERAL) {
logger.fatal("parent page is " + page.pageNumber.toHexString() + " ");
logger.fatal("data page is " + tmpPage.toHexString() + " ");
}
}
freeDataNode(p, node.getDataNodeOffset(), dataPageMixed, kContext);
}
if (node.isOverflowKey()) {
p = new PageNumber(ByteTool.bytesToInt(page.getPage(), node.getNodeOffset() + BTreeSpec.NODE_HEADER_SIZE, btreeSpec.isMsbFirst()));
p.setTreeId(id);
freeOverFlowPages(p, kContext);
}
}
//All info is got and then free this page
freeAPage(kContext, page);
}
private void freeDataNode(PageNumber pNumber, int offset, boolean dataPageMixed, KernelContext kContext) throws ChaiDBException {
DataPage dPage = new DataPage(id, pNumber, btreeSpec, getBuffer());
if (dataPageMixed) {
dPage.deleteNode(kContext, offset);
//if this datapage is empty and isn't LDP, free it. Otherwise, just release it later
if (dPage.getCurrNodeNumbers() == 0) {
freeAPage(kContext, dPage);
return;
}
} else {
DataNode node = new DataNode(dPage, offset);
if (node.isOverflow()) {
PageNumber p = new PageNumber(ByteTool.bytesToInt(dPage.getPage(), node.getNodeOffset() + BTreeSpec.DATA_NODE_HEADER_SIZE, btreeSpec.isMsbFirst()));
p.setTreeId(id);
freeOverFlowPages(p, kContext);
}
}
getBuffer().releasePage(id, pNumber, false);
}
//free overflowpage
private void freeOverFlowPages(PageNumber overflowPageNumber, KernelContext kContext) throws ChaiDBException {
while (overflowPageNumber.getPageNumber() > 0) {
BTreePage overflowPage = new BTreePage(id, overflowPageNumber, btreeSpec, getBuffer());
overflowPageNumber = overflowPage.nextPage;
freeAPage(kContext, overflowPage);
}
}
//In fact the two following method is same except for parameter BTreePage or DataPage.
//This is becasue there is no a superclass aggreate both.
private void freeAPage(KernelContext kContext, BTreePage recycledPage) throws ChaiDBException {
int txnId = kContext.getLocker();
boolean needLog = kContext.getNeedLog();
// put into freeList
/******************* Add by Leon, Sep 29 *****************/
if (needLog) {
int pgno = recycledPage.pageNumber.getPageNumber();
short upBound = recycledPage.upperBound;
//overflowpage: record the header.
short lowBound = recycledPage.isOverflow() ? (short) BTreeSpec.PAGE_HEADER_SIZE : recycledPage.lowerBound;
BTreeFreeOverflowPageLogRecord lr = new BTreeFreeOverflowPageLogRecord(id, pgno, txnId, ByteTool.copyByteArray(recycledPage.page, 0, lowBound), ByteTool.copyByteArray(recycledPage.page, upBound, BTreeSpec.PAGE_SIZE - upBound), btreeSpec.btree.getType());
lr.log();
}
/***************************/
getBuffer().addToFreeList(id, recycledPage.pageNumber, needLog ? new Integer(txnId) : null);
}
private void freeAPage(KernelContext kContext, DataPage recycledPage) throws ChaiDBException {
int txnId = kContext.getLocker();
boolean needLog = kContext.getNeedLog();
// put into freeList
/******************* Add by Leon, Sep 29 *****************/
if (needLog) {
int pgno = recycledPage.pageNumber.getPageNumber();
short upBound = recycledPage.upperBound;
BTreeFreePageWithDataLogRecord lr = new BTreeFreePageWithDataLogRecord(id, pgno, txnId, recycledPage.flags, recycledPage.docid, ByteTool.copyByteArray(recycledPage.page, 0, BTreeSpec.PAGE_HEADER_SIZE), ByteTool.copyByteArray(recycledPage.page, upBound, BTreeSpec.PAGE_SIZE - upBound), btreeSpec.btree.getType());
lr.log();
}
/***************************/
Integer iTxnID = new Integer(txnId);
if (getBuffer().isLatestDataPage(recycledPage.docid, recycledPage.pageNumber, iTxnID))
getBuffer().removeLatestDataPage(recycledPage.docid, id, recycledPage.pageNumber, iTxnID);
getBuffer().addToFreeList(id, recycledPage.pageNumber, needLog ? iTxnID : null);
}
}