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