PageRenderTime 60ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/branches/coding-style/fp-includes/core/core.bplustree.class.php

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

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