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