PageRenderTime 63ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/alexandrul/flatpress
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

  1. <?php
  2. /*
  3. *
  4. * PHP B+tree library
  5. * ==============
  6. *
  7. * (c)2008 E.Vacchi <real_nowhereman at users.sourceforge.net>
  8. * Based on the original work by Aaron Watters (bplustree.py)
  9. *
  10. * Classes:
  11. *
  12. * - BPlustTree
  13. * Mapping keys, integers
  14. *
  15. * - caching_BPT
  16. * Subclass of BPlusTree, caching
  17. * key,value pairs
  18. * read-only: create using BPlusTree, read
  19. * using caching_BPT
  20. *
  21. *
  22. * Usage
  23. * =====
  24. *
  25. * # creation
  26. * $f = fopen('myfile', 'w+');
  27. * $o = new BPlusTree($f, $seek_start, $node_size, $keylen);
  28. * $o->startup();
  29. *
  30. * $o->setitem('my-key', 123);
  31. * $o->setitem('my-key-2', 456);
  32. *
  33. * $o->delitem('my-key-2');
  34. *
  35. *
  36. * # read-only
  37. * $f = open('myfile', 'r');
  38. * $o = caching_BPT($f);
  39. *
  40. * $o->open();
  41. * echo $o->getitem('my-key');
  42. *
  43. * Methods:
  44. *
  45. * void setitem($key, $val)
  46. * int getitem($key)
  47. * void delitem($key)
  48. * bool has_key($key)
  49. * object walker() : returns an iterator
  50. *
  51. *
  52. * Walking (iterate)
  53. * =================
  54. *
  55. * $walker = $tree->walker(
  56. * string $key_lower, bool $include_lower,
  57. * string $key_upper, bool $include_upper);
  58. * while ($walker->valid) {
  59. * echo $walker->current_key(),
  60. * $walker->current_value();
  61. * $walker->next();
  62. * }
  63. * $walker->first(); #resets internal pointer
  64. *
  65. *
  66. * Internal FIFO
  67. * =============
  68. *
  69. * $tree->enable_fifo();
  70. * do_some_processing();
  71. * $tree->disable_fifo();
  72. *
  73. * This should make the processing (slightly) faster
  74. * when key accesses are localized. Don't use it in walking
  75. * (no need for it) or for single write operations.
  76. *
  77. * enable_fifo() takes an optional parameter $length
  78. * (defaults to 33) remember that larger fifos will consume
  79. * more memory.
  80. *
  81. *
  82. * Other options
  83. * =============
  84. *
  85. * This PHP implementation slightly differs from the Python
  86. * one, because you can choose a constant (affecting all of the
  87. * instanced objects) defining the order relation of the keys.
  88. *
  89. * Usually you would like your keys to be sorted ascending
  90. * (SORT_ASC, the default), but sometimes you might want
  91. * to create a btree where keys are kept in reverse order.
  92. *
  93. * In this case, you just have to define somewhere in your code
  94. *
  95. * define('BPT_SORT', SORT_DESC);
  96. *
  97. * and the include the library.
  98. *
  99. * This somehow weird approach should however make the computation
  100. * slightly faster: the setting is evaluated only once,
  101. * when including the library; the compare routine is then defined
  102. * accordingly, and never checked again; otherwise the compare
  103. * routine would have to check the setting each time it's called.
  104. *
  105. */
  106. function d($s) {
  107. return; // disable debug output
  108. if (is_array($s)) { $s = '{ '.implode(", ", $s) . ' }'; }
  109. $x = debug_backtrace();
  110. $f = @$x[1]['function'];
  111. $l = $x[0]['line'];
  112. echo "[{$f}:{$l}]\t", $s, "\n";
  113. #echo "---[{$x[2]['function']}:{$x[2]['line']}]\n";
  114. }
  115. error_reporting(E_ALL);
  116. if (!defined('BPT_SORT')) {
  117. /**
  118. * @const int type of sorting, defaults to SORT_ASC (ascending);
  119. * SORT_DESC (descending) is also possibile
  120. */
  121. define('BPT_SORT', SORT_ASC);
  122. }
  123. /**
  124. * @const int no room error
  125. */
  126. define('NOROOMERROR', -100);
  127. /**
  128. * @const int null node
  129. */
  130. define('BPT_NULL', 0);
  131. /**
  132. * @const int null seek position
  133. */
  134. define('BPT_NULLSEEK', 0);
  135. /**
  136. * @const string magic string for bplustree
  137. */
  138. define('BPT_VERSION_MAGIC', 'BPT01');
  139. #define('BPT_INT_SIZE', 4);
  140. /**
  141. * @const int root bit flag
  142. */
  143. define('BPT_FLAG_ROOT_BIT', 1);
  144. /**
  145. * @const int interior node flag
  146. */
  147. define('BPT_FLAG_INTERIOR', 2);
  148. /**
  149. * @const int root flag, shorthand for BPT_FLAG_ROOT_BIT | BPT_FLAG_INTERIOR
  150. */
  151. define('BPT_FLAG_ROOT', BPT_FLAG_ROOT_BIT | BPT_FLAG_INTERIOR);
  152. /**
  153. * @const int free node flag
  154. */
  155. define('BPT_FLAG_FREE', 4);
  156. /**
  157. * @const int leaf flag
  158. */
  159. define('BPT_FLAG_LEAF', 8);
  160. /**
  161. * @const int leaf+root flag, shorthand for BPT_FLAG_ROOT_BIT | BPT_FLAG_LEAF
  162. */
  163. define('BPT_FLAG_LEAFANDROOT', BPT_FLAG_ROOT_BIT | BPT_FLAG_LEAF);
  164. /**
  165. * Abstraction for array of pairs
  166. * (meaning with "pair" an array containing two elements)
  167. * works only read-only
  168. *
  169. */
  170. class pairs {
  171. /**
  172. * @var array of the first elements of each pair (private)
  173. *
  174. */
  175. var $a;
  176. /**
  177. * @var array of the second elements of each pair (private)
  178. *
  179. */
  180. var $b;
  181. /**
  182. * @var integer current size of the array of pairs
  183. *
  184. */
  185. var $count;
  186. /*
  187. * Constructor
  188. * @param array $a array of the first elements of each pair
  189. * @parma array $b array of the second elements of each pair
  190. *
  191. */
  192. function pairs($a, $b) {
  193. if (($v=count($a))!=count($b))
  194. trigger_error("Size of params must match", E_USER_ERROR);
  195. $this->a=$a; $this->b=$b;
  196. $this->count = $v;
  197. }
  198. /*
  199. * returns a slice of the current Couplets object as a new Couplets object
  200. * (works like array_slice())
  201. * @param integer $offset offset from the start of the array (count starting from zero)
  202. * @param integer|null $count number of elements to return starting from $offset
  203. *
  204. * @returns pairs object
  205. *
  206. */
  207. function &slice($offset, $count=null) {
  208. if (is_null($count)) $count = $this->count;
  209. $a = new pairs(
  210. array_slice($this->a, $offset, $count),
  211. array_slice($this->b, $offset, $count)
  212. );
  213. return $a;
  214. }
  215. /**
  216. * inserts a pair ($a, $b) at the offset $offset eventually
  217. * pushing other elements to the right
  218. * @param int $offset offset at which insert
  219. * @param mixed $a first element in the pair
  220. * @param mixed $b second element in the pair
  221. */
  222. function insert($offset, $a, $b) {
  223. array_splice($this->a, $offset, 0, $a);
  224. array_splice($this->b, $offset, 0, $b);
  225. $this->count++;
  226. }
  227. /**
  228. * inserts a pair ($a, $b) in the sub-array of pairs
  229. * between $lo and $hi, assuming the array is ordered,
  230. * comparing only the first elements of each pair
  231. * (assumes there aren't duplicates)
  232. * uses {@link BPT_keycmp} for comparing
  233. *
  234. * @param mixed $a first element of the pair
  235. * @param mixed $b second element of the pair
  236. * @param int $lo starting offset of the sub-array
  237. * @param int|nul $hi ending offset of the sub-array
  238. */
  239. function insort($a, $b, $lo=0, $hi=null) {
  240. if (is_null($hi)) $hi=$this->count;
  241. $A = $this->a;
  242. $X = $a;
  243. while($lo<$hi) {
  244. $mid = (int)(($lo+$hi)/2);
  245. if (BPT_keycmp($X,$A[$mid])<0) $hi=$mid;
  246. else $lo=$mid+1;
  247. }
  248. $this->insert($lo, $a, $b);
  249. }
  250. /**
  251. * removes the pair at the offset $offset
  252. * @param int $offset offset of the pair targeted for deletion
  253. */
  254. function remove($offset) {
  255. array_splice($this->a, $offset, 1);
  256. array_splice($this->b, $offset, 1);
  257. $this->count--;
  258. }
  259. /**
  260. * append at the end of the current object the contents
  261. * of another pairs object
  262. *
  263. * @param pairs $pairs a pair object of which the contents
  264. * will be appended to this
  265. */
  266. function append(&$pairs) {
  267. array_splice($this->a, $this->count, 0, $pairs->a);
  268. array_splice($this->b, $this->count, 0, $pairs->b);
  269. $this->count+=$pairs->count;
  270. }
  271. /**
  272. * make the object fields read-only
  273. */
  274. function __set($x,$y) {
  275. trigger_error("Can't edit pairs directly'", E_USER_ERROR);
  276. }
  277. }
  278. if (BPT_SORT == SORT_ASC) {
  279. /**
  280. * compares key $a and $b using a less-than or greather-than relation
  281. * depending on {@link BPT_SORT} constants
  282. *
  283. * the function is very simple, returns strcmp($a,$b) or -strcmp($a,$b)
  284. * depending on the BPT_SORT constant: to be a little bit faster, no check is done
  285. * by the function itself; instead it is <strong>defined</strong> at load time, depending
  286. * on the value of the BPT_SORT constant
  287. *
  288. */
  289. function BPT_keycmp($a,$b) { return strcmp($a,$b); }
  290. } else {
  291. function BPT_keycmp($a,$b) { return -strcmp($a,$b); }
  292. }
  293. /*
  294. function _BPT_bisect($a, $x, $lo=0, $hi=null) {
  295. if (is_null($hi))
  296. $hi=count($a);
  297. while($lo<$hi && $a[$lo++]<$x) ;
  298. return $lo;
  299. }
  300. */
  301. /**
  302. * locate an element $x or the nearest bigger one
  303. * in the array $a, starting from offset $lo
  304. * and limiting to offset $hi, assuming that $a is
  305. * ordered by the relation BPT_keycmp
  306. *
  307. * @param mixed $a source array
  308. * @param mixed $x element to find
  309. * @param int $lo leftmost offset
  310. * @param int|null $hi rightmost offset
  311. *
  312. * @returns integer
  313. *
  314. */
  315. function BPT_bisect($a, $x, $lo=0, $hi=null) {
  316. if (is_null($hi)) {
  317. $hi = count($a);
  318. }
  319. while ($lo < $hi) {
  320. $mid = (int)(($lo+$hi)/2);
  321. #if ($x < $a[$mid])
  322. if (BPT_keycmp($x,$a[$mid])<0)
  323. $hi = $mid;
  324. else
  325. $lo = $mid+1;
  326. }
  327. return $lo;
  328. }
  329. /*
  330. function BPT_insort(&$a, $x, $lo=0, $hi=null) {
  331. if (is_null($hi))
  332. $hi = count($a);
  333. while ($lo<$hi) {
  334. $mid = (int) (($lo+$hi)/2);
  335. if ($x < $a[$mid])
  336. $hi = $mid;
  337. else
  338. $lo = $mid+1;
  339. }
  340. array_splice($a, $lo, 0, array($x));
  341. }
  342. */
  343. /*
  344. * fifo of bplustree nodes
  345. */
  346. class BPlusTree_Node_Fifo {
  347. /**
  348. * @var array array of elements
  349. */
  350. var $fifo = array();
  351. /**
  352. * @var array dictionary (associative array) of elements
  353. */
  354. var $fifo_dict = array();
  355. /**
  356. * var int size of the fifo
  357. */
  358. var $size;
  359. /**
  360. * constructor
  361. * @param int $size specifies size (defaults to 30)
  362. */
  363. function BPlusTree_Node_Fifo($size=30) {
  364. $this->fifosize=$size;
  365. }
  366. /**
  367. * flushes all of the contents of the fifo
  368. * to disk
  369. */
  370. function flush_fifo(){
  371. reset($this->fifo);
  372. while(list(,$node)=each($this->fifo)){
  373. if ($node->dirty) {
  374. $node->store(1);
  375. }
  376. }
  377. $this->fifo = array();
  378. $this->fifo_dict = array();
  379. }
  380. }
  381. /**
  382. * defines structure and methods of the node
  383. * of a bplustree
  384. */
  385. class BPlusTree_Node {
  386. /**
  387. * @var integer flags (defined as BPT_* constants)
  388. * specifying the nature of the node (leaf, interior, and combos)
  389. *
  390. */
  391. var $flag;
  392. /**
  393. * @var integer number of child elements (or values, if a leaf)
  394. *
  395. */
  396. var $size;
  397. /**
  398. * @var int seek position in the file
  399. *
  400. */
  401. var $position;
  402. /**
  403. * @var resource stream where to output the data
  404. * (typically a file open with fopen())
  405. */
  406. var $infile;
  407. /**
  408. * @var int maximum lenght of a string key
  409. */
  410. var $keylen;
  411. /**
  412. * @var array array of strings, containing keys, of size $size
  413. */
  414. var $keys;
  415. /**
  416. * @var array array of longs, of size $size+1
  417. * if leaf, elements in [0,$size] are the values of each key in $keys:
  418. * at offset $size - ($size+1)-th element - there's the seek
  419. * position of the next leaf (or BPT_NULLSEEK if rightmost leaf)
  420. *
  421. * if interior,
  422. *
  423. * - offset 0 points to the child node where keys are
  424. * are all LESS than those in this node (actually, to $keys[0]),
  425. *
  426. * - offset 1 points to the child node where keys are GREATER or EQUAL to $keys[0]
  427. * but LESS than $keys[1],
  428. *
  429. * - offset 2 points to the child node where keys are >= $keys[1] but < $keys[2], etc...
  430. *
  431. *
  432. * with LESS, GREATER we always mean by the relation {@link BPT_keycmp}
  433. *
  434. *
  435. */
  436. var $indices;
  437. /**
  438. * @var bool controls deferred writes (using fifo)
  439. *
  440. */
  441. var $dirty= false;
  442. /**
  443. * @var BPlusTree_Node_Fifo object of type {@link BPlusTree_Node_Fifo}
  444. */
  445. var $fifo = null;
  446. /**
  447. * @var int number of valid keys in $keys
  448. */
  449. var $validkeys;
  450. /**
  451. * constructor
  452. * @param int $flag flag of current node
  453. * @param int $size size of node
  454. * @param int $keylen max key length
  455. * @param long $position seek position in file
  456. * @param resource resource stream (opened file)
  457. * @param BPlusTree_Node object from which cloning properties
  458. */
  459. function BPlusTree_Node($flag,
  460. $size,
  461. $keylen,
  462. $position,
  463. $infile,
  464. $cloner = null) {
  465. $this->flag = $flag;
  466. if ($size < 0) {
  467. trigger_error('size must be positive', E_USER_ERROR);
  468. }
  469. $this->size = $size;
  470. $this->keylen = $keylen;
  471. $this->position = $position;
  472. $this->infile = $infile;
  473. // last (+1) is successor seek TODO move to its own!
  474. $this->indices = array_fill(0, $size+1, BPT_NULL);
  475. $this->keys = array_fill(0, $size, '');
  476. if (is_null($cloner)) {
  477. $this->storage = 2 +
  478. /* 2 chars for flag, validkeys */
  479. $size*4+4 + /* n 4B-long indices + 1 4B-long next pointer*/
  480. $size*$keylen ; /* n keylen-bytes long keys */
  481. } else {
  482. $this->storage = $cloner->storage;
  483. $this->fifo = $cloner->fifo;
  484. }
  485. if ($flag == BPT_FLAG_INTERIOR || $flag == BPT_FLAG_ROOT) {
  486. $this->validkeys = -1;
  487. } else {
  488. $this->validkeys = 0;
  489. }
  490. }
  491. /**
  492. *
  493. * reinitialize keys
  494. *
  495. */
  496. function clear() {
  497. $size = $this->size;
  498. // re-init keys
  499. $this->keys = array_fill(0, $size, '');
  500. $this->validkeys = 0;
  501. if (($this->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
  502. // re-init all indices
  503. $this->indices = array_fill(0, $size+1, BPT_NULL);
  504. $this->validkeys = -1;
  505. } else {
  506. $fwd = $this->indices[$size]; // forward pointer
  507. $this->indices = array_fill(0, $size, BPT_NULL);
  508. $this->keys = array_fill(0, $size, '');
  509. $this->indices[] = $fwd;
  510. }
  511. }
  512. /**
  513. * returns clone of the obect at position $position
  514. * @param long $position seek position
  515. */
  516. function &getclone($position) {
  517. if ($this->fifo) {
  518. $dict =& $this->fifo->fifo_dict;
  519. if (isset($dict[$position])) {
  520. return $dict[$position];
  521. }
  522. }
  523. $o = new BPlusTree_Node(
  524. $this->flag,
  525. $this->size,
  526. $this->keylen,
  527. $position,
  528. $this->infile,
  529. $this
  530. );
  531. return $o;
  532. }
  533. /**
  534. * put first index (seek position for less-than child)
  535. *
  536. * @param int $index seek position
  537. */
  538. function putfirstindex($index) {
  539. if ($this->validkeys>=0)
  540. trigger_error("Can't putfirstindex on full node", E_USER_ERROR);
  541. $this->indices[0] = $index;
  542. $this->validkeys = 0;
  543. }
  544. /**
  545. * links node $node to this node as a child, using key $key
  546. * (this node must be interior)
  547. *
  548. * @param string $key key string
  549. * @param object $node node to link
  550. *
  551. */
  552. function putnode($key, &$node) {
  553. $position = $node->position;
  554. return $this->putposition($key, $position);
  555. # if ($x == NOROOMERROR) {print_r(debug_backtrace());fail();}
  556. }
  557. /*
  558. *
  559. * links a seek position $position to the key $key
  560. *
  561. * @param string $key key string
  562. * @param int $position seek position (pointer to the new child node)
  563. *
  564. */
  565. function putposition($key, $position) {
  566. if (($this->flag & BPT_FLAG_INTERIOR) != BPT_FLAG_INTERIOR) {
  567. trigger_error("Can't insert into leaf node", E_USER_ERROR);
  568. }
  569. $validkeys = $this->validkeys;
  570. $last = $this->validkeys+1;
  571. if ($this->validkeys>=$this->size) {
  572. #trigger_error('No room error', E_USER_WARNING);
  573. return NOROOMERROR;
  574. }
  575. // store the key
  576. if ($validkeys<0) { // no nodes currently
  577. d("no keys");
  578. $this->validkeys = 0;
  579. $this->indices[0] = $position;
  580. } else {
  581. // there are nodes
  582. $keys =& $this->keys;
  583. // is the key there already?
  584. if (in_array($key, $keys, true)) {
  585. if (array_search($key, $keys, true) < $validkeys)
  586. trigger_error("reinsert of node for existing key ($key)",
  587. E_USER_ERROR);
  588. }
  589. $place = BPT_bisect($keys, $key, 0, $validkeys);
  590. // insert at position $place
  591. array_splice($keys, $place, 0, $key);
  592. // delete last element
  593. unset($keys[$last]);
  594. $keys = array_values($keys); # reset array indices
  595. #array_splice($keys, $last, 1);
  596. // store the index
  597. $indices =& $this->indices;
  598. #echo "inserting $position before ", var_dump($indices,1), "\n";
  599. array_splice($indices, $place+1, 0, $position);
  600. unset($indices[$last+1]);
  601. $indices = array_values($indices);
  602. #array_splice($indices, $last+1, 1);
  603. $this->validkeys = $last;
  604. }
  605. }
  606. /**
  607. * deletes from interior nodes
  608. *
  609. * @param string $key target key
  610. */
  611. function delnode($key) {
  612. // {{{
  613. if (($this->flag & BPT_FLAG_INTERIOR) != BPT_FLAG_INTERIOR) {
  614. trigger_error("Can't delete node from leaf node");
  615. }
  616. if ($this->validkeys < 0) {
  617. trigger_error("No such key (empty)");
  618. }
  619. $validkeys = $this->validkeys;
  620. $indices =& $this->indices;
  621. $keys =& $this->keys;
  622. if (is_null($key)) {
  623. $place = 0;
  624. $indexplace = 0;
  625. } else {
  626. $place = array_search($key, $keys, true);
  627. $indexplace = $place+1;
  628. }
  629. #unset($indices[$indexplace]);
  630. array_splice($indices, $indexplace, 1);
  631. $indices[] = BPT_NULLSEEK;
  632. #$indices = array_values($indices);
  633. #unset($keys[$place]);
  634. array_splice($keys, $place, 1);
  635. $keys[] = '';
  636. #$keys = array_values($keys);
  637. $this->validkeys = $validkeys - 1;
  638. }
  639. // }}}
  640. /**
  641. * slices the $this->keys array to the number of valid keys
  642. * in $this->validkeys
  643. *
  644. * @returns array array of valid keys
  645. */
  646. function get_keys() {
  647. $validkeys = $this->validkeys;
  648. if ($validkeys<=0) {
  649. return array();
  650. }
  651. return array_slice($this->keys, 0, $validkeys);
  652. }
  653. /*
  654. * mimics python's map(None, a, b)
  655. * returns the list of (a,b) pairs
  656. * where a is in list $a and b is in list $b
  657. *
  658. *
  659. function _oldpairs($a, $b) {
  660. $c = array();
  661. reset($a);
  662. reset($b);
  663. while((list(,$v1) = each($a)) &&
  664. (list(,$v2) = each($b))) {
  665. $c[] = array($v1, $v2);
  666. }
  667. return $c;
  668. }
  669. */
  670. /**
  671. * mimic's python's map(None, a, b);
  672. * a, b must be of the same size
  673. *
  674. * @param array $a first array
  675. * @param array $b second array
  676. *
  677. * @returns object {@link pairs}
  678. */
  679. function &_pairs($a, $b) {
  680. $x = new pairs($a,$b);
  681. return $x;
  682. }
  683. /**
  684. * returns an object containing pairs (key, index)
  685. * for all of the valid keys and indices
  686. *
  687. * @param string $leftmost leftmost key corresponding
  688. * to first index (seek) in interior nodes; ignored in leaves
  689. *
  690. * @returns object pairs
  691. *
  692. */
  693. function keys_indices($leftmost) {
  694. $keys = $this->get_keys();
  695. if (($this->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
  696. // interior nodes start with
  697. // the pointer to the "less than key[0]" subtree:
  698. // we need pairs (key, indices) so we add the leftmost key
  699. // on top
  700. array_unshift($keys, $leftmost);
  701. }
  702. $indices = array_slice($this->indices, 0, count($keys));
  703. return $this->_pairs($keys, $indices);
  704. }
  705. /**
  706. * returns child, searching for $key in an interior node
  707. *
  708. * @param string $key target $key
  709. * @returns object BPlusTree_Node
  710. *
  711. */
  712. function &getnode($key) {
  713. if (($this->flag & BPT_FLAG_INTERIOR) != BPT_FLAG_INTERIOR) {
  714. trigger_error("cannot getnode from leaf node", E_USER_ERROR);
  715. }
  716. if (is_null($key))
  717. $index = 0;
  718. else
  719. $index = array_search($key, $this->keys, true)+1;
  720. $place = $this->indices[$index];
  721. if ($place<0) {
  722. debug_print_backtrace();
  723. trigger_error("Invalid position! ($place, $key)", E_USER_ERROR);
  724. }
  725. // fifo
  726. $fifo =& $this->fifo;
  727. if ($fifo) {
  728. $ff =& $fifo->fifo;
  729. $fd =& $fifo->fifo_dict;
  730. if (isset($fd[$place])) {
  731. $node =& $fd[$place];
  732. #unset($ff[$place]);
  733. $idx = array_search($node, $ff, true);
  734. array_splice($ff, $idx, 1);
  735. array_unshift($ff, $node);
  736. return $node;
  737. }
  738. }
  739. $node =& $this->getclone($place);
  740. $node =& $node->materialize();
  741. return $node;
  742. }
  743. /***** leaf mode operations *****/
  744. /**
  745. * if leaf returns the next leaf on the right
  746. *
  747. */
  748. function &next() {
  749. if (($this->flag & BPT_LEAF_FLAG) != BPT_FLAG_LEAF) {
  750. trigger_error("cannot get next for non-leaf", E_USER_ERROR);
  751. }
  752. $place = $this->indices[$this->size];
  753. if ($place == BPT_NULLSEEK)
  754. return null;
  755. else {
  756. $node =& $this->getclone($place);
  757. $node =& $node->materialize();
  758. return $node;
  759. }
  760. }
  761. /*
  762. function &prev() {
  763. if (($this->flag & BPT_LEAF_FLAG) != BPT_FLAG_LEAF) {
  764. trigger_error("cannot get next for non-leaf", E_USER_ERROR);
  765. }
  766. $place = $this->prev;
  767. if ($place == BPT_NULLSEEK)
  768. return null;
  769. else {
  770. $node =& $this->getclone($place);
  771. $node =& $node->materialize();
  772. return $node;
  773. }
  774. }
  775. */
  776. /**
  777. * put ($key, $val) in a leaf
  778. *
  779. * @param string $key target string
  780. * @param int $val value for $key
  781. */
  782. function putvalue($key, $val) {
  783. if (!is_string($key))
  784. trigger_error("$key must be string", E_USER_ERROR);
  785. if (($this->flag & BPT_FLAG_LEAF) != BPT_FLAG_LEAF) {
  786. #print_r($this);
  787. trigger_error("cannot get next for non-leaf ($key)", E_USER_ERROR);
  788. }
  789. $validkeys = $this->validkeys;
  790. $indices =& $this->indices;
  791. $keys =& $this->keys;
  792. if ($validkeys<=0) { // empty
  793. // first entry
  794. $indices[0] = $val;
  795. $keys[0] = $key;
  796. $this->validkeys = 1;
  797. } else {
  798. $place = null;
  799. if (in_array($key, $keys, true)) {
  800. $place = array_search($key, $keys, true);
  801. if ($place >= $validkeys) {
  802. $place = null;
  803. }
  804. }
  805. if (!is_null($place)) {
  806. $keys[$place] = $key;
  807. $indices[$place] = $val;
  808. } else {
  809. if ($validkeys >= $this->size) {
  810. #trigger_error("no room", E_USER_WARNING);
  811. return NOROOMERROR;
  812. }
  813. $place = BPT_bisect($keys, $key, 0, $validkeys);
  814. $last = $validkeys+1;
  815. # del keys[validkeys]
  816. # del indices[validkeys]
  817. #array_splice($keys, $validkeys, 1);
  818. unset($keys[$validkeys]);
  819. $keys = array_values($keys);
  820. #array_splice($indices, $validkeys, 1);
  821. unset($indices[$validkeys]);
  822. $indices = array_values($indices);
  823. array_splice($keys, $place, 0, $key);
  824. array_splice($indices, $place, 0, $val);
  825. #echo implode(', ', $keys), " ::: $place \n";
  826. $this->validkeys = $last;
  827. }
  828. }
  829. }
  830. /**
  831. * for each $key, $index in $keys_indices
  832. * put the correspoding values (assumes this is a leaf)
  833. *
  834. * @param object $keys_indices object of type {@link pairs}
  835. */
  836. function put_all_values($keys_indices) {
  837. $this->clear();
  838. $indices =& $this->indices;
  839. $keys =& $this->keys;
  840. $length = $this->validkeys = $keys_indices->count;#count($keys_indices);
  841. if ($length > $this->size)
  842. trigger_error("bad length $length", E_USER_ERROR);
  843. for ($i=0; $i<$length; $i++) {
  844. #list($keys[$i], $indices[$i]) = $keys_indices[$i];
  845. $keys[$i] = $keys_indices->a[$i];
  846. $indices[$i] = $keys_indices->b[$i];
  847. }
  848. }
  849. /**
  850. * for each $key, $index in $keys_indices
  851. * put the correspoding seek positions (assumes this is an interior node)
  852. *
  853. * @param int $first_position leftmost pointer (to less-than child)
  854. * @param object $keys_indices object of type {@link pairs}
  855. *
  856. */
  857. function put_all_positions($first_position, $keys_positions) {
  858. $this->clear();
  859. $indices =& $this->indices;
  860. $keys =& $this->keys;
  861. $length = $this->validkeys = $keys_positions->count;#count($keys_positions);
  862. if ($length > $this->size) {
  863. trigger_error("bad length $length", E_USER_ERROR);
  864. }
  865. $indices[0] = $first_position;
  866. for ($i=0; $i<$length; $i++) {
  867. #list($keys[$i], $indices[$i+1]) = $keys_positions[$i];
  868. $keys[$i] = $keys_positions->a[$i];
  869. $indices[$i+1] = $keys_positions->b[$i];
  870. }
  871. }
  872. /**
  873. * assuming this is a leaf, returns value for $key
  874. * @param $key string target key
  875. * @returns int|false corresponding integer or false if key is missing
  876. *
  877. */
  878. function getvalue(&$key, $loose=false) {
  879. #d(implode(",",$this->keys));
  880. #$place = array_search($key, $this->keys);
  881. $place = BPT_bisect($this->keys, $key, 0, $this->validkeys);
  882. if ($this->keys[$place-1] == $key) {
  883. return $this->indices[$place-1];
  884. } else {
  885. if ($loose) {
  886. if ($place>1) $place--;
  887. $key = $this->keys[$place];
  888. return $this->indices[$place];
  889. }
  890. trigger_error("key '$key' not found", E_USER_WARNING);
  891. return false;
  892. }
  893. }
  894. /**
  895. * if leaf, creates a neighbor for this node: a new leaf
  896. * linked to this
  897. *
  898. * @param int $position seek position for the new neighbor?
  899. * @returns object BPlusTree_Node
  900. *
  901. */
  902. function &newneighbour($position) {
  903. if (($this->flag & BPT_FLAG_LEAF) != BPT_FLAG_LEAF)
  904. trigger_error('cannot make leaf neighbour for non-leaf');
  905. // create clone
  906. $neighbour =& $this->getclone($position);
  907. $size = $this->size;
  908. $indices =& $this->indices;
  909. // linking siblings
  910. $neighbour->indices[$size] = $indices[$size];
  911. $indices[$size] = $position;
  912. return $neighbour;
  913. }
  914. /**
  915. * if leaf, returns the leaf next to this
  916. * @return object BPlusTree_Node
  917. */
  918. function &nextneighbour() {
  919. if (($this->flag & BPT_FLAG_LEAF) != BPT_FLAG_LEAF)
  920. trigger_error('cannot get leaf neighbour for non-leaf');
  921. $size = $this->size;
  922. $position = $this->indices[$size];
  923. if ($position == BPT_NULLSEEK) {
  924. $neighbour = null;
  925. } else {
  926. $neighbour = $this->getclone($position);
  927. $neighbour = $neighbour->materialize();
  928. }
  929. return $neighbour;
  930. }
  931. /*
  932. function &prevneighbour() {
  933. if (($this->flag & BPT_FLAG_LEAF) != BPT_FLAG_LEAF)
  934. trigger_error('cannot get leaf neighbour for non-leaf');
  935. #$size = $this->size;
  936. $position = $this->prev; # $this->indices[$size];
  937. if ($position == BPT_NULLSEEK) {
  938. return null;
  939. } else {
  940. $neighbour = $this->getclone($position);
  941. $neighbour = $neighbour->materialize();
  942. return $neighbour;
  943. }
  944. }*/
  945. /**
  946. * if leaf, deletes neighbor on the right, and re-link
  947. * with the following
  948. *
  949. * @param object $next target for deletion
  950. * @param free $free seek position of last free node in free list
  951. *
  952. * @returns int new free position
  953. */
  954. function delnext(&$next, $free) {
  955. d("delnext called:");
  956. #print_r($this);
  957. $size = $this->size;
  958. if ($this->indices[$size]!=$next->position) {
  959. trigger_error("invalid next pointer ".
  960. "{$this->indices[$size]}!={$next->position})", E_USER_ERROR);
  961. }
  962. $this->indices[$size] = $next->indices[$size];
  963. return $next->free($free);
  964. }
  965. /**
  966. * if leaf, deletes corresponding value
  967. *
  968. * @param string $key target key
  969. */
  970. function delvalue($key) {
  971. $keys =& $this->keys;
  972. $indices =& $this->indices;
  973. if (!in_array($key, $keys, true)) {
  974. d($keys);
  975. trigger_error ("missing key, can't delete", E_USER_ERROR);
  976. }
  977. $place = array_search($key, $keys, true);
  978. $validkeys = $this->validkeys;
  979. $prev = $validkeys-1;
  980. # delete
  981. array_splice($keys, $place, 1);
  982. array_splice($indices, $place, 1);
  983. #unset($keys[$place]);
  984. #$keys[]='';
  985. #$keys = array_values($keys);
  986. #unset($indices[$place]);
  987. #$indices[] = BPT_NULL;
  988. #$indices = array_values($indices);
  989. # insert NULLs/empties
  990. array_splice($keys, $prev, 0, '');
  991. array_splice($indices, $prev, 0, BPT_NULL);
  992. $this->validkeys=$prev;//validkeys-1
  993. }
  994. /*
  995. * add self to free list, retunr position as new free position
  996. *
  997. * @param int $freenodeposition current last free node
  998. *
  999. */
  1000. function free($freenodeposition) {
  1001. $this->flag = BPT_FLAG_FREE;
  1002. $this->indices[0] = $freenodeposition;
  1003. $this->store();
  1004. return $this->position;
  1005. }
  1006. /*
  1007. * assuming self is head of free list,
  1008. * pop self off freelist, return next free position;
  1009. * does not update file
  1010. *
  1011. * @param integer $flag flag for new node
  1012. * @return object new node
  1013. *
  1014. function unfree($flag) {
  1015. $next = $this->indices[0];
  1016. $this->flag = $flag;
  1017. $this->validkeys = 0;
  1018. $this->indices[0] = BPT_NULLSEEK;
  1019. $this->clear();
  1020. return $next;
  1021. }
  1022. */
  1023. /**
  1024. * get free node of same shape as self from $this->file;
  1025. * make one if none exist;
  1026. * assume $freeposition is seek position of next free node
  1027. *
  1028. * @param int $freeposition seek position of next freenode
  1029. * @param callback $freenode_callback is specified it is a func to call
  1030. * with a new free list head, if needed
  1031. *
  1032. * @returns array(&$node, $newfreeposition)
  1033. *
  1034. *
  1035. *
  1036. *
  1037. */
  1038. function getfreenode($freeposition, $freenode_callback=null) {
  1039. d("GETTING FREE AT $freeposition");
  1040. if ($freeposition == BPT_NULLSEEK) {
  1041. $file = $this->infile;
  1042. fseek($file, 0, SEEK_END);
  1043. $position = ftell($file);
  1044. d("ALLOCATING SPACE...");
  1045. $thenode =& $this->getclone($position);
  1046. $thenode->store();
  1047. return array(&$thenode, BPT_NULLSEEK);
  1048. } else {
  1049. $position = $freeposition;
  1050. $thenode = $this->getclone($position);
  1051. // get old node
  1052. $thenode = $thenode->materialize();
  1053. // ptr to next
  1054. $next = $thenode->indices[0];
  1055. if (!is_null($freenode_callback)) {
  1056. call_user_func($freenode_callback, $next);
  1057. }
  1058. $thenode->BplusTree_Node(
  1059. $this->flag,
  1060. $this->size,
  1061. $this->keylen,
  1062. $position,
  1063. $this->infile
  1064. );
  1065. $thenode->store(); // save reinit'ed node
  1066. return array(&$thenode, $next);
  1067. }
  1068. }
  1069. /**
  1070. *
  1071. * write this to file
  1072. *
  1073. * @param bool $force forces write back if fifo is enabled, defaults to false
  1074. *
  1075. */
  1076. function store($force = false) {
  1077. // {{{
  1078. $position = $this->position;
  1079. if (is_null($position))
  1080. trigger_error("position cannot be null",E_USER_ERROR);
  1081. $fifo =& $this->fifo;
  1082. if (!$force && $fifo) {
  1083. $fd =& $fifo->fifo_dict;
  1084. if (isset($fd[$this->position]) && $fd[$position] === $this) {
  1085. $this->dirty = true;
  1086. return; // defer processing
  1087. }
  1088. }
  1089. $f = $this->infile;
  1090. fseek($f, $position);
  1091. $data = $this->linearize();
  1092. fwrite($f, $data);
  1093. $last = ftell($f);
  1094. $this->dirty = false;
  1095. if (!$force && $this->fifo) {
  1096. $this->add_to_fifo();
  1097. }
  1098. return $last;
  1099. }
  1100. //}}}
  1101. /**
  1102. * load node from file
  1103. *
  1104. * @returns object BPlusTree_Node
  1105. *
  1106. */
  1107. function &materialize() {
  1108. $position = $this->position;
  1109. if ($this->fifo) {
  1110. $fifo =& $this->fifo;
  1111. $dict =& $fifo->fifo_dict;
  1112. $ff =& $fifo->fifo;
  1113. if (isset($dict[$position])) {
  1114. $node =& $dict[$position];
  1115. if ($node !== $ff[0]) {
  1116. $nidx = array_search($node, $ff, true);
  1117. unset($ff[$nidx]);
  1118. array_unshift($ff, $node);
  1119. }
  1120. return $node;
  1121. }
  1122. }
  1123. $f = $this->infile;
  1124. fseek($f, $position);
  1125. $data = fread($f, $this->storage);
  1126. $this->delinearize($data);
  1127. if ($this->fifo) {
  1128. $this->add_to_fifo();
  1129. }
  1130. return $this;
  1131. }
  1132. /**
  1133. * @returns string binary string encoding this node
  1134. */
  1135. function linearize() {
  1136. $params = array(
  1137. 'C2L'.($this->size+1),
  1138. $this->flag,
  1139. $this->validkeys
  1140. );
  1141. foreach($this->indices as $i)
  1142. $params[] = $i;
  1143. $s = call_user_func_array('pack', $params);
  1144. $x = '';
  1145. for($i = 0; $i<$this->validkeys; $i++) {
  1146. $k = $this->keys[$i];
  1147. if (strlen($k)>$this->keylen)
  1148. trigger_error("Invalid keylen for '$k'", E_USER_ERROR);
  1149. $x .= str_pad($k, $this->keylen, chr(0));
  1150. }
  1151. $x = str_pad($x, $this->size*$this->keylen, chr(0));
  1152. $s .= $x;
  1153. $l = strlen($s);
  1154. if (strlen($s) != $this->storage) {
  1155. trigger_error("bad storage $l != {$this->storage}", E_USER_ERROR);
  1156. }
  1157. return $s;
  1158. }
  1159. /**
  1160. * get properties of this node from the string $s encoded via {@link BPlusTree_Node::linearize}
  1161. *
  1162. * @param string $s binary string
  1163. *
  1164. */
  1165. function delinearize($s) {
  1166. //{{{
  1167. if (strlen($s)!=$this->storage)
  1168. trigger_error("bad storage", E_USER_ERROR);
  1169. $x = 'Cflag/Cvalidkeys/';
  1170. $n = $this->size+1;
  1171. for ($i = 0; $i<$n; $i++) {
  1172. $x .= "lindices{$i}/";
  1173. }
  1174. $arr = unpack($x, $s);
  1175. $this->flag = $arr['flag'];
  1176. $this->validkeys = $arr['validkeys'];
  1177. for ($i = 0; $i<$n; $i++) {
  1178. $this->indices[$i] = $arr["indices{$i}"];
  1179. }
  1180. for ($i = 0, $j = ($n*4+2); $i<$this->validkeys; $i++, $j+=$this->keylen) {
  1181. $this->keys[$i] = rtrim(substr($s, $j, $this->keylen));
  1182. }
  1183. }
  1184. //}}}
  1185. // foo dump
  1186. /**
  1187. *
  1188. * prints a dump of the tree on scree
  1189. * @param string $indent custom indentation
  1190. *
  1191. */
  1192. function dump($indent='') {
  1193. //{{{
  1194. $flag = $this->flag;
  1195. if ($flag == BPT_FLAG_FREE) {
  1196. echo "free->", $this->position, "\n";
  1197. $nextp = $this->indices[0];
  1198. if ($nextp!=BPT_NULLSEEK) {
  1199. $next =& $this->getclone($nextp);
  1200. $next =& $next->materialize();
  1201. $next->dump();
  1202. } else {
  1203. echo "!last\n";
  1204. }
  1205. return;
  1206. }
  1207. $nextindent = $indent . " ";
  1208. echo $indent;
  1209. switch ($flag) {
  1210. case BPT_FLAG_ROOT: echo "root"; break;
  1211. case BPT_FLAG_INTERIOR: echo "interior"; break;
  1212. case BPT_FLAG_LEAF: echo "leaf"; break;
  1213. case BPT_FLAG_LEAFANDROOT: echo "root&leaf"; break;
  1214. default : echo "invalid flag??? ", $flag;
  1215. }
  1216. echo "($flag) ";
  1217. echo " ", $this->position, " valid=", $this->validkeys, "\n";
  1218. echo $indent, "keys {", implode(', ', $this->keys), "}\n";
  1219. echo $indent, "seeks {", implode(", ", $this->indices),"}\n";
  1220. if (($flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
  1221. reset($this->indices);
  1222. while(list(,$i) = each($this->indices)) {
  1223. if ($i!=BPT_NULLSEEK) {
  1224. // interior
  1225. $n =& $this->getclone($i);
  1226. $n =& $n->materialize();
  1227. $n->dump($nextindent);
  1228. } else {
  1229. //leaf
  1230. continue;
  1231. }
  1232. }
  1233. }
  1234. echo $indent, "*****\n";
  1235. }//}}}*/
  1236. /**
  1237. * adds this node to fifo
  1238. */
  1239. function add_to_fifo() {
  1240. $fifo =& $this->fifo;
  1241. $ff =& $fifo->fifo;
  1242. $dict =& $fifo->fifo_dict;
  1243. $position = $this->position;
  1244. if(isset($dict[$position])) {
  1245. $old =& $dict[$position];
  1246. unset($dict[$position]);
  1247. # ff.remove(old)
  1248. array_splice($ff, array_search($old, $ff, true), 1);
  1249. }
  1250. $dict[$this->position] =& $this;
  1251. array_splice($ff, 0, 0, array(&$this));
  1252. if (count($ff)>$this->fifo->fifosize) {
  1253. $lastidx = count($ff)-1;
  1254. $last = $ff[$lastidx];
  1255. unset($ff[$lastidx]);
  1256. unset($dict[$last->position]);
  1257. if ($last->dirty) {
  1258. $last->store(true);
  1259. }
  1260. }
  1261. $is_o=true;
  1262. while((list(,$v)=each($ff)) && $is_o=is_object($v));
  1263. if (!$is_o) {trigger_error('ERR', E_USER_ERROR);}
  1264. }
  1265. /**
  1266. * @param int $size defaults to 33
  1267. *
  1268. */
  1269. function enable_fifo($size = 33) {
  1270. if ($size<5 || $size>1000000) {
  1271. trigger_error("size not valid $size");
  1272. }
  1273. $this->fifo = new BPlusTree_Node_Fifo($size);
  1274. }
  1275. /**
  1276. * disables fifo (first flushes to disk)
  1277. *
  1278. */
  1279. function disable_fifo() {
  1280. if ($this->fifo) {
  1281. $this->fifo->flush_fifo();
  1282. $this->fifo = null;
  1283. }
  1284. }
  1285. }
  1286. /**
  1287. * main class BPlusTree
  1288. * creates a B+Tree with string keys and integer values
  1289. *
  1290. * public methods are only {@link BPlusTree::getitem}
  1291. * {@link BPlusTree::setitem} {@link BPlusTree::delitem}
  1292. * {@link BPlusTree::walker}
  1293. *
  1294. *
  1295. */
  1296. class BPlusTree {
  1297. /**
  1298. * @var int number of values
  1299. */
  1300. var $length = null;
  1301. /**
  1302. * @var bool used for deferred writes (if fifo is enabled
  1303. */
  1304. var $dirty = false;
  1305. # var $headerformat = "%10d %10d %10d %10d %10d\n";
  1306. /**
  1307. * @var int seek position of root in file
  1308. */
  1309. var $root_seek = BPT_NULLSEEK;
  1310. /**
  1311. * @var int seek position of the start of the freelist
  1312. *
  1313. */
  1314. var $free = BPT_NULLSEEK;
  1315. /**
  1316. * @var object BPlusTree_Node root node
  1317. */
  1318. var $root = null; /* */
  1319. /**
  1320. * @var int length of the file header in bytes
  1321. */
  1322. var $headersize;
  1323. /**
  1324. * @var bool true if fifo is enabled
  1325. */
  1326. var $fifo_enabled = false;
  1327. /**
  1328. * constructor
  1329. * @param resource $infile resource of open file
  1330. * @param int $position offset from the beginning of the file (usually 0)
  1331. * @param int $nodesize size of the node
  1332. * @param int $keylen maximum lenght of a key in bytes (unicode extended chars evaluate to two chars)
  1333. */
  1334. function BPlusTree($infile, $pos=null, $nodesize=null, $keylen=10) {
  1335. if (!is_null($keylen) && $keylen<=2) {
  1336. trigger_error("$keylen must be greater than 2", E_USER_ERROR);
  1337. }
  1338. $this->root_seek = BPT_NULLSEEK;
  1339. $this->free = BPT_NULLSEEK;
  1340. $this->root = null;
  1341. $this->file = $infile;
  1342. #if ($nodesize<6) trigger_error("nodesize must be >= 6", E_USER_ERROR);
  1343. $this->nodesize = $nodesize;
  1344. $this->keylen = $keylen;
  1345. if (is_null($pos)) {
  1346. $pos = 0;
  1347. }
  1348. $this->position = $pos;
  1349. $this->headersize = 4*4+6; /* 4 4-byte longs, 1 char, 5-byte magic string*/
  1350. }
  1351. /**
  1352. * returns an iterator for the tree
  1353. * @param string $keylower key lower limit of the iterator
  1354. * @param bool|int $includelower if true $keylower is included in the iterator;
  1355. * if $includelower > 1 then 'loose' search is assumed:
  1356. * the tree will be walked starting from
  1357. * the key $k in the tree such as $k <= $keylower
  1358. * and such as there are NO other keys $k'
  1359. * such as $k < $k' <= $keylower
  1360. * @param string $keyupper key upper bound of the iterator
  1361. * @param bool $includeupper if true $keyupper is included in the iterator
  1362. */
  1363. function &walker(
  1364. &$keylower,
  1365. $includelower =null,
  1366. $keyupper =null,
  1367. $includeupper =null
  1368. ) {
  1369. $o = new BPlusWalker($this, $keylower, $includelower, $keyupper, $includeupper);
  1370. return $o;
  1371. }
  1372. /**
  1373. * @returns array array of properties of this object
  1374. */
  1375. function init_params() {
  1376. return array(
  1377. $this->file,
  1378. $this->position,
  1379. $this->nodesize,
  1380. $this->keylen
  1381. );
  1382. }
  1383. /**
  1384. * @returns object BPlusTree_Node of the root
  1385. */
  1386. function get_root() {
  1387. return $this->root;
  1388. }
  1389. /**
  1390. * updates the head of the freelist and writes back to file
  1391. * @param int $position seek position of the head of the freelist
  1392. */
  1393. function update_freelist($pos) {
  1394. if ($this->free!=$pos) {
  1395. $this->free = $pos;
  1396. $this->reset_header();
  1397. }
  1398. }
  1399. /**
  1400. * action to perform to setup a bplustree, header is reset, length truncated
  1401. * and a new root node is created
  1402. */
  1403. function startup() {
  1404. if (is_null($this->nodesize) || is_null($this->keylen)) {
  1405. trigger_error("cannot initialize without nodesize, keylen specified\n") ;
  1406. }
  1407. $this->length = 0;
  1408. $this->root_seek = 22; //pack('a5LCL3',...)
  1409. $this->reset_header();
  1410. $file = $this->file;
  1411. fseek($file, 0, SEEK_END);
  1412. $this->root = new BplusTree_Node(
  1413. BPT_FLAG_LEAFANDROOT,
  1414. $this->nodesize, $this->keylen, $this->root_seek, $file
  1415. );
  1416. $this->root->store();
  1417. }
  1418. /**
  1419. * reload the bplustree from file and setup for use
  1420. */
  1421. function open() {
  1422. $file = $this->file;
  1423. if ($this->get_parameters()===false)
  1424. return false;
  1425. $this->root = new BplusTree_Node(
  1426. BPT_FLAG_LEAFANDROOT,
  1427. $this->nodesize,
  1428. $this->keylen,
  1429. $this->root_seek,
  1430. $file
  1431. );
  1432. $this->root =& $this->root->materialize();
  1433. return true;
  1434. }
  1435. /**
  1436. * enable fifo
  1437. * @param int $size defaults to 33
  1438. */
  1439. function enable_fifo($size=33) {
  1440. $this->fifo_enabled = true;
  1441. $this->root->enable_fifo($size);
  1442. }
  1443. /**
  1444. * disables fifo (writes back header to file if needed)
  1445. *
  1446. */
  1447. function disable_fifo() {
  1448. $this->fifo_enabled = false;
  1449. if ($this->dirty) {
  1450. $this->reset_header();
  1451. $this->dirty = false;
  1452. }
  1453. $this->root->disable_fifo();
  1454. }
  1455. /**
  1456. *
  1457. * @returns string header string
  1458. */
  1459. function _makeheader() {
  1460. return pack('a5LCL3', BPT_VERSION_MAGIC,
  1461. $this->length, $this->keylen,
  1462. $this->nodesize, $this->root_seek, $this->free);
  1463. }
  1464. /**
  1465. * writes back header to file (if fifo is enabled write is deferred until
  1466. * fifo is again disabled
  1467. */
  1468. function reset_header() {
  1469. if ($this->fifo_enabled) {
  1470. $this->dirty = true;
  1471. d("[FIFO]: deferring header reset");
  1472. return;
  1473. }
  1474. $file = $this->file;
  1475. fseek($file, $this->position);
  1476. $s = $this->_makeheader();
  1477. fwrite($file, $s);
  1478. }
  1479. /**
  1480. * reads back properties/parameters of this tree from file;
  1481. * raises an error if version magic is wrong
  1482. *
  1483. * @returns bool false on failure, true on success
  1484. */
  1485. function get_parameters() {
  1486. $file = $this->file;
  1487. fseek($file, $this->position);
  1488. $data = fread($file, $this->headersize);
  1489. $hdr = unpack('a5magic/Llength/Ckeylen/Lnodesize/Lroot_seek/Lfree', $data);
  1490. if ($hdr['magic']!=BPT_VERSION_MAGIC) {
  1491. trigger_error("Version magic mismatch ({$hdr['magic']}!="
  1492. .BPT_VERSION_MAGIC.')', E_USER_WARNING);
  1493. return false;
  1494. }
  1495. $this->length = $hdr['length'];
  1496. $this->keylen = $hdr['keylen'];
  1497. $this->nodesize = $hdr['nodesize'];
  1498. $this->root_seek = $hdr['root_seek'];
  1499. $this->free = $hdr['free'];
  1500. return true;
  1501. }
  1502. /**
  1503. * @returns length of the tree (number of values)
  1504. */
  1505. function length() {
  1506. if (is_null($this->length)) {
  1507. if (false===$this->get_parameters()) return false;
  1508. }
  1509. return $this->length;
  1510. }
  1511. /**
  1512. * @param string &$key key to find.
  1513. * @param bool $loose if true searches the tree for the "nearest" key to $key;
  1514. *
  1515. * @returns int associated value
  1516. *
  1517. */
  1518. function getitem(&$key, $loose=false) {
  1519. if (is_null($this->root))
  1520. trigger_error("not open!", E_USER_ERROR);
  1521. return $this->find($key, $this->root, $loose);
  1522. }
  1523. /**
  1524. * traverses tree starting from $node, searching for $key
  1525. * @param string $key target key
  1526. * @param object BPlusTree_Node starting node
  1527. *
  1528. * @returns int|bool value at the leaf node containing key or false if key is missing
  1529. *
  1530. */
  1531. function find(&$key, &$node, $loose=false) {
  1532. while (($node->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
  1533. $thesekeys = $node->keys;
  1534. $validkeys = $node->validkeys;
  1535. #d(array_slice($thesekeys, 0, $validkeys));
  1536. $place = BPT_bisect($thesekeys, $key, 0, $validkeys);
  1537. if ($place>=$validkeys || BPT_keycmp($thesekeys[$place],$key)>0) {
  1538. #$thesekeys[$place]>$key) {
  1539. if ($place == 0)
  1540. $nodekey = null;
  1541. else
  1542. $nodekey=$thesekeys[$place-1];
  1543. } else {
  1544. $nodekey = $key;
  1545. }
  1546. $node =& $node->getnode($nodekey);
  1547. }
  1548. return $node->getvalue($key, $loose);
  1549. }
  1550. /**
  1551. * @param $key target key
  1552. * @returns bool false if key does not exists, true otherwise
  1553. */
  1554. function has_key(&$key, $loose=false) {
  1555. if (@$this->getitem($key, $loose)!==false) {
  1556. return true;
  1557. } else {
  1558. return false;
  1559. }
  1560. }
  1561. /**
  1562. * sets an item in the tree with key $key and value $val
  1563. *
  1564. * @param string $key
  1565. * @param integer $val (internally stored as a 4byte long: keep it in mind!)
  1566. *
  1567. *
  1568. */
  1569. function setitem($key, $val) {
  1570. if (!is_numeric($val))
  1571. trigger_error("Second parameter must be numeric", E_USER_ERROR);
  1572. $curr_length = $this->length;
  1573. $root =& $this->root;
  1574. if (is_null($root)) trigger_error("not open", E_USER_ERROR);
  1575. if (!is_string($key)) trigger_error("$key must be string", E_USER_ERROR);
  1576. if (strlen($key)>$this->keylen)
  1577. trigger_error("$key is too long: MAX is {$this->keylen}", E_USER_ERROR);
  1578. d( "STARTING FROM ROOT..." );
  1579. $test1 = $this->set($key, $val, $this->root);
  1580. if (!is_null($test1)) {
  1581. d("SPLITTING ROOT");
  1582. // getting new rightmost interior node
  1583. list($leftmost, $node) = $test1;
  1584. #print_r($test1);
  1585. d("LEFTMOST [$leftmost]");
  1586. // getting new non-leaf root
  1587. list($newroot, $this->free) = $root->getfreenode($this->free);
  1588. $newroot->flag = BPT_FLAG_ROOT;
  1589. /*
  1590. if ($root->flag == BPT_FLAG_LEAFANDROOT) {
  1591. $root->flag = BPT_FLAG_LEAF;
  1592. } else {
  1593. $root->flag = BPT_FLAG_INTERIOR;
  1594. }*/
  1595. // zero-ing root flag (makes an interior or leaf node
  1596. // respectively from a normal root or a leaf-root)
  1597. $root->flag &= ~BPT_FLAG_ROOT_BIT;
  1598. $newroot->clear();
  1599. $newroot->putfirstindex($root->position);
  1600. $newroot->putnode($leftmost, $node);
  1601. $this->root =& $newroot;
  1602. $this->root_seek = $newroot->position;
  1603. $newroot->store();
  1604. $root->store();
  1605. $this->reset_header();
  1606. d("root split.");
  1607. } else {
  1608. if ($this->length!=$curr_length) {
  1609. // length changed: updating header
  1610. $this->reset_header();
  1611. }
  1612. }
  1613. }
  1614. /**
  1615. * traverses subtree starting at $node, searching a place for $key
  1616. * and associates $val; split nodes if needed
  1617. *
  1618. * This function is not meant to be called outside the class, it is a
  1619. * support method for {@link BPlusTree::setitem}
  1620. *
  1621. * @param string $key
  1622. * @param int $val value associated to $key
  1623. * @param object BPlusTree_Node starting node
  1624. *
  1625. * @returns array|null a pair (leftmost, newnode) where "leftmost" is
  1626. * the leftmost key in newnode, and newnode is the split node;
  1627. * returns null if no split took place
  1628. */
  1629. function set($key, $val, &$node) {
  1630. //{{{
  1631. $keys =& $node->keys;
  1632. $validkeys = $node->validkeys;
  1633. if (($node->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
  1634. d("NON LEAF: FIND DESCENDANT");
  1635. // non-leaf: find descendant to insert
  1636. d($keys);
  1637. $place = BPT_bisect($keys, $key, 0, $validkeys);
  1638. if ($place >= $validkeys || BPT_keycmp($keys[$place],$key)>=0) {
  1639. #$keys[$place]>=$key) {
  1640. // insert at previous node
  1641. $index = $place;
  1642. } else {
  1643. $index = $place +1 ;
  1644. }
  1645. if ($index == 0)
  1646. $nodekey = null;
  1647. else
  1648. $nodekey =$keys[$place-1];
  1649. $nextnode =$node->getnode($nodekey);
  1650. $test = $this->set($key, $val, $nextnode);
  1651. // split ?
  1652. if (!is_null($test)) {
  1653. list($leftmost, $insertnode) = $test;
  1654. // TRY
  1655. $TRY = $node->putnode($leftmost, $insertnode);
  1656. if ($TRY == NOROOMERROR) {
  1657. d( "$key::SPLIT!" );
  1658. // EXCEPT
  1659. $insertindex = $insertnode->position;
  1660. list($newnode, $this->free) =
  1661. $node->getfreenode(
  1662. $this->free,
  1663. array(&$this, 'update_freelist')
  1664. );
  1665. $newnode->flag = BPT_FLAG_INTERIOR;
  1666. $ki = $node->keys_indices("dummy");
  1667. #list($dummy, $firstindex) = $ki[0]; #each($ki);
  1668. $firstindex = $ki->b[0];
  1669. #$ki = array_slice($ki, 1);
  1670. $ki->remove(0);
  1671. #print_r($ki);
  1672. // insert new pair
  1673. #BPT_insort($ki, array($leftmost, $insertindex));
  1674. $ki->insort($leftmost, $insertindex);
  1675. $newleftmost = $this->divide_entries(
  1676. $firstindex,
  1677. $node,
  1678. $newnode,
  1679. $ki
  1680. );
  1681. $node->store();
  1682. $newnode->store();
  1683. return array($newleftmost, &$newnode);
  1684. } else {
  1685. d( "$key::NO SPLIT" );
  1686. d($node->keys);
  1687. $node->store();
  1688. return null; // no split
  1689. }
  1690. }
  1691. } else {
  1692. // leaf
  1693. d("FOUND LEAF:");
  1694. d($keys);
  1695. if (!in_array($key, $keys, true)
  1696. || array_search($key, $keys, true) >= $validkeys) {
  1697. $newlength = $this->length +1;
  1698. } else {
  1699. $newlength = $this->length;
  1700. }
  1701. d("[LEAF] TRYING TO PUT $key=>$val");
  1702. if ($node->putvalue($key, $val)==NOROOMERROR) {
  1703. d("GOT NOROOMERROR");
  1704. $ki = $node->keys_indices("dummy");
  1705. #BPT_insort($ki, array($key, $val));
  1706. $ki->insort($key, $val);
  1707. list($newnode, $this->free) =
  1708. $node->getfreenode(
  1709. $this->free,
  1710. array(&$this, 'update_freelist')
  1711. );
  1712. d("CREATE NEW NEIGHBOUR");
  1713. $newnode =& $node->newneighbour($newnode->position);
  1714. $newnode->flag = BPT_FLAG_LEAF;
  1715. $newleftmost = $this->divide_entries(0, $node, $newnode, $ki);
  1716. $node->store();
  1717. #print_r($node);
  1718. #print_r($newnode);
  1719. $newnode->store();
  1720. $this->length = $newlength;
  1721. return array($newleftmost, &$newnode);
  1722. } else {
  1723. d("STORING NODE [{$node->position}]") ;
  1724. d($node->keys);
  1725. $node->store();
  1726. $this->length = $newlength;
  1727. return null;
  1728. }
  1729. }
  1730. }
  1731. //}}}
  1732. /**
  1733. *
  1734. * removes key from tree at node $node;
  1735. * triggers an error if $key does not exists
  1736. *
  1737. * not meant to be called outside the class, it is a support method
  1738. * for {@link BPlusTree::delitem}
  1739. *
  1740. * @param $key target key
  1741. * @param $node node from which start
  1742. *
  1743. * @returns array a pair(&$leftmost, $size): if leftmost changes it is a string with the new leftmost
  1744. * of $node otherwise returns array(null, $size)- caller will restructure node, if needed
  1745. * size is the new size of $node
  1746. *
  1747. */
  1748. function remove($key, &$node, $NESTING=0) {
  1749. $newnodekey = null;
  1750. d("NESTING LEVEL $NESTING");
  1751. d("($NESTING) current size = {$this->nodesize}");
  1752. // first of all we check if it is non-leaf
  1753. if (($node->flag & BPT_FLAG_INTERIOR) == BPT_FLAG_INTERIOR) {
  1754. // non-leaf
  1755. $keys =& $node->keys;
  1756. $validkeys =$node->validkeys;
  1757. $place = BPT_bisect($keys, $key, 0, $validkeys);
  1758. if ($place>=$validkeys || BPT_keycmp($keys[$place],$key)>=0) {
  1759. #$keys[$place]>=$key) {
  1760. // delete occurs before $place
  1761. // (remember that indices are [i_0,i_1,...,i_n]
  1762. // where i_0 points to the node where all keys are < K_search
  1763. // and i_1 points to the node where keys are k_1<=K_search<k_2)
  1764. $index = $place;
  1765. } else {
  1766. // delete occurs in $place (k_i <= K_search < k_(i+1) )
  1767. $index = $place + 1;
  1768. }
  1769. if ($index==0) {
  1770. $nodekey = null;
  1771. } else {
  1772. $nodekey = $keys[$place-1];
  1773. }
  1774. // get child node
  1775. $nextnode =& $node->getnode($nodekey);
  1776. // RECURSION! remove from nextnode;
  1777. // returns new leftmost if changed, otherwise null,
  1778. // and new size of the child node
  1779. list($lm, $size) = $this->remove($key, $nextnode, $NESTING+1);
  1780. // check now for size of nodesize: is it too small?
  1781. // (less than half)
  1782. $nodesize = $this->nodesize;
  1783. $half = (int)($nodesize/2);
  1784. # if($size==0) trigger_error("SIZE==0", E_USER_WARNING);
  1785. if ($size < $half) {
  1786. d("($NESTING) node too small ($size<$nodesize/2), redistribute children");
  1787. // node is too small, need to redistribute
  1788. // children
  1789. if (is_null($nodekey) && $validkeys == 0) {
  1790. #print_r($node);
  1791. trigger_error(
  1792. "invalid node, only one child",
  1793. E_USER_ERROR
  1794. );
  1795. }
  1796. if ($place >= $validkeys) {
  1797. // final node in row, get previous
  1798. $rightnode =& $nextnode;
  1799. $rightkey = $nodekey;
  1800. if ($validkeys<=1) {
  1801. $leftkey = null;
  1802. } else {
  1803. $leftkey = $keys[$place-2];
  1804. }
  1805. $leftnode =& $node->getnode($leftkey);
  1806. } else {
  1807. // non-final, get next
  1808. $leftnode =& $nextnode;
  1809. $leftkey = $nodekey;
  1810. if ($index == 0) {
  1811. $rightkey = $keys[0];
  1812. } else {
  1813. $rightkey = $keys[$place];
  1814. }
  1815. $rightnode = $node->getnode($rightkey);
  1816. }
  1817. // get all keys and indices
  1818. $rightki = $rightnode->keys_indices($rightkey);
  1819. $leftki = $leftnode->keys_indices($leftkey);
  1820. #$ki = array_merge($leftki, $rightki);
  1821. $leftki->append($rightki);
  1822. $ki =& $leftki;
  1823. #array_splice ($leftki, count($leftki), 0, $rightki);
  1824. $lki = $ki->count;#count($ki);
  1825. // merging?
  1826. if (($lki>$nodesize) || (
  1827. ($leftnode->flag & BPT_FLAG_LEAF)!=BPT_FLAG_LEAF
  1828. &&
  1829. ($lki>=$nodesize)
  1830. )) {
  1831. // redistribute
  1832. #list($newleftkey, $firstindex) = $ki[0];
  1833. $newleftkey = $ki->a[0];
  1834. $firstindex = $ki->b[0];
  1835. if (is_null($leftkey)) {
  1836. $newleftkey = $lm;
  1837. }
  1838. if (($leftnode->flag&BPT_FLAG_LEAF)!=BPT_FLAG_LEAF) {
  1839. // kill first pair
  1840. #$ki = array_slice($ki, 1);
  1841. $ki->remove(0);
  1842. }
  1843. $newrightkey = $this->divide_entries(
  1844. $firstindex,
  1845. $leftnode,
  1846. $rightnode,
  1847. $ki
  1848. );
  1849. // delete, reinsert right
  1850. $node->delnode($rightkey);
  1851. $node->putnode($newrightkey, $rightnode);
  1852. // same for left if first changed
  1853. if (!

Large files files are truncated, but you can click here to view the full file