]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/PearDB_pgsql.php
Replace IF by CASE in exists_link()
[SourceForge/phpwiki.git] / lib / WikiDB / backend / PearDB_pgsql.php
1 <?php // -*-php-*-
2 rcs_id('$Id: PearDB_pgsql.php,v 1.22 2005-11-16 07:36:22 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      */
49     function optimize() {
50         foreach ($this->_table_names as $table) {
51             $this->_dbh->query("VACUUM ANALYZE $table");
52         }
53         return 1;
54     }
55
56     function _quote($s) {
57         if (USE_BYTEA)
58             return pg_escape_bytea($s);
59         if (function_exists('pg_escape_string'))
60             return pg_escape_string($s);
61         else
62             return base64_encode($s);
63     }
64
65     function _unquote($s) {
66         if (USE_BYTEA)
67             return pg_unescape_bytea($s);
68         if (function_exists('pg_escape_string'))
69             return $s;
70         else
71             return base64_decode($s);
72     }
73
74     // Until the binary escape problems on pear pgsql are solved */
75     function get_cached_html($pagename) {
76         $dbh = &$this->_dbh;
77         $page_tbl = $this->_table_names['page_tbl'];
78         $data = $dbh->GetOne(sprintf("SELECT cached_html FROM $page_tbl WHERE pagename='%s'",
79                                      $dbh->escapeSimple($pagename)));
80         if ($data) return $this->_unquote($data);
81         else return '';
82     }
83
84     function set_cached_html($pagename, $data) {
85         $dbh = &$this->_dbh;
86         $page_tbl = $this->_table_names['page_tbl'];
87         if (USE_BYTEA)
88             $sth = $dbh->query(sprintf("UPDATE $page_tbl"
89                                        . " SET cached_html='%s'"
90                                        . " WHERE pagename='%s'",
91                                        $this->_quote($data), 
92                                        $dbh->escapeSimple($pagename)));
93         else
94             $sth = $dbh->query("UPDATE $page_tbl"
95                                        . " SET cached_html=?"
96                                        . " WHERE pagename=?",
97                                        // PearDB does NOT use pg_escape_string()! Oh dear.
98                                        array($this->_quote($data), $pagename));
99     }
100
101     /**
102      * Create a new revision of a page.
103      */
104     function set_versiondata($pagename, $version, $data) {
105         $dbh = &$this->_dbh;
106         $version_tbl = $this->_table_names['version_tbl'];
107         
108         $minor_edit = (int) !empty($data['is_minor_edit']);
109         unset($data['is_minor_edit']);
110         
111         $mtime = (int)$data['mtime'];
112         unset($data['mtime']);
113         assert(!empty($mtime));
114
115         @$content = (string) $data['%content'];
116         unset($data['%content']);
117         unset($data['%pagedata']);
118         
119         $this->lock();
120         $id = $this->_get_pageid($pagename, true);
121         $dbh->query(sprintf("DELETE FROM version WHERE id=%d AND version=%d", $id, $version));
122         $dbh->query(sprintf("INSERT INTO version (id,version,mtime,minor_edit,content,versiondata)" .
123                             " VALUES (%d, %d, %d, %d, '%s', '%s')",
124                             $id, $version, $mtime, $minor_edit,
125                             $this->_quote($content),
126                             $this->_serialize($data)));
127         $dbh->query(sprintf("SELECT update_recent (%d, %d)", $id, $version));
128         $this->unlock();
129     }
130
131     /**
132      * Delete an old revision of a page.
133      */
134     function delete_versiondata($pagename, $version) {
135         $dbh = &$this->_dbh;
136         $dbh->query(sprintf("SELECT delete_versiondata (%d, %d)", $id, $version));
137     }
138
139     /**
140      * Rename page in the database.
141      */
142     function rename_page ($pagename, $to) {
143         $dbh = &$this->_dbh;
144         extract($this->_table_names);
145         
146         $this->lock();
147         if (($id = $this->_get_pageid($pagename, false)) ) {
148             if ($new = $this->_get_pageid($to, false)) {
149                 // Cludge Alert!
150                 // This page does not exist (already verified before), but exists in the page table.
151                 // So we delete this page.
152                 $dbh->query("DELETE FROM $page_tbl WHERE id=$new");
153                 $dbh->query("DELETE FROM $version_tbl WHERE id=$new");
154                 $dbh->query("DELETE FROM $recent_tbl WHERE id=$new");
155                 $dbh->query("DELETE FROM $nonempty_tbl WHERE id=$new");
156                 // We have to fix all referring tables to the old id
157                 $dbh->query("UPDATE $link_tbl SET linkfrom=$id WHERE linkfrom=$new");
158                 $dbh->query("UPDATE $link_tbl SET linkto=$id WHERE linkto=$new");
159             }
160             $dbh->query(sprintf("UPDATE $page_tbl SET pagename='%s' WHERE id=$id",
161                                 $dbh->escapeSimple($to)));
162         }
163         $this->unlock();
164         return $id;
165     }
166
167     /**
168      * Lock all tables we might use.
169      */
170     function _lock_tables($write_lock = true) {
171         $this->_dbh->query("BEGIN");
172     }
173
174     /**
175      * Unlock all tables.
176      */
177     function _unlock_tables() {
178         $this->_dbh->query("COMMIT");
179     }
180
181     /**
182      * Serialize data
183      */
184     function _serialize($data) {
185         if (empty($data))
186             return '';
187         assert(is_array($data));
188         return $this->_quote(serialize($data));
189     }
190
191     /**
192      * Unserialize data
193      */
194     function _unserialize($data) {
195         if (empty($data))
196             return array();
197         // Base64 encoded data does not contain colons.
198         //  (only alphanumerics and '+' and '/'.)
199         if (substr($data,0,2) == 'a:')
200             return unserialize($data);
201         return unserialize($this->_unquote($data));
202     }
203
204     /**
205      * Title search.
206      */
207     function text_search($search, $fulltext=false, $sortby=false, $limit=false, 
208                          $exclude=false) 
209     {
210         $dbh = &$this->_dbh;
211         extract($this->_table_names);
212         $orderby = $this->sortby($sortby, 'db');
213         if ($sortby and $orderby) $orderby = ' ORDER BY ' . $orderby;
214
215         $searchclass = get_class($this)."_search";
216         // no need to define it everywhere and then fallback. memory!
217         if (!class_exists($searchclass))
218             $searchclass = "WikiDB_backend_PearDB_search";
219         $searchobj = new $searchclass($search, $dbh);
220         
221         $table = "$nonempty_tbl, $page_tbl";
222         $join_clause = "$nonempty_tbl.id=$page_tbl.id";
223         $fields = $this->page_tbl_fields;
224
225         if ($fulltext) {
226             $table .= ", $recent_tbl";
227             $join_clause .= " AND $page_tbl.id=$recent_tbl.id";
228
229             $table .= ", $version_tbl";
230             $join_clause .= " AND $page_tbl.id=$version_tbl.id AND latestversion=version";
231
232             $fields .= ", $page_tbl.pagedata as pagedata, " . $this->version_tbl_fields;
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         return $this->_pagename_match_clause($node) . " OR idxFTI @@ to_tsquery('$word')";
316     }
317 }
318
319 // (c-file-style: "gnu")
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 ?>