1 <?php rcs_id('$Id: dbaBase.php,v 1.10 2004-07-09 10:06:50 rurban Exp $');
3 require_once('lib/WikiDB/backend.php');
5 // FIXME:padding of data? Is it needed? dba_optimize() seems to do a good
6 // job at packing 'gdbm' (and 'db2') databases.
13 * Values: latestversion . ':' . flags . ':' serialized hash of page meta data
14 * Currently flags = 1 if latest version has empty content.
17 * Index: version:pagename
18 * Value: serialized hash of revision meta data, including:
19 * + quasi-meta-data %content
22 * index: 'o' . pagename
23 * value: serialized list of pages (names) which pagename links to.
24 * index: 'i' . pagename
25 * value: serialized list of pages which link to pagename
28 * Don't keep tables locked the whole time
31 * list of pagenames for get_all_pages
33 * RecentChanges support:
34 * lists of most recent edits (major, minor, either).
37 * Separate hit table, so we don't have to update the whole page entry
38 * each time we get a hit. (Maybe not so important though...).
41 require_once('lib/DbaPartition.php');
43 class WikiDB_backend_dbaBase
44 extends WikiDB_backend
46 function WikiDB_backend_dbaBase (&$dba) {
48 // TODO: page and version tables should be in their own files, probably.
49 // We'll pack them all in one for now (testing).
50 // 2004-07-09 10:07:30 rurban: It's fast enough this way.
51 $this->_pagedb = new DbaPartition($dba, 'p');
52 $this->_versiondb = new DbaPartition($dba, 'v');
53 $linkdbpart = new DbaPartition($dba, 'l');
54 $this->_linkdb = new WikiDB_backend_dbaBase_linktable($linkdbpart);
55 $this->_dbdb = new DbaPartition($dba, 'd');
58 function sortable_columns() {
59 return array('pagename','mtime'/*,'author_id','author'*/);
67 $this->_db->optimize();
75 $this->_linkdb->rebuild();
80 return $this->_linkdb->check();
83 function get_pagedata($pagename) {
84 $result = $this->_pagedb->get($pagename);
87 list(,,$packed) = explode(':', $result, 3);
88 $data = unserialize($packed);
92 function update_pagedata($pagename, $newdata) {
93 $result = $this->_pagedb->get($pagename);
95 list($latestversion,$flags,$data) = explode(':', $result, 3);
96 $data = unserialize($data);
99 $latestversion = $flags = 0;
103 foreach ($newdata as $key => $val) {
109 $this->_pagedb->set($pagename,
110 (int)$latestversion . ':'
115 function get_latest_version($pagename) {
116 return (int) $this->_pagedb->get($pagename);
119 function get_previous_version($pagename, $version) {
120 $versdb = &$this->_versiondb;
122 while (--$version > 0) {
123 if ($versdb->exists($version . ":$pagename"))
129 //check $want_content
130 function get_versiondata($pagename, $version, $want_content=false) {
131 $data = $this->_versiondb->get((int)$version . ":$pagename");
132 if (empty($data)) return false;
134 $data = unserialize($data);
136 $data['%content'] = !empty($data['%content']);
142 * Delete page from the database.
144 function delete_page($pagename) {
145 $pagedb = &$this->_pagedb;
146 $versdb = &$this->_versiondb;
148 $version = $this->get_latest_version($pagename);
149 while ($version > 0) {
150 $versdb->set($version-- . ":$pagename", false);
152 $pagedb->set($pagename, false);
154 $this->set_links($pagename, false);
157 function rename_page($pagename, $to) {
158 $data = get_pagedata($pagename);
159 if (isset($data['pagename']))
160 $data['pagename'] = $to;
161 //$vdata = get_versiondata($pagename, $version, 1);
162 //$this->delete_page($pagename);
163 $this->update_pagedata($to, $data);
168 * Delete an old revision of a page.
170 function delete_versiondata($pagename, $version) {
171 $versdb = &$this->_versiondb;
173 $latest = $this->get_latest_version($pagename);
175 assert($version > 0);
176 assert($version <= $latest);
178 $versdb->set((int)$version . ":$pagename", false);
180 if ($version == $latest) {
181 $previous = $this->get_previous_version($version);
183 $pvdata = $this->get_versiondata($pagename, $previous);
184 $is_empty = empty($pvdata['%content']);
188 $this->_update_latest_version($pagename, $previous, $is_empty);
193 * Create a new revision of a page.
195 function set_versiondata($pagename, $version, $data) {
196 $versdb = &$this->_versiondb;
198 $versdb->set((int)$version . ":$pagename", serialize($data));
199 if ($version > $this->get_latest_version($pagename))
200 $this->_update_latest_version($pagename, $version, empty($data['%content']));
203 function _update_latest_version($pagename, $latest, $flags) {
204 $pagedb = &$this->_pagedb;
206 $pdata = $pagedb->get($pagename);
208 list(,,$pagedata) = explode(':',$pdata,3);
210 $pagedata = serialize(array());
212 $pagedb->set($pagename, (int)$latest . ':' . (int)$flags . ":$pagedata");
215 //FIXME: support limit
216 function get_all_pages($include_deleted = false, $sortby=false, $limit=false) {
217 $pagedb = &$this->_pagedb;
219 for ($page = $pagedb->firstkey(); $page!== false; $page = $pagedb->nextkey()) {
221 assert(!empty($page));
225 if (!$include_deleted) {
226 if (!($data = $pagedb->get($page))) continue;
227 list($latestversion,$flags,) = explode(':', $data, 3);
229 if ($latestversion == 0 || $flags != 0)
230 continue; // current content is empty
234 $sortby = $this->sortby($sortby, 'db');
235 if ($sortby and !strstr($sortby, "hits ")) { // check for which column to sortby
236 usort($pages, 'WikiDB_backend_dbaBase_sortby_'.str_replace(' ','_',$sortby));
238 return new WikiDB_backend_dbaBase_pageiter($this, $pages);
241 function set_links($pagename, $links) {
242 $this->_linkdb->set_links($pagename, $links);
245 function get_links($pagename, $reversed = true) {
248 include_once('lib/WikiDB/backend/dumb/BackLinkIter.php');
249 $pages = $this->get_all_pages();
250 return new WikiDB_backend_dumb_BackLinkIter($this, $pages, $pagename);
253 $links = $this->_linkdb->get_links($pagename, $reversed);
254 return new WikiDB_backend_dbaBase_pageiter($this, $links);
258 function WikiDB_backend_dbaBase_sortby_pagename_ASC ($a, $b) {
259 return strcasecmp($a, $b);
261 function WikiDB_backend_dbaBase_sortby_pagename_DESC ($a, $b) {
262 return strcasecmp($b, $a);
264 function WikiDB_backend_dbaBase_sortby_mtime_ASC ($a, $b) {
265 return WikiDB_backend_dbaBase_sortby_num($a, $b, 'mtime');
267 function WikiDB_backend_dbaBase_sortby_mtime_DESC ($a, $b) {
268 return WikiDB_backend_dbaBase_sortby_num($b, $a, 'mtime');
271 function WikiDB_backend_dbaBase_sortby_hits_ASC ($a, $b) {
272 return WikiDB_backend_dbaBase_sortby_num($a, $b, 'hits');
274 function WikiDB_backend_dbaBase_sortby_hits_DESC ($a, $b) {
275 return WikiDB_backend_dbaBase_sortby_num($b, $a, 'hits');
278 function WikiDB_backend_dbaBase_sortby_num($aname, $bname, $field) {
280 $dbi = $request->getDbh();
281 // fields are stored in versiondata
282 $av = $dbi->_backend->get_latest_version($aname);
283 $bv = $dbi->_backend->get_latest_version($bname);
284 $a = $dbi->_backend->get_versiondata($aname, $av, false);
286 $b = $dbi->_backend->get_versiondata($bname, $bv, false);
288 if ((!isset($a[$field]) && !isset($b[$field])) || ($a[$field] === $b[$field])) {
291 return (!isset($a[$field]) || ($a[$field] < $b[$field])) ? -1 : 1;
295 class WikiDB_backend_dbaBase_pageiter
296 extends WikiDB_backend_iterator
298 function WikiDB_backend_dbaBase_pageiter(&$backend, &$pages) {
299 $this->_backend = $backend;
300 $this->_pages = $pages ? $pages : array();
304 if ( ! ($next = array_shift($this->_pages)) )
306 return array('pagename' => $next);
310 return count($this->_pages);
314 $this->_pages = array();
318 class WikiDB_backend_dbaBase_linktable
320 function WikiDB_backend_dbaBase_linktable(&$dba) {
324 //FIXME: try stroring link lists as hashes rather than arrays.
325 // (backlink deletion would be faster.)
327 function get_links($page, $reversed = true) {
328 return $this->_get_links($reversed ? 'i' : 'o', $page);
331 function set_links($page, $newlinks) {
333 $oldlinks = $this->_get_links('o', $page);
335 if (!is_array($newlinks)) {
336 assert(empty($newlinks));
340 $newlinks = array_unique($newlinks);
343 $this->_set_links('o', $page, $newlinks);
347 $new = current($newlinks);
348 $old = current($oldlinks);
349 while ($new !== false || $old !== false) {
350 if ($old === false || ($new !== false && $new < $old)) {
351 // $new is a new link (not in $oldlinks).
352 $this->_add_backlink($new, $page);
353 $new = next($newlinks);
355 elseif ($new === false || $old < $new) {
356 // $old is a obsolete link (not in $newlinks).
357 $this->_delete_backlink($old, $page);
358 $old = next($oldlinks);
361 // Unchanged link (in both $newlist and $oldlinks).
362 assert($new == $old);
363 $new = next($newlinks);
364 $old = next($oldlinks);
370 * Rebuild the back-link index.
372 * This should never be needed, but if the database gets hosed for some reason,
373 * this should put it back into a consistent state.
375 * We assume the forward links in the our table are correct, and recalculate
376 * all the backlinks appropriately.
378 function rebuild () {
381 // Delete the backlink tables, make a list of page names.
384 for ($key = $db->firstkey(); $key; $key = $db->nextkey()) {
387 elseif ($key[0] == 'o')
390 trigger_error("Bad key in linktable: '$key'", E_USER_WARNING);
394 foreach ($ikeys as $key) {
397 foreach ($okeys as $key) {
398 $page = substr($key,1);
399 $links = $this->_get_links('o', $page);
401 $this->set_links($page, $links);
408 // FIXME: check for sortedness and uniqueness in links lists.
410 for ($key = $db->firstkey(); $key; $key = $db->nextkey()) {
411 if (strlen($key) < 1 || ($key[0] != 'i' && $key[0] != 'o')) {
412 $errs[] = "Bad key '$key' in table";
415 $page = substr($key, 1);
416 if ($key[0] == 'o') {
418 foreach($this->_get_links('o', $page) as $link) {
419 if (!$this->_has_link('i', $link, $page))
420 $errs[] = "backlink entry missing for link '$page'->'$link'";
424 assert($key[0] == 'i');
426 foreach($this->_get_links('i', $page) as $link) {
427 if (!$this->_has_link('o', $link, $page))
428 $errs[] = "link entry missing for backlink '$page'<-'$link'";
433 return isset($errs) ? $errs : false;
437 function _add_backlink($page, $linkedfrom) {
438 $backlinks = $this->_get_links('i', $page);
439 $backlinks[] = $linkedfrom;
441 $this->_set_links('i', $page, $backlinks);
444 function _delete_backlink($page, $linkedfrom) {
445 $backlinks = $this->_get_links('i', $page);
446 foreach ($backlinks as $key => $backlink) {
447 if ($backlink == $linkedfrom)
448 unset($backlinks[$key]);
450 $this->_set_links('i', $page, $backlinks);
453 function _has_link($which, $page, $link) {
454 $links = $this->_get_links($which, $page);
455 foreach($links as $l) {
462 function _get_links($which, $page) {
463 $data = $this->_db->get($which . $page);
464 return $data ? unserialize($data) : array();
467 function _set_links($which, $page, &$links) {
468 $key = $which . $page;
470 $this->_db->set($key, serialize($links));
472 $this->_db->set($key, false);
476 // (c-file-style: "gnu")
481 // c-hanging-comment-ender-p: nil
482 // indent-tabs-mode: nil