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