File: PearDB_pgsql.php

package info (click to toggle)
phpwiki 1.3.14-3
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 15,716 kB
  • ctags: 23,548
  • sloc: php: 88,295; sql: 1,476; sh: 1,378; perl: 765; makefile: 602; awk: 28
file content (339 lines) | stat: -rwxr-xr-x 11,314 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
<?php // -*-php-*-
rcs_id('$Id: PearDB_pgsql.php,v 1.26 2007/01/04 16:45:49 rurban Exp $');

require_once('lib/ErrorManager.php');
require_once('lib/WikiDB/backend/PearDB.php');

if (!defined("USE_BYTEA")) // see schemas/psql-initialize.sql
    //define("USE_BYTEA", true);
    define("USE_BYTEA", false);

/*
Since 1.3.12 changed to use:
 * Foreign Keys 
 * ON DELETE CASCADE
 * tsearch2
*/

class WikiDB_backend_PearDB_pgsql
extends WikiDB_backend_PearDB
{
    function WikiDB_backend_PearDB_pgsql($dbparams) {
        // The pgsql handler of (at least my version of) the PEAR::DB
        // library generates three warnings when a database is opened:
        //
        //     Undefined index: options
        //     Undefined index: tty
        //     Undefined index: port
        //
        // This stuff is all just to catch and ignore these warnings,
        // so that they don't get reported to the user.  (They are
        // not consequential.)  

        global $ErrorManager;
        $ErrorManager->pushErrorHandler(new WikiMethodCb($this,'_pgsql_open_error'));
        $this->WikiDB_backend_PearDB($dbparams);
        $ErrorManager->popErrorHandler();
    }

    function _pgsql_open_error($error) {
        if (preg_match('/^Undefined\s+index:\s+(options|tty|port)/',
                       $error->errstr))
            return true;        // Ignore error
        return false;
    }
            
    /**
     * Pack tables. 
     * NOTE: Only the table owner can do this. Either fix the schema or setup autovacuum.
     */
    function optimize() {
        return 0;	// if the wikiuser is not the table owner

        foreach ($this->_table_names as $table) {
            $this->_dbh->query("VACUUM ANALYZE $table");
        }
        return 1;
    }

    function _quote($s) {
        if (USE_BYTEA)
            return pg_escape_bytea($s);
	if (function_exists('pg_escape_string'))
	    return pg_escape_string($s);
	else
	    return base64_encode($s);
    }

    function _unquote($s) {
        if (USE_BYTEA)
            return pg_unescape_bytea($s);
	if (function_exists('pg_escape_string'))
	    return $s;
	else
	    return base64_decode($s);
    }

    // Until the binary escape problems on pear pgsql are solved */
    function get_cached_html($pagename) {
        $dbh = &$this->_dbh;
        $page_tbl = $this->_table_names['page_tbl'];
        $data = $dbh->GetOne(sprintf("SELECT cached_html FROM $page_tbl WHERE pagename='%s'",
                                     $dbh->escapeSimple($pagename)));
        if ($data) return $this->_unquote($data);
        else return '';
    }

    function set_cached_html($pagename, $data) {
        $dbh = &$this->_dbh;
        $page_tbl = $this->_table_names['page_tbl'];
        if (USE_BYTEA)
            $sth = $dbh->query(sprintf("UPDATE $page_tbl"
                                       . " SET cached_html='%s'"
                                       . " WHERE pagename='%s'",
                                       $this->_quote($data), 
                                       $dbh->escapeSimple($pagename)));
        else
            $sth = $dbh->query("UPDATE $page_tbl"
                               . " SET cached_html=?"
                               . " WHERE pagename=?",
                               // PearDB does NOT use pg_escape_string()! Oh dear.
                               array($this->_quote($data), $pagename));
    }

    /**
     * Create a new revision of a page.
     */
    function _todo_set_versiondata($pagename, $version, $data) {
        $dbh = &$this->_dbh;
        $version_tbl = $this->_table_names['version_tbl'];
        
        $minor_edit = (int) !empty($data['is_minor_edit']);
        unset($data['is_minor_edit']);
        
        $mtime = (int)$data['mtime'];
        unset($data['mtime']);
        assert(!empty($mtime));

        @$content = (string) $data['%content'];
        unset($data['%content']);
        unset($data['%pagedata']);
        
        $this->lock();
        $id = $this->_get_pageid($pagename, true);
        $dbh->query(sprintf("DELETE FROM version WHERE id=%d AND version=%d", $id, $version));
        $dbh->query(sprintf("INSERT INTO version (id,version,mtime,minor_edit,content,versiondata)" .
                            " VALUES (%d, %d, %d, %d, '%s', '%s')",
                            $id, $version, $mtime, $minor_edit,
                            $this->_quote($content),
                            $this->_serialize($data)));
        // TODO: This function does not work yet
        $dbh->query(sprintf("SELECT update_recent (%d, %d)", $id, $version));
        $this->unlock();
    }

    /**
     * Delete an old revision of a page.
     */
    function _todo_delete_versiondata($pagename, $version) {
        $dbh = &$this->_dbh;
        // TODO: This function was removed
        $dbh->query(sprintf("SELECT delete_versiondata (%d, %d)", $id, $version));
    }

    /**
     * Rename page in the database.
     */
    function _todo_rename_page ($pagename, $to) {
        $dbh = &$this->_dbh;
        extract($this->_table_names);
        
        $this->lock();
        if (($id = $this->_get_pageid($pagename, false)) ) {
            if ($new = $this->_get_pageid($to, false)) {
                // Cludge Alert!
                // This page does not exist (already verified before), but exists in the page table.
                // So we delete this page in one step.
                $dbh->query("SELECT prepare_rename_page($id, $new)");
            }
            $dbh->query(sprintf("UPDATE $page_tbl SET pagename='%s' WHERE id=$id",
                                $dbh->escapeSimple($to)));
        }
        $this->unlock();
        return $id;
    }

    /**
     * Lock all tables we might use.
     */
    function _lock_tables($write_lock=true) {
        $this->_dbh->query("BEGIN");
    }

    /**
     * Unlock all tables.
     */
    function _unlock_tables() {
        $this->_dbh->query("COMMIT");
    }

    /**
     * Serialize data
     */
    function _serialize($data) {
        if (empty($data))
            return '';
        assert(is_array($data));
        return $this->_quote(serialize($data));
    }

    /**
     * Unserialize data
     */
    function _unserialize($data) {
        if (empty($data))
            return array();
        // Base64 encoded data does not contain colons.
        //  (only alphanumerics and '+' and '/'.)
        if (substr($data,0,2) == 'a:')
            return unserialize($data);
        return unserialize($this->_unquote($data));
    }

    /**
     * Title search.
     */
    function text_search($search, $fulltext=false, $sortby='', $limit='', 
                         $exclude='') 
    {
        $dbh = &$this->_dbh;
        extract($this->_table_names);
        $orderby = $this->sortby($sortby, 'db');
        if ($sortby and $orderby) $orderby = ' ORDER BY ' . $orderby;

        $searchclass = get_class($this)."_search";
        // no need to define it everywhere and then fallback. memory!
        if (!class_exists($searchclass))
            $searchclass = "WikiDB_backend_PearDB_search";
        $searchobj = new $searchclass($search, $dbh);
        
        $table = "$nonempty_tbl, $page_tbl";
        $join_clause = "$nonempty_tbl.id=$page_tbl.id";
        $fields = $this->page_tbl_fields;

        if ($fulltext) {
            $table .= ", $recent_tbl";
            $join_clause .= " AND $page_tbl.id=$recent_tbl.id";

            $table .= ", $version_tbl";
            $join_clause .= " AND $page_tbl.id=$version_tbl.id AND latestversion=version";

            $fields .= ", $page_tbl.pagedata as pagedata, " . $this->version_tbl_fields;
	    // TODO: title still ignored, need better rank and subselect
            $callback = new WikiMethodCb($searchobj, "_fulltext_match_clause");
            $search_string = $search->makeTsearch2SqlClauseObj($callback);
            $search_string = str_replace(array("%"," "), array("","&"), $search_string);
            $search_clause = "idxFTI @@ to_tsquery('$search_string')";
            if (!$orderby)
               $orderby = " ORDER BY rank(idxFTI, to_tsquery('$search_string')) DESC";
        } else {
            $callback = new WikiMethodCb($searchobj, "_pagename_match_clause");
            $search_clause = $search->makeSqlClauseObj($callback);
        }
        
        $sql = "SELECT $fields FROM $table"
            . " WHERE $join_clause"
            . "  AND ($search_clause)"
            . $orderby;
         if ($limit) {
             list($from, $count) = $this->limit($limit);
             $result = $dbh->limitQuery($sql, $from, $count);
         } else {
             $result = $dbh->query($sql);
         }
        
        $iter = new WikiDB_backend_PearDB_iter($this, $result);
        $iter->stoplisted = @$searchobj->stoplisted;
        return $iter;
    }

};

class WikiDB_backend_PearDB_pgsql_search
extends WikiDB_backend_PearDB_search
{
    function _pagename_match_clause($node) {
        $word = $node->sql();
        if ($node->op == 'REGEX') { // posix regex extensions
            return ($this->_case_exact 
                    ? "pagename ~* '$word'"
                    : "pagename ~ '$word'");
        } else {
            return ($this->_case_exact 
                    ? "pagename LIKE '$word'" 
                    : "pagename ILIKE '$word'");
        }
    }

    /*
     most used words:
select * from stat('select idxfti from version') order by ndoc desc, nentry desc, word limit 10;
      word       | ndoc | nentry
-----------------+------+--------
 plugin          |  112 |    418
 page            |   85 |    446
 phpwikidocument |   62 |     62
 use             |   48 |    169
 help            |   46 |     96
 wiki            |   44 |    102
 name            |   43 |    131
 phpwiki         |   42 |    173
 see             |   42 |     69
 default         |   39 |    124
    */
    
    /** 
     * use tsearch2. See schemas/psql-tsearch2.sql and /usr/share/postgresql/contrib/tsearch2.sql 
     * TODO: don't parse the words into nodes. rather replace "[ +]" with & and "-" with "!" and " or " with "|"
     * tsearch2 query language: @@ "word | word", "word & word", ! word
     * ~* '.*something that does not exist.*'
     */
    /*
     phrase search for "history lesson":

     SELECT id FROM tab WHERE ts_idx_col @@ to_tsquery('history&lesson')
     AND text_col ~* '.*history\\s+lesson.*';

     The full-text index will still be used, and the regex will be used to
     prune the results afterwards.
    */
    function _fulltext_match_clause($node) {
        $word = strtolower($node->word);
        $word = str_replace(" ", "&", $word); // phrase fix
        return $word;

        // clause specified above.
        return $this->_pagename_match_clause($node) . " OR idxFTI @@ to_tsquery('$word')";
    }
}

// $Log: PearDB_pgsql.php,v $
// Revision 1.26  2007/01/04 16:45:49  rurban
// Clarify API: sortby,limit and exclude are strings.
//
// Revision 1.25  2006/12/23 11:56:17  rurban
// note about vacuum permissions
//
// Revision 1.24  2006/12/22 00:27:37  rurban
// just add Log
//

// (c-file-style: "gnu")
// Local Variables:
// mode: php
// tab-width: 8
// c-basic-offset: 4
// c-hanging-comment-ender-p: nil
// indent-tabs-mode: nil
// End:   
?>