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