1 <?php rcs_id('$Id: dbaBase.php,v 1.7 2004-02-12 14:11:36 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
29 * Don't keep tables locked the whole time?
32 * list of pagenames for get_all_pages
34 * RecentChanges support:
35 * lists of most recent edits (major, minor, either).
38 * Separate hit table, so we don't have to update the whole page entry
39 * each time we get a hit. (Maybe not so important though...).
42 require_once('lib/DbaPartition.php');
44 class WikiDB_backend_dbaBase
45 extends WikiDB_backend
47 function WikiDB_backend_dbaBase (&$dba) {
49 // FIXME: page and version tables should be in their own files, probably.
50 // We'll pack them all in one for now (testing).
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');
63 $this->_db->optimize();
71 $this->_linkdb->rebuild();
76 return $this->_linkdb->check();
79 function get_pagedata($pagename) {
80 $result = $this->_pagedb->get($pagename);
83 list(,,$packed) = explode(':', $result, 3);
84 $data = unserialize($packed);
89 function update_pagedata($pagename, $newdata) {
90 $result = $this->_pagedb->get($pagename);
92 list($latestversion,$flags,$data) = explode(':', $result, 3);
93 $data = unserialize($data);
96 $latestversion = $flags = 0;
100 foreach ($newdata as $key => $val) {
106 $this->_pagedb->set($pagename,
107 (int)$latestversion . ':'
112 function get_latest_version($pagename) {
113 return (int) $this->_pagedb->get($pagename);
116 function get_previous_version($pagename, $version) {
117 $versdb = &$this->_versiondb;
119 while (--$version > 0) {
120 if ($versdb->exists($version . ":$pagename"))
126 function get_versiondata($pagename, $version, $want_content = false) {
127 $data = $this->_versiondb->get((int)$version . ":$pagename");
128 return $data ? unserialize($data) : false;
132 * Delete page from the database.
134 function delete_page($pagename) {
135 $pagedb = &$this->_pagedb;
136 $versdb = &$this->_versiondb;
138 $version = $this->get_latest_version($pagename);
139 while ($version > 0) {
140 $versdb->set($version-- . ":$pagename", false);
142 $pagedb->set($pagename, false);
144 $this->set_links($pagename, false);
147 function rename_page($pagename, $to) {
148 $data = get_pagedata($pagename);
149 if (isset($data['pagename']))
150 $data['pagename'] = $to;
151 //$vdata = get_versiondata($pagename, $version, 1);
152 //$this->delete_page($pagename);
153 $this->update_pagedata($to, $data);
158 * Delete an old revision of a page.
160 function delete_versiondata($pagename, $version) {
161 $versdb = &$this->_versiondb;
163 $latest = $this->get_latest_version($pagename);
165 assert($version > 0);
166 assert($version <= $latest);
168 $versdb->set((int)$version . ":$pagename", false);
170 if ($version == $latest) {
171 $previous = $this->get_previous_version($version);
173 $pvdata = $this->get_versiondata($pagename, $previous);
174 $is_empty = empty($pvdata['%content']);
178 $this->_update_latest_version($pagename, $previous, $is_empty);
183 * Create a new revision of a page.
185 function set_versiondata($pagename, $version, $data) {
186 $versdb = &$this->_versiondb;
188 $versdb->set((int)$version . ":$pagename", serialize($data));
189 if ($version > $this->get_latest_version($pagename))
190 $this->_update_latest_version($pagename, $version, empty($data['%content']));
193 function _update_latest_version($pagename, $latest, $flags) {
194 $pagedb = &$this->_pagedb;
196 $pdata = $pagedb->get($pagename);
198 list(,,$pagedata) = explode(':',$pdata,3);
200 $pagedata = serialize(array());
202 $pagedb->set($pagename, (int)$latest . ':' . (int)$flags . ":$pagedata");
205 function get_all_pages($include_deleted = false, $orderby='pagename') {
206 $pagedb = &$this->_pagedb;
209 for ($page = $pagedb->firstkey(); $page!== false; $page = $pagedb->nextkey()) {
211 assert(!empty($page));
215 if (!$include_deleted) {
216 list($latestversion,$flags,) = explode(':', $pagedb->get($page), 3);
217 if ($latestversion == 0 || $flags != 0)
218 continue; // current content is empty
222 usort($pages, 'WikiDB_backend_dbaBase_sortbypagename');
223 return new WikiDB_backend_dbaBase_pageiter($this, $pages);
226 function set_links($pagename, $links) {
227 $this->_linkdb->set_links($pagename, $links);
231 function get_links($pagename, $reversed = true) {
234 include_once('lib/WikiDB/backend/dumb/BackLinkIter.php');
235 $pages = $this->get_all_pages();
236 return new WikiDB_backend_dumb_BackLinkIter($this, $pages, $pagename);
239 $links = $this->_linkdb->get_links($pagename, $reversed);
240 return new WikiDB_backend_dbaBase_pageiter($this, $links);
244 function WikiDB_backend_dbaBase_sortbypagename ($a, $b) {
245 $aname = $a['pagename'];
246 $bname = $b['pagename'];
247 return strcasecmp($aname, $bname);
251 class WikiDB_backend_dbaBase_pageiter
252 extends WikiDB_backend_iterator
254 function WikiDB_backend_dbaBase_pageiter(&$backend, &$pages) {
255 $this->_backend = $backend;
256 $this->_pages = $pages ? $pages : array();
260 if ( ! ($next = array_shift($this->_pages)) )
262 return array('pagename' => $next);
266 $this->_pages = array();
270 class WikiDB_backend_dbaBase_linktable
272 function WikiDB_backend_dbaBase_linktable(&$dba) {
276 //FIXME: try stroring link lists as hashes rather than arrays.
277 // (backlink deletion would be faster.)
279 function get_links($page, $reversed = true) {
280 return $this->_get_links($reversed ? 'i' : 'o', $page);
283 function set_links($page, $newlinks) {
285 $oldlinks = $this->_get_links('o', $page);
287 if (!is_array($newlinks)) {
288 assert(empty($newlinks));
292 $newlinks = array_unique($newlinks);
295 $this->_set_links('o', $page, $newlinks);
299 $new = current($newlinks);
300 $old = current($oldlinks);
301 while ($new !== false || $old !== false) {
302 if ($old === false || ($new !== false && $new < $old)) {
303 // $new is a new link (not in $oldlinks).
304 $this->_add_backlink($new, $page);
305 $new = next($newlinks);
307 elseif ($new === false || $old < $new) {
308 // $old is a obsolete link (not in $newlinks).
309 $this->_delete_backlink($old, $page);
310 $old = next($oldlinks);
313 // Unchanged link (in both $newlist and $oldlinks).
314 assert($new == $old);
315 $new = next($newlinks);
316 $old = next($oldlinks);
322 * Rebuild the back-link index.
324 * This should never be needed, but if the database gets hosed for some reason,
325 * this should put it back into a consistent state.
327 * We assume the forward links in the our table are correct, and recalculate
328 * all the backlinks appropriately.
330 function rebuild () {
333 // Delete the backlink tables, make a list of page names.
336 for ($key = $db->firstkey(); $key; $key = $db->nextkey()) {
339 elseif ($key[0] == 'o')
342 trigger_error("Bad key in linktable: '$key'", E_USER_WARNING);
346 foreach ($ikeys as $key) {
349 foreach ($okeys as $key) {
350 $page = substr($key,1);
351 $links = $this->_get_links('o', $page);
353 $this->set_links($page, $links);
360 // FIXME: check for sortedness and uniqueness in links lists.
362 for ($key = $db->firstkey(); $key; $key = $db->nextkey()) {
363 if (strlen($key) < 1 || ($key[0] != 'i' && $key[0] != 'o')) {
364 $errs[] = "Bad key '$key' in table";
367 $page = substr($key, 1);
368 if ($key[0] == 'o') {
370 foreach($this->_get_links('o', $page) as $link) {
371 if (!$this->_has_link('i', $link, $page))
372 $errs[] = "backlink entry missing for link '$page'->'$link'";
376 assert($key[0] == 'i');
378 foreach($this->_get_links('i', $page) as $link) {
379 if (!$this->_has_link('o', $link, $page))
380 $errs[] = "link entry missing for backlink '$page'<-'$link'";
385 return isset($errs) ? $errs : false;
389 function _add_backlink($page, $linkedfrom) {
390 $backlinks = $this->_get_links('i', $page);
391 $backlinks[] = $linkedfrom;
393 $this->_set_links('i', $page, $backlinks);
396 function _delete_backlink($page, $linkedfrom) {
397 $backlinks = $this->_get_links('i', $page);
398 foreach ($backlinks as $key => $backlink) {
399 if ($backlink == $linkedfrom)
400 unset($backlinks[$key]);
402 $this->_set_links('i', $page, $backlinks);
405 function _has_link($which, $page, $link) {
406 $links = $this->_get_links($which, $page);
407 foreach($links as $l) {
414 function _get_links($which, $page) {
415 $data = $this->_db->get($which . $page);
416 return $data ? unserialize($data) : array();
419 function _set_links($which, $page, &$links) {
420 $key = $which . $page;
422 $this->_db->set($key, serialize($links));
424 $this->_db->set($key, false);
428 // (c-file-style: "gnu")
433 // c-hanging-comment-ender-p: nil
434 // indent-tabs-mode: nil