/trunk/flatpress/fp-includes/core/core.bplustree.class.php
PHP | 2668 lines | 1508 code | 323 blank | 837 comment | 259 complexity | ecef3ea88632451f0bf73e94a02fc68f MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, MIT
Large files files are truncated, but you can click here to view the full file
- <?php
- /*
- *
- * PHP B+tree library
- * ==============
- *
- * (c)2008 E.Vacchi <real_nowhereman at users.sourceforge.net>
- * Based on the original work by Aaron Watters (bplustree.py)
- *
- * Classes:
- *
- * - BPlustTree
- * Mapping keys, integers
- *
- * - caching_BPT
- * Subclass of BPlusTree, caching
- * key,value pairs
- * read-only: create using BPlusTree, read
- * using caching_BPT
- *
- *
- * Usage
- * =====
- *
- * # creation
- * $f = fopen('myfile', 'w+');
- * $o = new BPlusTree($f, $seek_start, $node_size, $keylen);
- * $o->startup();
- *
- * $o->setitem('my-key', 123);
- * $o->setitem('my-key-2', 456);
- *
- * $o->delitem('my-key-2');
- *
- *
- * # read-only
- * $f = open('myfile', 'r');
- * $o = caching_BPT($f);
- *
- * $o->open();
- * echo $o->getitem('my-key');
- *
- * Methods:
- *
- * void setitem($key, $val)
- * int getitem($key)
- * void delitem($key)
- * bool has_key($key)
- * object walker() : returns an iterator
- *
- *
- * Walking (iterate)
- * =================
- *
- * $walker = $tree->walker(
- * string $key_lower, bool $include_lower,
- * string $key_upper, bool $include_upper);
- * while ($walker->valid) {
- * echo $walker->current_key(),
- * $walker->current_value();
- * $walker->next();
- * }
- * $walker->first(); #resets internal pointer
- *
- *
- * Internal FIFO
- * =============
- *
- * $tree->enable_fifo();
- * do_some_processing();
- * $tree->disable_fifo();
- *
- * This should make the processing (slightly) faster
- * when key accesses are localized. Don't use it in walking
- * (no need for it) or for single write operations.
- *
- * enable_fifo() takes an optional parameter $length
- * (defaults to 33) remember that larger fifos will consume
- * more memory.
- *
- *
- * Other options
- * =============
- *
- * This PHP implementation slightly differs from the Python
- * one, because you can choose a constant (affecting all of the
- * instanced objects) defining the order relation of the keys.
- *
- * Usually you would like your keys to be sorted ascending
- * (SORT_ASC, the default), but sometimes you might want
- * to create a btree where keys are kept in reverse order.
- *
- * In this case, you just have to define somewhere in your code
- *
- * define('BPT_SORT', SORT_DESC);
- *
- * and the include the library.
- *
- * This somehow weird approach should however make the computation
- * slightly faster: the setting is evaluated only once,
- * when including the library; the compare routine is then defined
- * accordingly, and never checked again; otherwise the compare
- * routine would have to check the setting each time it's called.
- *
- */
- function d($s) {
- return; // disable debug output
- if (is_array($s)) { $s = '{ '.implode(", ", $s) . ' }'; }
- $x = debug_backtrace();
- $f = @$x[1]['function'];
- $l = $x[0]['line'];
- echo "[{$f}:{$l}]\t", $s, "\n";
- #echo "---[{$x[2]['function']}:{$x[2]['line']}]\n";
- }
- error_reporting(E_ALL);
- if (!defined('BPT_SORT')) {
- /**
- * @const int type of sorting, defaults to SORT_ASC (ascending);
- * SORT_DESC (descending) is also possibile
- */
- define('BPT_SORT', SORT_ASC);
- }
- /**
- * @const int no room error
- */
- define('NOROOMERROR', -100);
- /**
- * @const int null node
- */
- define('BPT_NULL', 0);
- /**
- * @const int null seek position
- */
- define('BPT_NULLSEEK', 0);
- /**
- * @const string magic string for bplustree
- */
- define('BPT_VERSION_MAGIC', 'BPT01');
- #define('BPT_INT_SIZE', 4);
- /**
- * @const int root bit flag
- */
- define('BPT_FLAG_ROOT_BIT', 1);
- /**
- * @const int interior node flag
- */
- define('BPT_FLAG_INTERIOR', 2);
- /**
- * @const int root flag, shorthand for BPT_FLAG_ROOT_BIT | BPT_FLAG_INTERIOR
- */
- define('BPT_FLAG_ROOT', BPT_FLAG_ROOT_BIT | BPT_FLAG_INTERIOR);
- /**
- * @const int free node flag
- */
- define('BPT_FLAG_FREE', 4);
- /**
- * @const int leaf flag
- */
- define('BPT_FLAG_LEAF', 8);
- /**
- * @const int leaf+root flag, shorthand for BPT_FLAG_ROOT_BIT | BPT_FLAG_LEAF
- */
- define('BPT_FLAG_LEAFANDROOT', BPT_FLAG_ROOT_BIT | BPT_FLAG_LEAF);
- /**
- * Abstraction for array of pairs
- * (meaning with "pair" an array containing two elements)
- * works only read-only
- *
- */
- class pairs {
- /**
- * @var array of the first elements of each pair (private)
- *
- */
- var $a;
- /**
- * @var array of the second elements of each pair (private)
- *
- */
- var $b;
- /**
- * @var integer current size of the array of pairs
- *
- */
- var $count;
-
- /*
- * Constructor
- * @param array $a array of the first elements of each pair
- * @parma array $b array of the second elements of each pair
- *
- */
- function pairs($a, $b) {
- if (($v=count($a))!=count($b))
- trigger_error("Size of params must match", E_USER_ERROR);
- $this->a=$a; $this->b=$b;
- $this->count = $v;
- }
- /*
- * returns a slice of the current Couplets object as a new Couplets object
- * (works like array_slice())
- * @param integer $offset offset from the start of the array (count starting from zero)
- * @param integer|null $count number of elements to return starting from $offset
- *
- * @returns pairs object
- *
- */
- function &slice($offset, $count=null) {
- if (is_null($count)) $count = $this->count;
- $a = new pairs(
- array_slice($this->a, $offset, $count),
- array_slice($this->b, $offset, $count)
- );
- return $a;
- }
- /**
- * inserts a pair ($a, $b) at the offset $offset eventually
- * pushing other elements to the right
- * @param int $offset offset at which insert
- * @param mixed $a first element in the pair
- * @param mixed $b second element in the pair
- */
- function insert($offset, $a, $b) {
- array_splice($this->a, $offset, 0, $a);
- array_splice($this->b, $offset, 0, $b);
- $this->count++;
- }
- /**
- * inserts a pair ($a, $b) in the sub-array of pairs
- * between $lo and $hi, assuming the array is ordered,
- * comparing only the first elements of each pair
- * (assumes there aren't duplicates)
- * uses {@link BPT_keycmp} for comparing
- *
- * @param mixed $a first element of the pair
- * @param mixed $b second element of the pair
- * @param int $lo starting offset of the sub-array
- * @param int|nul $hi ending offset of the sub-array
- */
- function insort($a, $b, $lo=0, $hi=null) {
- if (is_null($hi)) $hi=$this->count;
- $A = $this->a;
- $X = $a;
- while($lo<$hi) {
- $mid = (int)(($lo+$hi)/2);
- if (BPT_keycmp($X,$A[$mid])<0) $hi=$mid;
- else $lo=$mid+1;
- }
- $this->insert($lo, $a, $b);
- }
- /**
- * removes the pair at the offset $offset
- * @param int $offset offset of the pair targeted for deletion
- */
-
- function remove($offset) {
- array_splice($this->a, $offset, 1);
- array_splice($this->b, $offset, 1);
- $this->count--;
- }
- /**
- * append at the end of the current object the contents
- * of another pairs object
- *
- * @param pairs $pairs a pair object of which the contents
- * will be appended to this
- */
- function append(&$pairs) {
- array_splice($this->a, $this->count, 0, $pairs->a);
- array_splice($this->b, $this->count, 0, $pairs->b);
- $this->count+=$pairs->count;
- }
- /**
- * make the object fields read-only
- */
- function __set($x,$y) {
- trigger_error("Can't edit pairs directly'", E_USER_ERROR);
- }
- }
- if (BPT_SORT == SORT_ASC) {
- /**
- * compares key $a and $b using a less-than or greather-than relation
- * depending on {@link BPT_SORT} constants
- *
- * the function is very simple, returns strcmp($a,$b) or -strcmp($a,$b)
- * depending on the BPT_SORT constant: to be a little bit faster, no check is done
- * by the function itself; instead it is <strong>defined</strong> at load time, depending
- * on the value of the BPT_SORT constant
- *
- */
- function BPT_keycmp($a,$b) { return strcmp($a,$b); }
- } else {
- function BPT_keycmp($a,$b) { return -strcmp($a,$b); }
- }
- /*
- function _BPT_bisect($a, $x, $lo=0, $hi=null) {
- if (is_null($hi))
- $hi=count($a);
- while($lo<$hi && $a[$lo++]<$x) ;
- return $lo;
- }
- */
- /**
- * locate an element $x or the nearest bigger one
- * in the array $a, starting from offset $lo
- * and limiting to offset $hi, assuming that $a is
- * ordered by the relation BPT_keycmp
- *
- * @param mixed $a source array
- * @param mixed $x element to find
- * @param int $lo leftmost offset
- * @param int|null $hi rightmost offset
- *
- * @returns integer
- *
- */
- function BPT_bisect($a, $x, $lo=0, $hi=null) {
- if (is_null($hi)) {
- $hi = count($a);
- }
- while ($lo < $hi) {
- $mid = (int)(($lo+$hi)/2);
- #if ($x < $a[$mid])
- if (BPT_keycmp($x,$a[$mid])<0)
- $hi = $mid;
- else
- $lo = $mid+1;
- }
- return $lo;
- }
- /*
- function BPT_insort(&$a, $x, $lo=0, $hi=null) {
- if (is_null($hi))
- $hi = count($a);
- while ($lo<$hi) {
- $mid = (int) (($lo+$hi)/2);
- if ($x < $a[$mid])
- $hi = $mid;
- else
- $lo = $mid+1;
- }
- array_splice($a, $lo, 0, array($x));
- }
- */
- /*
- * fifo of bplustree nodes
- */
- class BPlusTree_Node_Fifo {
- /**
- * @var array array of elements
- */
- var $fifo = array();
- /**
- * @var array dictionary (associative array) of elements
- */
- var $fifo_dict = array();
- /**
- * var int size of the fifo
- */
- var $size;
-
- /**
- * constructor
- * @param int $size specifies size (defaults to 30)
- */
- function BPlusTree_Node_Fifo($size=30) {
- $this->fifosize=$size;
- }
- /**
- * flushes all of the contents of the fifo
- * to disk
- */
- function flush_fifo(){
- reset($this->fifo);
- while(list(,$node)=each($this->fifo)){
- if ($node->dirty) {
- $node->store(1);
- }
- }
- $this->fifo = array();
- $this->fifo_dict = array();
- }
- }
- /**
- * defines structure and methods of the node
- * of a bplustree
- */
- class BPlusTree_Node {
-
- /**
- * @var integer flags (defined as BPT_* constants)
- * specifying the nature of the node (leaf, interior, and combos)
- *
- */
- var $flag;
- /**
- * @var integer number of child elements (or values, if a leaf)
- *
- */
- var $size;
- /**
- * @var int seek position in the file
- *
- */
- var $position;
- /**
- * @var resource stream where to output the data
- * (typically a file open with fopen())
- */
- var $infile;
- /**
- * @var int maximum lenght of a string key
- */
- var $keylen;
- /**
- * @var array array of strings, containing keys, of size $size
- */
- var $keys;
- /**
- * @var array array of longs, of size $size+1
- * if leaf, elements in [0,$size] are the values of each key in $keys:
- * at offset $size - ($size+1)-th element - there's the seek
- * position of the next leaf (or BPT_NULLSEEK if rightmost leaf)
- *
- * if interior,
- *
- * - offset 0 points to the child node where keys are
- * are all LESS than those in this node (actually, to $keys[0]),
- *
- * - offset 1 points to the child node where keys are GREATER or EQUAL to $keys[0]
- * but LESS than $keys[1],
- *
- * - offset 2 points to the child node where keys are >= $keys[1] but < $keys[2], etc...
- *
- *
- * with LESS, GREATER we always mean by the relation {@link BPT_keycmp}
- *
- *
- */
- var $indices;
- /**
- * @var bool controls deferred writes (using fifo)
- *
- */
- var $dirty= false;
- /**
- * @var BPlusTree_Node_Fifo object of type {@link BPlusTree_Node_Fifo}
- */
- var $fifo = null;
- /**
- * @var int number of valid keys in $keys
- */
- var $validkeys;
-
- /**
- * constructor
- * @param int $flag flag of current node
- * @param int $size size of node
- * @param int $keylen max key length
- * @param long $position seek position in file
- * @param resource resource stream (opened file)
- * @param BPlusTree_Node object from which cloning properties
- */
- function BPlusTree_Node($flag,
- $size,
- $keylen,
- $position,
- $infile,
- $cloner = null) {
-
- $this->flag = $flag;
- if ($size < 0) {
- trigger_error('size must be positive', E_USER_ERROR);
- }
- $this->size = $size;
- $this->keylen = $keylen;
- $this->position = $position;
- $this->infile = $infile;
- // last (+1) is successor seek TODO move to its own!
- $this->indices = array_fill(0, $size+1, BPT_NULL);
- $this->keys = array_fill(0, $size, '');
-
- if (is_null($cloner)) {
- $this->storage = 2 +
- /* 2 chars for flag, validkeys */
- $size*4+4 + /* n 4B-long indices + 1 4B-long next pointer*/
- $size*$keylen ; /* n keylen-bytes long keys */
- } else {
- $this->storage = $cloner->storage;
- $this->fifo = $cloner->fifo;
- }
- if ($flag == BPT_FLAG_INTERIOR || $flag == BPT_FLAG_ROOT) {
- $this->validkeys = -1;
- } else {
- $this->validkeys = 0;
- }
- }
- /**
- *
- * reinitialize keys
- *
- */
- function clear() {
- $size = $this->size;
- // re-init keys
- $this->keys = array_fill(0, $size, '');
- $this->validkeys = 0;
- if (($this->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
- // re-init all indices
- $this->indices = array_fill(0, $size+1, BPT_NULL);
- $this->validkeys = -1;
- } else {
- $fwd = $this->indices[$size]; // forward pointer
- $this->indices = array_fill(0, $size, BPT_NULL);
- $this->keys = array_fill(0, $size, '');
- $this->indices[] = $fwd;
- }
- }
-
- /**
- * returns clone of the obect at position $position
- * @param long $position seek position
- */
- function &getclone($position) {
-
- if ($this->fifo) {
- $dict =& $this->fifo->fifo_dict;
- if (isset($dict[$position])) {
- return $dict[$position];
- }
- }
-
-
- $o = new BPlusTree_Node(
- $this->flag,
- $this->size,
- $this->keylen,
- $position,
- $this->infile,
- $this
- );
- return $o;
- }
-
- /**
- * put first index (seek position for less-than child)
- *
- * @param int $index seek position
- */
- function putfirstindex($index) {
- if ($this->validkeys>=0)
- trigger_error("Can't putfirstindex on full node", E_USER_ERROR);
- $this->indices[0] = $index;
- $this->validkeys = 0;
- }
- /**
- * links node $node to this node as a child, using key $key
- * (this node must be interior)
- *
- * @param string $key key string
- * @param object $node node to link
- *
- */
- function putnode($key, &$node) {
- $position = $node->position;
- return $this->putposition($key, $position);
- # if ($x == NOROOMERROR) {print_r(debug_backtrace());fail();}
- }
-
- /*
- *
- * links a seek position $position to the key $key
- *
- * @param string $key key string
- * @param int $position seek position (pointer to the new child node)
- *
- */
- function putposition($key, $position) {
- if (($this->flag & BPT_FLAG_INTERIOR) != BPT_FLAG_INTERIOR) {
- trigger_error("Can't insert into leaf node", E_USER_ERROR);
- }
- $validkeys = $this->validkeys;
- $last = $this->validkeys+1;
- if ($this->validkeys>=$this->size) {
- #trigger_error('No room error', E_USER_WARNING);
- return NOROOMERROR;
- }
- // store the key
- if ($validkeys<0) { // no nodes currently
- d("no keys");
- $this->validkeys = 0;
- $this->indices[0] = $position;
- } else {
- // there are nodes
- $keys =& $this->keys;
- // is the key there already?
- if (in_array($key, $keys, true)) {
- if (array_search($key, $keys, true) < $validkeys)
- trigger_error("reinsert of node for existing key ($key)",
- E_USER_ERROR);
- }
- $place = BPT_bisect($keys, $key, 0, $validkeys);
- // insert at position $place
- array_splice($keys, $place, 0, $key);
- // delete last element
- unset($keys[$last]);
- $keys = array_values($keys); # reset array indices
- #array_splice($keys, $last, 1);
- // store the index
- $indices =& $this->indices;
- #echo "inserting $position before ", var_dump($indices,1), "\n";
- array_splice($indices, $place+1, 0, $position);
- unset($indices[$last+1]);
- $indices = array_values($indices);
- #array_splice($indices, $last+1, 1);
- $this->validkeys = $last;
- }
- }
-
- /**
- * deletes from interior nodes
- *
- * @param string $key target key
- */
- function delnode($key) {
- // {{{
- if (($this->flag & BPT_FLAG_INTERIOR) != BPT_FLAG_INTERIOR) {
- trigger_error("Can't delete node from leaf node");
- }
- if ($this->validkeys < 0) {
- trigger_error("No such key (empty)");
- }
- $validkeys = $this->validkeys;
- $indices =& $this->indices;
- $keys =& $this->keys;
- if (is_null($key)) {
- $place = 0;
- $indexplace = 0;
- } else {
- $place = array_search($key, $keys, true);
- $indexplace = $place+1;
- }
-
- #unset($indices[$indexplace]);
- array_splice($indices, $indexplace, 1);
- $indices[] = BPT_NULLSEEK;
- #$indices = array_values($indices);
- #unset($keys[$place]);
- array_splice($keys, $place, 1);
- $keys[] = '';
- #$keys = array_values($keys);
- $this->validkeys = $validkeys - 1;
- }
- // }}}
- /**
- * slices the $this->keys array to the number of valid keys
- * in $this->validkeys
- *
- * @returns array array of valid keys
- */
- function get_keys() {
- $validkeys = $this->validkeys;
- if ($validkeys<=0) {
- return array();
- }
-
- return array_slice($this->keys, 0, $validkeys);
- }
-
-
- /*
- * mimics python's map(None, a, b)
- * returns the list of (a,b) pairs
- * where a is in list $a and b is in list $b
- *
- *
-
- function _oldpairs($a, $b) {
- $c = array();
- reset($a);
- reset($b);
- while((list(,$v1) = each($a)) &&
- (list(,$v2) = each($b))) {
- $c[] = array($v1, $v2);
- }
- return $c;
- }
- */
-
- /**
- * mimic's python's map(None, a, b);
- * a, b must be of the same size
- *
- * @param array $a first array
- * @param array $b second array
- *
- * @returns object {@link pairs}
- */
- function &_pairs($a, $b) {
- $x = new pairs($a,$b);
- return $x;
- }
- /**
- * returns an object containing pairs (key, index)
- * for all of the valid keys and indices
- *
- * @param string $leftmost leftmost key corresponding
- * to first index (seek) in interior nodes; ignored in leaves
- *
- * @returns object pairs
- *
- */
- function keys_indices($leftmost) {
- $keys = $this->get_keys();
- if (($this->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
- // interior nodes start with
- // the pointer to the "less than key[0]" subtree:
- // we need pairs (key, indices) so we add the leftmost key
- // on top
- array_unshift($keys, $leftmost);
- }
- $indices = array_slice($this->indices, 0, count($keys));
- return $this->_pairs($keys, $indices);
- }
- /**
- * returns child, searching for $key in an interior node
- *
- * @param string $key target $key
- * @returns object BPlusTree_Node
- *
- */
- function &getnode($key) {
- if (($this->flag & BPT_FLAG_INTERIOR) != BPT_FLAG_INTERIOR) {
- trigger_error("cannot getnode from leaf node", E_USER_ERROR);
- }
- if (is_null($key))
- $index = 0;
- else
- $index = array_search($key, $this->keys, true)+1;
- $place = $this->indices[$index];
- if ($place<0) {
- debug_print_backtrace();
- trigger_error("Invalid position! ($place, $key)", E_USER_ERROR);
- }
- // fifo
-
- $fifo =& $this->fifo;
- if ($fifo) {
- $ff =& $fifo->fifo;
- $fd =& $fifo->fifo_dict;
- if (isset($fd[$place])) {
- $node =& $fd[$place];
- #unset($ff[$place]);
- $idx = array_search($node, $ff, true);
- array_splice($ff, $idx, 1);
- array_unshift($ff, $node);
- return $node;
- }
- }
-
- $node =& $this->getclone($place);
- $node =& $node->materialize();
- return $node;
- }
- /***** leaf mode operations *****/
- /**
- * if leaf returns the next leaf on the right
- *
- */
- function &next() {
- if (($this->flag & BPT_LEAF_FLAG) != BPT_FLAG_LEAF) {
- trigger_error("cannot get next for non-leaf", E_USER_ERROR);
- }
- $place = $this->indices[$this->size];
- if ($place == BPT_NULLSEEK)
- return null;
- else {
- $node =& $this->getclone($place);
- $node =& $node->materialize();
- return $node;
- }
-
- }
- /*
- function &prev() {
- if (($this->flag & BPT_LEAF_FLAG) != BPT_FLAG_LEAF) {
- trigger_error("cannot get next for non-leaf", E_USER_ERROR);
- }
- $place = $this->prev;
- if ($place == BPT_NULLSEEK)
- return null;
- else {
- $node =& $this->getclone($place);
- $node =& $node->materialize();
- return $node;
- }
-
- }
- */
- /**
- * put ($key, $val) in a leaf
- *
- * @param string $key target string
- * @param int $val value for $key
- */
- function putvalue($key, $val) {
- if (!is_string($key))
- trigger_error("$key must be string", E_USER_ERROR);
- if (($this->flag & BPT_FLAG_LEAF) != BPT_FLAG_LEAF) {
- #print_r($this);
- trigger_error("cannot get next for non-leaf ($key)", E_USER_ERROR);
- }
- $validkeys = $this->validkeys;
- $indices =& $this->indices;
- $keys =& $this->keys;
- if ($validkeys<=0) { // empty
- // first entry
- $indices[0] = $val;
- $keys[0] = $key;
- $this->validkeys = 1;
- } else {
- $place = null;
- if (in_array($key, $keys, true)) {
- $place = array_search($key, $keys, true);
- if ($place >= $validkeys) {
- $place = null;
- }
- }
- if (!is_null($place)) {
- $keys[$place] = $key;
- $indices[$place] = $val;
- } else {
-
- if ($validkeys >= $this->size) {
- #trigger_error("no room", E_USER_WARNING);
- return NOROOMERROR;
- }
- $place = BPT_bisect($keys, $key, 0, $validkeys);
- $last = $validkeys+1;
-
- # del keys[validkeys]
- # del indices[validkeys]
- #array_splice($keys, $validkeys, 1);
- unset($keys[$validkeys]);
- $keys = array_values($keys);
- #array_splice($indices, $validkeys, 1);
- unset($indices[$validkeys]);
- $indices = array_values($indices);
- array_splice($keys, $place, 0, $key);
- array_splice($indices, $place, 0, $val);
-
- #echo implode(', ', $keys), " ::: $place \n";
- $this->validkeys = $last;
- }
- }
- }
- /**
- * for each $key, $index in $keys_indices
- * put the correspoding values (assumes this is a leaf)
- *
- * @param object $keys_indices object of type {@link pairs}
- */
- function put_all_values($keys_indices) {
- $this->clear();
- $indices =& $this->indices;
- $keys =& $this->keys;
- $length = $this->validkeys = $keys_indices->count;#count($keys_indices);
- if ($length > $this->size)
- trigger_error("bad length $length", E_USER_ERROR);
- for ($i=0; $i<$length; $i++) {
- #list($keys[$i], $indices[$i]) = $keys_indices[$i];
- $keys[$i] = $keys_indices->a[$i];
- $indices[$i] = $keys_indices->b[$i];
- }
- }
- /**
- * for each $key, $index in $keys_indices
- * put the correspoding seek positions (assumes this is an interior node)
- *
- * @param int $first_position leftmost pointer (to less-than child)
- * @param object $keys_indices object of type {@link pairs}
- *
- */
- function put_all_positions($first_position, $keys_positions) {
- $this->clear();
- $indices =& $this->indices;
- $keys =& $this->keys;
- $length = $this->validkeys = $keys_positions->count;#count($keys_positions);
- if ($length > $this->size) {
- trigger_error("bad length $length", E_USER_ERROR);
- }
- $indices[0] = $first_position;
- for ($i=0; $i<$length; $i++) {
- #list($keys[$i], $indices[$i+1]) = $keys_positions[$i];
- $keys[$i] = $keys_positions->a[$i];
- $indices[$i+1] = $keys_positions->b[$i];
- }
- }
- /**
- * assuming this is a leaf, returns value for $key
- * @param $key string target key
- * @returns int|false corresponding integer or false if key is missing
- *
- */
- function getvalue(&$key, $loose=false) {
- #d(implode(",",$this->keys));
- #$place = array_search($key, $this->keys);
- $place = BPT_bisect($this->keys, $key, 0, $this->validkeys);
- if ($this->keys[$place-1] == $key) {
- return $this->indices[$place-1];
- } else {
- if ($loose) {
- if ($place>1) $place--;
- $key = $this->keys[$place];
- return $this->indices[$place];
- }
- trigger_error("key '$key' not found", E_USER_WARNING);
- return false;
- }
-
- }
- /**
- * if leaf, creates a neighbor for this node: a new leaf
- * linked to this
- *
- * @param int $position seek position for the new neighbor?
- * @returns object BPlusTree_Node
- *
- */
- function &newneighbour($position) {
- if (($this->flag & BPT_FLAG_LEAF) != BPT_FLAG_LEAF)
- trigger_error('cannot make leaf neighbour for non-leaf');
- // create clone
- $neighbour =& $this->getclone($position);
- $size = $this->size;
- $indices =& $this->indices;
- // linking siblings
- $neighbour->indices[$size] = $indices[$size];
- $indices[$size] = $position;
- return $neighbour;
- }
- /**
- * if leaf, returns the leaf next to this
- * @return object BPlusTree_Node
- */
- function &nextneighbour() {
- if (($this->flag & BPT_FLAG_LEAF) != BPT_FLAG_LEAF)
- trigger_error('cannot get leaf neighbour for non-leaf');
- $size = $this->size;
- $position = $this->indices[$size];
- if ($position == BPT_NULLSEEK) {
- $neighbour = null;
- } else {
- $neighbour = $this->getclone($position);
- $neighbour = $neighbour->materialize();
- }
- return $neighbour;
-
- }
-
- /*
- function &prevneighbour() {
- if (($this->flag & BPT_FLAG_LEAF) != BPT_FLAG_LEAF)
- trigger_error('cannot get leaf neighbour for non-leaf');
- #$size = $this->size;
- $position = $this->prev; # $this->indices[$size];
- if ($position == BPT_NULLSEEK) {
- return null;
- } else {
- $neighbour = $this->getclone($position);
- $neighbour = $neighbour->materialize();
- return $neighbour;
- }
-
- }*/
- /**
- * if leaf, deletes neighbor on the right, and re-link
- * with the following
- *
- * @param object $next target for deletion
- * @param free $free seek position of last free node in free list
- *
- * @returns int new free position
- */
- function delnext(&$next, $free) {
- d("delnext called:");
- #print_r($this);
- $size = $this->size;
- if ($this->indices[$size]!=$next->position) {
- trigger_error("invalid next pointer ".
- "{$this->indices[$size]}!={$next->position})", E_USER_ERROR);
- }
- $this->indices[$size] = $next->indices[$size];
- return $next->free($free);
- }
- /**
- * if leaf, deletes corresponding value
- *
- * @param string $key target key
- */
- function delvalue($key) {
- $keys =& $this->keys;
- $indices =& $this->indices;
- if (!in_array($key, $keys, true)) {
- d($keys);
- trigger_error ("missing key, can't delete", E_USER_ERROR);
- }
- $place = array_search($key, $keys, true);
- $validkeys = $this->validkeys;
- $prev = $validkeys-1;
- # delete
- array_splice($keys, $place, 1);
- array_splice($indices, $place, 1);
- #unset($keys[$place]);
- #$keys[]='';
- #$keys = array_values($keys);
- #unset($indices[$place]);
- #$indices[] = BPT_NULL;
- #$indices = array_values($indices);
-
- # insert NULLs/empties
- array_splice($keys, $prev, 0, '');
- array_splice($indices, $prev, 0, BPT_NULL);
-
- $this->validkeys=$prev;//validkeys-1
- }
-
- /*
- * add self to free list, retunr position as new free position
- *
- * @param int $freenodeposition current last free node
- *
- */
- function free($freenodeposition) {
- $this->flag = BPT_FLAG_FREE;
- $this->indices[0] = $freenodeposition;
- $this->store();
- return $this->position;
- }
-
- /*
- * assuming self is head of free list,
- * pop self off freelist, return next free position;
- * does not update file
- *
- * @param integer $flag flag for new node
- * @return object new node
- *
- function unfree($flag) {
- $next = $this->indices[0];
- $this->flag = $flag;
- $this->validkeys = 0;
- $this->indices[0] = BPT_NULLSEEK;
- $this->clear();
- return $next;
- }
- */
- /**
- * get free node of same shape as self from $this->file;
- * make one if none exist;
- * assume $freeposition is seek position of next free node
- *
- * @param int $freeposition seek position of next freenode
- * @param callback $freenode_callback is specified it is a func to call
- * with a new free list head, if needed
- *
- * @returns array(&$node, $newfreeposition)
- *
- *
- *
- *
- */
- function getfreenode($freeposition, $freenode_callback=null) {
- d("GETTING FREE AT $freeposition");
- if ($freeposition == BPT_NULLSEEK) {
- $file = $this->infile;
- fseek($file, 0, SEEK_END);
- $position = ftell($file);
- d("ALLOCATING SPACE...");
- $thenode =& $this->getclone($position);
- $thenode->store();
- return array(&$thenode, BPT_NULLSEEK);
- } else {
- $position = $freeposition;
- $thenode = $this->getclone($position);
- // get old node
- $thenode = $thenode->materialize();
- // ptr to next
- $next = $thenode->indices[0];
- if (!is_null($freenode_callback)) {
- call_user_func($freenode_callback, $next);
- }
- $thenode->BplusTree_Node(
- $this->flag,
- $this->size,
- $this->keylen,
- $position,
- $this->infile
- );
- $thenode->store(); // save reinit'ed node
- return array(&$thenode, $next);
- }
- }
- /**
- *
- * write this to file
- *
- * @param bool $force forces write back if fifo is enabled, defaults to false
- *
- */
- function store($force = false) {
- // {{{
- $position = $this->position;
- if (is_null($position))
- trigger_error("position cannot be null",E_USER_ERROR);
-
- $fifo =& $this->fifo;
- if (!$force && $fifo) {
- $fd =& $fifo->fifo_dict;
- if (isset($fd[$this->position]) && $fd[$position] === $this) {
- $this->dirty = true;
- return; // defer processing
- }
- }
-
- $f = $this->infile;
- fseek($f, $position);
- $data = $this->linearize();
- fwrite($f, $data);
- $last = ftell($f);
- $this->dirty = false;
-
- if (!$force && $this->fifo) {
- $this->add_to_fifo();
- }
-
- return $last;
- }
- //}}}
-
- /**
- * load node from file
- *
- * @returns object BPlusTree_Node
- *
- */
- function &materialize() {
- $position = $this->position;
-
- if ($this->fifo) {
- $fifo =& $this->fifo;
- $dict =& $fifo->fifo_dict;
- $ff =& $fifo->fifo;
- if (isset($dict[$position])) {
- $node =& $dict[$position];
- if ($node !== $ff[0]) {
- $nidx = array_search($node, $ff, true);
- unset($ff[$nidx]);
- array_unshift($ff, $node);
- }
- return $node;
- }
- }
-
- $f = $this->infile;
- fseek($f, $position);
- $data = fread($f, $this->storage);
- $this->delinearize($data);
-
-
- if ($this->fifo) {
- $this->add_to_fifo();
- }
-
- return $this;
- }
- /**
- * @returns string binary string encoding this node
- */
- function linearize() {
- $params = array(
- 'C2L'.($this->size+1),
- $this->flag,
- $this->validkeys
- );
- foreach($this->indices as $i)
- $params[] = $i;
- $s = call_user_func_array('pack', $params);
- $x = '';
- for($i = 0; $i<$this->validkeys; $i++) {
- $k = $this->keys[$i];
- if (strlen($k)>$this->keylen)
- trigger_error("Invalid keylen for '$k'", E_USER_ERROR);
- $x .= str_pad($k, $this->keylen, chr(0));
- }
-
- $x = str_pad($x, $this->size*$this->keylen, chr(0));
- $s .= $x;
- $l = strlen($s);
- if (strlen($s) != $this->storage) {
- trigger_error("bad storage $l != {$this->storage}", E_USER_ERROR);
- }
-
- return $s;
- }
- /**
- * get properties of this node from the string $s encoded via {@link BPlusTree_Node::linearize}
- *
- * @param string $s binary string
- *
- */
-
- function delinearize($s) {
- //{{{
- if (strlen($s)!=$this->storage)
- trigger_error("bad storage", E_USER_ERROR);
- $x = 'Cflag/Cvalidkeys/';
- $n = $this->size+1;
- for ($i = 0; $i<$n; $i++) {
- $x .= "lindices{$i}/";
- }
- $arr = unpack($x, $s);
- $this->flag = $arr['flag'];
- $this->validkeys = $arr['validkeys'];
- for ($i = 0; $i<$n; $i++) {
- $this->indices[$i] = $arr["indices{$i}"];
- }
-
- for ($i = 0, $j = ($n*4+2); $i<$this->validkeys; $i++, $j+=$this->keylen) {
-
- $this->keys[$i] = rtrim(substr($s, $j, $this->keylen));
- }
-
- }
- //}}}
- // foo dump
- /**
- *
- * prints a dump of the tree on scree
- * @param string $indent custom indentation
- *
- */
- function dump($indent='') {
- //{{{
- $flag = $this->flag;
- if ($flag == BPT_FLAG_FREE) {
- echo "free->", $this->position, "\n";
- $nextp = $this->indices[0];
- if ($nextp!=BPT_NULLSEEK) {
- $next =& $this->getclone($nextp);
- $next =& $next->materialize();
- $next->dump();
- } else {
- echo "!last\n";
- }
- return;
- }
- $nextindent = $indent . " ";
- echo $indent;
- switch ($flag) {
- case BPT_FLAG_ROOT: echo "root"; break;
- case BPT_FLAG_INTERIOR: echo "interior"; break;
- case BPT_FLAG_LEAF: echo "leaf"; break;
- case BPT_FLAG_LEAFANDROOT: echo "root&leaf"; break;
- default : echo "invalid flag??? ", $flag;
- }
- echo "($flag) ";
- echo " ", $this->position, " valid=", $this->validkeys, "\n";
- echo $indent, "keys {", implode(', ', $this->keys), "}\n";
- echo $indent, "seeks {", implode(", ", $this->indices),"}\n";
- if (($flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
- reset($this->indices);
- while(list(,$i) = each($this->indices)) {
- if ($i!=BPT_NULLSEEK) {
- // interior
- $n =& $this->getclone($i);
- $n =& $n->materialize();
- $n->dump($nextindent);
- } else {
- //leaf
- continue;
- }
- }
- }
- echo $indent, "*****\n";
- }//}}}*/
-
- /**
- * adds this node to fifo
- */
- function add_to_fifo() {
- $fifo =& $this->fifo;
- $ff =& $fifo->fifo;
- $dict =& $fifo->fifo_dict;
- $position = $this->position;
- if(isset($dict[$position])) {
- $old =& $dict[$position];
- unset($dict[$position]);
- # ff.remove(old)
- array_splice($ff, array_search($old, $ff, true), 1);
- }
- $dict[$this->position] =& $this;
- array_splice($ff, 0, 0, array(&$this));
- if (count($ff)>$this->fifo->fifosize) {
- $lastidx = count($ff)-1;
- $last = $ff[$lastidx];
- unset($ff[$lastidx]);
- unset($dict[$last->position]);
- if ($last->dirty) {
- $last->store(true);
- }
- }
- $is_o=true;
- while((list(,$v)=each($ff)) && $is_o=is_object($v));
- if (!$is_o) {trigger_error('ERR', E_USER_ERROR);}
- }
- /**
- * @param int $size defaults to 33
- *
- */
- function enable_fifo($size = 33) {
- if ($size<5 || $size>1000000) {
- trigger_error("size not valid $size");
- }
- $this->fifo = new BPlusTree_Node_Fifo($size);
- }
- /**
- * disables fifo (first flushes to disk)
- *
- */
- function disable_fifo() {
- if ($this->fifo) {
- $this->fifo->flush_fifo();
- $this->fifo = null;
- }
- }
-
- }
- /**
- * main class BPlusTree
- * creates a B+Tree with string keys and integer values
- *
- * public methods are only {@link BPlusTree::getitem}
- * {@link BPlusTree::setitem} {@link BPlusTree::delitem}
- * {@link BPlusTree::walker}
- *
- *
- */
- class BPlusTree {
- /**
- * @var int number of values
- */
- var $length = null;
- /**
- * @var bool used for deferred writes (if fifo is enabled
- */
- var $dirty = false;
- # var $headerformat = "%10d %10d %10d %10d %10d\n";
- /**
- * @var int seek position of root in file
- */
- var $root_seek = BPT_NULLSEEK;
- /**
- * @var int seek position of the start of the freelist
- *
- */
- var $free = BPT_NULLSEEK;
- /**
- * @var object BPlusTree_Node root node
- */
- var $root = null; /* */
- /**
- * @var int length of the file header in bytes
- */
- var $headersize;
- /**
- * @var bool true if fifo is enabled
- */
- var $fifo_enabled = false;
- /**
- * constructor
- * @param resource $infile resource of open file
- * @param int $position offset from the beginning of the file (usually 0)
- * @param int $nodesize size of the node
- * @param int $keylen maximum lenght of a key in bytes (unicode extended chars evaluate to two chars)
- */
- function BPlusTree($infile, $pos=null, $nodesize=null, $keylen=10) {
- if (!is_null($keylen) && $keylen<=2) {
- trigger_error("$keylen must be greater than 2", E_USER_ERROR);
- }
- $this->root_seek = BPT_NULLSEEK;
- $this->free = BPT_NULLSEEK;
- $this->root = null;
- $this->file = $infile;
- #if ($nodesize<6) trigger_error("nodesize must be >= 6", E_USER_ERROR);
- $this->nodesize = $nodesize;
- $this->keylen = $keylen;
- if (is_null($pos)) {
- $pos = 0;
- }
- $this->position = $pos;
- $this->headersize = 4*4+6; /* 4 4-byte longs, 1 char, 5-byte magic string*/
- }
- /**
- * returns an iterator for the tree
- * @param string $keylower key lower limit of the iterator
- * @param bool|int $includelower if true $keylower is included in the iterator;
- * if $includelower > 1 then 'loose' search is assumed:
- * the tree will be walked starting from
- * the key $k in the tree such as $k <= $keylower
- * and such as there are NO other keys $k'
- * such as $k < $k' <= $keylower
- * @param string $keyupper key upper bound of the iterator
- * @param bool $includeupper if true $keyupper is included in the iterator
- */
- function &walker(
- &$keylower,
- $includelower =null,
- $keyupper =null,
- $includeupper =null
- ) {
-
- $o = new BPlusWalker($this, $keylower, $includelower, $keyupper, $includeupper);
- return $o;
- }
-
- /**
- * @returns array array of properties of this object
- */
- function init_params() {
- return array(
- $this->file,
- $this->position,
- $this->nodesize,
- $this->keylen
- );
- }
- /**
- * @returns object BPlusTree_Node of the root
- */
- function get_root() {
- return $this->root;
- }
- /**
- * updates the head of the freelist and writes back to file
- * @param int $position seek position of the head of the freelist
- */
- function update_freelist($pos) {
- if ($this->free!=$pos) {
- $this->free = $pos;
- $this->reset_header();
- }
- }
- /**
- * action to perform to setup a bplustree, header is reset, length truncated
- * and a new root node is created
- */
- function startup() {
- if (is_null($this->nodesize) || is_null($this->keylen)) {
- trigger_error("cannot initialize without nodesize, keylen specified\n") ;
- }
- $this->length = 0;
- $this->root_seek = 22; //pack('a5LCL3',...)
- $this->reset_header();
- $file = $this->file;
- fseek($file, 0, SEEK_END);
- $this->root = new BplusTree_Node(
- BPT_FLAG_LEAFANDROOT,
- $this->nodesize, $this->keylen, $this->root_seek, $file
- );
- $this->root->store();
- }
- /**
- * reload the bplustree from file and setup for use
- */
- function open() {
- $file = $this->file;
- if ($this->get_parameters()===false)
- return false;
- $this->root = new BplusTree_Node(
- BPT_FLAG_LEAFANDROOT,
- $this->nodesize,
- $this->keylen,
- $this->root_seek,
- $file
- );
- $this->root =& $this->root->materialize();
- return true;
- }
- /**
- * enable fifo
- * @param int $size defaults to 33
- */
- function enable_fifo($size=33) {
- $this->fifo_enabled = true;
- $this->root->enable_fifo($size);
- }
- /**
- * disables fifo (writes back header to file if needed)
- *
- */
- function disable_fifo() {
- $this->fifo_enabled = false;
- if ($this->dirty) {
- $this->reset_header();
- $this->dirty = false;
- }
- $this->root->disable_fifo();
- }
- /**
- *
- * @returns string header string
- */
- function _makeheader() {
- return pack('a5LCL3', BPT_VERSION_MAGIC,
- $this->length, $this->keylen,
- $this->nodesize, $this->root_seek, $this->free);
- }
- /**
- * writes back header to file (if fifo is enabled write is deferred until
- * fifo is again disabled
- */
- function reset_header() {
-
- if ($this->fifo_enabled) {
- $this->dirty = true;
- d("[FIFO]: deferring header reset");
- return;
- }
- $file = $this->file;
- fseek($file, $this->position);
-
- $s = $this->_makeheader();
- fwrite($file, $s);
- }
-
- /**
- * reads back properties/parameters of this tree from file;
- * raises an error if version magic is wrong
- *
- * @returns bool false on failure, true on success
- */
- function get_parameters() {
- $file = $this->file;
- fseek($file, $this->position);
- $data = fread($file, $this->headersize);
- $hdr = unpack('a5magic/Llength/Ckeylen/Lnodesize/Lroot_seek/Lfree', $data);
- if ($hdr['magic']!=BPT_VERSION_MAGIC) {
- trigger_error("Version magic mismatch ({$hdr['magic']}!="
- .BPT_VERSION_MAGIC.')', E_USER_WARNING);
- return false;
- }
- $this->length = $hdr['length'];
- $this->keylen = $hdr['keylen'];
- $this->nodesize = $hdr['nodesize'];
- $this->root_seek = $hdr['root_seek'];
- $this->free = $hdr['free'];
- return true;
- }
- /**
- * @returns length of the tree (number of values)
- */
- function length() {
- if (is_null($this->length)) {
- if (false===$this->get_parameters()) return false;
- }
- return $this->length;
- }
- /**
- * @param string &$key key to find.
- * @param bool $loose if true searches the tree for the "nearest" key to $key;
- *
- * @returns int associated value
- *
- */
- function getitem(&$key, $loose=false) {
- if (is_null($this->root))
- trigger_error("not open!", E_USER_ERROR);
- return $this->find($key, $this->root, $loose);
- }
-
- /**
- * traverses tree starting from $node, searching for $key
- * @param string $key target key
- * @param object BPlusTree_Node starting node
- *
- * @returns int|bool value at the leaf node containing key or false if key is missing
- *
- */
- function find(&$key, &$node, $loose=false) {
-
- while (($node->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
- $thesekeys = $node->keys;
- $validkeys = $node->validkeys;
- #d(array_slice($thesekeys, 0, $validkeys));
-
- $place = BPT_bisect($thesekeys, $key, 0, $validkeys);
- if ($place>=$validkeys || BPT_keycmp($thesekeys[$place],$key)>0) {
- #$thesekeys[$place]>$key) {
- if ($place == 0)
- $nodekey = null;
- else
- $nodekey=$thesekeys[$place-1];
- } else {
- $nodekey = $key;
- }
- $node =& $node->getnode($nodekey);
- }
- return $node->getvalue($key, $loose);
- }
- /**
- * @param $key target key
- * @returns bool false if key does not exists, true otherwise
- */
- function has_key(&$key, $loose=false) {
- if (@$this->getitem($key, $loose)!==false) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * sets an item in the tree with key $key and value $val
- *
- * @param string $key
- * @param integer $val (internally stored as a 4byte long: keep it in mind!)
- *
- *
- */
- function setitem($key, $val) {
- if (!is_numeric($val))
- trigger_error("Second parameter must be numeric", E_USER_ERROR);
- $curr_length = $this->length;
- $root =& $this->root;
- if (is_null($root)) trigger_error("not open", E_USER_ERROR);
- if (!is_string($key)) trigger_error("$key must be string", E_USER_ERROR);
- if (strlen($key)>$this->keylen)
- trigger_error("$key is too long: MAX is {$this->keylen}", E_USER_ERROR);
-
-
- d( "STARTING FROM ROOT..." );
- $test1 = $this->set($key, $val, $this->root);
- if (!is_null($test1)) {
- d("SPLITTING ROOT");
- // getting new rightmost interior node
- list($leftmost, $node) = $test1;
- #print_r($test1);
- d("LEFTMOST [$leftmost]");
- // getting new non-leaf root
- list($newroot, $this->free) = $root->getfreenode($this->free);
- $newroot->flag = BPT_FLAG_ROOT;
- /*
- if ($root->flag == BPT_FLAG_LEAFANDROOT) {
- $root->flag = BPT_FLAG_LEAF;
- } else {
- $root->flag = BPT_FLAG_INTERIOR;
- }*/
-
- // zero-ing root flag (makes an interior or leaf node
- // respectively from a normal root or a leaf-root)
- $root->flag &= ~BPT_FLAG_ROOT_BIT;
- $newroot->clear();
- $newroot->putfirstindex($root->position);
- $newroot->putnode($leftmost, $node);
- $this->root =& $newroot;
- $this->root_seek = $newroot->position;
- $newroot->store();
- $root->store();
- $this->reset_header();
- d("root split.");
- } else {
- if ($this->length!=$curr_length) {
- // length changed: updating header
- $this->reset_header();
- }
- }
- }
- /**
- * traverses subtree starting at $node, searching a place for $key
- * and associates $val; split nodes if needed
- *
- * This function is not meant to be called outside the class, it is a
- * support method for {@link BPlusTree::setitem}
- *
- * @param string $key
- * @param int $val value associated to $key
- * @param object BPlusTree_Node starting node
- *
- * @returns array|null a pair (leftmost, newnode) where "leftmost" is
- * the leftmost key in newnode, and newnode is the split node;
- * returns null if no split took place
- */
- function set($key, $val, &$node) {
- //{{{
- $keys =& $node->keys;
- $validkeys = $node->validkeys;
- if (($node->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
- d("NON LEAF: FIND DESCENDANT");
- // non-leaf: find descendant to insert
- d($keys);
- $place = BPT_bisect($keys, $key, 0, $validkeys);
-
- if ($place >= $validkeys || BPT_keycmp($keys[$place],$key)>=0) {
- #$keys[$place]>=$key) {
- // insert at previous node
- $index = $place;
- } else {
- $index = $place +1 ;
- }
- if ($index == 0)
- $nodekey = null;
- else
- $nodekey =$keys[$place-1];
-
- $nextnode =$node->getnode($nodekey);
- $test = $this->set($key, $val, $nextnode);
- // split ?
- if (!is_null($test)) {
- list($leftmost, $insertnode) = $test;
-
- // TRY
- $TRY = $node->putnode($leftmost, $insertnode);
- if ($TRY == NOROOMERROR) {
- d( "$key::SPLIT!" );
- // EXCEPT
- $insertindex = $insertnode->position;
-
- list($newnode, $this->free) =
- $node->getfreenode(
- $this->free,
- array(&$this, 'update_freelist')
- );
-
- $newnode->flag = BPT_FLAG_INTERIOR;
- $ki = $node->keys_indices("dummy");
- #list($dummy, $firstindex) = $ki[0]; #each($ki);
- $firstindex = $ki->b[0];
- #$ki = array_slice($ki, 1);
- $ki->remove(0);
- #print_r($ki);
- // insert new pair
- #BPT_insort($ki, array($leftmost, $insertindex));
- $ki->insort($leftmost, $insertindex);
- $newleftmost = $this->divide_entries(
- $firstindex,
- $node,
- $newnode,
- $ki
- );
- $node->store();
- $newnode->store();
- return array($newleftmost, &$newnode);
- } else {
- d( "$key::NO SPLIT" );
- d($node->keys);
- $node->store();
- return null; // no split
- }
- }
- } else {
- // leaf
- d("FOUND LEAF:");
- d($keys);
- if (!in_array($key, $keys, true)
- || array_search($key, $keys, true) >= $validkeys) {
- $newlength = $this->length +1;
- } else {
- $newlength = $this->length;
- }
- d("[LEAF] TRYING TO PUT $key=>$val");
- if ($node->putvalue($key, $val)==NOROOMERROR) {
- d("GOT NOROOMERROR");
-
- $ki = $node->keys_indices("dummy");
- #BPT_insort($ki, array($key, $val));
- $ki->insort($key, $val);
- list($newnode, $this->free) =
- $node->getfreenode(
- $this->free,
- array(&$this, 'update_freelist')
- );
- d("CREATE NEW NEIGHBOUR");
- $newnode =& $node->newneighbour($newnode->position);
- $newnode->flag = BPT_FLAG_LEAF;
- $newleftmost = $this->divide_entries(0, $node, $newnode, $ki);
- $node->store();
- #print_r($node);
- #print_r($newnode);
- $newnode->store();
- $this->length = $newlength;
- return array($newleftmost, &$newnode);
- } else {
- d("STORING NODE [{$node->position}]") ;
- d($node->keys);
- $node->store();
- $this->length = $newlength;
- return null;
- }
- }
- }
- //}}}
- /**
- *
- * removes key from tree at node $node;
- * triggers an error if $key does not exists
- *
- * not meant to be called outside the class, it is a support method
- * for {@link BPlusTree::delitem}
- *
- * @param $key target key
- * @param $node node from which start
- *
- * @returns array a pair(&$leftmost, $size): if leftmost changes it is a string with the new leftmost
- * of $node otherwise returns array(null, $size)- caller will restructure node, if needed
- * size is the new size of $node
- *
- */
- function remove($key, &$node, $NESTING=0) {
- $newnodekey = null;
- d("NESTING LEVEL $NESTING");
- d("($NESTING) current size = {$this->nodesize}");
- // first of all we check if it is non-leaf
- if (($node->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
- // non-leaf
-
- $keys =& $node->keys;
- $validkeys =$node->validkeys;
- $place = BPT_bisect($keys, $key, 0, $validkeys);
- if ($place>=$validkeys || BPT_keycmp($keys[$place],$key)>=0) {
- #$keys[$place]>=$key) {
- // delete occurs before $place
- // (remember that indices are [i_0,i_1,...,i_n]
- // where i_0 points to the node where all keys are < K_search
- // and i_1 points to the node where keys are k_1<=K_search<k_2)
- $index = $place;
- } else {
- // delete occurs in $place (k_i <= K_search < k_(i+1) )
- $index = $place + 1;
- }
- if ($index==0) {
- $nodekey = null;
- } else {
- $nodekey = $keys[$place-1];
- }
- // get child node
- $nextnode =& $node->getnode($nodekey);
- // RECURSION! remove from nextnode;
- // returns new leftmost if changed, otherwise null,
- // and new size of the child node
- list($lm, $size) = $this->remove($key, $nextnode, $NESTING+1);
- // check now for size of nodesize: is it too small?
- // (less than half)
- $nodesize = $this->nodesize;
- $half = (int)($nodesize/2);
- # if($size==0) trigger_error("SIZE==0", E_USER_WARNING);
- if ($size < $half) {
- d("($NESTING) node too small ($size<$nodesize/2), redistribute children");
- // node is too small, need to redistribute
- // children
- if (is_null($nodekey) && $validkeys == 0) {
- #print_r($node);
- trigger_error(
- "invalid node, only one child",
- E_USER_ERROR
- );
- }
-
- if ($place >= $validkeys) {
- // final node in row, get previous
- $rightnode =& $nextnode;
- $rightkey = $nodekey;
- if ($validkeys<=1) {
- $leftkey = null;
- } else {
- $leftkey = $keys[$place-2];
- }
- $leftnode =& $node->getnode($leftkey);
- } else {
- // non-final, get next
- $leftnode =& $nextnode;
- $leftkey = $nodekey;
- if ($index == 0) {
- $rightkey = $keys[0];
- } else {
- $rightkey = $keys[$place];
- }
- $rightnode = $node->getnode($rightkey);
- }
- // get all keys and indices
- $rightki = $rightnode->keys_indices($rightkey);
- $leftki = $leftnode->keys_indices($leftkey);
-
- #$ki = array_merge($leftki, $rightki);
- $leftki->append($rightki);
- $ki =& $leftki;
-
- #array_splice ($leftki, count($leftki), 0, $rightki);
- $lki = $ki->count;#count($ki);
- // merging?
- if (($lki>$nodesize) || (
- ($leftnode->flag & BPT_FLAG_LEAF)!=BPT_FLAG_LEAF
- &&
- ($lki>=$nodesize)
- )) {
- // redistribute
- #list($newleftkey, $firstindex) = $ki[0];
- $newleftkey = $ki->a[0];
- $firstindex = $ki->b[0];
- if (is_null($leftkey)) {
- $newleftkey = $lm;
- }
- if (($leftnode->flag&BPT_FLAG_LEAF)!=BPT_FLAG_LEAF) {
- // kill first pair
- #$ki = array_slice($ki, 1);
- $ki->remove(0);
- }
- $newrightkey = $this->divide_entries(
- $firstindex,
- $leftnode,
- $rightnode,
- $ki
- );
- // delete, reinsert right
- $node->delnode($rightkey);
- $node->putnode($newrightkey, $rightnode);
- // same for left if first changed
- if (!…
Large files files are truncated, but you can click here to view the full file