/*
* 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.hbt;
import org.apache.log4j.Logger;
import org.chaidb.db.KernelContext;
import org.chaidb.db.api.DuplicatedKeyIterator;
import org.chaidb.db.api.keys.NodeId;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.index.IDBIndex;
import org.chaidb.db.index.IDuplicatedKeyBTree;
import org.chaidb.db.index.Key;
import org.chaidb.db.index.btree.AbstractBTree;
import org.chaidb.db.index.btree.BTreeNode;
import org.chaidb.db.index.btree.BTreePage;
import org.chaidb.db.index.btree.bufmgr.PageNumber;
import org.chaidb.db.lock.LockManager;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
/**
* HyperBTree, support duplicated key and persistent features.
* It will be used with INT index, STRING index, DATE index, etc.
* User: arefool
* Date: Mar 13, 2003
* Time: 5:30:26 PM
*/
public class HyperBTree extends AbstractBTree implements IDuplicatedKeyBTree {
private static final Logger logger = Logger.getLogger(HyperBTree.class);
/**
* returns the index type of this index
*
* @return A short integer representing the index type
*/
public short getType() {
return IDBIndex.HYPER_BTREE;
}
protected AbstractBTree GenerateClonedBTree() throws ChaiDBException {
return new HyperBTree();
}
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);
Iterator it = null;
Object tmpId = null;
while (keys.hasMoreElements()) {
Key key = (Key) keys.nextElement();
it = lookup(key, kContext);
while (it.hasNext()) {
tmpId = it.next();
(newBTree).store(key, tmpId, IDBIndex.STORE_REPLACE, kContext);
}
}
} finally {
newBTree.release(kContext);
this.release(kContext);
}
}
/**
* Delete a value from HyperBTree.
* If the key has no-value after the value is deleted, this operation will delete the key-value Pairs.
*
* @param key
* @param value
* @param kContext
* @return
* @throws ChaiDBException
*/
public boolean delete(Key key, Object value, KernelContext kContext) throws ChaiDBException {
kContext.checkLock(getBTreeName());
if (value == null || !(value instanceof NodeId)) {
throw new ChaiDBException(ErrorCode.BTREE_DEBUG, "the value is invalid , maybe null or not node id type.");
}
this.getBTreeSpec().setModified(true);
NodeId nodeid = (NodeId) value;
NodeId tmpId = null;
DuplicatedKeyIterator dki = lookup(key, kContext);
try {
while (dki.hasNext()) {
tmpId = (NodeId) dki.next();
if (tmpId.equals(nodeid)) {
dki.remove();
return true;
}
} //end while
} finally {
dki.close();
}
return false;
}
public int delete(int docId, KernelContext kContext) throws ChaiDBException {
kContext.checkLock(getBTreeName());
this.getBTreeSpec().setModified(true);
try {
this.acquire(kContext, LockManager.LOCK_WRITE);
int ret = 0;
Enumeration enuKeys = keys(kContext);
while (enuKeys.hasMoreElements()) {
Key key = (Key) enuKeys.nextElement();
ret += delete(key, docId, kContext);
}
return ret;
} finally {
this.release(kContext);
}
}
/**
* Delete a key from HyperBTree.
*
* @param key
* @param docId
* @param kContext
* @return the counts of have deleted successfully
* @throws ChaiDBException
*/
public int delete(Key key, int docId, KernelContext kContext) throws ChaiDBException {
kContext.checkLock(getBTreeName());
this.getBTreeSpec().setModified(true);
int ret = 0;
DuplicatedKeyIterator dki = lookup(key, kContext);
try {
while (dki.hasNext()) {
NodeId nodeid = (NodeId) dki.next();
if (nodeid.getDocId() == docId) {
dki.remove();
ret++;
}
} //end while
} finally {
dki.close();
}
return ret;
}
/**
* Return an Iterator to get all corresponding value of a key.
*
* @param key
* @param kContext
* @return
* @throws ChaiDBException
*/
public DuplicatedKeyIterator lookup(Key key, KernelContext kContext) throws ChaiDBException {
kContext.checkLock(getBTreeName());
HyperBTreeIterator it = null;
it = HyperBTreeIterator.createIterator(this, kContext);
try {
PageNumber root = getTopRoot();
BTreePage rootPage = new BTreePage(id, root, btreeSpec, buffer);
if (rootPage.getPage() == null) {
return it;
}
BTreeNode n = rootPage.search(key, kContext);
if (n != null) {
NodePosition firstNode = new NodePosition(n.getPageNumber().getPageNumber(), n.getDataNodeOffset());
it.initIterator(firstNode);
}
// unfix root and leaf page.
buffer.releasePage(id, rootPage.pageNumber, false);
} finally {
doFinalJob();
}
return it;
}
/**
* returns the values which key is in the range of bottomKey and topKey
* it's used in UDI btree for range-base search in XPath and XQuery
*
* @param minKey smallest key value
* @param maxKey largest key value
* @param includeMinKey true means nodes with the minKey are included
* in the value list. Otherwise, they are excluded.
* @param includeMaxKey true means nodes with the maxKey are included
* in the value list. Otherwise, they are excluded.
* @return Vector nodeId list which key is in the range of minKey and
* maxKey,if not found, return null
* @throws ChaiDBException Exception while looking up the key
*/
public ArrayList rangeLookup(Key minKey, Key maxKey, boolean includeMinKey, boolean includeMaxKey, KernelContext kContext) throws ChaiDBException {
kContext.checkLock(getBTreeName());
ArrayList keys = rangeLookupKeys(minKey, maxKey, includeMinKey, includeMaxKey, kContext);
try {
ArrayList newList = new ArrayList(keys.size());
DuplicatedKeyIterator it = null;
Key key;
for (int i = 0; i < keys.size(); i++) {
key = (Key) keys.get(i);
it = lookup(key, kContext);
newList.add(it);
}
return newList;
} catch (Exception ee) {
logger.error(ee);
String details = "Converter failed in Hyper BTree to decode : " + ee.toString() + ".";
throw new ChaiDBException(ErrorCode.CONVERTER_DECODING_ERROR, details);
}
}
public ArrayList rangeLookupKeys(Key minKey, Key maxKey, boolean includeMinKey, boolean includeMaxKey, KernelContext kContext) throws ChaiDBException {
kContext.checkLock(getBTreeName());
ArrayList values = new ArrayList();
try {
PageNumber root = getTopRoot();
rangeLookupKeys(minKey, maxKey, includeMinKey, includeMaxKey, root, values, kContext);
} finally {
doFinalJob();
}
return values;
}
/**
* returns the values which key is in the range of bottomKey and topKey
* it's used in UDI btree for range-base search in XPath and XQuery
*
* @param minKey minKey smallest key value
* @param maxKey maxKey largest key value
* @return Vector nodeId list which key is in the range of minKey and
* maxKey,if not found, return null
* @throws ChaiDBException Exception while looking up the key
*/
public ArrayList rangeLookup(Key minKey, Key maxKey, KernelContext kContext) throws ChaiDBException {
kContext.checkLock(getBTreeName());
return rangeLookup(minKey, maxKey, true, true, kContext);
}
//here convert is not performed
protected void rangeLookupKeys(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.searchKeys(minKey, maxKey, includeMinKey, includeMaxKey, kContext, values);
// unfix root and leaf page.
getBuffer().releasePage(id, page.pageNumber, false);
}
}
/**
* returns enumeration of keys.
*
* @return Enumeration of keys
*/
public Enumeration keys(KernelContext kContext) {
try {
kContext.checkLock(getBTreeName());
return new HyperBTreeEnumerator(this, btreeSpec, buffer, kContext);
} catch (ChaiDBException e) {
logger.error(e);
return null;
}
}
}