]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/PearDB_ffpgsql.php
PAGE_PREFIX becomes a variable (Fusionforge only)
[SourceForge/phpwiki.git] / lib / WikiDB / backend / PearDB_ffpgsql.php
1 <?php
2
3 /*
4  * Copyright (C) 2001-2009 $ThePhpWikiProgrammingTeam
5  * Copyright (C) 2010 Alain Peyrat, Alcatel-Lucent
6  *
7  * This file is part of PhpWiki.
8  *
9  * PhpWiki is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * PhpWiki is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24 /*
25  * Standard Alcatel-Lucent disclaimer for contributing to open source
26  *
27  * "The Fusionforge backend ("Contribution") has not been tested and/or
28  * validated for release as or in products, combinations with products or
29  * other commercial use. Any use of the Contribution is entirely made at
30  * the user's own responsibility and the user can not rely on any features,
31  * functionalities or performances Alcatel-Lucent has attributed to the
32  * Contribution.
33  *
34  * THE CONTRIBUTION BY ALCATEL-LUCENT IS PROVIDED AS IS, WITHOUT WARRANTY
35  * OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
36  * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, COMPLIANCE,
37  * NON-INTERFERENCE AND/OR INTERWORKING WITH THE SOFTWARE TO WHICH THE
38  * CONTRIBUTION HAS BEEN MADE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
39  * ALCATEL-LUCENT BE LIABLE FOR ANY DAMAGES OR OTHER LIABLITY, WHETHER IN
40  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
41  * CONTRIBUTION OR THE USE OR OTHER DEALINGS IN THE CONTRIBUTION, WHETHER
42  * TOGETHER WITH THE SOFTWARE TO WHICH THE CONTRIBUTION RELATES OR ON A STAND
43  * ALONE BASIS."
44  */
45
46 require_once 'lib/ErrorManager.php';
47 require_once 'lib/WikiDB/backend/PearDB_pgsql.php';
48
49 class WikiDB_backend_PearDB_ffpgsql
50     extends WikiDB_backend_PearDB_pgsql
51 {
52     function __construct($dbparams)
53     {
54         $dbparams['dsn'] = str_replace('ffpgsql:', 'pgsql:', $dbparams['dsn']);
55         parent::__construct($dbparams);
56
57         global $page_prefix;
58         $p = strlen($page_prefix) + 1;
59         $page_tbl = $this->_table_names['page_tbl'];
60         $this->page_tbl_fields = "$page_tbl.id AS id, substring($page_tbl.pagename from $p) AS pagename, $page_tbl.hits AS hits";
61
62         pg_set_client_encoding("iso-8859-1");
63     }
64
65     function get_all_pagenames()
66     {
67         $dbh = &$this->_dbh;
68         extract($this->_table_names);
69         global $page_prefix;
70         $pat = $page_prefix;
71         $p = strlen($pat) + 1;
72         return $dbh->getCol("SELECT substring(pagename from $p)"
73             . " FROM $nonempty_tbl, $page_tbl"
74             . " WHERE $nonempty_tbl.id=$page_tbl.id"
75             . " AND substring($page_tbl.pagename from 0 for $p) = '$pat'");
76     }
77
78     function numPages($filter = false, $exclude = '')
79     {
80         $dbh = &$this->_dbh;
81         extract($this->_table_names);
82         global $page_prefix;
83         $pat = $page_prefix;
84         $p = strlen($pat) + 1;
85         return $dbh->getOne("SELECT count(*)"
86             . " FROM $nonempty_tbl, $page_tbl"
87             . " WHERE $nonempty_tbl.id=$page_tbl.id"
88             . " AND substring($page_tbl.pagename from 0 for $p) = '$pat'");
89     }
90
91     function get_pagedata($pagename)
92     {
93         global $page_prefix;
94         return parent::get_pagedata($page_prefix . $pagename);
95     }
96
97     function update_pagedata($pagename, $newdata)
98     {
99         $dbh = &$this->_dbh;
100         $page_tbl = $this->_table_names['page_tbl'];
101
102         // Hits is the only thing we can update in a fast manner.
103         if (count($newdata) == 1 && isset($newdata['hits'])) {
104             // Note that this will fail silently if the page does not
105             // have a record in the page table.  Since it's just the
106             // hit count, who cares?
107             global $page_prefix;
108             $pagename = $page_prefix . $pagename;
109             $dbh->query(sprintf("UPDATE $page_tbl SET hits=%d WHERE pagename='%s'",
110                 $newdata['hits'], $dbh->escapeSimple($pagename)));
111             return;
112         }
113
114         $this->lock(array($page_tbl), true);
115         $data = $this->get_pagedata($pagename);
116         if (!$data) {
117             $data = array();
118             $this->_get_pageid($pagename, true); // Creates page record
119         }
120
121         if (isset($data['hits'])) {
122             $hits = (int)$data['hits'];
123             unset($data['hits']);
124         } else {
125             $hits = 0;
126         }
127
128         foreach ($newdata as $key => $val) {
129             if ($key == 'hits')
130                 $hits = (int)$val;
131             else if (empty($val))
132                 unset($data[$key]);
133             else
134                 $data[$key] = $val;
135         }
136
137         /* Portability issue -- not all DBMS supports huge strings
138          * so we need to 'bind' instead of building a simple SQL statment.
139          * Note that we do not need to escapeSimple when we bind
140         $dbh->query(sprintf("UPDATE $page_tbl"
141                             . " SET hits=%d, pagedata='%s'"
142                             . " WHERE pagename='%s'",
143                             $hits,
144                             $dbh->escapeSimple($this->_serialize($data)),
145                             $dbh->escapeSimple($pagename)));
146         */
147         global $page_prefix;
148         $pagename = $page_prefix . $pagename;
149         $dbh->query("UPDATE $page_tbl"
150                 . " SET hits=?, pagedata=?"
151                 . " WHERE pagename=?",
152             array($hits, $this->_serialize($data), $pagename));
153         $this->unlock(array($page_tbl));
154     }
155
156     function get_latest_version($pagename)
157     {
158         global $page_prefix;
159         return parent::get_latest_version($page_prefix . $pagename);
160     }
161
162     function get_previous_version($pagename, $version)
163     {
164         global $page_prefix;
165         return parent::get_previous_version($page_prefix . $pagename, $version);
166     }
167
168     function get_versiondata($pagename, $version, $want_content = false)
169     {
170         $dbh = &$this->_dbh;
171         extract($this->_table_names);
172         extract($this->_expressions);
173
174         // assert(is_string($pagename) and $pagename != "");
175         // assert($version > 0);
176
177         //trigger_error("GET_REVISION $pagename $version $want_content", E_USER_NOTICE);
178         // FIXME: optimization: sometimes don't get page data?
179         if ($want_content) {
180             $fields = $this->page_tbl_fields
181                 . ",$page_tbl.pagedata as pagedata,"
182                 . $this->version_tbl_fields;
183         } else {
184             $fields = $this->page_tbl_fields . ","
185                 . "mtime, minor_edit, versiondata,"
186                 . "$iscontent AS have_content";
187         }
188
189         global $page_prefix;
190         $pagename = $page_prefix . $pagename;
191         $result = $dbh->getRow(sprintf("SELECT $fields"
192                     . " FROM $page_tbl, $version_tbl"
193                     . " WHERE $page_tbl.id=$version_tbl.id"
194                     . "  AND pagename='%s'"
195                     . "  AND version=%d",
196                 $dbh->escapeSimple($pagename), $version),
197             DB_FETCHMODE_ASSOC);
198
199         return $this->_extract_version_data($result);
200     }
201
202     function get_cached_html($pagename)
203     {
204         global $page_prefix;
205         return parent::get_cached_html($page_prefix . $pagename);
206     }
207
208     function set_cached_html($pagename, $data)
209     {
210         global $page_prefix;
211         parent::set_cached_html($page_prefix . $pagename, $data);
212     }
213
214     function _get_pageid($pagename, $create_if_missing = false)
215     {
216
217         // check id_cache
218         global $request;
219         $cache =& $request->_dbi->_cache->_id_cache;
220         if (isset($cache[$pagename])) {
221             if ($cache[$pagename] or !$create_if_missing) {
222                 return $cache[$pagename];
223             }
224         }
225
226         // attributes play this game.
227         if ($pagename === '') return 0;
228
229         $dbh = &$this->_dbh;
230         $page_tbl = $this->_table_names['page_tbl'];
231         global $page_prefix;
232         $pagename = $page_prefix . $pagename;
233
234         $query = sprintf("SELECT id FROM $page_tbl WHERE pagename='%s'",
235             $dbh->escapeSimple($pagename));
236
237         if (!$create_if_missing)
238             return $dbh->getOne($query);
239
240         $id = $dbh->getOne($query);
241         if (empty($id)) {
242             $this->lock(array($page_tbl), true); // write lock
243             $max_id = $dbh->getOne("SELECT MAX(id) FROM $page_tbl");
244             $id = $max_id + 1;
245             // requires createSequence and on mysql lock the interim table ->getSequenceName
246             //$id = $dbh->nextId($page_tbl . "_id");
247             $dbh->query(sprintf("INSERT INTO $page_tbl"
248                     . " (id,pagename,hits)"
249                     . " VALUES (%d,'%s',0)",
250                 $id, $dbh->escapeSimple($pagename)));
251             $this->unlock(array($page_tbl));
252         }
253         return $id;
254     }
255
256     function purge_page($pagename)
257     {
258         $dbh = &$this->_dbh;
259         extract($this->_table_names);
260
261         $this->lock();
262         if (($id = $this->_get_pageid($pagename, false))) {
263             $dbh->query("DELETE FROM $nonempty_tbl WHERE id=$id");
264             $dbh->query("DELETE FROM $recent_tbl   WHERE id=$id");
265             $dbh->query("DELETE FROM $version_tbl  WHERE id=$id");
266             $dbh->query("DELETE FROM $link_tbl     WHERE linkfrom=$id");
267             $nlinks = $dbh->getOne("SELECT COUNT(*) FROM $link_tbl WHERE linkto=$id");
268             if ($nlinks) {
269                 // We're still in the link table (dangling link) so we can't delete this
270                 // altogether.
271                 $dbh->query("UPDATE $page_tbl SET hits=0, pagedata='' WHERE id=$id");
272                 $result = 0;
273             } else {
274                 $dbh->query("DELETE FROM $page_tbl WHERE id=$id");
275                 $result = 1;
276             }
277         } else {
278             $result = -1; // already purged or not existing
279         }
280         $this->unlock();
281         return $result;
282     }
283
284     function get_links($pagename, $reversed = true, $include_empty = false,
285                        $sortby = '', $limit = '', $exclude = '',
286                        $want_relations = false)
287     {
288         $dbh = &$this->_dbh;
289         extract($this->_table_names);
290
291         if ($reversed)
292             list($have, $want) = array('linkee', 'linker');
293         else
294             list($have, $want) = array('linker', 'linkee');
295         $orderby = $this->sortby($sortby, 'db', array('pagename'));
296         if ($orderby) $orderby = " ORDER BY $want." . $orderby;
297         if ($exclude) // array of pagenames
298             $exclude = " AND $want.pagename NOT IN " . $this->_sql_set($exclude);
299         else
300             $exclude = '';
301
302         global $page_prefix;
303         $pat = $page_prefix;
304         $p = strlen($pat) + 1;
305
306         $qpagename = $dbh->escapeSimple($pagename);
307         // MeV+APe 2007-11-14
308         // added "dummyname" so that database accepts "ORDER BY"
309         $sql = "SELECT DISTINCT $want.id AS id, substring($want.pagename from $p) AS pagename, $want.pagename AS dummyname,"
310             . ($want_relations ? " related.pagename as linkrelation" : " $want.hits AS hits")
311             . " FROM "
312             . (!$include_empty ? "$nonempty_tbl, " : '')
313             . " $page_tbl linkee, $page_tbl linker, $link_tbl "
314             . ($want_relations ? " JOIN $page_tbl related ON ($link_tbl.relation=related.id)" : '')
315             . " WHERE linkfrom=linker.id AND linkto=linkee.id"
316             . " AND $have.pagename='$pat$qpagename'"
317             . " AND substring($want.pagename from 0 for $p) = '$pat'"
318             . (!$include_empty ? " AND $nonempty_tbl.id=$want.id" : "")
319             //. " GROUP BY $want.id"
320             . $exclude
321             . $orderby;
322         if ($limit) {
323             // extract from,count from limit
324             list($from, $count) = $this->limit($limit);
325             $result = $dbh->limitQuery($sql, $from, $count);
326         } else {
327             $result = $dbh->query($sql);
328         }
329
330         return new WikiDB_backend_PearDB_iter($this, $result);
331     }
332
333     public function get_all_pages($include_empty = false,
334                                   $sortby = '', $limit = '', $exclude = '')
335     {
336         $dbh = &$this->_dbh;
337         extract($this->_table_names);
338
339         global $page_prefix;
340         $pat = $page_prefix;
341         $p = strlen($pat) + 1;
342
343         $orderby = $this->sortby($sortby, 'db');
344         if ($orderby) $orderby = ' ORDER BY ' . $orderby;
345         if ($exclude) // array of pagenames
346             $exclude = " AND $page_tbl.pagename NOT IN " . $this->_sql_set($exclude);
347         else
348             $exclude = '';
349
350         if (strstr($orderby, 'mtime ')) { // multiple columns possible
351             if ($include_empty) {
352                 $sql = "SELECT "
353                     . $this->page_tbl_fields
354                     . " FROM $page_tbl, $recent_tbl, $version_tbl"
355                     . " WHERE $page_tbl.id=$recent_tbl.id"
356                     . " AND $page_tbl.id=$version_tbl.id AND latestversion=version"
357                     . " AND substring($page_tbl.pagename from 0 for $p) = '$pat'"
358                     . $exclude
359                     . $orderby;
360             } else {
361                 $sql = "SELECT "
362                     . $this->page_tbl_fields
363                     . " FROM $nonempty_tbl, $page_tbl, $recent_tbl, $version_tbl"
364                     . " WHERE $nonempty_tbl.id=$page_tbl.id"
365                     . " AND $page_tbl.id=$recent_tbl.id"
366                     . " AND $page_tbl.id=$version_tbl.id AND latestversion=version"
367                     . " AND substring($page_tbl.pagename from 0 for $p) = '$pat'"
368                     . $exclude
369                     . $orderby;
370             }
371         } else {
372             if ($include_empty) {
373                 $sql = "SELECT "
374                     . $this->page_tbl_fields
375                     . " FROM $page_tbl"
376                     . ($exclude ? " WHERE $exclude" : '')
377                     . ($exclude ? " AND " : " WHERE ")
378                     . " substring($page_tbl.pagename from 0 for $p) = '$pat'"
379                     . $orderby;
380             } else {
381                 $sql = "SELECT "
382                     . $this->page_tbl_fields
383                     . " FROM $nonempty_tbl, $page_tbl"
384                     . " WHERE $nonempty_tbl.id=$page_tbl.id"
385                     . " AND substring($page_tbl.pagename from 0 for $p) = '$pat'"
386                     . $exclude
387                     . $orderby;
388             }
389         }
390         if ($limit && $orderby) {
391             // extract from,count from limit
392             list($from, $count) = $this->limit($limit);
393             $result = $dbh->limitQuery($sql, $from, $count);
394             $options = array('limit_by_db' => 1);
395         } else {
396             $result = $dbh->query($sql);
397             $options = array('limit_by_db' => 0);
398         }
399         return new WikiDB_backend_PearDB_iter($this, $result, $options);
400     }
401
402     public function most_popular($limit = 20, $sortby = '-hits')
403     {
404         $dbh = &$this->_dbh;
405         extract($this->_table_names);
406         global $page_prefix;
407         $pat = $page_prefix;
408         $p = strlen($pat) + 1;
409         if ($limit < 0) {
410             $order = "hits ASC";
411             $limit = -$limit;
412             $where = "";
413         } else {
414             $order = "hits DESC";
415             $where = " AND hits > 0";
416         }
417         $orderby = '';
418         if ($sortby != '-hits') {
419             if ($order = $this->sortby($sortby, 'db'))
420                 $orderby = " ORDER BY " . $order;
421         } else {
422             $orderby = " ORDER BY $order";
423         }
424         //$limitclause = $limit ? " LIMIT $limit" : '';
425         $sql = "SELECT "
426             . $this->page_tbl_fields
427             . " FROM $nonempty_tbl, $page_tbl"
428             . " WHERE $nonempty_tbl.id=$page_tbl.id"
429             . " AND substring($page_tbl.pagename from 0 for $p) = '$pat'"
430             . $where
431             . $orderby;
432         if ($limit) {
433             list($from, $count) = $this->limit($limit);
434             $result = $dbh->limitQuery($sql, $from, $count);
435         } else {
436             $result = $dbh->query($sql);
437         }
438
439         return new WikiDB_backend_PearDB_iter($this, $result);
440     }
441
442     public function most_recent($params)
443     {
444         $limit = 0;
445         $since = 0;
446         $include_minor_revisions = false;
447         $exclude_major_revisions = false;
448         $include_all_revisions = false;
449         extract($params);
450
451         $dbh = &$this->_dbh;
452         extract($this->_table_names);
453
454         $pick = array();
455         if ($since)
456             $pick[] = "mtime >= $since";
457
458         if ($include_all_revisions) {
459             // Include all revisions of each page.
460             $table = "$page_tbl, $version_tbl";
461             $join_clause = "$page_tbl.id=$version_tbl.id";
462
463             if ($exclude_major_revisions) {
464                 // Include only minor revisions
465                 $pick[] = "minor_edit <> 0";
466             } elseif (!$include_minor_revisions) {
467                 // Include only major revisions
468                 $pick[] = "minor_edit = 0";
469             }
470         } else {
471             $table = "$page_tbl, $recent_tbl";
472             $join_clause = "$page_tbl.id=$recent_tbl.id";
473             $table .= ", $version_tbl";
474             $join_clause .= " AND $version_tbl.id=$page_tbl.id";
475
476             if ($exclude_major_revisions) {
477                 // Include only most recent minor revision
478                 $pick[] = 'version=latestminor';
479             } elseif (!$include_minor_revisions) {
480                 // Include only most recent major revision
481                 $pick[] = 'version=latestmajor';
482             } else {
483                 // Include only the latest revision (whether major or minor).
484                 $pick[] = 'version=latestversion';
485             }
486         }
487         $order = "DESC";
488         if ($limit < 0) {
489             $order = "ASC";
490             $limit = -$limit;
491         }
492         // $limitclause = $limit ? " LIMIT $limit" : '';
493         $where_clause = $join_clause;
494         if ($pick)
495             $where_clause .= " AND " . join(" AND ", $pick);
496
497         global $page_prefix;
498         $pat = $page_prefix;
499         $p = strlen($pat) + 1;
500
501         // FIXME: use SQL_BUFFER_RESULT for mysql?
502         $sql = "SELECT "
503             . $this->page_tbl_fields . ", " . $this->version_tbl_fields
504             . " FROM $table"
505             . " WHERE $where_clause"
506             . " AND substring($page_tbl.pagename from 0 for $p) = '$pat'"
507             . " ORDER BY mtime $order";
508         if ($limit) {
509             list($from, $count) = $this->limit($limit);
510             $result = $dbh->limitQuery($sql, $from, $count);
511         } else {
512             $result = $dbh->query($sql);
513         }
514         return new WikiDB_backend_PearDB_iter($this, $result);
515     }
516
517     function wanted_pages($exclude_from = '', $exclude = '', $sortby = '', $limit = '')
518     {
519         $dbh = &$this->_dbh;
520         extract($this->_table_names);
521         global $page_prefix;
522         $pat = $page_prefix;
523         $p = strlen($pat) + 1;
524         if ($orderby = $this->sortby($sortby, 'db', array('pagename', 'wantedfrom')))
525             $orderby = 'ORDER BY ' . $orderby;
526
527         if ($exclude_from) // array of pagenames
528             $exclude_from = " AND pp.pagename NOT IN " . $this->_sql_set($exclude_from);
529         if ($exclude) // array of pagenames
530             $exclude = " AND p.pagename NOT IN " . $this->_sql_set($exclude);
531
532         $p = strlen($page_prefix) + 1;
533         $sql = "SELECT substring(p.pagename from $p) AS wantedfrom, substring(pp.pagename from $p) AS pagename"
534             . " FROM $page_tbl p, $link_tbl linked"
535             . " LEFT JOIN $page_tbl pp ON linked.linkto = pp.id"
536             . " LEFT JOIN $nonempty_tbl ne ON linked.linkto = ne.id"
537             . " WHERE ne.id IS NULL"
538             . " AND p.id = linked.linkfrom"
539             . " AND substring(p.pagename from 0 for $p) = '$pat'"
540             . " AND substring(pp.pagename from 0 for $p) = '$pat'"
541             . $exclude_from
542             . $exclude
543             . $orderby;
544         if ($limit) {
545             // oci8 error: WHERE NULL = NULL appended
546             list($from, $count) = $this->limit($limit);
547             $result = $dbh->limitQuery($sql, $from, $count * 3);
548         } else {
549             $result = $dbh->query($sql);
550         }
551         return new WikiDB_backend_PearDB_generic_iter($this, $result);
552     }
553
554     function rename_page($pagename, $to)
555     {
556         $dbh = &$this->_dbh;
557         extract($this->_table_names);
558
559         $this->lock();
560         if (($id = $this->_get_pageid($pagename, false))) {
561             if ($new = $this->_get_pageid($to, false)) {
562                 // Cludge Alert!
563                 // This page does not exist (already verified before), but exists in the page table.
564                 // So we delete this page.
565                 $dbh->query("DELETE FROM $nonempty_tbl WHERE id=$new");
566                 $dbh->query("DELETE FROM $recent_tbl WHERE id=$new");
567                 $dbh->query("DELETE FROM $version_tbl WHERE id=$new");
568                 // We have to fix all referring tables to the old id
569                 $dbh->query("UPDATE $link_tbl SET linkfrom=$id WHERE linkfrom=$new");
570                 $dbh->query("UPDATE $link_tbl SET linkto=$id WHERE linkto=$new");
571                 $dbh->query("DELETE FROM $page_tbl WHERE id=$new");
572             }
573             global $page_prefix;
574             $dbh->query(sprintf("UPDATE $page_tbl SET pagename='%s' WHERE id=$id",
575                 $dbh->escapeSimple($page_prefix . $to)));
576         }
577         $this->unlock();
578         return $id;
579     }
580
581     function is_wiki_page($pagename)
582     {
583         global $page_prefix;
584         return parent::is_wiki_page($page_prefix . $pagename);
585     }
586
587     function increaseHitCount($pagename)
588     {
589         global $page_prefix;
590         parent::increaseHitCount($page_prefix . $pagename);
591     }
592
593     function _serialize($data)
594     {
595         return WikiDB_backend_PearDB::_serialize($data);
596     }
597
598     /**
599      * Pack tables.
600      * NOTE: Disable vacuum, wikiuser is not the table owner
601      */
602     function optimize()
603     {
604         return true;
605     }
606
607     /**
608      * Text search (title or full text)
609      */
610     public function text_search($search, $fulltext = false,
611                                 $sortby = '', $limit = '', $exclude = '')
612     {
613         $dbh = &$this->_dbh;
614         extract($this->_table_names);
615         global $page_prefix;
616         $pat = $page_prefix;
617         $len = strlen($pat) + 1;
618         $orderby = $this->sortby($sortby, 'db');
619         if ($sortby and $orderby) $orderby = ' ORDER BY ' . $orderby;
620
621         $searchclass = get_class($this) . "_search";
622         // no need to define it everywhere and then fallback. memory!
623         if (!class_exists($searchclass))
624             $searchclass = "WikiDB_backend_PearDB_search";
625         $searchobj = new $searchclass($search, $dbh);
626
627         $table = "$nonempty_tbl, $page_tbl";
628         $join_clause = "$nonempty_tbl.id=$page_tbl.id";
629         $fields = $this->page_tbl_fields;
630
631         if ($fulltext) {
632             $table .= ", $recent_tbl";
633             $join_clause .= " AND $page_tbl.id=$recent_tbl.id";
634
635             $table .= ", $version_tbl";
636             $join_clause .= " AND $page_tbl.id=$version_tbl.id AND latestversion=version";
637
638             $fields .= ", $page_tbl.pagedata as pagedata, " . $this->version_tbl_fields;
639             // TODO: title still ignored, need better rank and subselect
640             $callback = new WikiMethodCb($searchobj, "_fulltext_match_clause");
641             $search_string = $search->makeTsearch2SqlClauseObj($callback);
642             $search_string = str_replace('%', '', $search_string);
643             $search_clause = "substring(plugin_wiki_page.pagename from 0 for $len) = '$pat') AND (";
644
645             $search_clause .= "idxFTI @@ plainto_tsquery('english', '$search_string')";
646             if (!$orderby)
647                 $orderby = " ORDER BY ts_rank(idxFTI, plainto_tsquery('english', '$search_string')) DESC";
648         } else {
649             $callback = new WikiMethodCb($searchobj, "_pagename_match_clause");
650             $search_clause = "substring(plugin_wiki_page.pagename from 0 for $len) = '$pat') AND (";
651             $search_clause .= $search->makeSqlClauseObj($callback);
652         }
653
654         $sql = "SELECT $fields FROM $table"
655             . " WHERE $join_clause"
656             . "  AND ($search_clause)"
657             . $orderby;
658         if ($limit) {
659             list($from, $count) = $this->limit($limit);
660             $result = $dbh->limitQuery($sql, $from, $count);
661         } else {
662             $result = $dbh->query($sql);
663         }
664
665         $iter = new WikiDB_backend_PearDB_iter($this, $result);
666         $iter->stoplisted = @$searchobj->stoplisted;
667         return $iter;
668     }
669
670     function exists_link($pagename, $link, $reversed = false)
671     {
672         $dbh = &$this->_dbh;
673         extract($this->_table_names);
674
675         if ($reversed)
676             list($have, $want) = array('linkee', 'linker');
677         else
678             list($have, $want) = array('linker', 'linkee');
679         $qpagename = $dbh->escapeSimple($pagename);
680         $qlink = $dbh->escapeSimple($link);
681         $row = $dbh->GetRow("SELECT $want.pagename as result"
682             . " FROM $link_tbl, $page_tbl linker, $page_tbl linkee, $nonempty_tbl"
683             . " WHERE linkfrom=linker.id AND linkto=linkee.id"
684             . " AND $have.pagename='$qpagename'"
685             . " AND $want.pagename='$qlink'"
686             . " LIMIT 1");
687         return $row['result'] ? 1 : 0;
688     }
689 }
690
691 class WikiDB_backend_PearDB_ffpgsql_search
692     extends WikiDB_backend_PearDB_pgsql_search
693 {
694     function _pagename_match_clause($node)
695     {
696         $word = $node->sql();
697         // @alu: use _quote maybe instead of direct pg_escape_string
698         $word = pg_escape_string($word);
699         global $page_prefix;
700         $len = strlen($page_prefix) + 1;
701         if ($node->op == 'REGEX') { // posix regex extensions
702             return ($this->_case_exact
703                 ? "substring(pagename from $len) ~* '$word'"
704                 : "substring(pagename from $len) ~ '$word'");
705         } else {
706             return ($this->_case_exact
707                 ? "substring(pagename from $len) LIKE '$word'"
708                 : "substring(pagename from $len) ILIKE '$word'");
709         }
710     }
711
712     /**
713      * use tsearch2. See schemas/psql-tsearch2.sql and /usr/share/postgresql/contrib/tsearch2.sql
714      * TODO: don't parse the words into nodes. rather replace "[ +]" with & and "-" with "!" and " or " with "|"
715      * tsearch2 query language: @@ "word | word", "word & word", ! word
716      * ~* '.*something that does not exist.*'
717      */
718     /*
719      phrase search for "history lesson":
720
721      SELECT id FROM tab WHERE ts_idx_col @@ to_tsquery('history&lesson')
722      AND text_col ~* '.*history\\s+lesson.*';
723
724      The full-text index will still be used, and the regex will be used to
725      prune the results afterwards.
726     */
727     function _fulltext_match_clause($node)
728     {
729         $word = strtolower($node->word);
730         // $word = str_replace(" ", "&", $word); // phrase fix
731
732         // @alu: use _quote maybe instead of direct pg_escape_string
733         $word = pg_escape_string($word);
734
735         return $word;
736     }
737 }
738
739 // Local Variables:
740 // mode: php
741 // tab-width: 8
742 // c-basic-offset: 4
743 // c-hanging-comment-ender-p: nil
744 // indent-tabs-mode: nil
745 // End: