]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/PearDB_pgsql.php
Remove rcs_id
[SourceForge/phpwiki.git] / lib / WikiDB / backend / PearDB_pgsql.php
1 <?php // -*-php-*-
2 // $Id$
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", true);
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='', $limit='',
207                          $exclude='')
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             // TODO: title still ignored, need better rank and subselect
233             $callback = new WikiMethodCb($searchobj, "_fulltext_match_clause");
234             $search_string = $search->makeTsearch2SqlClauseObj($callback);
235             $search_string = str_replace(array("%"," "), array("","&"), $search_string);
236             $search_clause = "idxFTI @@ to_tsquery('$search_string')";
237             if (!$orderby)
238                $orderby = " ORDER BY rank(idxFTI, to_tsquery('$search_string')) DESC";
239         } else {
240             $callback = new WikiMethodCb($searchobj, "_pagename_match_clause");
241             $search_clause = $search->makeSqlClauseObj($callback);
242         }
243       
244         $sql = "SELECT $fields FROM $table"
245             . " WHERE $join_clause"
246             . "  AND ($search_clause)"
247             . $orderby;
248          if ($limit) {
249              list($from, $count) = $this->limit($limit);
250              $result = $dbh->limitQuery($sql, $from, $count);
251          } else {
252              $result = $dbh->query($sql);
253          }
254       
255         $iter = new WikiDB_backend_PearDB_iter($this, $result);
256         $iter->stoplisted = @$searchobj->stoplisted;
257         return $iter;
258     }
259
260 };
261
262 class WikiDB_backend_PearDB_pgsql_search
263 extends WikiDB_backend_PearDB_search
264 {
265     function _pagename_match_clause($node) {
266         $word = $node->sql();
267         if ($node->op == 'REGEX') { // posix regex extensions
268             return ($this->_case_exact
269                     ? "pagename ~* '$word'"
270                     : "pagename ~ '$word'");
271         } else {
272             return ($this->_case_exact
273                     ? "pagename LIKE '$word'"
274                     : "pagename ILIKE '$word'");
275         }
276     }
277
278     /*
279      most used words:
280 select * from stat('select idxfti from version') order by ndoc desc, nentry desc, word limit 10;
281       word       | ndoc | nentry
282 -----------------+------+--------
283  plugin          |  112 |    418
284  page            |   85 |    446
285  phpwikidocument |   62 |     62
286  use             |   48 |    169
287  help            |   46 |     96
288  wiki            |   44 |    102
289  name            |   43 |    131
290  phpwiki         |   42 |    173
291  see             |   42 |     69
292  default         |   39 |    124
293     */
294   
295     /**
296      * use tsearch2. See schemas/psql-tsearch2.sql and /usr/share/postgresql/contrib/tsearch2.sql
297      * TODO: don't parse the words into nodes. rather replace "[ +]" with & and "-" with "!" and " or " with "|"
298      * tsearch2 query language: @@ "word | word", "word & word", ! word
299      * ~* '.*something that does not exist.*'
300      */
301     /*
302      phrase search for "history lesson":
303
304      SELECT id FROM tab WHERE ts_idx_col @@ to_tsquery('history&lesson')
305      AND text_col ~* '.*history\\s+lesson.*';
306
307      The full-text index will still be used, and the regex will be used to
308      prune the results afterwards.
309     */
310     function _fulltext_match_clause($node) {
311         $word = strtolower($node->word);
312         $word = str_replace(" ", "&", $word); // phrase fix
313         return $word;
314
315         // clause specified above.
316         return $this->_pagename_match_clause($node) . " OR idxFTI @@ to_tsquery('$word')";
317     }
318 }
319
320 // Local Variables:
321 // mode: php
322 // tab-width: 8
323 // c-basic-offset: 4
324 // c-hanging-comment-ender-p: nil
325 // indent-tabs-mode: nil
326 // End: 
327 ?>