]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/PearDB_pgsql.php
note about vacuum permissions
[SourceForge/phpwiki.git] / lib / WikiDB / backend / PearDB_pgsql.php
1 <?php // -*-php-*-
2 rcs_id('$Id: PearDB_pgsql.php,v 1.25 2006-12-23 11:56:17 rurban Exp $');
3
4 require_once('lib/ErrorManager.php');
5 require_once('lib/WikiDB/backend/PearDB.php');
6
7 if (!defined("USE_BYTEA")) // see schemas/psql-initialize.sql
8     define("USE_BYTEA", false);
9     //define("USE_BYTEA", false);
10
11 /*
12 Since 1.3.12 changed to use:
13  * Foreign Keys 
14  * ON DELETE CASCADE
15  * tsearch2
16 */
17
18 class WikiDB_backend_PearDB_pgsql
19 extends WikiDB_backend_PearDB
20 {
21     function WikiDB_backend_PearDB_pgsql($dbparams) {
22         // The pgsql handler of (at least my version of) the PEAR::DB
23         // library generates three warnings when a database is opened:
24         //
25         //     Undefined index: options
26         //     Undefined index: tty
27         //     Undefined index: port
28         //
29         // This stuff is all just to catch and ignore these warnings,
30         // so that they don't get reported to the user.  (They are
31         // not consequential.)  
32
33         global $ErrorManager;
34         $ErrorManager->pushErrorHandler(new WikiMethodCb($this,'_pgsql_open_error'));
35         $this->WikiDB_backend_PearDB($dbparams);
36         $ErrorManager->popErrorHandler();
37     }
38
39     function _pgsql_open_error($error) {
40         if (preg_match('/^Undefined\s+index:\s+(options|tty|port)/',
41                        $error->errstr))
42             return true;        // Ignore error
43         return false;
44     }
45             
46     /**
47      * Pack tables. 
48      * NOTE: Only the table owner can do this. Either fix the schema or setup autovacuum.
49      */
50     function optimize() {
51         return 0;       // if the wikiuser is not the table owner
52
53         foreach ($this->_table_names as $table) {
54             $this->_dbh->query("VACUUM ANALYZE $table");
55         }
56         return 1;
57     }
58
59     function _quote($s) {
60         if (USE_BYTEA)
61             return pg_escape_bytea($s);
62         if (function_exists('pg_escape_string'))
63             return pg_escape_string($s);
64         else
65             return base64_encode($s);
66     }
67
68     function _unquote($s) {
69         if (USE_BYTEA)
70             return pg_unescape_bytea($s);
71         if (function_exists('pg_escape_string'))
72             return $s;
73         else
74             return base64_decode($s);
75     }
76
77     // Until the binary escape problems on pear pgsql are solved */
78     function get_cached_html($pagename) {
79         $dbh = &$this->_dbh;
80         $page_tbl = $this->_table_names['page_tbl'];
81         $data = $dbh->GetOne(sprintf("SELECT cached_html FROM $page_tbl WHERE pagename='%s'",
82                                      $dbh->escapeSimple($pagename)));
83         if ($data) return $this->_unquote($data);
84         else return '';
85     }
86
87     function set_cached_html($pagename, $data) {
88         $dbh = &$this->_dbh;
89         $page_tbl = $this->_table_names['page_tbl'];
90         if (USE_BYTEA)
91             $sth = $dbh->query(sprintf("UPDATE $page_tbl"
92                                        . " SET cached_html='%s'"
93                                        . " WHERE pagename='%s'",
94                                        $this->_quote($data), 
95                                        $dbh->escapeSimple($pagename)));
96         else
97             $sth = $dbh->query("UPDATE $page_tbl"
98                                . " SET cached_html=?"
99                                . " WHERE pagename=?",
100                                // PearDB does NOT use pg_escape_string()! Oh dear.
101                                array($this->_quote($data), $pagename));
102     }
103
104     /**
105      * Create a new revision of a page.
106      */
107     function _todo_set_versiondata($pagename, $version, $data) {
108         $dbh = &$this->_dbh;
109         $version_tbl = $this->_table_names['version_tbl'];
110         
111         $minor_edit = (int) !empty($data['is_minor_edit']);
112         unset($data['is_minor_edit']);
113         
114         $mtime = (int)$data['mtime'];
115         unset($data['mtime']);
116         assert(!empty($mtime));
117
118         @$content = (string) $data['%content'];
119         unset($data['%content']);
120         unset($data['%pagedata']);
121         
122         $this->lock();
123         $id = $this->_get_pageid($pagename, true);
124         $dbh->query(sprintf("DELETE FROM version WHERE id=%d AND version=%d", $id, $version));
125         $dbh->query(sprintf("INSERT INTO version (id,version,mtime,minor_edit,content,versiondata)" .
126                             " VALUES (%d, %d, %d, %d, '%s', '%s')",
127                             $id, $version, $mtime, $minor_edit,
128                             $this->_quote($content),
129                             $this->_serialize($data)));
130         // TODO: This function does not work yet
131         $dbh->query(sprintf("SELECT update_recent (%d, %d)", $id, $version));
132         $this->unlock();
133     }
134
135     /**
136      * Delete an old revision of a page.
137      */
138     function _todo_delete_versiondata($pagename, $version) {
139         $dbh = &$this->_dbh;
140         // TODO: This function was removed
141         $dbh->query(sprintf("SELECT delete_versiondata (%d, %d)", $id, $version));
142     }
143
144     /**
145      * Rename page in the database.
146      */
147     function _todo_rename_page ($pagename, $to) {
148         $dbh = &$this->_dbh;
149         extract($this->_table_names);
150         
151         $this->lock();
152         if (($id = $this->_get_pageid($pagename, false)) ) {
153             if ($new = $this->_get_pageid($to, false)) {
154                 // Cludge Alert!
155                 // This page does not exist (already verified before), but exists in the page table.
156                 // So we delete this page in one step.
157                 $dbh->query("SELECT prepare_rename_page($id, $new)");
158             }
159             $dbh->query(sprintf("UPDATE $page_tbl SET pagename='%s' WHERE id=$id",
160                                 $dbh->escapeSimple($to)));
161         }
162         $this->unlock();
163         return $id;
164     }
165
166     /**
167      * Lock all tables we might use.
168      */
169     function _lock_tables($write_lock = true) {
170         $this->_dbh->query("BEGIN");
171     }
172
173     /**
174      * Unlock all tables.
175      */
176     function _unlock_tables() {
177         $this->_dbh->query("COMMIT");
178     }
179
180     /**
181      * Serialize data
182      */
183     function _serialize($data) {
184         if (empty($data))
185             return '';
186         assert(is_array($data));
187         return $this->_quote(serialize($data));
188     }
189
190     /**
191      * Unserialize data
192      */
193     function _unserialize($data) {
194         if (empty($data))
195             return array();
196         // Base64 encoded data does not contain colons.
197         //  (only alphanumerics and '+' and '/'.)
198         if (substr($data,0,2) == 'a:')
199             return unserialize($data);
200         return unserialize($this->_unquote($data));
201     }
202
203     /**
204      * Title search.
205      */
206     function text_search($search, $fulltext=false, $sortby=false, $limit=false, 
207                          $exclude=false) 
208     {
209         $dbh = &$this->_dbh;
210         extract($this->_table_names);
211         $orderby = $this->sortby($sortby, 'db');
212         if ($sortby and $orderby) $orderby = ' ORDER BY ' . $orderby;
213
214         $searchclass = get_class($this)."_search";
215         // no need to define it everywhere and then fallback. memory!
216         if (!class_exists($searchclass))
217             $searchclass = "WikiDB_backend_PearDB_search";
218         $searchobj = new $searchclass($search, $dbh);
219         
220         $table = "$nonempty_tbl, $page_tbl";
221         $join_clause = "$nonempty_tbl.id=$page_tbl.id";
222         $fields = $this->page_tbl_fields;
223
224         if ($fulltext) {
225             $table .= ", $recent_tbl";
226             $join_clause .= " AND $page_tbl.id=$recent_tbl.id";
227
228             $table .= ", $version_tbl";
229             $join_clause .= " AND $page_tbl.id=$version_tbl.id AND latestversion=version";
230
231             $fields .= ", $page_tbl.pagedata as pagedata, " . $this->version_tbl_fields;
232             $callback = new WikiMethodCb($searchobj, "_fulltext_match_clause");
233             $search_string = $search->makeTsearch2SqlClauseObj($callback);
234             $search_string = str_replace(array("%"," "), array("","&"), $search_string);
235             $search_clause = "idxFTI @@ to_tsquery('$search_string')";
236             if (!$orderby)
237                $orderby = " ORDER BY rank(idxFTI, to_tsquery('$search_string')) DESC";
238         } else {
239             $callback = new WikiMethodCb($searchobj, "_pagename_match_clause");
240             $search_clause = $search->makeSqlClauseObj($callback);
241         }
242         
243         $sql = "SELECT $fields FROM $table"
244             . " WHERE $join_clause"
245             . "  AND ($search_clause)"
246             . $orderby;
247          if ($limit) {
248              list($from, $count) = $this->limit($limit);
249              $result = $dbh->limitQuery($sql, $from, $count);
250          } else {
251              $result = $dbh->query($sql);
252          }
253         
254         $iter = new WikiDB_backend_PearDB_iter($this, $result);
255         $iter->stoplisted = @$searchobj->stoplisted;
256         return $iter;
257     }
258
259 };
260
261 class WikiDB_backend_PearDB_pgsql_search
262 extends WikiDB_backend_PearDB_search
263 {
264     function _pagename_match_clause($node) {
265         $word = $node->sql();
266         if ($node->op == 'REGEX') { // posix regex extensions
267             return ($this->_case_exact 
268                     ? "pagename ~* '$word'"
269                     : "pagename ~ '$word'");
270         } else {
271             return ($this->_case_exact 
272                     ? "pagename LIKE '$word'" 
273                     : "pagename ILIKE '$word'");
274         }
275     }
276
277     /*
278      most used words:
279 select * from stat('select idxfti from version') order by ndoc desc, nentry desc, word limit 10;
280       word       | ndoc | nentry
281 -----------------+------+--------
282  plugin          |  112 |    418
283  page            |   85 |    446
284  phpwikidocument |   62 |     62
285  use             |   48 |    169
286  help            |   46 |     96
287  wiki            |   44 |    102
288  name            |   43 |    131
289  phpwiki         |   42 |    173
290  see             |   42 |     69
291  default         |   39 |    124
292     */
293     
294     /** 
295      * use tsearch2. See schemas/psql-tsearch2.sql and /usr/share/postgresql/contrib/tsearch2.sql 
296      * TODO: don't parse the words into nodes. rather replace "[ +]" with & and "-" with "!" and " or " with "|"
297      * tsearch2 query language: @@ "word | word", "word & word", ! word
298      * ~* '.*something that does not exist.*'
299      */
300     /*
301      phrase search for "history lesson":
302
303      SELECT id FROM tab WHERE ts_idx_col @@ to_tsquery('history&lesson')
304      AND text_col ~* '.*history\\s+lesson.*';
305
306      The full-text index will still be used, and the regex will be used to
307      prune the results afterwards.
308     */
309     function _fulltext_match_clause($node) {
310         $word = strtolower($node->word);
311         $word = str_replace(" ", "&", $word); // phrase fix
312         return $word;
313         
314         return $this->_pagename_match_clause($node) . " OR idxFTI @@ to_tsquery('$word')";
315     }
316 }
317
318 // $Log: not supported by cvs2svn $
319 // Revision 1.24  2006/12/22 00:27:37  rurban
320 // just add Log
321 //
322
323 // (c-file-style: "gnu")
324 // Local Variables:
325 // mode: php
326 // tab-width: 8
327 // c-basic-offset: 4
328 // c-hanging-comment-ender-p: nil
329 // indent-tabs-mode: nil
330 // End:   
331 ?>