]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/PearDB_pgsql.php
just add Log
[SourceForge/phpwiki.git] / lib / WikiDB / backend / PearDB_pgsql.php
1 <?php // -*-php-*-
2 rcs_id('$Id: PearDB_pgsql.php,v 1.24 2006-12-22 00:27:37 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 _todo_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         // TODO: This function does not work yet
128         $dbh->query(sprintf("SELECT update_recent (%d, %d)", $id, $version));
129         $this->unlock();
130     }
131
132     /**
133      * Delete an old revision of a page.
134      */
135     function _todo_delete_versiondata($pagename, $version) {
136         $dbh = &$this->_dbh;
137         // TODO: This function was removed
138         $dbh->query(sprintf("SELECT delete_versiondata (%d, %d)", $id, $version));
139     }
140
141     /**
142      * Rename page in the database.
143      */
144     function _todo_rename_page ($pagename, $to) {
145         $dbh = &$this->_dbh;
146         extract($this->_table_names);
147         
148         $this->lock();
149         if (($id = $this->_get_pageid($pagename, false)) ) {
150             if ($new = $this->_get_pageid($to, false)) {
151                 // Cludge Alert!
152                 // This page does not exist (already verified before), but exists in the page table.
153                 // So we delete this page in one step.
154                 $dbh->query("SELECT prepare_rename_page($id, $new)");
155             }
156             $dbh->query(sprintf("UPDATE $page_tbl SET pagename='%s' WHERE id=$id",
157                                 $dbh->escapeSimple($to)));
158         }
159         $this->unlock();
160         return $id;
161     }
162
163     /**
164      * Lock all tables we might use.
165      */
166     function _lock_tables($write_lock = true) {
167         $this->_dbh->query("BEGIN");
168     }
169
170     /**
171      * Unlock all tables.
172      */
173     function _unlock_tables() {
174         $this->_dbh->query("COMMIT");
175     }
176
177     /**
178      * Serialize data
179      */
180     function _serialize($data) {
181         if (empty($data))
182             return '';
183         assert(is_array($data));
184         return $this->_quote(serialize($data));
185     }
186
187     /**
188      * Unserialize data
189      */
190     function _unserialize($data) {
191         if (empty($data))
192             return array();
193         // Base64 encoded data does not contain colons.
194         //  (only alphanumerics and '+' and '/'.)
195         if (substr($data,0,2) == 'a:')
196             return unserialize($data);
197         return unserialize($this->_unquote($data));
198     }
199
200     /**
201      * Title search.
202      */
203     function text_search($search, $fulltext=false, $sortby=false, $limit=false, 
204                          $exclude=false) 
205     {
206         $dbh = &$this->_dbh;
207         extract($this->_table_names);
208         $orderby = $this->sortby($sortby, 'db');
209         if ($sortby and $orderby) $orderby = ' ORDER BY ' . $orderby;
210
211         $searchclass = get_class($this)."_search";
212         // no need to define it everywhere and then fallback. memory!
213         if (!class_exists($searchclass))
214             $searchclass = "WikiDB_backend_PearDB_search";
215         $searchobj = new $searchclass($search, $dbh);
216         
217         $table = "$nonempty_tbl, $page_tbl";
218         $join_clause = "$nonempty_tbl.id=$page_tbl.id";
219         $fields = $this->page_tbl_fields;
220
221         if ($fulltext) {
222             $table .= ", $recent_tbl";
223             $join_clause .= " AND $page_tbl.id=$recent_tbl.id";
224
225             $table .= ", $version_tbl";
226             $join_clause .= " AND $page_tbl.id=$version_tbl.id AND latestversion=version";
227
228             $fields .= ", $page_tbl.pagedata as pagedata, " . $this->version_tbl_fields;
229             $callback = new WikiMethodCb($searchobj, "_fulltext_match_clause");
230             $search_string = $search->makeTsearch2SqlClauseObj($callback);
231             $search_string = str_replace(array("%"," "), array("","&"), $search_string);
232             $search_clause = "idxFTI @@ to_tsquery('$search_string')";
233             if (!$orderby)
234                $orderby = " ORDER BY rank(idxFTI, to_tsquery('$search_string')) DESC";
235         } else {
236             $callback = new WikiMethodCb($searchobj, "_pagename_match_clause");
237             $search_clause = $search->makeSqlClauseObj($callback);
238         }
239         
240         $sql = "SELECT $fields FROM $table"
241             . " WHERE $join_clause"
242             . "  AND ($search_clause)"
243             . $orderby;
244          if ($limit) {
245              list($from, $count) = $this->limit($limit);
246              $result = $dbh->limitQuery($sql, $from, $count);
247          } else {
248              $result = $dbh->query($sql);
249          }
250         
251         $iter = new WikiDB_backend_PearDB_iter($this, $result);
252         $iter->stoplisted = @$searchobj->stoplisted;
253         return $iter;
254     }
255
256 };
257
258 class WikiDB_backend_PearDB_pgsql_search
259 extends WikiDB_backend_PearDB_search
260 {
261     function _pagename_match_clause($node) {
262         $word = $node->sql();
263         if ($node->op == 'REGEX') { // posix regex extensions
264             return ($this->_case_exact 
265                     ? "pagename ~* '$word'"
266                     : "pagename ~ '$word'");
267         } else {
268             return ($this->_case_exact 
269                     ? "pagename LIKE '$word'" 
270                     : "pagename ILIKE '$word'");
271         }
272     }
273
274     /*
275      most used words:
276 select * from stat('select idxfti from version') order by ndoc desc, nentry desc, word limit 10;
277       word       | ndoc | nentry
278 -----------------+------+--------
279  plugin          |  112 |    418
280  page            |   85 |    446
281  phpwikidocument |   62 |     62
282  use             |   48 |    169
283  help            |   46 |     96
284  wiki            |   44 |    102
285  name            |   43 |    131
286  phpwiki         |   42 |    173
287  see             |   42 |     69
288  default         |   39 |    124
289     */
290     
291     /** 
292      * use tsearch2. See schemas/psql-tsearch2.sql and /usr/share/postgresql/contrib/tsearch2.sql 
293      * TODO: don't parse the words into nodes. rather replace "[ +]" with & and "-" with "!" and " or " with "|"
294      * tsearch2 query language: @@ "word | word", "word & word", ! word
295      * ~* '.*something that does not exist.*'
296      */
297     /*
298      phrase search for "history lesson":
299
300      SELECT id FROM tab WHERE ts_idx_col @@ to_tsquery('history&lesson')
301      AND text_col ~* '.*history\\s+lesson.*';
302
303      The full-text index will still be used, and the regex will be used to
304      prune the results afterwards.
305     */
306     function _fulltext_match_clause($node) {
307         $word = strtolower($node->word);
308         $word = str_replace(" ", "&", $word); // phrase fix
309         return $word;
310         
311         return $this->_pagename_match_clause($node) . " OR idxFTI @@ to_tsquery('$word')";
312     }
313 }
314
315 // $Log: not supported by cvs2svn $
316
317 // (c-file-style: "gnu")
318 // Local Variables:
319 // mode: php
320 // tab-width: 8
321 // c-basic-offset: 4
322 // c-hanging-comment-ender-p: nil
323 // indent-tabs-mode: nil
324 // End:   
325 ?>