flatpress/fp-includes/core/core.bplustree.class.php

2606 lines
60 KiB
PHP
Executable File
Raw Blame History

<?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) {
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', -1);
/**
* @const int null seek position
*/
define('BPT_NULLSEEK', -1);
/**
* @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(array $a, array $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];
}
}
return new BPlusTree_Node(
$this->flag,
$this->size,
$this->keylen,
$position,
$this->infile,
$this
);
}
/**
* 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)) {
if (array_search($key, $keys) < $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);
$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)+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)) {
$place = array_search($key, $keys);
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) {
#d(implode(",",$this->keys));
$place = array_search($key, $this->keys);
if ($place!==false) {
return $this->indices[$place];
} else {
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<6F>
* @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)) {
d($keys);
trigger_error ("missing key, can't delete", E_USER_ERROR);
}
$place = array_search($key, $keys);
$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) {d("CAPUTT");f();}
}
/**
* @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 $includelower if true $keylower is included in the iterator
* @param string $keyupper key upper bound of the iterator
* @param bool $includeupper if true $keyupper is included in the iterator
*/
function walker(
$keylower =null,
$includelower =null,
$keyupper =null,
$includeupper =null
) {
return new BPlusWalker($this, $keylower, $includelower, $keyupper, $includeupper);
}
/**
* @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->reset_header();
$file = $this->file;
fseek($file, 0, SEEK_END);
$this->root_seek = ftell($file);
$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
* @returns int associated value
*
*/
function getitem($key) {
if (is_null($this->root))
trigger_error("not open!", E_USER_ERROR);
return $this->find($key, $this->root);
}
/**
* 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) {
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);
}
/**
* @param $key target key
* @returns bool false if key does not exists, true otherwise
*/
function has_key($key) {
if ($this->getitem($key)!==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) {
$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)
|| array_search($key, $keys) >= $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 (!is_null($leftkey) && $leftkey!=$newleftkey) {
$node->delnode($leftkey);
$node->putnode($newleftkey, $leftnode);
}
$node->store();
$leftnode->store();
$rightnode->store();
} else {
d("($NESTING) node too small, need merge left<-right");
// merge into left, free right
d($leftnode->keys);
d($leftnode->indices);
d($rightnode->indices);
#list($newleftkey, $firstindex) = $ki[0];
$newleftkey = $ki->a[0];
$firstindex = $ki->b[0];
if (($leftnode->flag&BPT_FLAG_LEAF)!=BPT_FLAG_LEAF) {
$leftnode->put_all_positions(
$firstindex,
$ki->slice(1)
#array_slice($ki, 1)
);
} else {
$leftnode->put_all_values($ki);
}
if ($rightnode->flag==BPT_FLAG_LEAF) {
$this->free = $leftnode->delnext(
$rightnode, $this->free
);
} else {
$this->free = $rightnode->free($this->free);
}
if (!is_null($leftkey) && $newleftkey!=$leftkey) {
d("$newleftkey!=$leftkey");
$node->delnode($leftkey);
$node->putnode($newleftkey, $leftnode);
}
$node->delnode($rightkey);
$node->store();
$leftnode->store();
d('redist:');
d($node->keys);
d($leftnode->keys);
$this->reset_header();
}
if (is_null($leftkey))
$newnodekey = $lm;
} else {
// no restructuring,
// update leftmost if needed
if (is_null($nodekey)) {
// we changed leftmost child,
// we return a new leftmost key to update parent
// ($lm is null if no update is needed)
$newnodekey = $lm;
} elseif(!is_null($lm)) {
// child's leftmost has changed:
// delete old reference
$node->delnode($nodekey);
// change reference with new key
$node->putnode($lm, $nextnode);
}
} // end restructuring if
} else {
//leaf, base case: just delete.
if ($node->validkeys<1) {
// only for empty root
trigger_error("No such key $key", E_USER_ERROR);
}
$first=$node->keys[0];
d($node->keys);
$node->delvalue($key);
d($node->keys);
$rest = $node->keys[0];
if ($first!=$rest) {
$newnodekey = $rest;
}
$node->store();
$this->length--;
d("NEWNODEKEY: $newnodekey");
d("VALIDKEYS: {$node->validkeys}");
}
d($node->keys);
return array($newnodekey, $node->validkeys);
}
/**
*
* equally divides $entries ("array" of pairs (key,index) -
* implemented with a pair object) between two nodes $node1 and $node2
*
* @param int $firstindex if interior node, leftmost index (pointer to less-than sub-tree) for $node1
* @param object $node1 BPlusTree_Node first destination node
* @param object $node2 BplusTree_Node second destination node
* @param object $entries {@link pairs} object
*
* @returns string leftmost key of $node1
*/
function divide_entries($firstindex, &$node1, &$node2, &$entries) {
//{{{
#$middle = (int)(count($entries)/2);
$middle = (int)($entries->count/2);
d("divide entries at $middle");
#$left = array_slice($entries, 0, $middle);
#$right = array_slice($entries, $middle);
$left = $entries->slice(0, $middle);
$right = $entries->slice($middle);
if (($node1->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
d("DIVIDING INTERIOR\n");
#list($leftmost, $midindex) = $right[0];
$leftmost = $right->a[0];
$midindex = $right->b[0];
$node1->put_all_positions($firstindex, $left);
#$node2->put_all_positions($midindex, array_slice($right, 1));
$node2->put_all_positions($midindex, $right->slice(1));
d($node1->keys);
d($node2->keys);
if (in_array(
array_fill(0,$node1->size,''),
array($node1->keys,$node2->keys))
) {
trigger_error("splitting an empty node!", E_USER_ERROR);
}
return $leftmost;
} else {
d("DIVIDING non-INTERIOR");
$node1->put_all_values($left);
$node2->put_all_values($right);
d($node1->keys);
d($node2->keys);
// returns right leftmost
#return $right[0][0];
return $right->a[0];
}
}
// }}}
/**
* delete item $key
* @param string $key the key to delete
*
*/
function delitem($key) {
$root = $this->root;
$currentlength = $this->length;
$this->remove($key, $root, $NESTING=0);
if ($root->flag == BPT_FLAG_ROOT) {
$validkeys = $root->validkeys;
if ($validkeys <1) {
if ($validkeys<0) {
trigger_error(
"invalid empty non-leaf root",
E_USER_ERROR
);
}
$this->root =& $root->getnode(null);
$newroot =& $this->root;
$this->root_seek = $newroot->position;
$this->free = $root->free($this->free);
$this->reset_header();
/*
if ($newroot->flag == BPT_FLAG_LEAF) {
$newroot->flag = BPT_FLAG_LEAFANDROOT;
} else {
$newroot->flag = BPT_FLAG_ROOT;
}
*/
$newroot->flag |= BPT_FLAG_ROOT_BIT;
$newroot->store();
} elseif ($this->length != $currentlength) {
$this->reset_header();
}
} elseif($root->flag != BPT_FLAG_LEAFANDROOT) {
trigger_error("invalid flag for root", E_USER_ERROR);
} elseif ($this->length != $currentlength) {
$this->reset_header();
}
}
function _dump() {
$free =& $this->root->getclone($this->free);
for ($i=$this->headersize;
!feof($this->file);
fseek($this->file, $i), $i+=$free->storage) {
$s = fread($this->file, $free->storage);
$free->delinearize($s);
#print_r($free);
}
}
/**
* dumps contents of the tree to screen
*/
function dump() {
$this->root->dump() ;
if ($this->free != BPT_NULLSEEK) {
$free =& $this->root->getclone($this->free);
$free =& $free->materialize();
$free->dump();
}
}
}
class BPlusWalker {
var $tree;
var $keylower;
var $includelower;
var $keyupper;
var $includeupper;
function BPlusWalker(
&$tree,
$keylower=null,
$includelower=null,
$keyupper=null,
$includeupper=null){
$this->tree =& $tree;
$this->keylower = $keylower;
$this->includelower = $includelower;
$this->keyupper = $keyupper;
$this->includeupper = $includeupper;
if ($this->tree->get_root()==null) {
$this->tree->open();
}
$node = $this->tree->get_root();
while(BPT_FLAG_INTERIOR == ($node->flag & BPT_FLAG_INTERIOR)) {
if (is_null($keylower)) {
$nkey = null;
} else {
$keys =& $node->get_keys();
$n_keys = count($keys);
$place = BPT_bisect($keys, $keylower);
if ($place==0) {
$nkey = null;
} elseif ($place>$n_keys) {
$nkey = $keys[$n_keys-1];
} else {
$nkey = $keys[$place-1];
}
}
$node =& $node->getnode($nkey);
}
$this->startnode =& $node;
$this->node =& $node;
$this->node_index = null;
$this->valid = 0;
$this->first();
}
function first() {
$this->node =& $this->startnode;
$node =& $this->node;
$keys =& $node->keys;
$keylower = $this->keylower;
$keyupper = $this->keyupper;
$validkeys= $node->validkeys;
$this->valid=0;
if ($keylower==null) {
$this->node_index = 0;
$this->valid=1;
} elseif (in_array($keylower, $keys) && $this->includelower) {
$this->node_index = array_search($keylower, $keys);
$index = $this->node_index;
if ($index<$validkeys) {
$this->valid = 1;
}
}
if (!$this->valid) {
$place = BPT_bisect($keys, $keylower, 0, $validkeys);
if ($place < $validkeys) {
$index = $this->node_index = $place;
$testk = $keys[$index];
/*
if ($testk>$keylower ||
($this->includelower && $testk==$keylower)) {
$this->valid = true;
} else {
$this->valid = false;
}
*/
$this->valid = BPT_keycmp($testk,$keylower)<0||#$testk>$keylower ||
($this->includelower && $testk==$keylower);
} else {
$next =& $node->nextneighbour();
if (!is_null($next)) {
$this->startnode =& $next;
$this->first();
return;
} else {
$this->valid = 0;
}
}
if ($this->valid && !is_null($keyupper)) {
$key = $this->current_key();
$this->valid= (
BPT_keycmp($key,$keyupper)<0 #$key<$keyupper
||($this->includeupper && $key==$keyupper));
}
}
}
function current_key() {
if ($this->valid) return $this->node->keys[$this->node_index];
else trigger_error("Not a valid index ({$this->node_index})");
}
function current_value() {
if ($this->valid) return $this->node->indices[$this->node_index];
else trigger_error("Not a valid index ({$this->node_index})");
}
function current() {
if ($this->valid) {
return array(
$this->node->keys[$this->node_index],
$this->node->indices[$this->node_index]
);
} else {
trigger_error("Not a valid index ({$this->node_index})");
}
}
function next() {
$nextp = $this->node_index+1;
$node =& $this->node;
if ($nextp>=$node->validkeys) {
$next =& $node->nextneighbour();
if (is_null($next)) {
$this->valid = 0;
return;
}
$this->node =& $next;
$node =& $next;
$nextp = 0;
}
if($node->validkeys <= $nextp) {
$this->valid = 0;
} else {
$testkey = $node->keys[$nextp];
$keyupper = $this->keyupper;
$this->valid =( is_null($keyupper) ||
BPT_keycmp($testkey,$upper)<0||
#$testkey < $keyupper ||
($this->includeupper && $testkey == keyupper) );
if ($this->valid) $this->node_index = $nextp;
}
return $this->valid;
}
}
class caching_BPT extends BPlusTree {
var $cache = array();
function getitem($key) {
if (isset($cache[$key]))
return $cache[$key];
else return ($cache[$key] = parent::getitem($key));
}
function resetcache() {
$this->cache = array();
}
function nope() {
trigger_error("operation not permitted in caching_BPT", E_USER_WARNING);
}
function setitem() { $this->nope(); }
function delitem() { $this->nope(); }
}
class SBPlusTree extends BPlusTree {
var $maxstring; var $stringfile;
function SBPlusTree($infile, $stringfile,
$maxstring = 256,
$pos=null, $nodesize=null, $keylen=null) {
parent::BPlusTree($infile, $pos, $nodesize, $keylen);
$this->stringfile = $stringfile;
$this->maxstring = $maxstring;
}
function getstring($seek) {
fseek($this->stringfile, $seek);
$s = fread($this->stringfile, $this->maxstring);
return rtrim($s);
}
function setstring($s) {
fseek($this->stringfile, 0, SEEK_END);
$seek = ftell($this->stringfile);
// nul-pad string
$x = str_pad($s, $this->maxstring, chr(0));
fwrite($this->stringfile, $x);
return $seek;
}
function getitem($key) {
$seek = parent::getitem($key);
return $this->getstring($seek);
}
function setitem($key, $val) {
$seek = $this->setstring($val);
parent::setitem($key, $seek);
return $seek;
}
function walker(
$keylower =null,
$includelower =null,
$keyupper =null,
$includeupper =null
) {
return new SBPlusWalker($this, $keylower, $includelower, $keyupper, $includeupper);
}
}
class SBPlusWalker extends BPlusWalker {
function current_value() {
$id = parent::current_value();
return $this->tree->getstring($id);
}
}
class caching_SBPT extends SBPlusTree {
var $cache = array();
function getitem($key) {
if (isset($cache[$key]))
return $cache[$key];
else return ($cache[$key] = parent::getitem($key));
}
function resetcache() {
$this->cache = array();
}
function nope() {
trigger_error("operation not permitted in caching_BPT", E_USER_WARNING);
}
function setitem() { $this->nope(); }
function delitem() { $this->nope(); }
}
class BPlusUtils {
function recopy_bplus($fromfile, $tofile, $class='BPlusTree') {
$fromtree = new $class($fromfile);
$fromtree->open;
list($f, $p, $n, $k) = $fromtree->init_params();
$totree = new $class($tofile, $p, $n, $k);
$totree->startup();
return BPlusUtils::recopy_tree($fromtree,$totree);
}
function recopy_tree($fromtree, $totree) {
list($f, $p, $n, $k) = $totree->init_params();
// ....
}
}