package com.neocoretechs.bigsack.btree; import java.io.IOException; import java.util.ArrayList; import java.util.Random; import java.util.Stack; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import com.neocoretechs.bigsack.Props; import com.neocoretechs.bigsack.io.Optr; import com.neocoretechs.bigsack.io.ThreadPoolManager; import com.neocoretechs.bigsack.io.pooled.GlobalDBIO; import com.neocoretechs.bigsack.io.pooled.ObjectDBIO; /* * Copyright (c) 2003, NeoCoreTechs * All rights reserved. * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * Neither the name of NeoCoreTechs nor the names of its contributors may be * used to endorse or promote products derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ /** * Main bTree class. Manipulates retrieval stack of bTreeKeyPages and provides access * to seek/add/delete functions. * Important to note that the data is stored as arrays serialized out in key pages. Related to that * is the concept of element 0 of those arrays being 'this', hence the special treatment in CRUD. * Unlike a binary search tree, each node of a B-tree may have a variable number of keys and children. * The keys are stored in ascending order. Each node either is a leaf node or * it has some associated children that are the root nodes of subtrees. * The left child node of a node's element contains all nodes (elements) with keys less than or equal to the node element's key * but greater than the preceding node element's key. * If a node becomes full, a split operation is performed during the insert operation. * The split operation transforms a full node with 2*T-1 elements into two nodes with T-1 elements each * and moves the median key of the two nodes into its parent node. * The elements left of the median (middle) element of the splitted node remain in the original node. * The new node becomes the child node immediately to the right of the median element that was moved to the parent node. * * Example (T = 4): * 1. R = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * 2. Add key 8 * * 3. R = | 4 | * / \ * | 1 | 2 | 3 | | 5 | 6 | 7 | 8 | * * @author Groff Copyright (C) NeoCoreTechs 2015,2017 */ public final class BTreeMain { private static boolean DEBUG = false; // General debug, overrides other levels private static boolean DEBUGCURRENT = false; // alternate debug level to view current page assignment of BTreeKeyPage private static boolean DEBUGSEARCH = false; // traversal debug private static boolean TEST = false; // Do a table scan and key count at startup private static boolean ALERT = true; // Info level messages private static boolean OVERWRITE = false; // flag to determine whether value data is overwritten for a key or its ignored static int BOF = 1; static int EOF = 2; static int NOTFOUND = 3; static int ALREADYEXISTS = 4; static int STACKERROR = 5; static int TREEERROR = 6; static int MAXSTACK = 1024; private BTreeKeyPage root; //private long numKeys; // the 'current' items reflect the current status of the tree BTreeKeyPage currentPage; int currentIndex; int currentChild; @SuppressWarnings("rawtypes") private Comparable currentKey; private Object currentObject; private Stack<TraversalStackElement> stack = new Stack<TraversalStackElement>(); private CyclicBarrier nodeSplitSynch = new CyclicBarrier(3); private NodeSplitThread leftNodeSplitThread, rightNodeSplitThread; private ObjectDBIO sdbio; static int T = (BTreeKeyPage.MAXKEYS/2)+1; public BTreeMain(ObjectDBIO sdbio) throws IOException { this.sdbio = sdbio; currentPage = setRoot(BTreeKeyPage.getPageFromPool(sdbio, 0L)); if( DEBUG ) System.out.println("Root BTreeKeyPage: "+currentPage); currentIndex = 0; currentChild = 0; // Append the worker name to thread pool identifiers, if there, dont overwrite existing thread group ThreadPoolManager.init(new String[]{"NODESPLITWORKER"}, false); leftNodeSplitThread = new NodeSplitThread(nodeSplitSynch); rightNodeSplitThread = new NodeSplitThread(nodeSplitSynch); ThreadPoolManager.getInstance().spin(leftNodeSplitThread,"NODESPLITWORKER"); ThreadPoolManager.getInstance().spin(rightNodeSplitThread,"NODESPLITWORKER"); // Consistency check test, also needed to get number of keys // Performs full tree/table scan, tallys record count if( TEST ) { // Attempt to retrieve last good key count long numKeys = 0; long tim = System.currentTimeMillis(); //if( sdbio.getDBName().contains("MapDomainRange") || sdbio.getDBName().contains("MapRangeDomain")) //printBTree(getRoot()); numKeys = count(); System.out.println("Consistency check for "+sdbio.getDBName()+" returned "+numKeys+" keys in "+(System.currentTimeMillis()-tim)+" ms."); } if( ALERT ) System.out.println("Database "+sdbio.getDBName()+" ready."); } /** * Returns number of table scanned keys, sets numKeys field * @throws IOException */ public synchronized long count() throws IOException { rewind(); int i; long numKeys = 0; long tim = System.currentTimeMillis(); if( currentPage != null ) { ++numKeys; while ((i = gotoNextKey()) == 0) { if( DEBUG ) System.out.println("gotoNextKey returned: "+currentPage.keyArray[currentIndex]); ++numKeys; } } //if( DEBUG ) System.out.println("Count for "+sdbio.getDBName()+" returned "+numKeys+" keys in "+(System.currentTimeMillis()-tim)+" ms."); // deallocate outstanding blocks in all tablespaces sdbio.deallocOutstanding(); clearStack(); return numKeys; } public synchronized boolean isEmpty() { return (getRoot().numKeys == 0); } /** * currentPage and currentIndex set. * @param targetKey The Comparable key to seek * @return data Object if found. Null otherwise. * @exception IOException if read failure */ @SuppressWarnings("rawtypes") public synchronized Object seekObject(Comparable targetKey) throws IOException { TreeSearchResult tsr = search(targetKey); if (tsr.atKey) { currentPage = tsr.page; currentIndex = tsr.insertPoint; currentChild = tsr.insertPoint; setCurrent(); return getCurrentObject(); } else { return null; } } /** * Seek the key, if we dont find it leave the tree at it position closest greater than element. * If we do find it return true in atKey of result and leave at found key. * Calls search, which calls clearStack, repositionStack and setCurrent. * @param targetKey The Comparable key to seek * @return search result with key data * @exception IOException if read failure */ @SuppressWarnings("rawtypes") public synchronized TreeSearchResult seekKey(Comparable targetKey) throws IOException { TreeSearchResult tsr = search(targetKey); if( DEBUG || DEBUGSEARCH) System.out.println("SeekKey state is currentIndex:"+currentIndex+" targKey:"+targetKey+" "+currentPage); return tsr; } /** * Add to deep store, Set operation. * @param key * @return * @throws IOException */ public int add(Comparable key) throws IOException { return add(key, null); } /** * Add an object and/or key to the deep store. Traverse the BTree for the insertion point and insert. Map operation. * @param key * @param object * @return 0 for key absent, 1 for key exists * @throws IOException */ public int add(Comparable key, Object object) throws IOException { BTreeKeyPage rootNode = getRoot(); TreeSearchResult usr = update(rootNode, key, object); if (!usr.atKey) { BTreeKeyPage targetNode = usr.page; if (rootNode.numKeys == (2 * T - 1)) { splitNodeBalance(rootNode); // re position insertion point after split if( DEBUG ) System.out.println("BTreeMain.add calling reposition after splitRootNode for key:"+key+" node:"+rootNode); TreeSearchResult repos = reposition(rootNode, key); targetNode = repos.page; } insertIntoNode(targetNode, key, object); // Insert the key into the B-Tree with root rootNode. } return (usr.atKey ? 1 : 0); } /** * Traverse the tree and insert object for key if we find the key. * At each page descent, check the index and compare, if equal put data to array at the spot * and return true. If we are a leaf node and find no match return false; * If we are not leaf node and find no match, get child at key less than this. Find to where the * KEY is LESS THAN the contents of the key array. * @param key The key to search for * @param object the object data to update, if null, ignore data put and return true. If OVERWRITE flag false, also ignore * @return The index of the insertion point if < 0 else if >= 0 the found index point * @throws IOException */ private TreeSearchResult update(BTreeKeyPage node, Comparable key, Object object) throws IOException { int i = 0; BTreeKeyPage sourcePage = node; while (sourcePage != null) { i = 0; while (i < sourcePage.numKeys && key.compareTo(sourcePage.keyArray[i]) > 0) { i++; } if (i < sourcePage.numKeys && key.compareTo(sourcePage.keyArray[i]) == 0) { if( object != null && OVERWRITE) { sourcePage.deleteData(sdbio, i); sourcePage.putDataToArray(object,i); } return new TreeSearchResult(sourcePage, i, true); } if (sourcePage.mIsLeafNode) { if( DEBUG ) System.out.println("BTreeMain.update set to return index :"+i+" for "+sourcePage); return new TreeSearchResult(sourcePage, i, false); } else { BTreeKeyPage targetPage = sourcePage.getPage(sdbio, i);// get the page at the index of the given page if( targetPage == null ) break; sdbio.deallocOutstanding(node.pageId); sourcePage = targetPage; } } if( DEBUG ) System.out.println("BTreeMain.update set to return index :"+i+" on fallthrough for "+sourcePage); return new TreeSearchResult(sourcePage, i, false); } /** * Sets up the return BTreeKeyPage similar to 'reposition' but this public method initializes root node etc. * The purpose is to provide a detached locate method to do intermediate key checks before insert, then use * 'add' with the BTreeKeyPage in the TreeSearchResult returned from this method. * If the TreeSearchResult.insertPoint is > 0 then insertPoint - 1 points to the key that immediately * precedes the target key. * @param node * @param key * @return * @throws IOException */ public TreeSearchResult locate(Comparable key) throws IOException { BTreeKeyPage rootNode = getRoot(); return reposition(rootNode, key); } /** * Same as update without the actual updating. * Traverse the tree for the given key. * At each page descent, check the index and compare, if equal put data to array at the spot * and return true. If we are a leaf node and find no match return false; * If we are not leaf node and find no match, get child at key less than this. Find to where the * KEY is LESS THAN the contents of the key array. * @param key The key to search for * @param object the object data to update, if null, ignore data put and return true. If OVERWRITE flag false, also ignore * @return The index of the insertion point if < 0 else if >= 0 the found index point * @throws IOException */ TreeSearchResult reposition(BTreeKeyPage node, Comparable key) throws IOException { int i = 0; BTreeKeyPage sourcePage = node; while (sourcePage != null) { i = 0; while (i < sourcePage.numKeys && key.compareTo(sourcePage.keyArray[i]) > 0) { i++; } if (i < sourcePage.numKeys && key.compareTo(sourcePage.keyArray[i]) == 0) { if( DEBUG ) System.out.println("BTreeMain.reposition set to return index :"+i+" after locating key for "+sourcePage); return new TreeSearchResult(sourcePage, i, true); } // Its a leaf node and we fell through, return the index but not 'atKey' if (sourcePage.mIsLeafNode) { if( DEBUG ) System.out.println("BTreeMain.reposition set to return index :"+i+" for leaf "+sourcePage); return new TreeSearchResult(sourcePage, i, false); } else { BTreeKeyPage targetPage = sourcePage.getPage(sdbio, i);// get the page at the index of the given page if( targetPage == null ) break; sdbio.deallocOutstanding(node.pageId); sourcePage = targetPage; } } if( DEBUG ) System.out.println("BTreeMain.reposition set to return index :"+i+" for fallthrough "+sourcePage); return new TreeSearchResult(sourcePage, i, false); } /** * Split a node of the B-Tree into 3 nodes with the 2 children balanced * This method will only be called if root node is full, or if a leaf fills on insert; it leaves original root in place * The method forms 2 requests that are spun off to 2 node split threads. The logic in this method * synchronizes via a cyclicbarrier and monitor to manipulate the parent node properly after * the worker threads are done. One of the the threads will have inserted the key to one of the * new children upon completion. The request hold the position. * @param parentNode The node we are splitting, old root, becomes parent of both nodes at root * @throws IOException */ void splitNodeBalance(BTreeKeyPage parentNode) throws IOException { if( DEBUG ) System.out.println("BTreeMain.splitNodeBalance :"+parentNode); NodeSplitRequest lnsr = new NodeSplitRequest(sdbio, parentNode, NodeSplitRequest.NODETYPE.NODE_LEFT); NodeSplitRequest rnsr = new NodeSplitRequest(sdbio, parentNode, NodeSplitRequest.NODETYPE.NODE_RIGHT); try { leftNodeSplitThread.queueRequest(lnsr); rightNodeSplitThread.queueRequest(rnsr); // await the other nodes nodeSplitSynch.await(); // once we synch, we have 2 nodes, one each in the requests synchronized(parentNode) { int nullKeys = 0; int goodKeys = 0; // find the blanks in indexes, we only remove beginning blanks where keys were removed for(int j = 0; j < BTreeKeyPage.MAXKEYS; j++) { if(parentNode.keyArray[j] == null ) ++nullKeys; else break; } if( DEBUG ) { System.out.println("BTreeMain.splitNodeBalance nullKeys:"+nullKeys); } // find the number of good keys from the nulls forward for(int j = nullKeys; j < BTreeKeyPage.MAXKEYS; j++) { if(parentNode.keyArray[j] != null ) ++goodKeys; else break; } // set as non leaf so proper insertion and compaction occurs parentNode.mIsLeafNode = false; if( DEBUG ) { System.out.println("BTreeMain.splitNodeBalance goodKeys:"+goodKeys+" parent:"+parentNode); } if( nullKeys > 0 ) { for(int j = nullKeys; j < goodKeys+nullKeys; j++) { if( DEBUG ) { System.out.println("BTreeMain.splitNode moveKeyData:"+j+" nulls:"+nullKeys); } moveKeyData(parentNode, j, parentNode, j-nullKeys, true); if( DEBUG ) { System.out.println("BTreeMain.splitNodeBalance moveChildData:"+j+" nulls:"+nullKeys); } moveChildData(parentNode, j, parentNode, j-nullKeys, true); } if( DEBUG ) { System.out.println("BTreeMain.splitNodeBalance moveKeyData parent source:"+String.valueOf(nullKeys+goodKeys+1)+" to:"+goodKeys); } // get the node at position+1, the rightmost key pointer moveChildData(parentNode, nullKeys+goodKeys, parentNode, goodKeys, true); } parentNode.numKeys = goodKeys; parentNode.setUpdated(true); if( DEBUG ) { System.out.println("BTreeMain.spliNodeBalance moving to putPage:"+currentPage); } // // Write the three new pages back to deep store and log them along the way // Deallocate (unlatch) them after the write completes. // parentNode.putPage(sdbio); ((BTreeKeyPage)lnsr.getObjectReturn()).putPage(sdbio); ((BTreeKeyPage)rnsr.getObjectReturn()).putPage(sdbio); //sdbio.deallocOutstanding(parentNode.pageId); //sdbio.deallocOutstanding(((BTreeKeyPage)lnsr.getObjectReturn()).pageId); //sdbio.deallocOutstanding(((BTreeKeyPage)rnsr.getObjectReturn()).pageId); } } catch (InterruptedException | BrokenBarrierException e) { return; // executor shutdown } } /** * Insert into BTree node . The node is checked for leaf status, and if so, the key is inserted in to that key. * To ensure that the key is slot is available a split may be performed beforehand. * If the node is a non leaf we have to look for the key that is least and in addition has a right pointer since * the only way we add nodes is through a split * @param node * @param key * @param object * @throws IOException */ void insertIntoNode(BTreeKeyPage node, Comparable key, Object object) throws IOException { if( DEBUG ) System.out.println("BTreeMain.insertIntoNode:"+key+" "+object+" Node:"+node); int i = node.numKeys - 1; if (node.mIsLeafNode) { // If node is full, initiate split, as we do with a full root, we are going to split this // node by pulling the center key, and creating 2 new balanced children if (node.numKeys == BTreeKeyPage.MAXKEYS) { splitNodeBalance(node); insertIntoNode(reposition(node, key).page, key, object); return; } // If node is not a full node insert the new element into its proper place within node. while (i >= 0 && key.compareTo(node.keyArray[i]) < 0) { moveKeyData(node, i, node, i+1, false); i--; } i++; node.keyArray[i] = key; // leaf, no child data if( object != null ) { node.dataArray[i] = object; node.dataIdArray[i] = Optr.emptyPointer; node.dataUpdatedArray[i] = true; } node.numKeys++; node.setUpdated(true); node.putPage(sdbio); } else { // x is not a leaf node. We can't just stick k in because it doesn't have any children; // children are really only created when we split a node, so we don't get an unbalanced tree. // We find a child of x where we can (recursively) insert k. We read that child in from disk. // If that child is full, we split it and figure out which one k belongs in. // Then we recursively insert k into this child (which we know is non-full, because if it were, // we would have split it). // // Move back from the last key of node until we find the child pointer to the node // that is the root node of the subtree where the new element should be placed. // look for the one to the right of target where an entry has a child and newKey is greater (or equal) i = node.numKeys-1; int childPos = i + 1; // keep going backward checking while key is < keyArray or no child pointer // if not found the key was > all pointers while (i >= 0 && (node.pageIdArray[childPos] == -1 || key.compareTo(node.keyArray[i]) < 0)) { i--; childPos--; } // If our index value went to -1, // most likely a tree fault. We have recursively entered, searching for a leaf node, instead, // we wound up here where we got a far left index indicating our range went out of the min and // max that started the tree traversal. if( i == -1 ) { //++i; //++childPos; throw new IOException("BTreeMain.insertNonLeafNode bad index result"); } if( DEBUG ) System.out.println("BTreeMain.insertIntoNonLeafNode index:"+i+" child:"+childPos); BTreeKeyPage npage = node.getPage(sdbio,childPos); if( npage != null ) { // check to see if intended child node insertion point is full if (npage.numKeys == BTreeKeyPage.MAXKEYS) { // yes, split the node at insertion point, RECURSE! splitChildNode(node, i, childPos, npage); // repeat search to find child pointer to node that is root of insertion subtree TreeSearchResult res = reposition(node, key); npage = res.page; if( DEBUG ) System.out.println("BTreeMain.insertIntoNonLeafNode reposition result:"+res.insertPoint+" "+res.page); } } else throw new IOException("non leaf node target page indvalid at index "+i+" child pos:"+childPos+" inserting key "+key+" for node "+node); insertIntoNode(npage, key, object); } } /** * Split the node, node, of a B-Tree into two nodes that both contain T-1 elements and * move node's median key up to the parentNode. * This method will only be called if node is full; node is the i-th child of parentNode. * Note that this is the only time we ever create a child. Doing a split doesn't increase the * height of a tree, because we only add a sibling to existing keys at the same level. * Thus, the only time the height of the tree ever increases is when we split the root. * So we satisfy the part of the definition that says "each leaf must occur at the same depth." * if index is negative insert to the left of 0, else to the right of index * childIndex is the location in the parent where the new node pointer will go. * @param parentNode * @param keyIndex * @param childIndex * @param node * @throws IOException */ void splitChildNode(BTreeKeyPage parentNode, int keyIndex, int childIndex, BTreeKeyPage node) throws IOException { if( DEBUG ) System.out.println("BTreeMain.splitChildNode index:"+keyIndex+" childIndex:"+childIndex+"parent:"+parentNode+" target:"+node); BTreeKeyPage newNode = BTreeKeyPage.getPageFromPool(sdbio); newNode.mIsLeafNode = node.mIsLeafNode; newNode.numKeys = T - 1; for (int j = 0; j < T - 1; j++) { // Copy the last T-1 elements of node into newNode moveKeyData(node, j + T, newNode, j, false); } if (!newNode.mIsLeafNode) { for (int j = 0; j < T; j++) { // Copy the last T pointers of node into newNode moveChildData(node, j + T, newNode, j, false); //newNode.mChildNodes[j] = node.mChildNodes[j + T]; } for (int j = T; j <= node.numKeys; j++) { node.nullPageArray(j); //node.mChildNodes[j] = null; } } for (int j = T; j < node.numKeys; j++) { node.keyArray[j] = null; node.dataArray[j] = null; node.dataIdArray[j] = Optr.emptyPointer; } node.numKeys = T - 1; // // The logic above only dealt with hardwired rules on interchange // now we deal with the variable index manipulation involving our key and child pointers // in most cases the child will be key+1 for a right insert, but, it may be // 0, 0 for an insert at the beginning where the new node is to the left // Insert a (child) pointer to node newNode into the parentNode, moving other keys and pointers as necessary. // First, move the child key page data down 1 slot to make room for new page pointer for (int j = parentNode.numKeys; j >= childIndex; j--) { moveChildData(parentNode, j, parentNode, j+1, false); //parentNode.mChildNodes[j + 1] = parentNode.mChildNodes[j]; } // insert the new node as a child at the designated page index in the parent node, parentNode.pageArray[childIndex] = newNode; // also insert its Id from page pool parentNode.pageIdArray[childIndex] = newNode.pageId; // set it to updated for write parentNode.setUpdated(true); // clear the keyIndex slot in the parent node by moving everything down 1 in reverse for (int j = parentNode.numKeys - 1; j >= keyIndex; j--) { moveKeyData(parentNode, j, parentNode, j+1, false); //parentNode.mKeys[j + 1] = parentNode.mKeys[j]; //parentNode.mObjects[j + 1] = parentNode.mObjects[j]; } // insert the mid key (T-1, middle key number adjusted to index) data to the designated spot in the parent node // parentNode.keyArray[keyIndex] = node.keyArray[T - 1]; parentNode.dataArray[keyIndex] = node.dataArray[T - 1]; parentNode.dataIdArray[keyIndex] = node.dataIdArray[T - 1]; parentNode.dataUpdatedArray[keyIndex] = node.dataUpdatedArray[T - 1]; // zero the old node mid key, its moved node.keyArray[T - 1] = null; node.dataArray[T - 1] = null; node.dataIdArray[T - 1] = null; // bump the parent node key count parentNode.numKeys++; // they both were updated node.setUpdated(true); parentNode.setUpdated(true); // put to log and deep store node.putPage(sdbio); parentNode.putPage(sdbio); if( DEBUG ) System.out.println("BTreeMain.splitChildNode EXIT parent:"+parentNode+" index:"+keyIndex+" target:"+node+" new:"+newNode); } /** * Move the data from source and source index to target and targetIndex for the two pages. * Optionally null the source. * @param source * @param sourceIndex * @param target * @param targetIndex */ public static void moveKeyData(BTreeKeyPage source, int sourceIndex, BTreeKeyPage target, int targetIndex, boolean nullify) { target.keyArray[targetIndex] = source.keyArray[sourceIndex]; target.dataArray[targetIndex] = source.dataArray[sourceIndex]; target.dataIdArray[targetIndex] = source.dataIdArray[sourceIndex]; target.dataUpdatedArray[targetIndex] = source.dataUpdatedArray[sourceIndex]; if( nullify ) { source.keyArray[sourceIndex] = null; source.dataArray[sourceIndex] = null; source.dataIdArray[sourceIndex] = Optr.emptyPointer; source.dataUpdatedArray[sourceIndex] = false; // have not updated off-page data, just moved its pointer } } /** * Move the data from source and source index to target and targetIndex for the two pages. * Optionally null the source. * @param source * @param sourceIndex * @param target * @param targetIndex */ public static void moveChildData(BTreeKeyPage source, int sourceIndex, BTreeKeyPage target, int targetIndex, boolean nullify) { target.pageArray[targetIndex] = source.pageArray[sourceIndex]; target.pageIdArray[targetIndex] = source.pageIdArray[sourceIndex]; if( nullify ) { source.nullPageArray(sourceIndex); } } /** * Remove key/data object. * @param newKey The key to delete * @return 0 if ok, <>0 if error * @exception IOException if seek or write failure */ @SuppressWarnings("rawtypes") public synchronized int delete(Comparable newKey) throws IOException { BTreeKeyPage tpage; int tindex; BTreeKeyPage leftPage; BTreeKeyPage rightPage; // Is the key there? if(!(search(newKey)).atKey) return (NOTFOUND); if( DEBUG ) System.out.println("--ENTERING DELETE LOOP FOR "+newKey+" with currentPage "+currentPage); while (true) { // If either left or right pointer // is null, we're at a leaf or a // delete is percolating up the tree // Collapse the page at the key // location if ((currentPage.getPage(getIO(), currentIndex) == null) || (currentPage.getPage(getIO(), currentIndex + 1) == null)) { if( Props.DEBUG ) System.out.println("Delete loop No left/right pointer on "+currentPage); // Save non-null pointer if (currentPage.pageArray[currentIndex] == null) tpage = currentPage.getPage(getIO(), currentIndex + 1); else tpage = currentPage.getPage(getIO(), currentIndex); if( Props.DEBUG ) System.out.println("Delete selected non-null page is "+tpage); // At leaf - delete the key/data currentPage.deleteData(getIO(), currentIndex); currentPage.delete(currentIndex); if( Props.DEBUG ) System.out.println("Just deleted "+currentPage+" @ "+currentIndex); // Rewrite non-null pointer currentPage.putPageToArray(tpage, currentIndex); // If we've deleted the last key from the page, eliminate the // page and pop up a level. Null the page pointer // at the index we've popped to. // If we can't pop, all the keys have been eliminated (or, they should have). // Null the root, clear the keycount if (currentPage.numKeys == 0) { // Following guards against leaks if( DEBUG ) System.out.println("Delete found numKeys 0"); currentPage.nullPageArray(0); if (pop()) { // Null pointer to node // just deleted if( DEBUG ) System.out.println("Delete popped "+currentPage+" nulling and decrementing key count"); currentPage.nullPageArray(currentIndex); //--numKeys; //if( DEBUG ) System.out.println("Key count now "+numKeys+" returning"); // Perform re-seek to re-establish location //search(newKey); return (0); } if( DEBUG ) System.out.println("Cant pop, clear root"); // Can't pop -- clear the root // if root had pointer make new root if (tpage != null) { if( DEBUG ) System.out.println("Delete tpage not null, setting page Id/current page root "+tpage); tpage.pageId = 0L; setRoot(tpage); currentPage = tpage; } else { // no more keys if( DEBUG ) System.out.println("Delete No more keys, setting new root page"); setRoot(new BTreeKeyPage(0L)); getRoot().setUpdated(true); currentPage = null; //numKeys = 0; } //atKey = false; if( DEBUG ) System.out.println("Delete returning"); return (0); } // If we haven't deleted the last key, see if we have few enough // keys on a sibling node to coalesce the two. If the // keycount is even, look at the sibling to the left first; // if the keycount is odd, look at the sibling to the right first. if( DEBUG ) System.out.println("Have not deleted last key"); if (stack.size() == 0) { // At root - no siblings if( DEBUG ) System.out.println("Delete @ root w/no siblings"); //--numKeys; //search(newKey); //if( DEBUG ) System.out.println("Delete returning after numKeys set to "+numKeys); return (0); } // Get parent page and index if( DEBUG ) System.out.println("Delete get parent page and index"); tpage = (stack.get(stack.size() - 1)).keyPage; tindex =(stack.get(stack.size() - 1)).index; if( DEBUG ) System.out.println("Delete tpage now "+tpage+" and index now "+tindex+" with stack depth "+stack.size()); // Get sibling pages if( DEBUG ) System.out.println("Delete Getting sibling pages"); if (tindex > 0) { leftPage = tpage.getPage(getIO(), tindex - 1); if( DEBUG ) System.out.println("Delete tindex > 0 @ "+tindex+" left page "+leftPage); } else { leftPage = null; if( DEBUG ) System.out.println("Delete tindex not > 0 @ "+tindex+" left page "+leftPage); } if (tindex < tpage.numKeys) { rightPage = tpage.getPage(getIO(), tindex + 1); } else { rightPage = null; } if( DEBUG ) System.out.println("Delete tindex "+tindex+" tpage.numKeys "+tpage.numKeys+" right page "+rightPage); // Decide which sibling if( DEBUG ) System.out.println("Delete find sibling from "+leftPage+" -- " + rightPage); //if (numKeys % 2 == 0) if( new Random().nextInt(2) == 0 ) if (leftPage == null) leftPage = currentPage; else rightPage = currentPage; else if (rightPage == null) rightPage = currentPage; else leftPage = currentPage; if( DEBUG ) System.out.println("Delete found sibling from "+leftPage+" -- " + rightPage); // assertion check if (leftPage == null || rightPage == null) { if( DEBUG ) System.out.println("ASSERTION CHECK FAILED, left/right page null in delete"); return (TREEERROR); } // Are the siblings small enough to coalesce // address coalesce later //if (leftPage.numKeys + rightPage.numKeys + 1 > BTreeKeyPage.MAXKEYS) { // Coalescing not possible, exit //--numKeys; //search(newKey); //if( DEBUG ) System.out.println("Cant coalesce, returning with keys="+numKeys); return (0); /* } else { // Coalescing is possible. Grab the parent key, build a new // node. Discard the old node. // (If sibling is left page, then the new page is left page, // plus parent key, plus original page. New page is old left // page. If sibling is right page, then the new page // is the original page, the parent key, plus the right // page. // Once the new page is created, delete the key on the // parent page. Then cycle back up and delete the parent key.) coalesce(tpage, tindex, leftPage, rightPage); if( Props.DEBUG ) System.out.println("Coalesced "+tpage+" -- "+tindex+" -- "+leftPage+" -- "+rightPage); // Null right page of parent tpage.nullPageArray(tindex + 1); // Pop up and delete parent pop(); if( Props.DEBUG ) System.out.println("Delete Popped, now continuing loop"); continue; } */ } else { // Not at a leaf. Get a successor or predecessor key. // Copy the predecessor/successor key into the deleted key's slot, then "move // down" to the leaf and do a delete there. // Note that doing the delete could cause a "percolation" up the tree. // Save current page and and index if( Props.DEBUG ) System.out.println("Delete Not a leaf, find next key"); tpage = currentPage; tindex = currentIndex; if (currentPage.getPage(getIO(), currentIndex) != null) { // Get predecessor if possible if( DEBUG ) System.out.println("Delete Seeking right tree"); seekRightTree(); } else { // Get successor if (currentPage.getPage(getIO(), currentIndex + 1) == null) { if( DEBUG ) System.out.println("Delete cant get successor, returning"); return (TREEERROR); } currentIndex++; seekLeftTree(); if( DEBUG ) System.out.println("Delete cant seek left tree, returning"); return (STACKERROR); } // Replace key/data with successor/predecessor tpage.putKeyToArray(currentPage.keyArray[currentIndex], tindex); tpage.putDataToArray( currentPage.getDataFromArray(getIO(), currentIndex), tindex); if( DEBUG ) System.out.println("Delete re-entring loop to delete key on leaf of tpage "+tpage); // Reenter loop to delete key on leaf } } } /** * Coalesce a node * Combine the left page, the parent key (at offset index) and the right * page contents. * The contents of the right page are nulled. Note that it's the job of the * caller to delete the parent. * @param parent The parent page * @param index The index on the parent page to move to end of left key * @param left The left page * @param right The right page * @exception IOException If seek/writes fail */ private void coalesce ( BTreeKeyPage parent, int index, BTreeKeyPage left, BTreeKeyPage right) throws IOException { int i, j; // Append the parent key to the end of the left key page left.putKeyToArray(parent.keyArray[index], left.numKeys); left.putDataToArray(parent.getDataFromArray(getIO(), index), left.numKeys); // Append the contents of the right // page onto the left key page j = left.numKeys + 1; for (i = 0; i < right.numKeys; i++) { left.putPageToArray(right.getPage(getIO(), i), j); left.putDataToArray(right.getDataFromArray(getIO(), i), j); left.keyArray[j] = right.keyArray[i]; j++; } left.putPageToArray( right.getPage(getIO(), right.numKeys), left.numKeys + right.numKeys + 1); left.numKeys += right.numKeys + 1; // Null the right page (no leaks) for (i = 0; i < right.numKeys; i++) { right.nullPageArray(i); right.keyArray[i] = null; right.putDataToArray(null, i); } right.nullPageArray(right.numKeys); } /** * Rewind current position to beginning of tree. Sets up stack with pages and indexes * such that traversal can take place. Remember to clear stack after these operations. * @exception IOException If read fails */ public synchronized void rewind() throws IOException { currentPage = getRoot(); currentIndex = 0; currentChild = 0; clearStack(); seekLeftTree(); } /** * Set current position to end of tree.Sets up stack with pages and indexes * such that traversal can take place. Remember to clear stack after these operations. * @exception IOException If read fails */ public synchronized void toEnd() throws IOException { currentPage = getRoot(); currentIndex = currentPage.numKeys-1; currentChild = currentPage.numKeys; clearStack(); seekRightTree(); } /** * Same as reposition but we populate the stack * Traverse the tree for the given key. * At each page descent, check the index and compare, if equal put data to array at the spot * and return true. If we are a leaf node and find no match return false; * If we are not leaf node and find no match, get child at key less than this. Find to where the * KEY is LESS THAN the contents of the key array. * @param key The key to search for * @param object the object data to update, if null, ignore data put and return true. If OVERWRITE flag false, also ignore * @return The index of the insertion point if < 0 else if >= 0 the found index point * @throws IOException */ private TreeSearchResult repositionStack(BTreeKeyPage node, Comparable key) throws IOException { int i = 0; BTreeKeyPage sourcePage = node; if( DEBUG || DEBUGSEARCH) { System.out.println("BTreeMain.repositionStack key:"+key+" node:"+node); } while (sourcePage != null) { i = 0; //while (i < sourcePage.numKeys && key.compareTo(sourcePage.keyArray[i]) > 0) { while (i < sourcePage.numKeys && sourcePage.keyArray[i].compareTo(key) < 0) { i++; } //if (i < sourcePage.numKeys && key.compareTo(sourcePage.keyArray[i]) == 0) { if (i < sourcePage.numKeys && sourcePage.keyArray[i].compareTo(key) == 0) { if( DEBUG || DEBUGSEARCH ) System.out.println("BTreeMain.repositionStack set to return index :"+i+" after locating key for "+sourcePage); return new TreeSearchResult(sourcePage, i, true); } if (sourcePage.mIsLeafNode) { if( DEBUG || DEBUGSEARCH) System.out.println("BTreeMain.repositionStack set to return index :"+i+" for leaf "+sourcePage); if( i >= sourcePage.numKeys || sourcePage.keyArray[i].compareTo(key) < 0 ) { currentPage = sourcePage; popUntilValid(true); return new TreeSearchResult(currentPage, currentIndex, true); } return new TreeSearchResult(sourcePage, i, true); } else { BTreeKeyPage targetPage = sourcePage.getPage(sdbio, i);// get the page at the index of the given page if( DEBUG || DEBUGSEARCH) { System.out.println("BTreeMain.repositionStack traverse next page for key:"+key+" page:"+targetPage); } if( targetPage == null ) break; TraversalStackElement tse = new TraversalStackElement(sourcePage, i, i); stack.push(tse); sdbio.deallocOutstanding(sourcePage.pageId); sourcePage = targetPage; } } if( DEBUG || DEBUGSEARCH) System.out.println("BTreeMain.repositionStack set to return index :"+i+" for fallthrough "+sourcePage); return new TreeSearchResult(sourcePage, i, false); } /** * Seek to location of next key in tree. * Attempt to advance the child index at the current node. If it advances beyond numKeys, a pop * is necessary to get us to the previous level. We repeat the process a that level, advancing index * and checking, and again repeating pop. * If we get to a currentIndex advanced to the proper index, we know we are not at a leaf since we * popped the node as a parent, so we know there is a subtree somewhere, so descend the subtree * of the first key to the left that has a child until we reach a terminal 'leaf' node. * We are finished when we are at the root and can no longer traverse right. this is because we popped all the way up, * and there are no more subtrees to traverse. * Note that we dont deal with keys at all here, just child pointers. * @return 0 if ok, != 0 if error * @exception IOException If read fails */ public synchronized int gotoNextKey() throws IOException { if( DEBUG || DEBUGSEARCH ) { System.out.println("BTreeMain.gotoNextKey "/*page:"+currentPage+*/+" index "+currentIndex); } // If we are at a key, then advance the index if (currentChild < currentPage.numKeys) { currentChild++; // If its a leaf and we just bumped we can return if its in range if( currentPage.mIsLeafNode) { if(currentChild != currentPage.numKeys) { currentIndex = currentChild; setCurrent(); return 0; } } // Otherwise, we have to decide the return value, descend subtree // Not a leaf node with valid index, so we have to either return the index or find a subtree to descend // should be positioned at key to which we descend left subtree // BTreeKeyPage tPage = currentPage.getPage(sdbio, currentChild); if (tPage == null) { // Pointer is null, we can return this index as we cant descend subtree if( DEBUG || DEBUGSEARCH) { System.out.println("BTreeMain.gotoNextKey index "+currentChild+" pointer is null"); } // // No child pointer. If the current pointer is at end of key range we have to pop // otherwise we can return this key if(currentChild != currentPage.numKeys) { currentIndex = currentChild; setCurrent(); return 0; } } else { // There is a child pointer // seek to "leftmost" key in currentPage subtree,the one we just tested, using currentChild seekLeftTree(); setCurrent(); return (0); } } // If we are here we are at the end of the key range // cant move right, are we at root? If so, we must end // we have reached the end of keys // we must pop // pop to parent and fall through when we hit root or are not out of keys // go up 1 and right to the next key, if no right continue to pop until we have a right key // if we hit the root, protocol right. in any case we have to reassert // if there is a subtree follow it, else return the key return popUntilValid(true); } /** * Go to location of previous key in tree * @return 0 if ok, <>0 if error * @exception IOException If read fails */ public synchronized int gotoPrevKey() throws IOException { if( DEBUG || DEBUGSEARCH ) { System.out.println("BTreeMain.gotoNextKey "/*page:"+currentPage+*/+" index "+currentIndex); } // If we are at a key, then reduce the index if (currentChild > 0) { --currentChild; // If its a leaf and we just bumped we can return if its in range if( currentPage.mIsLeafNode) { currentIndex = currentChild; setCurrent(); return 0; } // Otherwise, we have to decide the return value, descend subtree // Not a leaf node with valid index, so we have to either return the index or find a subtree to descend // should be positioned at key to which we descend left subtree // BTreeKeyPage tPage = currentPage.getPage(sdbio, currentChild); if (tPage == null) { // Pointer is null, we can return this index as we cant descend subtree if( DEBUG || DEBUGSEARCH) { System.out.println("BTreeMain.gotoPrevKey index "+currentChild+" pointer is null, returning"); } // // No child pointer. If the current pointer is at end of key range we have to pop // otherwise we can return this key if(currentChild != 0) { currentIndex = currentChild; setCurrent(); return 0; } } else { // There is a child pointer // seek to "rightmost" key in currentPage subtree,the one we just tested, using currentChild seekRightTree(); setCurrent(); return (0); } } return popUntilValid(false); } /** * Pop the stack until we reach a valid spot in traversal. * The currentPage, currentChild are used, setCurrent() is called on exit; * @return EOF if we reach root and cannot traverse right * @throws IOException */ public synchronized int popUntilValid(boolean next) throws IOException { // If we are here we are at the end of the key range // cant move left, are we at root? If so, we must end if( currentPage.pageId == 0L) return EOF; // we have reached the end of keys // we must pop // pop to parent and fall through when we hit root or are not out of keys // go up 1 and right to the next key, if no right continue to pop until we have a right key // if we hit the root, protocol right. in any case we have to reassert // if there is a subtree follow it, else return the key //Comparable key = currentPage.keyArray[currentIndex]; while( pop() ) { if(DEBUG || DEBUGSEARCH) { System.out.println("BTreeMain.popUntilValid POP index:"+currentIndex+" child:"+currentChild+" page:"+currentPage); } // we know its not a leaf, we popped to it // If we pop, and we are at the end of key range, and our key is not valid, pop if( next ) { if(currentIndex >= currentPage.numKeys /*&& currentPage.keyArray[currentIndex].compareTo(key) < 0*/ ) continue; // on a non-leaf, at an index heading right (ascending), break from pop } else { if(currentIndex <= 0 /* && currentPage.keyArray[currentIndex].compareTo(key) > 0*/ ) continue; // on a non-leaf, at an index heading left (descending), break from pop } // no pointer move, are we at root? //if( currentPage.pageId == 0L) { // setCurrent(); // return EOF; //} // break; } // should be at position where we return key from which we previously descended // pop sets current indexes setCurrent(); return 0; } /** * Set the current object and key based on value of currentPage. * and currentIndex */ public synchronized void setCurrent() throws IOException { if( DEBUG || DEBUGCURRENT) System.out.println("BTreeMain.setCurrent page:"+currentPage+" index:"+currentIndex); setCurrentKey(currentPage.keyArray[currentIndex]); setCurrentObject(currentPage.getDataFromArray(sdbio, currentIndex)); } /** * Set the current object and key based on value of currentPage. * and currentIndex */ public synchronized Comparable setCurrentKey() throws IOException { if( DEBUG || DEBUGCURRENT) System.out.println("BTreeMain.setCurrentKey page:"+currentPage+" index:"+currentIndex); setCurrentKey(currentPage.keyArray[currentIndex]); return currentPage.keyArray[currentIndex]; } /** * Utilize reposition to locate key. Set currentPage, currentIndex, currentKey, and currentChild. * deallocOutstanding is called before exit. * @param targetKey The key to position to in BTree * @return TreeSearchResult containing page, insertion index, atKey = true for key found * @throws IOException */ public synchronized TreeSearchResult search(Comparable targetKey) throws IOException { currentPage = getRoot(); currentIndex = 0; currentChild = 0; TreeSearchResult tsr = null; clearStack(); if (currentPage != null) { tsr = repositionStack(currentPage, targetKey); setCurrent(tsr); } if( DEBUG || DEBUGSEARCH) { System.out.println("BTreeMain.search returning with currentPage:"+currentPage+" index:"+currentIndex+" child:"+currentChild); } sdbio.deallocOutstanding(currentPage.pageId); return tsr; } /** * Seeks to leftmost key in current subtree. Takes the currentChild and currentIndex from currentPage and uses the * child at currentChild to descend the subtree and gravitate left. */ private void seekLeftTree() throws IOException { if( DEBUG || DEBUGSEARCH ) { System.out.println("BTreeMain.seekLeftTree page:"+currentPage+" index:"+currentIndex+" child:"+currentChild); } BTreeKeyPage tPage = currentPage.getPage(sdbio, currentChild); while (tPage != null) { // Push the 'old' value of currentPage, currentIndex etc as a TraversalStackElement since we are // intent on descending. After push and verification that tPage is not null, set page to tPage. push(); if( DEBUG || DEBUGSEARCH ) System.out.println("BTreeMain.seekLeftTree PUSH using "+currentPage+" index:"+currentIndex+" child:"+currentChild+" new entry:"+tPage); currentIndex = 0; currentChild = 0; currentPage = tPage; setCurrentKey(currentPage.keyArray[currentIndex]); tPage = currentPage.getPage(sdbio, currentChild); } } /** * Seeks to rightmost key in current subtree */ private void seekRightTree() throws IOException { BTreeKeyPage tPage = currentPage.getPage(sdbio, currentChild); while (tPage != null) { push(); if( DEBUG || DEBUGSEARCH) System.out.println("BTreeMain.seekRightTree PUSH using "+currentPage+" index:"+currentIndex+" child:"+currentChild+" new entry:"+tPage); currentIndex = currentPage.numKeys - 1; currentChild = currentPage.numKeys; currentPage = tPage; setCurrentKey(currentPage.keyArray[currentIndex]); tPage = currentPage.getPage(sdbio, currentChild); } } /** * Internal routine to push stack. Pushes a TraversalStackElement * set keyPageStack[stackDepth] to currentPage * set indexStack[stackDepth] to currentIndex * Sets stackDepth up by 1 * @param * @return true if stackDepth not at MAXSTACK, false otherwise */ private void push() { //if (stackDepth == MAXSTACK) // throw new RuntimeException("Maximum retrieval stack depth exceeded at "+stackDepth); if( currentPage == null ) throw new RuntimeException("BTreeMain.push cant push a null page to stack"); //keyPageStack[stackDepth] = currentPage; //childStack[stackDepth] = currentChild; //indexStack[stackDepth++] = currentIndex; stack.push(new TraversalStackElement(currentPage, currentIndex, currentChild)); if( DEBUG ) { System.out.print("BTreeMain.Push:"); printStack(); } } /** * Internal routine to pop stack. * sets stackDepth - 1, * currentPage to keyPageStack[stackDepth] and * currentIndex to indexStack[stackDepth] * @return false if stackDepth reaches 0, true otherwise * */ private boolean pop() { //if (stackDepth == 0) if( stack.isEmpty() ) return (false); //stackDepth--; //currentPage = keyPageStack[stackDepth]; //currentIndex = indexStack[stackDepth]; //currentChild = childStack[stackDepth]; TraversalStackElement tse = stack.pop(); currentPage = tse.keyPage; currentIndex = tse.index; currentChild = tse.child; if( DEBUG ) { System.out.print("BTreeMain.Pop:"); printStack(); } return (true); } private void printStack() { System.out.println("Stack Depth:"+stack.size()); //for(int i = 0; i < stack.size(); i++) { // TraversalStackElement tse = stack.get(i); // System.out.println("index:"+i+" "+GlobalDBIO.valueOf(tse.keyPage.pageId)+" "+tse.index+" "+tse.child); //} } /** * Internal routine to clear references on stack. Just does stack.clear */ public void clearStack() { //for (int i = 0; i < MAXSTACK; i++) // keyPageStack[i] = null; //stackDepth = 0; stack.clear(); } /** * Set the currentPage, currentIndex, currentChild to TreeSearchResult values. * then calls setCurrent() to populate current key and Object data values. * @param tsr * @throws IOException */ public void setCurrent(TreeSearchResult tsr) throws IOException { currentPage = tsr.page; currentIndex = tsr.insertPoint; currentChild = tsr.insertPoint; setCurrent(); // sets up currenKey and currentObject; } public Object getCurrentObject() { return currentObject; } public void setCurrentObject(Object currentObject) { this.currentObject = currentObject; } @SuppressWarnings("rawtypes") public Comparable getCurrentKey() { return currentKey; } @SuppressWarnings("rawtypes") public void setCurrentKey(Comparable currentKey) { this.currentKey = currentKey; } public ObjectDBIO getIO() { return sdbio; } public void setIO(ObjectDBIO sdbio) { this.sdbio = sdbio; } public BTreeKeyPage getRoot() { return root; } /** * Set the root node value, return it as fluid pattern * @param root * @return */ public BTreeKeyPage setRoot(BTreeKeyPage root) { this.root = root; return root; } // Inorder walk over the tree. void printBTree(BTreeKeyPage node) throws IOException { if (node != null) { if (node.mIsLeafNode) { System.out.print("Leaf node:"); for (int i = 0; i < node.numKeys; i++) { System.out.print("INDEX:"+i+" node:"+node.keyArray[i] + ", "); } System.out.println("\n"); } else { System.out.print("NonLeaf node:"); int i; for (i = 0; i < node.numKeys; i++) { BTreeKeyPage btk = node.getPage(sdbio,i); printBTree(btk); System.out.print("INDEX:"+i+" node:"+ node.keyArray[i] + ", "); } // get last far right node printBTree(node.getPage(sdbio,i)); System.out.println("\n"); } } } void validate() throws Exception { ArrayList<Comparable> array = getKeys(getRoot()); for (int i = 0; i < array.size() - 1; i++) { if (array.get(i).compareTo(array.get(i + 1)) >= 0) { throw new Exception("B-Tree invalid: " + array.get(i) + " greater than " + array.get(i + 1)); } } } // Inorder walk over the tree. ArrayList<Comparable> getKeys(BTreeKeyPage node) throws IOException { ArrayList<Comparable> array = new ArrayList<Comparable>(); if (node != null) { if (node.mIsLeafNode) { for (int i = 0; i < node.numKeys; i++) { array.add(node.keyArray[i]); } } else { int i; for (i = 0; i < node.numKeys; i++) { array.addAll(getKeys(node.getPage(sdbio,i))); array.add(node.keyArray[i]); } array.addAll(getKeys(node.getPage(sdbio,i))); } } return array; } }