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