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