Passed
Push — develop ( c6c0b1...90e9a5 )
by Felipe
10:30 queued 02:43
created

Postgres::getTriggerDef()   F

Complexity

Conditions 20
Paths > 20000

Size

Total Lines 115
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 65
nc 124416
nop 1
dl 0
loc 115
rs 2
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A Postgres::dropTrigger() 0 13 2
A Postgres::createTrigger() 0 14 1
A Postgres::alterTrigger() 0 11 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.45
5
 */
6
7
namespace PHPPgAdmin\Database;
8
9
/**
10
 * A Class that implements the DB Interface for Postgres
11
 * Note: This Class uses ADODB and returns RecordSets.
12
 *
13
 * Id: Postgres.php,v 1.320 2008/02/20 20:43:09 ioguix Exp $
14
 *
15
 * @package PHPPgAdmin
16
 */
17
class Postgres extends ADOdbBase
18
{
19
    use \PHPPgAdmin\Traits\HelperTrait;
20
    use \PHPPgAdmin\Traits\SequenceTrait;
21
    use \PHPPgAdmin\Traits\ViewTrait;
22
    use \PHPPgAdmin\Traits\IndexTrait;
23
    use \PHPPgAdmin\Traits\RoleTrait;
24
    use \PHPPgAdmin\Traits\AggregateTrait;
25
    use \PHPPgAdmin\Traits\TableTrait;
26
    use \PHPPgAdmin\Traits\DomainTrait;
27
    use \PHPPgAdmin\Traits\FtsTrait;
28
    use \PHPPgAdmin\Traits\FunctionTrait;
29
30
    public $lang;
31
    public $conf;
32
    protected $container;
33
    protected $server_info;
34
35
    public function __construct(&$conn, $container, $server_info)
36
    {
37
        //$this->prtrace('major_version :' . $this->major_version);
38
        $this->conn      = $conn;
39
        $this->container = $container;
40
41
        $this->lang        = $container->get('lang');
42
        $this->conf        = $container->get('conf');
43
        $this->server_info = $server_info;
44
    }
45
46
    /**
47
     * Fetch a URL (or array of URLs) for a given help page.
48
     *
49
     * @param string $help
50
     *
51
     * @return null|array|string the help page or pages related to the $help topic, or null if none exists
52
     */
53
    public function getHelp($help)
54
    {
55
        $this->getHelpPages();
56
57
        if (isset($this->help_page[$help])) {
58
            if (is_array($this->help_page[$help])) {
59
                $urls = [];
60
                foreach ($this->help_page[$help] as $link) {
61
                    $urls[] = $this->help_base . $link;
62
                }
63
64
                return $urls;
65
            }
66
67
            return $this->help_base . $this->help_page[$help];
68
        }
69
70
        return null;
71
    }
72
73
    /**
74
     * Gets the help pages.
75
     * get help page by instancing the corresponding help class
76
     * if $this->help_page and $this->help_base are set, this function is a noop.
77
     */
78
    public function getHelpPages()
79
    {
80
        if ($this->help_page === null || $this->help_base === null) {
81
            $help_classname = '\PHPPgAdmin\Help\PostgresDoc' . str_replace('.', '', $this->major_version);
82
83
            $help_class = new $help_classname($this->conf, $this->major_version);
84
85
            $this->help_base = $help_class->getHelpBase();
86
        }
87
    }
88
89
    // Formatting functions
90
91
    /**
92
     * Outputs the HTML code for a particular field.
93
     *
94
     * @param string $name   The name to give the field
95
     * @param mixed  $value  The value of the field.  Note this could be 'numeric(7,2)' sort of thing...
96
     * @param string $type   The database type of the field
97
     * @param array  $extras An array of attributes name as key and attributes' values as value
98
     */
99
    public function printField($name, $value, $type, $extras = [])
100
    {
101
        $lang = $this->lang;
102
103
        // Determine actions string
104
        $extra_str = '';
105
        foreach ($extras as $k => $v) {
106
            $extra_str .= " {$k}=\"" . htmlspecialchars($v) . '"';
107
        }
108
109
        switch (substr($type, 0, 9)) {
110
            case 'bool':
111
            case 'boolean':
112
                if ($value !== null && $value == '') {
113
                    $value = null;
114
                } elseif ($value == 'true') {
115
                    $value = 't';
116
                } elseif ($value == 'false') {
117
                    $value = 'f';
118
                }
119
120
                // If value is null, 't' or 'f'...
121
                if ($value === null || $value == 't' || $value == 'f') {
122
                    echo '<select name="', htmlspecialchars($name), "\"{$extra_str}>\n";
123
                    echo '<option value=""', ($value === null) ? ' selected="selected"' : '', "></option>\n";
124
                    echo '<option value="t"', ($value == 't') ? ' selected="selected"' : '', ">{$lang['strtrue']}</option>\n";
125
                    echo '<option value="f"', ($value == 'f') ? ' selected="selected"' : '', ">{$lang['strfalse']}</option>\n";
126
                    echo "</select>\n";
127
                } else {
128
                    echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
129
                }
130
131
                break;
132
            case 'bytea':
133
            case 'bytea[]':
134
                if (!is_null($value)) {
135
                    $value = $this->escapeBytea($value);
136
                }
137
            // no break
138
            case 'text':
139
            case 'text[]':
140
            case 'json':
141
            case 'jsonb':
142
            case 'xml':
143
            case 'xml[]':
144
                $n = substr_count($value, "\n");
145
                $n = $n < 5 ? max(2, $n) : $n;
146
                $n = $n > 20 ? 20 : $n;
147
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"85\"{$extra_str}>\n";
148
                echo htmlspecialchars($value);
149
                echo "</textarea>\n";
150
151
                break;
152
            case 'character':
153
            case 'character[]':
154
                $n = substr_count($value, "\n");
155
                $n = $n < 5 ? 5 : $n;
156
                $n = $n > 20 ? 20 : $n;
157
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"35\"{$extra_str}>\n";
158
                echo htmlspecialchars($value);
159
                echo "</textarea>\n";
160
161
                break;
162
            default:
163
                echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
164
165
                break;
166
        }
167
    }
168
169
    /**
170
     * Return all information about a particular database.
171
     *
172
     * @param string $database The name of the database to retrieve
173
     *
174
     * @return \PHPPgAdmin\ADORecordSet The database info
175
     */
176
    public function getDatabase($database)
177
    {
178
        $this->clean($database);
179
        $sql = "SELECT * FROM pg_database WHERE datname='{$database}'";
180
181
        return $this->selectSet($sql);
182
    }
183
184
    /**
185
     * Return all database available on the server.
186
     *
187
     * @param null|string $currentdatabase database name that should be on top of the resultset
188
     *
189
     * @return \PHPPgAdmin\ADORecordSet A list of databases, sorted alphabetically
190
     */
191
    public function getDatabases($currentdatabase = null)
192
    {
193
        $conf        = $this->conf;
194
        $server_info = $this->server_info;
195
196
        //$this->prtrace('server_info', $server_info);
197
198
        if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) {
199
            $username = $server_info['username'];
200
            $this->clean($username);
201
            $clause = " AND pr.rolname='{$username}'";
202
        } else {
203
            $clause = '';
204
        }
205
        if (isset($server_info['useonlydefaultdb']) && $server_info['useonlydefaultdb']) {
206
            $currentdatabase = $server_info['defaultdb'];
207
            $clause .= " AND pdb.datname = '{$currentdatabase}' ";
208
        }
209
210
        if (isset($server_info['hiddendbs']) && $server_info['hiddendbs']) {
211
            $hiddendbs = $server_info['hiddendbs'];
212
213
            $not_in = "('" . implode("','", $hiddendbs) . "')";
214
            $clause .= " AND pdb.datname NOT IN {$not_in} ";
215
        }
216
217
        if ($currentdatabase != null) {
218
            $this->clean($currentdatabase);
219
            $orderby = "ORDER BY pdb.datname = '{$currentdatabase}' DESC, pdb.datname";
220
        } else {
221
            $orderby = 'ORDER BY pdb.datname';
222
        }
223
224
        if (!$conf['show_system']) {
225
            $where = ' AND NOT pdb.datistemplate';
226
        } else {
227
            $where = ' AND pdb.datallowconn';
228
        }
229
230
        $sql = "
231
			SELECT pdb.datname AS datname,
232
                    pr.rolname AS datowner,
233
                    pg_encoding_to_char(encoding) AS datencoding,
234
				    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pdb.oid=pd.objoid AND pd.classoid='pg_database'::regclass) AS datcomment,
235
				    (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace,
236
				CASE WHEN pg_catalog.has_database_privilege(current_user, pdb.oid, 'CONNECT')
237
					THEN pg_catalog.pg_database_size(pdb.oid)
238
					ELSE -1 -- set this magic value, which we will convert to no access later
239
				END as dbsize,
240
                pdb.datcollate,
241
                pdb.datctype
242
			FROM pg_catalog.pg_database pdb
243
            LEFT JOIN pg_catalog.pg_roles pr ON (pdb.datdba = pr.oid)
244
			WHERE true
245
				{$where}
246
				{$clause}
247
			{$orderby}";
248
249
        return $this->selectSet($sql);
250
    }
251
252
    /**
253
     * Determines whether or not a user is a super user.
254
     *
255
     * @param string $username The username of the user
256
     *
257
     * @return bool true if is a super user, false otherwise
258
     */
259
    public function isSuperUser($username = '')
260
    {
261
        $this->clean($username);
262
263
        if (empty($username)) {
264
            $val = pg_parameter_status($this->conn->_connectionID, 'is_superuser');
265
            if ($val !== false) {
266
                return $val == 'on';
267
            }
268
        }
269
270
        $sql = "SELECT usesuper FROM pg_user WHERE usename='{$username}'";
271
272
        $usesuper = $this->selectField($sql, 'usesuper');
273
        if ($usesuper == -1) {
274
            return false;
275
        }
276
277
        return $usesuper == 't';
278
    }
279
280
    /**
281
     * Return the database comment of a db from the shared description table.
282
     *
283
     * @param string $database the name of the database to get the comment for
284
     *
285
     * @return \PHPPgAdmin\ADORecordSet recordset of the db comment info
286
     */
287
    public function getDatabaseComment($database)
288
    {
289
        $this->clean($database);
290
        $sql = "SELECT description
291
                FROM pg_catalog.pg_database
292
                JOIN pg_catalog.pg_shdescription
293
                ON (oid=objoid AND classoid='pg_database'::regclass)
294
                WHERE pg_database.datname = '{$database}' ";
295
296
        return $this->selectSet($sql);
297
    }
298
299
    /**
300
     * Return the database owner of a db.
301
     *
302
     * @param string $database the name of the database to get the owner for
303
     *
304
     * @return \PHPPgAdmin\ADORecordSet recordset of the db owner info
305
     */
306
    public function getDatabaseOwner($database)
307
    {
308
        $this->clean($database);
309
        $sql = "SELECT usename FROM pg_user, pg_database WHERE pg_user.usesysid = pg_database.datdba AND pg_database.datname = '{$database}' ";
310
311
        return $this->selectSet($sql);
312
    }
313
314
    // Help functions
315
316
    // Database functions
317
318
    /**
319
     * Returns the current database encoding.
320
     *
321
     * @return string The encoding.  eg. SQL_ASCII, UTF-8, etc.
322
     */
323
    public function getDatabaseEncoding()
324
    {
325
        return pg_parameter_status($this->conn->_connectionID, 'server_encoding');
326
    }
327
328
    /**
329
     * Returns the current default_with_oids setting.
330
     *
331
     * @return string default_with_oids setting
332
     */
333
    public function getDefaultWithOid()
334
    {
335
        $sql = 'SHOW default_with_oids';
336
337
        return $this->selectField($sql, 'default_with_oids');
338
    }
339
340
    /**
341
     * Creates a database.
342
     *
343
     * @param string $database   The name of the database to create
344
     * @param string $encoding   Encoding of the database
345
     * @param string $tablespace (optional) The tablespace name
346
     * @param string $comment
347
     * @param string $template
348
     * @param string $lc_collate
349
     * @param string $lc_ctype
350
     *
351
     * @return int 0 success
352
     */
353
    public function createDatabase(
354
        $database,
355
        $encoding,
356
        $tablespace = '',
357
        $comment = '',
358
        $template = 'template1',
359
        $lc_collate = '',
360
        $lc_ctype = ''
361
    ) {
362
        $this->fieldClean($database);
363
        $this->clean($encoding);
364
        $this->fieldClean($tablespace);
365
        $this->fieldClean($template);
366
        $this->clean($lc_collate);
367
        $this->clean($lc_ctype);
368
369
        $sql = "CREATE DATABASE \"{$database}\" WITH TEMPLATE=\"{$template}\"";
370
371
        if ($encoding != '') {
372
            $sql .= " ENCODING='{$encoding}'";
373
        }
374
375
        if ($lc_collate != '') {
376
            $sql .= " LC_COLLATE='{$lc_collate}'";
377
        }
378
379
        if ($lc_ctype != '') {
380
            $sql .= " LC_CTYPE='{$lc_ctype}'";
381
        }
382
383
        if ($tablespace != '' && $this->hasTablespaces()) {
384
            $sql .= " TABLESPACE \"{$tablespace}\"";
385
        }
386
387
        $status = $this->execute($sql);
388
        if ($status != 0) {
389
            return -1;
390
        }
391
392
        if ($comment != '' && $this->hasSharedComments()) {
393
            $status = $this->setComment('DATABASE', $database, '', $comment);
394
            if ($status != 0) {
395
                return -2;
396
            }
397
        }
398
399
        return 0;
400
    }
401
402
    /**
403
     * Cleans (escapes) an object name (eg. table, field).
404
     *
405
     * @param null|string $str The string to clean, by reference
406
     *
407
     * @return null|string The cleaned string
408
     */
409
    public function fieldClean(&$str)
410
    {
411
        if (!$str) {
412
            return null;
413
        }
414
415
        $str = str_replace('"', '""', $str);
416
417
        return $str;
418
    }
419
420
    /**
421
     * Drops a database.
422
     *
423
     * @param string $database The name of the database to drop
424
     *
425
     * @return int 0 if operation was successful
426
     */
427
    public function dropDatabase($database)
428
    {
429
        $this->fieldClean($database);
430
        $sql = "DROP DATABASE \"{$database}\"";
431
432
        return $this->execute($sql);
433
    }
434
435
    /**
436
     * Alters a database
437
     * the multiple return vals are for postgres 8+ which support more functionality in alter database.
438
     *
439
     * @param string $dbName   The name of the database
440
     * @param string $newName  new name for the database
441
     * @param string $newOwner The new owner for the database
442
     * @param string $comment
443
     *
444
     * @return bool|int 0 success
445
     */
446
    public function alterDatabase($dbName, $newName, $newOwner = '', $comment = '')
447
    {
448
        $status = $this->beginTransaction();
449
        if ($status != 0) {
450
            $this->rollbackTransaction();
451
452
            return -1;
453
        }
454
455
        if ($dbName != $newName) {
456
            $status = $this->alterDatabaseRename($dbName, $newName);
457
            if ($status != 0) {
458
                $this->rollbackTransaction();
459
460
                return -3;
461
            }
462
            $dbName = $newName;
463
        }
464
465
        if ($newOwner != '') {
466
            $status = $this->alterDatabaseOwner($newName, $newOwner);
467
            if ($status != 0) {
468
                $this->rollbackTransaction();
469
470
                return -2;
471
            }
472
        }
473
474
        $this->fieldClean($dbName);
475
        $status = $this->setComment('DATABASE', $dbName, '', $comment);
476
        if ($status != 0) {
477
            $this->rollbackTransaction();
478
479
            return -4;
480
        }
481
482
        return $this->endTransaction();
483
    }
484
485
    /**
486
     * Renames a database, note that this operation cannot be
487
     * performed on a database that is currently being connected to.
488
     *
489
     * @param string $oldName name of database to rename
490
     * @param string $newName new name of database
491
     *
492
     * @return int 0 on success
493
     */
494
    public function alterDatabaseRename($oldName, $newName)
495
    {
496
        $this->fieldClean($oldName);
497
        $this->fieldClean($newName);
498
499
        if ($oldName != $newName) {
500
            $sql = "ALTER DATABASE \"{$oldName}\" RENAME TO \"{$newName}\"";
501
502
            return $this->execute($sql);
503
        }
504
505
        return 0;
506
    }
507
508
    /**
509
     * Changes ownership of a database
510
     * This can only be done by a superuser or the owner of the database.
511
     *
512
     * @param string $dbName   database to change ownership of
513
     * @param string $newOwner user that will own the database
514
     *
515
     * @return int 0 on success
516
     */
517
    public function alterDatabaseOwner($dbName, $newOwner)
518
    {
519
        $this->fieldClean($dbName);
520
        $this->fieldClean($newOwner);
521
522
        $sql = "ALTER DATABASE \"{$dbName}\" OWNER TO \"{$newOwner}\"";
523
524
        return $this->execute($sql);
525
    }
526
527
    /**
528
     * Returns prepared transactions information.
529
     *
530
     * @param null|string $database (optional) Find only prepared transactions executed in a specific database
531
     *
532
     * @return \PHPPgAdmin\ADORecordSet A recordset
533
     */
534
    public function getPreparedXacts($database = null)
535
    {
536
        if ($database === null) {
537
            $sql = 'SELECT * FROM pg_prepared_xacts';
538
        } else {
539
            $this->clean($database);
540
            $sql = "SELECT transaction, gid, prepared, owner FROM pg_prepared_xacts
541
				WHERE database='{$database}' ORDER BY owner";
542
        }
543
544
        return $this->selectSet($sql);
545
    }
546
547
    /**
548
     * Searches all system catalogs to find objects that match a certain name.
549
     *
550
     * @param string $term   The search term
551
     * @param string $filter The object type to restrict to ('' means no restriction)
552
     *
553
     * @return \PHPPgAdmin\ADORecordSet A recordset
554
     */
555
    public function findObject($term, $filter)
556
    {
557
        $conf = $this->conf;
558
559
        /*about escaping:
560
         * SET standard_conforming_string is not available before 8.2
561
         * So we must use PostgreSQL specific notation :/
562
         * E'' notation is not available before 8.1
563
         * $$ is available since 8.0
564
         * Nothing specific from 7.4
565
         */
566
567
        // Escape search term for ILIKE match
568
        $this->clean($term);
569
        $this->clean($filter);
570
        $term = str_replace('_', '\_', $term);
571
        $term = str_replace('%', '\%', $term);
572
573
        // Exclude system relations if necessary
574
        if (!$conf['show_system']) {
575
            // XXX: The mention of information_schema here is in the wrong place, but
576
            // it's the quickest fix to exclude the info schema from 7.4
577
            $where     = " AND pn.nspname NOT LIKE \$_PATERN_\$pg\\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'";
578
            $lan_where = 'AND pl.lanispl';
579
        } else {
580
            $where     = '';
581
            $lan_where = '';
582
        }
583
584
        // Apply outer filter
585
        $sql = '';
586
        if ($filter != '') {
587
            $sql = 'SELECT * FROM (';
588
        }
589
590
        $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$";
591
592
        $sql .= "
593
			SELECT 'SCHEMA' AS type, oid, NULL AS schemaname, NULL AS relname, nspname AS name
594
				FROM pg_catalog.pg_namespace pn WHERE nspname ILIKE {$term} {$where}
595
			UNION ALL
596
			SELECT CASE WHEN relkind='r' THEN 'TABLE' WHEN relkind='v' THEN 'VIEW' WHEN relkind='S' THEN 'SEQUENCE' END, pc.oid,
597
				pn.nspname, NULL, pc.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn
598
				WHERE pc.relnamespace=pn.oid AND relkind IN ('r', 'v', 'S') AND relname ILIKE {$term} {$where}
599
			UNION ALL
600
			SELECT CASE WHEN pc.relkind='r' THEN 'COLUMNTABLE' ELSE 'COLUMNVIEW' END, NULL, pn.nspname, pc.relname, pa.attname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
601
				pg_catalog.pg_attribute pa WHERE pc.relnamespace=pn.oid AND pc.oid=pa.attrelid
602
				AND pa.attname ILIKE {$term} AND pa.attnum > 0 AND NOT pa.attisdropped AND pc.relkind IN ('r', 'v') {$where}
603
			UNION ALL
604
			SELECT 'FUNCTION', pp.oid, pn.nspname, NULL, pp.proname || '(' || pg_catalog.oidvectortypes(pp.proargtypes) || ')' FROM pg_catalog.pg_proc pp, pg_catalog.pg_namespace pn
605
				WHERE pp.pronamespace=pn.oid AND NOT pp.proisagg AND pp.proname ILIKE {$term} {$where}
606
			UNION ALL
607
			SELECT 'INDEX', NULL, pn.nspname, pc.relname, pc2.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
608
				pg_catalog.pg_index pi, pg_catalog.pg_class pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pi.indrelid
609
				AND pi.indexrelid=pc2.oid
610
				AND NOT EXISTS (
611
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
612
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
613
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
614
				)
615
				AND pc2.relname ILIKE {$term} {$where}
616
			UNION ALL
617
			SELECT 'CONSTRAINTTABLE', NULL, pn.nspname, pc.relname, pc2.conname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
618
				pg_catalog.pg_constraint pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pc2.conrelid AND pc2.conrelid != 0
619
				AND CASE WHEN pc2.contype IN ('f', 'c') THEN TRUE ELSE NOT EXISTS (
620
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
621
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
622
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
623
				) END
624
				AND pc2.conname ILIKE {$term} {$where}
625
			UNION ALL
626
			SELECT 'CONSTRAINTDOMAIN', pt.oid, pn.nspname, pt.typname, pc.conname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn,
627
				pg_catalog.pg_constraint pc WHERE pt.typnamespace=pn.oid AND pt.oid=pc.contypid AND pc.contypid != 0
628
				AND pc.conname ILIKE {$term} {$where}
629
			UNION ALL
630
			SELECT 'TRIGGER', NULL, pn.nspname, pc.relname, pt.tgname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
631
				pg_catalog.pg_trigger pt WHERE pc.relnamespace=pn.oid AND pc.oid=pt.tgrelid
632
					AND ( pt.tgconstraint = 0 OR NOT EXISTS
633
					(SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
634
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
635
					WHERE d.classid = pt.tableoid AND d.objid = pt.oid AND d.deptype = 'i' AND c.contype = 'f'))
636
				AND pt.tgname ILIKE {$term} {$where}
637
			UNION ALL
638
			SELECT 'RULETABLE', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
639
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
640
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
641
				WHERE c.relkind='r' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
642
			UNION ALL
643
			SELECT 'RULEVIEW', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
644
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
645
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
646
				WHERE c.relkind='v' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
647
		";
648
649
        // Add advanced objects if show_advanced is set
650
        if ($conf['show_advanced']) {
651
            $sql .= "
652
				UNION ALL
653
				SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL,
654
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
655
					WHERE pt.typnamespace=pn.oid AND typname ILIKE {$term}
656
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
657
					{$where}
658
			 	UNION ALL
659
				SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn
660
					WHERE po.oprnamespace=pn.oid AND oprname ILIKE {$term} {$where}
661
				UNION ALL
662
				SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc,
663
					pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE {$term} {$where}
664
				UNION ALL
665
				SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl
666
					WHERE lanname ILIKE {$term} {$lan_where}
667
				UNION ALL
668
				SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p
669
					LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid
670
					WHERE p.proisagg AND p.proname ILIKE {$term} {$where}
671
				UNION ALL
672
				SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po,
673
					pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid
674
					AND po.opcname ILIKE {$term} {$where}
675
			";
676
        } else {
677
            // Otherwise just add domains
678
            $sql .= "
679
				UNION ALL
680
				SELECT 'DOMAIN', pt.oid, pn.nspname, NULL,
681
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
682
					WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE {$term}
683
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
684
					{$where}
685
			";
686
        }
687
688
        if ($filter != '') {
689
            // We use like to make RULE, CONSTRAINT and COLUMN searches work
690
            $sql .= ") AS sub WHERE type LIKE '{$filter}%' ";
691
        }
692
693
        $sql .= 'ORDER BY type, schemaname, relname, name';
694
695
        return $this->selectSet($sql);
696
    }
697
698
    /**
699
     * Returns all available variable information.
700
     *
701
     * @return \PHPPgAdmin\ADORecordSet A recordset
702
     */
703
    public function getVariables()
704
    {
705
        $sql = 'SHOW ALL';
706
707
        return $this->selectSet($sql);
708
    }
709
710
    // Schema functons
711
712
    /**
713
     * Return all schemas in the current database.
714
     *
715
     * @return \PHPPgAdmin\ADORecordSet All schemas, sorted alphabetically
716
     */
717
    public function getSchemas()
718
    {
719
        $conf = $this->conf;
720
721
        if (!$conf['show_system']) {
722
            $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'";
723
        } else {
724
            $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
725
        }
726
727
        $sql = "
728
			SELECT pn.nspname,
729
                   pu.rolname AS nspowner,
730
				   pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment,
731
                   pg_size_pretty(SUM(pg_total_relation_size(pg_class.oid))) as schema_size
732
			FROM pg_catalog.pg_namespace pn
733
            LEFT JOIN pg_catalog.pg_class  ON relnamespace = pn.oid
734
			LEFT JOIN pg_catalog.pg_roles pu ON (pn.nspowner = pu.oid)
735
			{$where}
736
            GROUP BY pn.nspname, pu.rolname, pg_catalog.obj_description(pn.oid, 'pg_namespace')
737
			ORDER BY nspname";
738
739
        return $this->selectSet($sql);
740
    }
741
742
    /**
743
     * Sets the current working schema.  Will also set Class variable.
744
     *
745
     * @param string $schema The the name of the schema to work in
746
     *
747
     * @return int 0 if operation was successful
748
     */
749
    public function setSchema($schema)
750
    {
751
        // Get the current schema search path, including 'pg_catalog'.
752
        $search_path = $this->getSearchPath();
753
        // Prepend $schema to search path
754
        array_unshift($search_path, $schema);
755
        $status = $this->setSearchPath($search_path);
756
        if ($status == 0) {
757
            $this->_schema = $schema;
758
759
            return 0;
760
        }
761
762
        return $status;
763
    }
764
765
    /**
766
     * Return the current schema search path.
767
     *
768
     * @return array array of schema names
769
     */
770
    public function getSearchPath()
771
    {
772
        $sql = 'SELECT current_schemas(false) AS search_path';
773
774
        return $this->phpArray($this->selectField($sql, 'search_path'));
775
    }
776
777
    /**
778
     * Sets the current schema search path.
779
     *
780
     * @param mixed $paths An array of schemas in required search order
781
     *
782
     * @return int 0 if operation was successful
783
     */
784
    public function setSearchPath($paths)
785
    {
786
        if (!is_array($paths)) {
787
            return -1;
788
        }
789
790
        if (sizeof($paths) == 0) {
791
            return -2;
792
        }
793
        if (sizeof($paths) == 1 && $paths[0] == '') {
794
            // Need to handle empty paths in some cases
795
            $paths[0] = 'pg_catalog';
796
        }
797
798
        // Loop over all the paths to check that none are empty
799
        $temp = [];
800
        foreach ($paths as $schema) {
801
            if ($schema != '') {
802
                $temp[] = $schema;
803
            }
804
        }
805
        $this->fieldArrayClean($temp);
806
807
        $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"';
808
809
        return $this->execute($sql);
810
    }
811
812
    /**
813
     * Creates a new schema.
814
     *
815
     * @param string $schemaname    The name of the schema to create
816
     * @param string $authorization (optional) The username to create the schema for
817
     * @param string $comment       (optional) If omitted, defaults to nothing
818
     *
819
     * @return bool|int 0 success
820
     */
821
    public function createSchema($schemaname, $authorization = '', $comment = '')
822
    {
823
        $this->fieldClean($schemaname);
824
        $this->fieldClean($authorization);
825
826
        $sql = "CREATE SCHEMA \"{$schemaname}\"";
827
        if ($authorization != '') {
828
            $sql .= " AUTHORIZATION \"{$authorization}\"";
829
        }
830
831
        if ($comment != '') {
832
            $status = $this->beginTransaction();
833
            if ($status != 0) {
834
                return -1;
835
            }
836
        }
837
838
        // Create the new schema
839
        $status = $this->execute($sql);
840
        if ($status != 0) {
841
            $this->rollbackTransaction();
842
843
            return -1;
844
        }
845
846
        // Set the comment
847
        if ($comment != '') {
848
            $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
849
            if ($status != 0) {
850
                $this->rollbackTransaction();
851
852
                return -1;
853
            }
854
855
            return $this->endTransaction();
856
        }
857
858
        return 0;
859
    }
860
861
    /**
862
     * Updates a schema.
863
     *
864
     * @param string $schemaname The name of the schema to drop
865
     * @param string $comment    The new comment for this schema
866
     * @param string $name       new name for this schema
867
     * @param string $owner      The new owner for this schema
868
     *
869
     * @return bool|int 0 success
870
     */
871
    public function updateSchema($schemaname, $comment, $name, $owner)
872
    {
873
        $this->fieldClean($schemaname);
874
        $this->fieldClean($name);
875
        $this->fieldClean($owner);
876
877
        $status = $this->beginTransaction();
878
        if ($status != 0) {
879
            $this->rollbackTransaction();
880
881
            return -1;
882
        }
883
884
        $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
885
        if ($status != 0) {
886
            $this->rollbackTransaction();
887
888
            return -1;
889
        }
890
891
        $schema_rs = $this->getSchemaByName($schemaname);
892
        /* Only if the owner change */
893
        if ($schema_rs->fields['ownername'] != $owner) {
894
            $sql    = "ALTER SCHEMA \"{$schemaname}\" OWNER TO \"{$owner}\"";
895
            $status = $this->execute($sql);
896
            if ($status != 0) {
897
                $this->rollbackTransaction();
898
899
                return -1;
900
            }
901
        }
902
903
        // Only if the name has changed
904
        if ($name != $schemaname) {
905
            $sql    = "ALTER SCHEMA \"{$schemaname}\" RENAME TO \"{$name}\"";
906
            $status = $this->execute($sql);
907
            if ($status != 0) {
908
                $this->rollbackTransaction();
909
910
                return -1;
911
            }
912
        }
913
914
        return $this->endTransaction();
915
    }
916
917
    /**
918
     * Return all information relating to a schema.
919
     *
920
     * @param string $schema The name of the schema
921
     *
922
     * @return \PHPPgAdmin\ADORecordSet Schema information
923
     */
924
    public function getSchemaByName($schema)
925
    {
926
        $this->clean($schema);
927
        $sql = "
928
			SELECT nspname, nspowner, r.rolname AS ownername, nspacl,
929
				pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment
930
			FROM pg_catalog.pg_namespace pn
931
				LEFT JOIN pg_roles as r ON pn.nspowner = r.oid
932
			WHERE nspname='{$schema}'";
933
934
        return $this->selectSet($sql);
935
    }
936
937
    // Table functions
938
939
    /**
940
     * Drops a schema.
941
     *
942
     * @param string $schemaname The name of the schema to drop
943
     * @param bool   $cascade    True to cascade drop, false to restrict
944
     *
945
     * @return int 0 if operation was successful
946
     */
947
    public function dropSchema($schemaname, $cascade)
948
    {
949
        $this->fieldClean($schemaname);
950
951
        $sql = "DROP SCHEMA \"{$schemaname}\"";
952
        if ($cascade) {
953
            $sql .= ' CASCADE';
954
        }
955
956
        return $this->execute($sql);
957
    }
958
959
    /**
960
     * Formats a type correctly for display.  Postgres 7.0 had no 'format_type'
961
     * built-in function, and hence we need to do it manually.
962
     *
963
     * @param string $typname The name of the type
964
     * @param string $typmod  The contents of the typmod field
965
     *
966
     * @return bool|string
967
     */
968
    public function formatType($typname, $typmod)
969
    {
970
        // This is a specific constant in the 7.0 source
971
        $varhdrsz = 4;
972
973
        // If the first character is an underscore, it's an array type
974
        $is_array = false;
975
        if (substr($typname, 0, 1) == '_') {
976
            $is_array = true;
977
            $typname  = substr($typname, 1);
978
        }
979
980
        // Show lengths on bpchar and varchar
981
        if ($typname == 'bpchar') {
982
            $len  = $typmod - $varhdrsz;
983
            $temp = 'character';
984
            if ($len > 1) {
985
                $temp .= "({$len})";
986
            }
987
        } elseif ($typname == 'varchar') {
988
            $temp = 'character varying';
989
            if ($typmod != -1) {
990
                $temp .= '(' . ($typmod - $varhdrsz) . ')';
991
            }
992
        } elseif ($typname == 'numeric') {
993
            $temp = 'numeric';
994
            if ($typmod != -1) {
995
                $tmp_typmod = $typmod - $varhdrsz;
996
                $precision  = ($tmp_typmod >> 16) & 0xffff;
997
                $scale      = $tmp_typmod & 0xffff;
998
                $temp .= "({$precision}, {$scale})";
999
            }
1000
        } else {
1001
            $temp = $typname;
1002
        }
1003
1004
        // Add array qualifier if it's an array
1005
        if ($is_array) {
1006
            $temp .= '[]';
1007
        }
1008
1009
        return $temp;
1010
    }
1011
1012
    /**
1013
     * Given an array of attnums and a relation, returns an array mapping
1014
     * attribute number to attribute name.
1015
     *
1016
     * @param string $table The table to get attributes for
1017
     * @param array  $atts  An array of attribute numbers
1018
     *
1019
     * @return array|int An array mapping attnum to attname or error code
1020
     *                   - -1 $atts must be an array
1021
     *                   - -2 wrong number of attributes found
1022
     */
1023
    public function getAttributeNames($table, $atts)
1024
    {
1025
        $c_schema = $this->_schema;
1026
        $this->clean($c_schema);
1027
        $this->clean($table);
1028
        $this->arrayClean($atts);
1029
1030
        if (!is_array($atts)) {
1031
            return -1;
1032
        }
1033
1034
        if (sizeof($atts) == 0) {
1035
            return [];
1036
        }
1037
1038
        $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
1039
			attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
1040
			relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
1041
			AND attnum IN ('" . join("','", $atts) . "')";
1042
1043
        $rs = $this->selectSet($sql);
1044
        if ($rs->recordCount() != sizeof($atts)) {
1045
            return -2;
1046
        }
1047
1048
        $temp = [];
1049
        while (!$rs->EOF) {
1050
            $temp[$rs->fields['attnum']] = $rs->fields['attname'];
1051
            $rs->moveNext();
1052
        }
1053
1054
        return $temp;
1055
    }
1056
1057
    /**
1058
     * Cleans (escapes) an array.
1059
     *
1060
     * @param array $arr The array to clean, by reference
1061
     *
1062
     * @return array The cleaned array
1063
     */
1064
    public function arrayClean(&$arr)
1065
    {
1066
        foreach ($arr as $k => $v) {
1067
            if ($v === null) {
1068
                continue;
1069
            }
1070
1071
            $arr[$k] = pg_escape_string($v);
1 ignored issue
show
Bug introduced by
The call to pg_escape_string() has too few arguments starting with data. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1071
            $arr[$k] = /** @scrutinizer ignore-call */ pg_escape_string($v);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1072
        }
1073
1074
        return $arr;
1075
    }
1076
1077
    /**
1078
     * Grabs an array of users and their privileges for an object,
1079
     * given its type.
1080
     *
1081
     * @param string      $object The name of the object whose privileges are to be retrieved
1082
     * @param string      $type   The type of the object (eg. database, schema, relation, function or language)
1083
     * @param null|string $table  Optional, column's table if type = column
1084
     *
1085
     * @return array|int Privileges array or error code
1086
     *                   - -1         invalid type
1087
     *                   - -2         object not found
1088
     *                   - -3         unknown privilege type
1089
     */
1090
    public function getPrivileges($object, $type, $table = null)
1091
    {
1092
        $c_schema = $this->_schema;
1093
        $this->clean($c_schema);
1094
        $this->clean($object);
1095
1096
        switch ($type) {
1097
            case 'column':
1098
                $this->clean($table);
1099
                $sql = "
1100
					SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl
1101
					FROM pg_catalog.pg_attribute a
1102
						LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid)
1103
						LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid)
1104
					WHERE n.nspname='{$c_schema}'
1105
						AND c.relname='{$table}'
1106
						AND a.attname='{$object}'";
1107
1108
                break;
1109
            case 'table':
1110
            case 'view':
1111
            case 'sequence':
1112
                $sql = "
1113
					SELECT relacl AS acl FROM pg_catalog.pg_class
1114
					WHERE relname='{$object}'
1115
						AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace
1116
							WHERE nspname='{$c_schema}')";
1117
1118
                break;
1119
            case 'database':
1120
                $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'";
1121
1122
                break;
1123
            case 'function':
1124
                // Since we fetch functions by oid, they are already constrained to
1125
                // the current schema.
1126
                $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'";
1127
1128
                break;
1129
            case 'language':
1130
                $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'";
1131
1132
                break;
1133
            case 'schema':
1134
                $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'";
1135
1136
                break;
1137
            case 'tablespace':
1138
                $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'";
1139
1140
                break;
1141
            default:
1142
                return -1;
1143
        }
1144
1145
        // Fetch the ACL for object
1146
        $acl = $this->selectField($sql, 'acl');
1147
        if ($acl == -1) {
1148
            return -2;
1149
        }
1150
1151
        if ($acl == '' || $acl === null || !(bool) $acl) {
1152
            return [];
1153
        }
1154
1155
        return $this->parseACL($acl);
1156
    }
1157
1158
    /**
1159
     * Internal function used for parsing ACLs.
1160
     *
1161
     * @param string $acl The ACL to parse (of type aclitem[])
1162
     *
1163
     * @return array|int Privileges array or integer with error code
1164
     *
1165
     * @internal bool $in_quotes toggles acl in_quotes attribute
1166
     */
1167
    protected function parseACL($acl)
1168
    {
1169
        // Take off the first and last characters (the braces)
1170
        $acl = substr($acl, 1, strlen($acl) - 2);
1171
1172
        // Pick out individual ACE's by carefully parsing.  This is necessary in order
1173
        // to cope with usernames and stuff that contain commas
1174
        $aces      = [];
1175
        $i         = $j         = 0;
1176
        $in_quotes = false;
1177
        while ($i < strlen($acl)) {
1178
            // If current char is a double quote and it's not escaped, then
1179
            // enter quoted bit
1180
            $char = substr($acl, $i, 1);
1181
            if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) {
1182
                $in_quotes = !$in_quotes;
1183
            } elseif ($char == ',' && !$in_quotes) {
1184
                // Add text so far to the array
1185
                $aces[] = substr($acl, $j, $i - $j);
1186
                $j      = $i + 1;
1187
            }
1188
            ++$i;
1189
        }
1190
        // Add final text to the array
1191
        $aces[] = substr($acl, $j);
1192
1193
        // Create the array to be returned
1194
        $temp = [];
1195
1196
        // For each ACE, generate an entry in $temp
1197
        foreach ($aces as $v) {
1198
            // If the ACE begins with a double quote, strip them off both ends
1199
            // and unescape backslashes and double quotes
1200
            // $unquote = false;
1201
            if (strpos($v, '"') === 0) {
1202
                $v = substr($v, 1, strlen($v) - 2);
1203
                $v = str_replace('\\"', '"', $v);
1204
                $v = str_replace('\\\\', '\\', $v);
1205
            }
1206
1207
            // Figure out type of ACE (public, user or group)
1208
            if (strpos($v, '=') === 0) {
1209
                $atype = 'public';
1210
            } else {
1211
                if ($this->hasRoles()) {
1212
                    $atype = 'role';
1213
                } else {
1214
                    if (strpos($v, 'group ') === 0) {
1215
                        $atype = 'group';
1216
                        // Tear off 'group' prefix
1217
                        $v = substr($v, 6);
1218
                    } else {
1219
                        $atype = 'user';
1220
                    }
1221
                }
1222
            }
1223
1224
            // Break on unquoted equals sign...
1225
            $i         = 0;
1226
            $in_quotes = false;
1227
            $entity    = null;
1228
            $chars     = null;
1229
            while ($i < strlen($v)) {
1230
                // If current char is a double quote and it's not escaped, then
1231
                // enter quoted bit
1232
                $char      = substr($v, $i, 1);
1233
                $next_char = substr($v, $i + 1, 1);
1234
                if ($char == '"' && ($i == 0 || $next_char != '"')) {
1235
                    $in_quotes = !$in_quotes;
1236
                } elseif ($char == '"' && $next_char == '"') {
1237
                    // Skip over escaped double quotes
1238
                    ++$i;
1239
                } elseif ($char == '=' && !$in_quotes) {
1240
                    // Split on current equals sign
1241
                    $entity = substr($v, 0, $i);
1242
                    $chars  = substr($v, $i + 1);
1243
1244
                    break;
1245
                }
1246
                ++$i;
1247
            }
1248
1249
            // Check for quoting on entity name, and unescape if necessary
1250
            if (strpos($entity, '"') === 0) {
1251
                $entity = substr($entity, 1, strlen($entity) - 2);
1252
                $entity = str_replace('""', '"', $entity);
1253
            }
1254
1255
            // New row to be added to $temp
1256
            // (type, grantee, privileges, grantor, grant option?
1257
            $row = [$atype, $entity, [], '', []];
1258
1259
            // Loop over chars and add privs to $row
1260
            for ($i = 0; $i < strlen($chars); ++$i) {
1261
                // Append to row's privs list the string representing
1262
                // the privilege
1263
                $char = substr($chars, $i, 1);
1264
                if ($char == '*') {
1265
                    $row[4][] = $this->privmap[substr($chars, $i - 1, 1)];
1266
                } elseif ($char == '/') {
1267
                    $grantor = substr($chars, $i + 1);
1268
                    // Check for quoting
1269
                    if (strpos($grantor, '"') === 0) {
1270
                        $grantor = substr($grantor, 1, strlen($grantor) - 2);
1271
                        $grantor = str_replace('""', '"', $grantor);
1272
                    }
1273
                    $row[3] = $grantor;
1274
1275
                    break;
1276
                } else {
1277
                    if (!isset($this->privmap[$char])) {
1278
                        return -3;
1279
                    }
1280
1281
                    $row[2][] = $this->privmap[$char];
1282
                }
1283
            }
1284
1285
            // Append row to temp
1286
            $temp[] = $row;
1287
        }
1288
1289
        return $temp;
1290
    }
1291
1292
    // Rule functions
1293
1294
    /**
1295
     * Returns all details for a particular type.
1296
     *
1297
     * @param string $typname The name of the view to retrieve
1298
     *
1299
     * @return \PHPPgAdmin\ADORecordSet type info
1300
     */
1301
    public function getType($typname)
1302
    {
1303
        $this->clean($typname);
1304
1305
        $sql = "SELECT typtype, typbyval, typname, typinput AS typin, typoutput AS typout, typlen, typalign
1306
			FROM pg_type WHERE typname='{$typname}'";
1307
1308
        return $this->selectSet($sql);
1309
    }
1310
1311
    /**
1312
     * Returns a list of all types in the database.
1313
     *
1314
     * @param bool $all        If true, will find all available types, if false just those in search path
1315
     * @param bool $tabletypes If true, will include table types
1316
     * @param bool $domains    If true, will include domains
1317
     *
1318
     * @return \PHPPgAdmin\ADORecordSet A recordset
1319
     */
1320
    public function getTypes($all = false, $tabletypes = false, $domains = false)
1321
    {
1322
        if ($all) {
1323
            $where = '1 = 1';
1324
        } else {
1325
            $c_schema = $this->_schema;
1326
            $this->clean($c_schema);
1327
            $where = "n.nspname = '{$c_schema}'";
1328
        }
1329
        // Never show system table types
1330
        $where2 = "AND c.relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname LIKE 'pg@_%' ESCAPE '@')";
1331
1332
        // Create type filter
1333
        $tqry = "'c'";
1334
        if ($tabletypes) {
1335
            $tqry .= ", 'r', 'v'";
1336
        }
1337
1338
        // Create domain filter
1339
        if (!$domains) {
1340
            $where .= " AND t.typtype != 'd'";
1341
        }
1342
1343
        $sql = "SELECT
1344
				t.typname AS basename,
1345
				pg_catalog.format_type(t.oid, NULL) AS typname,
1346
				pu.usename AS typowner,
1347
				t.typtype,
1348
				pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment
1349
			FROM (pg_catalog.pg_type t
1350
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace)
1351
				LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid
1352
			WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2}))
1353
			AND t.typname !~ '^_'
1354
			AND {$where}
1355
			ORDER BY typname
1356
		";
1357
1358
        return $this->selectSet($sql);
1359
    }
1360
1361
    /**
1362
     * Creates a new type.
1363
     *
1364
     * @param string $typname
1365
     * @param string $typin
1366
     * @param string $typout
1367
     * @param string $typlen
1368
     * @param string $typdef
1369
     * @param string $typelem
1370
     * @param string $typdelim
1371
     * @param string $typbyval
1372
     * @param string $typalign
1373
     * @param string $typstorage
1374
     *
1375
     * @return int 0 if operation was successful
1376
     *
1377
     * @internal param $ ...
1378
     */
1379
    public function createType(
1380
        $typname,
1381
        $typin,
1382
        $typout,
1383
        $typlen,
1384
        $typdef,
1385
        $typelem,
1386
        $typdelim,
1387
        $typbyval,
1388
        $typalign,
1389
        $typstorage
1390
    ) {
1391
        $f_schema = $this->_schema;
1392
        $this->fieldClean($f_schema);
1393
        $this->fieldClean($typname);
1394
        $this->fieldClean($typin);
1395
        $this->fieldClean($typout);
1396
1397
        $sql = "
1398
			CREATE TYPE \"{$f_schema}\".\"{$typname}\" (
1399
				INPUT = \"{$typin}\",
1400
				OUTPUT = \"{$typout}\",
1401
				INTERNALLENGTH = {$typlen}";
1402
        if ($typdef != '') {
1403
            $sql .= ", DEFAULT = {$typdef}";
1404
        }
1405
1406
        if ($typelem != '') {
1407
            $sql .= ", ELEMENT = {$typelem}";
1408
        }
1409
1410
        if ($typdelim != '') {
1411
            $sql .= ", DELIMITER = {$typdelim}";
1412
        }
1413
1414
        if ($typbyval) {
1415
            $sql .= ', PASSEDBYVALUE, ';
1416
        }
1417
1418
        if ($typalign != '') {
1419
            $sql .= ", ALIGNMENT = {$typalign}";
1420
        }
1421
1422
        if ($typstorage != '') {
1423
            $sql .= ", STORAGE = {$typstorage}";
1424
        }
1425
1426
        $sql .= ')';
1427
1428
        return $this->execute($sql);
1429
    }
1430
1431
    /**
1432
     * Drops a type.
1433
     *
1434
     * @param string $typname The name of the type to drop
1435
     * @param bool   $cascade True to cascade drop, false to restrict
1436
     *
1437
     * @return int 0 if operation was successful
1438
     */
1439
    public function dropType($typname, $cascade)
1440
    {
1441
        $f_schema = $this->_schema;
1442
        $this->fieldClean($f_schema);
1443
        $this->fieldClean($typname);
1444
1445
        $sql = "DROP TYPE \"{$f_schema}\".\"{$typname}\"";
1446
        if ($cascade) {
1447
            $sql .= ' CASCADE';
1448
        }
1449
1450
        return $this->execute($sql);
1451
    }
1452
1453
    /**
1454
     * Creates a new enum type in the database.
1455
     *
1456
     * @param string $name       The name of the type
1457
     * @param array  $values     An array of values
1458
     * @param string $typcomment Type comment
1459
     *
1460
     * @return bool|int 0 success
1461
     */
1462
    public function createEnumType($name, $values, $typcomment)
1463
    {
1464
        $f_schema = $this->_schema;
1465
        $this->fieldClean($f_schema);
1466
        $this->fieldClean($name);
1467
1468
        if (empty($values)) {
1469
            return -2;
1470
        }
1471
1472
        $status = $this->beginTransaction();
1473
        if ($status != 0) {
1474
            return -1;
1475
        }
1476
1477
        $values = array_unique($values);
1478
1479
        $nbval = count($values);
1480
1481
        for ($i = 0; $i < $nbval; ++$i) {
1482
            $this->clean($values[$i]);
1483
        }
1484
1485
        $sql = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS ENUM ('";
1486
        $sql .= implode("','", $values);
1487
        $sql .= "')";
1488
1489
        $status = $this->execute($sql);
1490
        if ($status) {
1491
            $this->rollbackTransaction();
1492
1493
            return -1;
1494
        }
1495
1496
        if ($typcomment != '') {
1497
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
1498
            if ($status) {
1499
                $this->rollbackTransaction();
1500
1501
                return -1;
1502
            }
1503
        }
1504
1505
        return $this->endTransaction();
1506
    }
1507
1508
    /**
1509
     * Get defined values for a given enum.
1510
     *
1511
     * @param string $name
1512
     *
1513
     * @return \PHPPgAdmin\ADORecordSet A recordset
1514
     */
1515
    public function getEnumValues($name)
1516
    {
1517
        $this->clean($name);
1518
1519
        $sql = "SELECT enumlabel AS enumval
1520
		FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON (t.oid=e.enumtypid)
1521
		WHERE t.typname = '{$name}' ORDER BY e.oid";
1522
1523
        return $this->selectSet($sql);
1524
    }
1525
1526
    // Operator functions
1527
1528
    /**
1529
     * Creates a new composite type in the database.
1530
     *
1531
     * @param string $name       The name of the type
1532
     * @param int    $fields     The number of fields
1533
     * @param array  $field      An array of field names
1534
     * @param array  $type       An array of field types
1535
     * @param array  $array      An array of '' or '[]' for each type if it's an array or not
1536
     * @param array  $length     An array of field lengths
1537
     * @param array  $colcomment An array of comments
1538
     * @param string $typcomment Type comment
1539
     *
1540
     * @return bool|int 0 success
1541
     */
1542
    public function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment)
1543
    {
1544
        $f_schema = $this->_schema;
1545
        $this->fieldClean($f_schema);
1546
        $this->fieldClean($name);
1547
1548
        $status = $this->beginTransaction();
1549
        if ($status != 0) {
1550
            return -1;
1551
        }
1552
1553
        $found       = false;
1554
        $first       = true;
1555
        $comment_sql = ''; // Accumulate comments for the columns
1556
        $sql         = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS (";
1557
        for ($i = 0; $i < $fields; ++$i) {
1558
            $this->fieldClean($field[$i]);
1559
            $this->clean($type[$i]);
1560
            $this->clean($length[$i]);
1561
            $this->clean($colcomment[$i]);
1562
1563
            // Skip blank columns - for user convenience
1564
            if ($field[$i] == '' || $type[$i] == '') {
1565
                continue;
1566
            }
1567
1568
            // If not the first column, add a comma
1569
            if (!$first) {
1570
                $sql .= ', ';
1571
            } else {
1572
                $first = false;
1573
            }
1574
1575
            switch ($type[$i]) {
1576
                // Have to account for weird placing of length for with/without
1577
                // time zone types
1578
                case 'timestamp with time zone':
1579
                case 'timestamp without time zone':
1580
                    $qual = substr($type[$i], 9);
1581
                    $sql .= "\"{$field[$i]}\" timestamp";
1582
                    if ($length[$i] != '') {
1583
                        $sql .= "({$length[$i]})";
1584
                    }
1585
1586
                    $sql .= $qual;
1587
1588
                    break;
1589
                case 'time with time zone':
1590
                case 'time without time zone':
1591
                    $qual = substr($type[$i], 4);
1592
                    $sql .= "\"{$field[$i]}\" time";
1593
                    if ($length[$i] != '') {
1594
                        $sql .= "({$length[$i]})";
1595
                    }
1596
1597
                    $sql .= $qual;
1598
1599
                    break;
1600
                default:
1601
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
1602
                    if ($length[$i] != '') {
1603
                        $sql .= "({$length[$i]})";
1604
                    }
1605
            }
1606
            // Add array qualifier if necessary
1607
            if ($array[$i] == '[]') {
1608
                $sql .= '[]';
1609
            }
1610
1611
            if ($colcomment[$i] != '') {
1612
                $comment_sql .= "COMMENT ON COLUMN \"{$f_schema}\".\"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
1613
            }
1614
1615
            $found = true;
1616
        }
1617
1618
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
1619
            return -1;
1620
        }
1621
1622
        $sql .= ')';
1623
1624
        $status = $this->execute($sql);
1625
        if ($status) {
1626
            $this->rollbackTransaction();
1627
1628
            return -1;
1629
        }
1630
1631
        if ($typcomment != '') {
1632
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
1633
            if ($status) {
1634
                $this->rollbackTransaction();
1635
1636
                return -1;
1637
            }
1638
        }
1639
1640
        if ($comment_sql != '') {
1641
            $status = $this->execute($comment_sql);
1642
            if ($status) {
1643
                $this->rollbackTransaction();
1644
1645
                return -1;
1646
            }
1647
        }
1648
1649
        return $this->endTransaction();
1650
    }
1651
1652
    /**
1653
     * Returns a list of all casts in the database.
1654
     *
1655
     * @return \PHPPgAdmin\ADORecordSet All casts
1656
     */
1657
    public function getCasts()
1658
    {
1659
        $conf = $this->conf;
1660
1661
        if ($conf['show_system']) {
1662
            $where = '';
1663
        } else {
1664
            $where = '
1665
				AND n1.nspname NOT LIKE $$pg\_%$$
1666
				AND n2.nspname NOT LIKE $$pg\_%$$
1667
				AND n3.nspname NOT LIKE $$pg\_%$$
1668
			';
1669
        }
1670
1671
        $sql = "
1672
			SELECT
1673
				c.castsource::pg_catalog.regtype AS castsource,
1674
				c.casttarget::pg_catalog.regtype AS casttarget,
1675
				CASE WHEN c.castfunc=0 THEN NULL
1676
				ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc,
1677
				c.castcontext,
1678
				obj_description(c.oid, 'pg_cast') as castcomment
1679
			FROM
1680
				(pg_catalog.pg_cast c LEFT JOIN pg_catalog.pg_proc p ON c.castfunc=p.oid JOIN pg_catalog.pg_namespace n3 ON p.pronamespace=n3.oid),
1681
				pg_catalog.pg_type t1,
1682
				pg_catalog.pg_type t2,
1683
				pg_catalog.pg_namespace n1,
1684
				pg_catalog.pg_namespace n2
1685
			WHERE
1686
				c.castsource=t1.oid
1687
				AND c.casttarget=t2.oid
1688
				AND t1.typnamespace=n1.oid
1689
				AND t2.typnamespace=n2.oid
1690
				{$where}
1691
			ORDER BY 1, 2
1692
		";
1693
1694
        return $this->selectSet($sql);
1695
    }
1696
1697
    /**
1698
     * Returns a list of all conversions in the database.
1699
     *
1700
     * @return \PHPPgAdmin\ADORecordSet All conversions
1701
     */
1702
    public function getConversions()
1703
    {
1704
        $c_schema = $this->_schema;
1705
        $this->clean($c_schema);
1706
        $sql = "
1707
			SELECT
1708
			       c.conname,
1709
			       pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding,
1710
			       pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding,
1711
			       c.condefault,
1712
			       pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment
1713
			FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n
1714
			WHERE n.oid = c.connamespace
1715
			      AND n.nspname='{$c_schema}'
1716
			ORDER BY 1;
1717
		";
1718
1719
        return $this->selectSet($sql);
1720
    }
1721
1722
    // Operator Class functions
1723
1724
    /**
1725
     * Edits a rule on a table OR view.
1726
     *
1727
     * @param string $name    The name of the new rule
1728
     * @param string $event   SELECT, INSERT, UPDATE or DELETE
1729
     * @param string $table   Table on which to create the rule
1730
     * @param string $where   Where to execute the rule, '' indicates always
1731
     * @param bool   $instead True if an INSTEAD rule, false otherwise
1732
     * @param string $type    NOTHING for a do nothing rule, SOMETHING to use given action
1733
     * @param string $action  The action to take
1734
     *
1735
     * @return int 0 if operation was successful
1736
     */
1737
    public function setRule($name, $event, $table, $where, $instead, $type, $action)
1738
    {
1739
        return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true);
1740
    }
1741
1742
    // FTS functions
1743
1744
    /**
1745
     * Creates a rule.
1746
     *
1747
     * @param string $name    The name of the new rule
1748
     * @param string $event   SELECT, INSERT, UPDATE or DELETE
1749
     * @param string $table   Table on which to create the rule
1750
     * @param string $where   When to execute the rule, '' indicates always
1751
     * @param bool   $instead True if an INSTEAD rule, false otherwise
1752
     * @param string $type    NOTHING for a do nothing rule, SOMETHING to use given action
1753
     * @param string $action  The action to take
1754
     * @param bool   $replace (optional) True to replace existing rule, false
1755
     *                        otherwise
1756
     *
1757
     * @return int 0 if operation was successful
1758
     */
1759
    public function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false)
1760
    {
1761
        $f_schema = $this->_schema;
1762
        $this->fieldClean($f_schema);
1763
        $this->fieldClean($name);
1764
        $this->fieldClean($table);
1765
        if (!in_array($event, $this->rule_events, true)) {
1766
            return -1;
1767
        }
1768
1769
        $sql = 'CREATE';
1770
        if ($replace) {
1771
            $sql .= ' OR REPLACE';
1772
        }
1773
1774
        $sql .= " RULE \"{$name}\" AS ON {$event} TO \"{$f_schema}\".\"{$table}\"";
1775
        // Can't escape WHERE clause
1776
        if ($where != '') {
1777
            $sql .= " WHERE {$where}";
1778
        }
1779
1780
        $sql .= ' DO';
1781
        if ($instead) {
1782
            $sql .= ' INSTEAD';
1783
        }
1784
1785
        if ($type == 'NOTHING') {
1786
            $sql .= ' NOTHING';
1787
        } else {
1788
            $sql .= " ({$action})";
1789
        }
1790
1791
        return $this->execute($sql);
1792
    }
1793
1794
    /**
1795
     * Removes a rule from a table OR view.
1796
     *
1797
     * @param string $rule     The rule to drop
1798
     * @param string $relation The relation from which to drop
1799
     * @param string $cascade  True to cascade drop, false to restrict
1800
     *
1801
     * @return int 0 if operation was successful
1802
     */
1803
    public function dropRule($rule, $relation, $cascade)
1804
    {
1805
        $f_schema = $this->_schema;
1806
        $this->fieldClean($f_schema);
1807
        $this->fieldClean($rule);
1808
        $this->fieldClean($relation);
1809
1810
        $sql = "DROP RULE \"{$rule}\" ON \"{$f_schema}\".\"{$relation}\"";
1811
        if ($cascade) {
1812
            $sql .= ' CASCADE';
1813
        }
1814
1815
        return $this->execute($sql);
1816
    }
1817
1818
    /**
1819
     * Grabs a single trigger.
1820
     *
1821
     * @param string $table   The name of a table whose triggers to retrieve
1822
     * @param string $trigger The name of the trigger to retrieve
1823
     *
1824
     * @return \PHPPgAdmin\ADORecordSet A recordset
1825
     */
1826
    public function getTrigger($table, $trigger)
1827
    {
1828
        $c_schema = $this->_schema;
1829
        $this->clean($c_schema);
1830
        $this->clean($table);
1831
        $this->clean($trigger);
1832
1833
        $sql = "
1834
            SELECT * FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c
1835
            WHERE t.tgrelid=c.oid AND c.relname='{$table}' AND t.tgname='{$trigger}'
1836
                AND c.relnamespace=(
1837
                    SELECT oid FROM pg_catalog.pg_namespace
1838
                    WHERE nspname='{$c_schema}')";
1839
1840
        return $this->selectSet($sql);
1841
    }
1842
1843
    /**
1844
     * Returns a list of all functions in the database.
1845
     *
1846
     * @param bool        $all  If true, will find all available functions, if false just those in search path
1847
     * @param null|string $type If not null, will find all functions with return value = type
1848
     *
1849
     * @return \PHPPgAdmin\ADORecordSet All functions
1850
     */
1851
    public function getFunctions($all = false, $type = null)
1852
    {
1853
        if ($all) {
1854
            $where    = 'pg_catalog.pg_function_is_visible(p.oid)';
1855
            $distinct = 'DISTINCT ON (p.proname)';
1856
1857
            if ($type) {
1858
                $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') ";
1859
            }
1860
        } else {
1861
            $c_schema = $this->_schema;
1862
            $this->clean($c_schema);
1863
            $where    = "n.nspname = '{$c_schema}'";
1864
            $distinct = '';
1865
        }
1866
1867
        $sql = "
1868
			SELECT
1869
				{$distinct}
1870
				p.oid AS prooid,
1871
				p.proname,
1872
				p.proretset,
1873
				pg_catalog.format_type(p.prorettype, NULL) AS proresult,
1874
				pg_catalog.oidvectortypes(p.proargtypes) AS proarguments,
1875
				pl.lanname AS prolanguage,
1876
				pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment,
1877
				p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
1878
				CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns,
1879
				coalesce(u.usename::text,p.proowner::text) AS proowner
1880
1881
			FROM pg_catalog.pg_proc p
1882
				INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
1883
				INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang
1884
				LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner
1885
			WHERE NOT p.proisagg
1886
				AND {$where}
1887
			ORDER BY p.proname, proresult
1888
			";
1889
1890
        return $this->selectSet($sql);
1891
    }
1892
1893
    /**
1894
     * Creates a trigger.
1895
     *
1896
     * @param string $tgname      The name of the trigger to create
1897
     * @param string $table       The name of the table
1898
     * @param string $tgproc      The function to execute
1899
     * @param string $tgtime      BEFORE or AFTER
1900
     * @param string $tgevent     Event
1901
     * @param string $tgfrequency
1902
     * @param string $tgargs      The function arguments
1903
     *
1904
     * @return int 0 if operation was successful
1905
     */
1906
    public function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs)
1907
    {
1908
        $f_schema = $this->_schema;
1909
        $this->fieldClean($f_schema);
1910
        $this->fieldClean($tgname);
1911
        $this->fieldClean($table);
1912
        $this->fieldClean($tgproc);
1913
1914
        /* No Statement Level Triggers in PostgreSQL (by now) */
1915
        $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime}
1916
				{$tgevent} ON \"{$f_schema}\".\"{$table}\"
1917
				FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})";
1918
1919
        return $this->execute($sql);
1920
    }
1921
1922
    /**
1923
     * Alters a trigger.
1924
     *
1925
     * @param string $table   The name of the table containing the trigger
1926
     * @param string $trigger The name of the trigger to alter
1927
     * @param string $name    The new name for the trigger
1928
     *
1929
     * @return int 0 if operation was successful
1930
     */
1931
    public function alterTrigger($table, $trigger, $name)
1932
    {
1933
        $f_schema = $this->_schema;
1934
        $this->fieldClean($f_schema);
1935
        $this->fieldClean($table);
1936
        $this->fieldClean($trigger);
1937
        $this->fieldClean($name);
1938
1939
        $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_schema}\".\"{$table}\" RENAME TO \"{$name}\"";
1940
1941
        return $this->execute($sql);
1942
    }
1943
1944
    /**
1945
     * Drops a trigger.
1946
     *
1947
     * @param string $tgname  The name of the trigger to drop
1948
     * @param string $table   The table from which to drop the trigger
1949
     * @param bool   $cascade True to cascade drop, false to restrict
1950
     *
1951
     * @return int 0 if operation was successful
1952
     */
1953
    public function dropTrigger($tgname, $table, $cascade)
1954
    {
1955
        $f_schema = $this->_schema;
1956
        $this->fieldClean($f_schema);
1957
        $this->fieldClean($tgname);
1958
        $this->fieldClean($table);
1959
1960
        $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\"";
1961
        if ($cascade) {
1962
            $sql .= ' CASCADE';
1963
        }
1964
1965
        return $this->execute($sql);
1966
    }
1967
1968
    /**
1969
     * Enables a trigger.
1970
     *
1971
     * @param string $tgname The name of the trigger to enable
1972
     * @param string $table  The table in which to enable the trigger
1973
     *
1974
     * @return int 0 if operation was successful
1975
     */
1976
    public function enableTrigger($tgname, $table)
1977
    {
1978
        $f_schema = $this->_schema;
1979
        $this->fieldClean($f_schema);
1980
        $this->fieldClean($tgname);
1981
        $this->fieldClean($table);
1982
1983
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\"";
1984
1985
        return $this->execute($sql);
1986
    }
1987
1988
    /**
1989
     * Disables a trigger.
1990
     *
1991
     * @param string $tgname The name of the trigger to disable
1992
     * @param string $table  The table in which to disable the trigger
1993
     *
1994
     * @return int 0 if operation was successful
1995
     */
1996
    public function disableTrigger($tgname, $table)
1997
    {
1998
        $f_schema = $this->_schema;
1999
        $this->fieldClean($f_schema);
2000
        $this->fieldClean($tgname);
2001
        $this->fieldClean($table);
2002
2003
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\"";
2004
2005
        return $this->execute($sql);
2006
    }
2007
2008
    /**
2009
     * Returns a list of all operators in the database.
2010
     *
2011
     * @return \PHPPgAdmin\ADORecordSet All operators
2012
     */
2013
    public function getOperators()
2014
    {
2015
        $c_schema = $this->_schema;
2016
        $this->clean($c_schema);
2017
        // We stick with the subselects here, as you cannot ORDER BY a regtype
2018
        $sql = "
2019
			SELECT
2020
            	po.oid,	po.oprname,
2021
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname,
2022
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname,
2023
				po.oprresult::pg_catalog.regtype AS resultname,
2024
		        pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment
2025
			FROM
2026
				pg_catalog.pg_operator po
2027
			WHERE
2028
				po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')
2029
			ORDER BY
2030
				po.oprname, oprleftname, oprrightname
2031
		";
2032
2033
        return $this->selectSet($sql);
2034
    }
2035
2036
    /**
2037
     * Drops an operator.
2038
     *
2039
     * @param mixed $operator_oid The OID of the operator to drop
2040
     * @param bool  $cascade      True to cascade drop, false to restrict
2041
     *
2042
     * @return int 0 if operation was successful
2043
     */
2044
    public function dropOperator($operator_oid, $cascade)
2045
    {
2046
        // Function comes in with $object as operator OID
2047
        $opr      = $this->getOperator($operator_oid);
2048
        $f_schema = $this->_schema;
2049
        $this->fieldClean($f_schema);
2050
        $this->fieldClean($opr->fields['oprname']);
2051
2052
        $sql = "DROP OPERATOR \"{$f_schema}\".{$opr->fields['oprname']} (";
2053
        // Quoting or formatting here???
2054
        if ($opr->fields['oprleftname'] !== null) {
2055
            $sql .= $opr->fields['oprleftname'] . ', ';
2056
        } else {
2057
            $sql .= 'NONE, ';
2058
        }
2059
2060
        if ($opr->fields['oprrightname'] !== null) {
2061
            $sql .= $opr->fields['oprrightname'] . ')';
2062
        } else {
2063
            $sql .= 'NONE)';
2064
        }
2065
2066
        if ($cascade) {
2067
            $sql .= ' CASCADE';
2068
        }
2069
2070
        return $this->execute($sql);
2071
    }
2072
2073
    /**
2074
     * Returns all details for a particular operator.
2075
     *
2076
     * @param mixed $operator_oid The oid of the operator
2077
     *
2078
     * @return \PHPPgAdmin\ADORecordSet Function info
2079
     */
2080
    public function getOperator($operator_oid)
2081
    {
2082
        $this->clean($operator_oid);
2083
2084
        $sql = "
2085
			SELECT
2086
            	po.oid, po.oprname,
2087
				oprleft::pg_catalog.regtype AS oprleftname,
2088
				oprright::pg_catalog.regtype AS oprrightname,
2089
				oprresult::pg_catalog.regtype AS resultname,
2090
				po.oprcanhash,
2091
				oprcanmerge,
2092
				oprcom::pg_catalog.regoperator AS oprcom,
2093
				oprnegate::pg_catalog.regoperator AS oprnegate,
2094
				po.oprcode::pg_catalog.regproc AS oprcode,
2095
				po.oprrest::pg_catalog.regproc AS oprrest,
2096
				po.oprjoin::pg_catalog.regproc AS oprjoin
2097
			FROM
2098
				pg_catalog.pg_operator po
2099
			WHERE
2100
				po.oid='{$operator_oid}'
2101
		";
2102
2103
        return $this->selectSet($sql);
2104
    }
2105
2106
    /**
2107
     *  Gets all opclasses.
2108
     *
2109
     * @return \PHPPgAdmin\ADORecordSet A recordset
2110
     */
2111
    public function getOpClasses()
2112
    {
2113
        $c_schema = $this->_schema;
2114
        $this->clean($c_schema);
2115
        $sql = "
2116
			SELECT
2117
				pa.amname, po.opcname,
2118
				po.opcintype::pg_catalog.regtype AS opcintype,
2119
				po.opcdefault,
2120
				pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment
2121
			FROM
2122
				pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn
2123
			WHERE
2124
				po.opcmethod=pa.oid
2125
				AND po.opcnamespace=pn.oid
2126
				AND pn.nspname='{$c_schema}'
2127
			ORDER BY 1,2
2128
			";
2129
2130
        return $this->selectSet($sql);
2131
    }
2132
2133
    /**
2134
     * Gets all languages.
2135
     *
2136
     * @param bool $all True to get all languages, regardless of show_system
2137
     *
2138
     * @return \PHPPgAdmin\ADORecordSet A recordset
2139
     */
2140
    public function getLanguages($all = false)
2141
    {
2142
        $conf = $this->conf;
2143
2144
        if ($conf['show_system'] || $all) {
2145
            $where = '';
2146
        } else {
2147
            $where = 'WHERE lanispl';
2148
        }
2149
2150
        $sql = "
2151
			SELECT
2152
				lanname, lanpltrusted,
2153
				lanplcallfoid::pg_catalog.regproc AS lanplcallf
2154
			FROM
2155
				pg_catalog.pg_language
2156
			{$where}
2157
			ORDER BY lanname
2158
		";
2159
2160
        return $this->selectSet($sql);
2161
    }
2162
2163
    /**
2164
     * Retrieves information for all tablespaces.
2165
     *
2166
     * @param bool $all Include all tablespaces (necessary when moving objects back to the default space)
2167
     *
2168
     * @return \PHPPgAdmin\ADORecordSet A recordset
2169
     */
2170
    public function getTablespaces($all = false)
2171
    {
2172
        $conf = $this->conf;
2173
2174
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2175
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2176
					FROM pg_catalog.pg_tablespace";
2177
2178
        if (!$conf['show_system'] && !$all) {
2179
            $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$';
2180
        }
2181
2182
        $sql .= ' ORDER BY spcname';
2183
2184
        return $this->selectSet($sql);
2185
    }
2186
2187
    // Misc functions
2188
2189
    /**
2190
     * Retrieves a tablespace's information.
2191
     *
2192
     * @param string $spcname
2193
     *
2194
     * @return \PHPPgAdmin\ADORecordSet A recordset
2195
     */
2196
    public function getTablespace($spcname)
2197
    {
2198
        $this->clean($spcname);
2199
2200
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2201
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2202
					FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'";
2203
2204
        return $this->selectSet($sql);
2205
    }
2206
2207
    /**
2208
     * Creates a tablespace.
2209
     *
2210
     * @param string $spcname  The name of the tablespace to create
2211
     * @param string $spcowner The owner of the tablespace. '' for current
2212
     * @param string $spcloc   The directory in which to create the tablespace
2213
     * @param string $comment
2214
     *
2215
     * @return int 0 success
2216
     */
2217
    public function createTablespace($spcname, $spcowner, $spcloc, $comment = '')
2218
    {
2219
        $this->fieldClean($spcname);
2220
        $this->clean($spcloc);
2221
2222
        $sql = "CREATE TABLESPACE \"{$spcname}\"";
2223
2224
        if ($spcowner != '') {
2225
            $this->fieldClean($spcowner);
2226
            $sql .= " OWNER \"{$spcowner}\"";
2227
        }
2228
2229
        $sql .= " LOCATION '{$spcloc}'";
2230
2231
        $status = $this->execute($sql);
2232
        if ($status != 0) {
2233
            return -1;
2234
        }
2235
2236
        if ($comment != '' && $this->hasSharedComments()) {
2237
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2238
            if ($status != 0) {
2239
                return -2;
2240
            }
2241
        }
2242
2243
        return 0;
2244
    }
2245
2246
    /**
2247
     * Alters a tablespace.
2248
     *
2249
     * @param string $spcname The name of the tablespace
2250
     * @param string $name    The new name for the tablespace
2251
     * @param string $owner   The new owner for the tablespace
2252
     * @param string $comment
2253
     *
2254
     * @return bool|int 0 success
2255
     */
2256
    public function alterTablespace($spcname, $name, $owner, $comment = '')
2257
    {
2258
        $this->fieldClean($spcname);
2259
        $this->fieldClean($name);
2260
        $this->fieldClean($owner);
2261
2262
        // Begin transaction
2263
        $status = $this->beginTransaction();
2264
        if ($status != 0) {
2265
            return -1;
2266
        }
2267
2268
        // Owner
2269
        $sql    = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\"";
2270
        $status = $this->execute($sql);
2271
        if ($status != 0) {
2272
            $this->rollbackTransaction();
2273
2274
            return -2;
2275
        }
2276
2277
        // Rename (only if name has changed)
2278
        if ($name != $spcname) {
2279
            $sql    = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\"";
2280
            $status = $this->execute($sql);
2281
            if ($status != 0) {
2282
                $this->rollbackTransaction();
2283
2284
                return -3;
2285
            }
2286
2287
            $spcname = $name;
2288
        }
2289
2290
        // Set comment if it has changed
2291
        if (trim($comment) != '' && $this->hasSharedComments()) {
2292
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2293
            if ($status != 0) {
2294
                return -4;
2295
            }
2296
        }
2297
2298
        return $this->endTransaction();
2299
    }
2300
2301
    /**
2302
     * Drops a tablespace.
2303
     *
2304
     * @param string $spcname The name of the domain to drop
2305
     *
2306
     * @return int 0 if operation was successful
2307
     */
2308
    public function dropTablespace($spcname)
2309
    {
2310
        $this->fieldClean($spcname);
2311
2312
        $sql = "DROP TABLESPACE \"{$spcname}\"";
2313
2314
        return $this->execute($sql);
2315
    }
2316
2317
    /**
2318
     * Analyze a database.
2319
     *
2320
     * @param string $table (optional) The table to analyze
2321
     *
2322
     * @return bool 0 if successful
2323
     */
2324
    public function analyzeDB($table = '')
2325
    {
2326
        if ($table != '') {
2327
            $f_schema = $this->_schema;
2328
            $this->fieldClean($f_schema);
2329
            $this->fieldClean($table);
2330
2331
            $sql = "ANALYZE \"{$f_schema}\".\"{$table}\"";
2332
        } else {
2333
            $sql = 'ANALYZE';
2334
        }
2335
2336
        return $this->execute($sql);
2337
    }
2338
2339
    /**
2340
     * Vacuums a database.
2341
     *
2342
     * @param string $table   The table to vacuum
2343
     * @param bool   $analyze If true, also does analyze
2344
     * @param bool   $full    If true, selects "full" vacuum
2345
     * @param bool   $freeze  If true, selects aggressive "freezing" of tuples
2346
     *
2347
     * @return bool 0 if successful
2348
     */
2349
    public function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false)
2350
    {
2351
        $sql = 'VACUUM';
2352
        if ($full) {
2353
            $sql .= ' FULL';
2354
        }
2355
2356
        if ($freeze) {
2357
            $sql .= ' FREEZE';
2358
        }
2359
2360
        if ($analyze) {
2361
            $sql .= ' ANALYZE';
2362
        }
2363
2364
        if ($table != '') {
2365
            $f_schema = $this->_schema;
2366
            $this->fieldClean($f_schema);
2367
            $this->fieldClean($table);
2368
            $sql .= " \"{$f_schema}\".\"{$table}\"";
2369
        }
2370
2371
        return $this->execute($sql);
2372
    }
2373
2374
    /**
2375
     * Returns all autovacuum global configuration.
2376
     *
2377
     * @return array associative array array( param => value, ...)
2378
     */
2379
    public function getAutovacuum()
2380
    {
2381
        $_defaults = $this->selectSet(
2382
            "SELECT name, setting
2383
			FROM pg_catalog.pg_settings
2384
			WHERE
2385
				name = 'autovacuum'
2386
				OR name = 'autovacuum_vacuum_threshold'
2387
				OR name = 'autovacuum_vacuum_scale_factor'
2388
				OR name = 'autovacuum_analyze_threshold'
2389
				OR name = 'autovacuum_analyze_scale_factor'
2390
				OR name = 'autovacuum_vacuum_cost_delay'
2391
				OR name = 'autovacuum_vacuum_cost_limit'
2392
				OR name = 'vacuum_freeze_min_age'
2393
				OR name = 'autovacuum_freeze_max_age'
2394
			"
2395
        );
2396
2397
        $ret = [];
2398
        while (!$_defaults->EOF) {
2399
            $ret[$_defaults->fields['name']] = $_defaults->fields['setting'];
2400
            $_defaults->moveNext();
2401
        }
2402
2403
        return $ret;
2404
    }
2405
2406
    /**
2407
     * Returns all available autovacuum per table information.
2408
     *
2409
     * @param string $table          table name
2410
     * @param bool   $vacenabled     true if vacuum is enabled
2411
     * @param int    $vacthreshold   vacuum threshold
2412
     * @param int    $vacscalefactor vacuum scalefactor
2413
     * @param int    $anathresold    analyze threshold
2414
     * @param int    $anascalefactor analyze scale factor
2415
     * @param int    $vaccostdelay   vacuum cost delay
2416
     * @param int    $vaccostlimit   vacuum cost limit
2417
     *
2418
     * @return bool 0 if successful
2419
     */
2420
    public function saveAutovacuum(
2421
        $table,
2422
        $vacenabled,
2423
        $vacthreshold,
2424
        $vacscalefactor,
2425
        $anathresold,
2426
        $anascalefactor,
2427
        $vaccostdelay,
2428
        $vaccostlimit
2429
    ) {
2430
        $f_schema = $this->_schema;
2431
        $this->fieldClean($f_schema);
2432
        $this->fieldClean($table);
2433
2434
        $params = [];
2435
2436
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
2437
2438
        if (!empty($vacenabled)) {
2439
            $this->clean($vacenabled);
2440
            $params[] = "autovacuum_enabled='{$vacenabled}'";
2441
        }
2442
        if (!empty($vacthreshold)) {
2443
            $this->clean($vacthreshold);
2444
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
2445
        }
2446
        if (!empty($vacscalefactor)) {
2447
            $this->clean($vacscalefactor);
2448
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
2449
        }
2450
        if (!empty($anathresold)) {
2451
            $this->clean($anathresold);
2452
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
2453
        }
2454
        if (!empty($anascalefactor)) {
2455
            $this->clean($anascalefactor);
2456
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
2457
        }
2458
        if (!empty($vaccostdelay)) {
2459
            $this->clean($vaccostdelay);
2460
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
2461
        }
2462
        if (!empty($vaccostlimit)) {
2463
            $this->clean($vaccostlimit);
2464
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
2465
        }
2466
2467
        $sql = $sql . implode(',', $params) . ');';
2468
2469
        return $this->execute($sql);
2470
    }
2471
2472
    // Type conversion routines
2473
2474
    /**
2475
     * Drops autovacuum config for a table.
2476
     *
2477
     * @param string $table The table
2478
     *
2479
     * @return bool 0 if successful
2480
     */
2481
    public function dropAutovacuum($table)
2482
    {
2483
        $f_schema = $this->_schema;
2484
        $this->fieldClean($f_schema);
2485
        $this->fieldClean($table);
2486
2487
        return $this->execute(
2488
            "
2489
			ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
2490
				autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
2491
				autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
2492
			);"
2493
        );
2494
    }
2495
2496
    /**
2497
     * Returns all available process information.
2498
     *
2499
     * @param null|string $database (optional) Find only connections to specified database
2500
     *
2501
     * @return \PHPPgAdmin\ADORecordSet A recordset
2502
     */
2503
    public function getProcesses($database = null)
2504
    {
2505
        if ($database === null) {
2506
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
2507
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
2508
				FROM pg_catalog.pg_stat_activity
2509
				ORDER BY datname, usename, pid";
2510
        } else {
2511
            $this->clean($database);
2512
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
2513
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
2514
				FROM pg_catalog.pg_stat_activity
2515
				WHERE datname='{$database}'
2516
				ORDER BY usename, pid";
2517
        }
2518
2519
        return $this->selectSet($sql);
2520
    }
2521
2522
    // interfaces Statistics collector functions
2523
2524
    /**
2525
     * Returns table locks information in the current database.
2526
     *
2527
     * @return \PHPPgAdmin\ADORecordSet A recordset
2528
     */
2529
    public function getLocks()
2530
    {
2531
        $conf = $this->conf;
2532
2533
        if (!$conf['show_system']) {
2534
            $where = 'AND pn.nspname NOT LIKE $$pg\_%$$';
2535
        } else {
2536
            $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
2537
        }
2538
2539
        $sql = "
2540
			SELECT
2541
				pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction,
2542
				(select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid'
2543
					and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction
2544
			FROM
2545
				pg_catalog.pg_locks pl,
2546
				pg_catalog.pg_class pc,
2547
				pg_catalog.pg_namespace pn
2548
			WHERE
2549
				pl.relation = pc.oid AND pc.relnamespace=pn.oid
2550
			{$where}
2551
			ORDER BY pid,nspname,tablename";
2552
2553
        return $this->selectSet($sql);
2554
    }
2555
2556
    /**
2557
     * Sends a cancel or kill command to a process.
2558
     *
2559
     * @param int    $pid    The ID of the backend process
2560
     * @param string $signal 'CANCEL' or 'KILL'
2561
     *
2562
     * @return int 0 success
2563
     */
2564
    public function sendSignal($pid, $signal)
2565
    {
2566
        // Clean
2567
        $pid = (int) $pid;
2568
2569
        if ($signal == 'CANCEL') {
2570
            $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val";
2571
        } elseif ($signal == 'KILL') {
2572
            $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val";
2573
        } else {
2574
            return -1;
2575
        }
2576
2577
        // Execute the query
2578
        $val = $this->selectField($sql, 'val');
2579
2580
        if ($val === 'f') {
2581
            return -1;
2582
        }
2583
2584
        if ($val === 't') {
2585
            return 0;
2586
        }
2587
2588
        return -1;
2589
    }
2590
2591
    /**
2592
     * Executes an SQL script as a series of SQL statements.  Returns
2593
     * the result of the final step.  This is a very complicated lexer
2594
     * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in
2595
     * the PostgreSQL source code.
2596
     * XXX: It does not handle multibyte languages properly.
2597
     *
2598
     * @param string        $name     Entry in $_FILES to use
2599
     * @param null|callable $callback (optional) Callback function to call with each query, its result and line number
2600
     *
2601
     * @return bool true for general success, false on any failure
2602
     */
2603
    public function executeScript($name, $callback = null)
2604
    {
2605
        // This whole function isn't very encapsulated, but hey...
2606
        $conn = $this->conn->_connectionID;
2607
        if (!is_uploaded_file($_FILES[$name]['tmp_name'])) {
2608
            return false;
2609
        }
2610
2611
        $fd = fopen($_FILES[$name]['tmp_name'], 'rb');
2612
        if ($fd === false) {
2613
            return false;
2614
        }
2615
2616
        // Build up each SQL statement, they can be multiline
2617
        $query_buf    = null;
2618
        $query_start  = 0;
2619
        $in_quote     = 0;
2620
        $in_xcomment  = 0;
2621
        $bslash_count = 0;
2622
        $dol_quote    = '';
2623
        $paren_level  = 0;
2624
        $len          = 0;
2625
        $i            = 0;
2626
        $prevlen      = 0;
2627
        $thislen      = 0;
2628
        $lineno       = 0;
2629
2630
        // Loop over each line in the file
2631
        while (!feof($fd)) {
2632
            $line = fgets($fd);
2633
            ++$lineno;
2634
2635
            // Nothing left on line? Then ignore...
2636
            if (trim($line) == '') {
2637
                continue;
2638
            }
2639
2640
            $len         = strlen($line);
2641
            $query_start = 0;
2642
2643
            /**
2644
             * Parse line, looking for command separators.
2645
             *
2646
             * The current character is at line[i], the prior character at line[i
2647
             * - prevlen], the next character at line[i + thislen].
2648
             */
2649
            $prevlen = 0;
2650
            $thislen = ($len > 0) ? 1 : 0;
2651
2652
            for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) {
2653
                /* was the previous character a backslash? */
2654
                if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') {
2655
                    $this->prtrace('bslash_count', $bslash_count, $line);
2656
                    ++$bslash_count;
2657
                } else {
2658
                    $bslash_count = 0;
2659
                }
2660
2661
                /*
2662
                 * It is important to place the in_* test routines before the
2663
                 * in_* detection routines. i.e. we have to test if we are in
2664
                 * a quote before testing for comments.
2665
                 */
2666
2667
                /* in quote? */
2668
                if ($in_quote !== 0) {
2669
                    //$this->prtrace('in_quote', $in_quote, $line);
2670
                    /*
2671
                     * end of quote if matching non-backslashed character.
2672
                     * backslashes don't count for double quotes, though.
2673
                     */
2674
                    if (substr($line, $i, 1) == $in_quote &&
2675
                        ($bslash_count % 2 == 0 || $in_quote == '"')
2676
                    ) {
2677
                        $in_quote = 0;
2678
                    }
2679
                } elseif ($dol_quote) {
2680
                    $this->prtrace('dol_quote', $dol_quote, $line);
2681
                    if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) {
2682
                        $this->advance_1($i, $prevlen, $thislen);
2683
                        while (substr($line, $i, 1) != '$') {
2684
                            $this->advance_1($i, $prevlen, $thislen);
2685
                        }
2686
2687
                        $dol_quote = '';
2688
                    }
2689
                } elseif (substr($line, $i, 2) == '/*') {
2690
                    $this->prtrace('open_xcomment', $in_xcomment, $line, $i, $prevlen, $thislen);
2691
                    if ($in_xcomment == 0) {
2692
                        ++$in_xcomment;
2693
                        $finishpos = strpos(substr($line, $i, $len), '*/');
2694
                        if ($finishpos === false) {
2695
                            $line = substr($line, 0, $i); /* remove comment */
2696
                            break;
2697
                        }
2698
                        $pre         = substr($line, 0, $i);
2699
                        $post        = substr($line, $i + 2 + $finishpos, $len);
2700
                        $line        = $pre . ' ' . $post;
2701
                        $in_xcomment = 0;
2702
                        $i           = 0;
2703
                    }
2704
                } elseif ($in_xcomment) {
2705
                    $position = strpos(substr($line, $i, $len), '*/');
2706
                    if ($position === false) {
2707
                        $line = '';
2708
2709
                        break;
2710
                    }
2711
2712
                    $substr = substr($line, $i, 2);
2713
2714
                    if ($substr == '*/' && !--$in_xcomment) {
2715
                        $line = substr($line, $i + 2, $len);
2716
                        $i += 2;
2717
                        $this->advance_1($i, $prevlen, $thislen);
2718
                    }
2719
                    // old logic
2720
                    //  } else if (substr($line, $i, 2) == '/*') {
2721
                    //      if ($in_xcomment == 0) {
2722
                    //          ++$in_xcomment;
2723
                    //          $this->advance_1($i, $prevlen, $thislen);
2724
                    //      }
2725
                    //  } else if ($in_xcomment) {
2726
                    //      $substr = substr($line, $i, 2);
2727
                    //      if ($substr == '*/' && !--$in_xcomment) {
2728
                    //          $this->advance_1($i, $prevlen, $thislen);
2729
                    //      }
2730
                } elseif (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
2731
                    $in_quote = substr($line, $i, 1);
2732
                } elseif (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
2733
                    $dol_end   = strpos(substr($line, $i + 1), '$');
2734
                    $dol_quote = substr($line, $i, $dol_end + 1);
2735
                    $this->advance_1($i, $prevlen, $thislen);
2736
                    while (substr($line, $i, 1) != '$') {
2737
                        $this->advance_1($i, $prevlen, $thislen);
2738
                    }
2739
                } else {
2740
                    if (substr($line, $i, 2) == '--') {
2741
                        $line = substr($line, 0, $i); /* remove comment */
2742
                        break;
2743
                    } /* count nested parentheses */
2744
2745
                    if (substr($line, $i, 1) == '(') {
2746
                        ++$paren_level;
2747
                    } elseif (substr($line, $i, 1) == ')' && $paren_level > 0) {
2748
                        --$paren_level;
2749
                    } elseif (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) {
2750
                        $subline = substr(substr($line, 0, $i), $query_start);
2751
                        /*
2752
                         * insert a cosmetic newline, if this is not the first
2753
                         * line in the buffer
2754
                         */
2755
                        if (strlen($query_buf) > 0) {
2756
                            $query_buf .= "\n";
2757
                        }
2758
2759
                        /* append the line to the query buffer */
2760
                        $query_buf .= $subline;
2761
                        /* is there anything in the query_buf? */
2762
                        if (trim($query_buf)) {
2763
                            $query_buf .= ';';
2764
2765
                            // Execute the query. PHP cannot execute
2766
                            // empty queries, unlike libpq
2767
                            $res = @pg_query($conn, $query_buf);
2768
2769
                            // Call the callback function for display
2770
                            if ($callback !== null) {
2771
                                $callback($query_buf, $res, $lineno);
2772
                            }
2773
2774
                            // Check for COPY request
2775
                            if (pg_result_status($res) == 4) {
2776
                                // 4 == PGSQL_COPY_FROM
2777
                                while (!feof($fd)) {
2778
                                    $copy = fgets($fd, 32768);
2779
                                    ++$lineno;
2780
                                    pg_put_line($conn, $copy);
2781
                                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2782
                                        pg_end_copy($conn);
2783
2784
                                        break;
2785
                                    }
2786
                                }
2787
                            }
2788
                        }
2789
                        $query_buf   = null;
2790
                        $query_start = $i + $thislen;
2791
                    } elseif (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) {
2792
                        $sub = substr($line, $i, $thislen);
2793
                        while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) {
2794
                            /* keep going while we still have identifier chars */
2795
                            $this->advance_1($i, $prevlen, $thislen);
2796
                            $sub = substr($line, $i, $thislen);
2797
                        }
2798
                        // Since we're now over the next character to be examined, it is necessary
2799
                        // to move back one space.
2800
                        $i -= $prevlen;
2801
                    }
2802
                }
2803
            } // end for
2804
2805
            /* Put the rest of the line in the query buffer. */
2806
            $subline = substr($line, $query_start);
2807
2808
            if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) {
2809
                if (strlen($query_buf) > 0) {
2810
                    $query_buf .= "\n";
2811
                }
2812
2813
                $query_buf .= $subline;
2814
            }
2815
2816
            $line = null;
2817
        } // end while
2818
2819
        /*
2820
         * Process query at the end of file without a semicolon, so long as
2821
         * it's non-empty.
2822
         */
2823
        if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) {
2824
            // Execute the query
2825
            $res = @pg_query($conn, $query_buf);
2826
2827
            // Call the callback function for display
2828
            if ($callback !== null) {
2829
                $callback($query_buf, $res, $lineno);
2830
            }
2831
2832
            // Check for COPY request
2833
            if (pg_result_status($res) == 4) {
2834
                // 4 == PGSQL_COPY_FROM
2835
                while (!feof($fd)) {
2836
                    $copy = fgets($fd, 32768);
2837
                    ++$lineno;
2838
                    pg_put_line($conn, $copy);
2839
                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2840
                        pg_end_copy($conn);
2841
2842
                        break;
2843
                    }
2844
                }
2845
            }
2846
        }
2847
2848
        fclose($fd);
2849
2850
        return true;
2851
    }
2852
2853
    /**
2854
     * A private helper method for executeScript that advances the
2855
     * character by 1.  In psql this is careful to take into account
2856
     * multibyte languages, but we don't at the moment, so this function
2857
     * is someone redundant, since it will always advance by 1.
2858
     *
2859
     * @param int $i       The current character position in the line
2860
     * @param int $prevlen Length of previous character (ie. 1)
2861
     * @param int $thislen Length of current character (ie. 1)
2862
     */
2863
    protected function advance_1(&$i, &$prevlen, &$thislen)
2864
    {
2865
        $prevlen = $thislen;
2866
        $i += $thislen;
2867
        $thislen = 1;
2868
    }
2869
2870
    /**
2871
     * Private helper method to detect a valid $foo$ quote delimiter at
2872
     * the start of the parameter dquote.
2873
     *
2874
     * @param string $dquote
2875
     *
2876
     * @return bool true if valid, false otherwise
2877
     */
2878
    protected function valid_dolquote($dquote)
2879
    {
2880
        // XXX: support multibyte
2881
        return preg_match('/^[$][$]/', $dquote) || preg_match('/^[$][_[:alpha:]][_[:alnum:]]*[$]/', $dquote);
2882
    }
2883
2884
    // Capabilities
2885
2886
    /**
2887
     * Returns a recordset of all columns in a query.  Supports paging.
2888
     *
2889
     * @param string   $type      Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier,
2890
     *                            or 'SELECT" if it's a select query
2891
     * @param string   $table     The base table of the query.  NULL for no table.
2892
     * @param string   $query     The query that is being executed.  NULL for no query.
2893
     * @param string   $sortkey   The column number to sort by, or '' or null for no sorting
2894
     * @param string   $sortdir   The direction in which to sort the specified column ('asc' or 'desc')
2895
     * @param null|int $page      The page of the relation to retrieve
2896
     * @param null|int $page_size The number of rows per page
2897
     * @param int      $max_pages (return-by-ref) The max number of pages in the relation
2898
     *
2899
     * @return int|\PHPPgAdmin\ADORecordSet A  recordset on success or an int with error code
2900
     *                                      - -1 transaction error
2901
     *                                      - -2 counting error
2902
     *                                      - -3 page or page_size invalid
2903
     *                                      - -4 unknown type
2904
     *                                      - -5 failed setting transaction read only
2905
     */
2906
    public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages)
2907
    {
2908
        // Check that we're not going to divide by zero
2909
        if (!is_numeric($page_size) || $page_size != (int) $page_size || $page_size <= 0) {
2910
            return -3;
2911
        }
2912
2913
        // If $type is TABLE, then generate the query
2914
        switch ($type) {
2915
            case 'TABLE':
2916
                if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
2917
                    $orderby = [$sortkey => $sortdir];
2918
                } else {
2919
                    $orderby = [];
2920
                }
2921
2922
                $query = $this->getSelectSQL($table, [], [], [], $orderby);
2923
2924
                break;
2925
            case 'QUERY':
2926
            case 'SELECT':
2927
                // Trim query
2928
                $query = trim($query);
2929
                // Trim off trailing semi-colon if there is one
2930
                if (substr($query, strlen($query) - 1, 1) == ';') {
2931
                    $query = substr($query, 0, strlen($query) - 1);
2932
                }
2933
2934
                break;
2935
            default:
2936
                return -4;
2937
        }
2938
2939
        // Generate count query
2940
        $count = "SELECT COUNT(*) AS total FROM (${query}) AS sub";
2941
2942
        // Open a transaction
2943
        $status = $this->beginTransaction();
2944
        if ($status != 0) {
2945
            return -1;
2946
        }
2947
2948
        // If backend supports read only queries, then specify read only mode
2949
        // to avoid side effects from repeating queries that do writes.
2950
        if ($this->hasReadOnlyQueries()) {
2951
            $status = $this->execute('SET TRANSACTION READ ONLY');
2952
            if ($status != 0) {
2953
                $this->rollbackTransaction();
2954
2955
                return -5;
2956
            }
2957
        }
2958
2959
        // Count the number of rows
2960
        $total = $this->browseQueryCount($count);
2961
        if ($total < 0) {
2962
            $this->rollbackTransaction();
2963
2964
            return -2;
2965
        }
2966
2967
        // Calculate max pages
2968
        $max_pages = ceil($total / $page_size);
2969
2970
        // Check that page is less than or equal to max pages
2971
        if (!is_numeric($page) || $page != (int) $page || $page > $max_pages || $page < 1) {
2972
            $this->rollbackTransaction();
2973
2974
            return -3;
2975
        }
2976
2977
        // Set fetch mode to NUM so that duplicate field names are properly returned
2978
        // for non-table queries.  Since the SELECT feature only allows selecting one
2979
        // table, duplicate fields shouldn't appear.
2980
        if ($type == 'QUERY') {
2981
            $this->conn->setFetchMode(ADODB_FETCH_NUM);
2982
        }
2983
2984
        // Figure out ORDER BY.  Sort key is always the column number (based from one)
2985
        // of the column to order by.  Only need to do this for non-TABLE queries
2986
        if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
2987
            $orderby = " ORDER BY {$sortkey}";
2988
            // Add sort order
2989
            if ($sortdir == 'desc') {
2990
                $orderby .= ' DESC';
2991
            } else {
2992
                $orderby .= ' ASC';
2993
            }
2994
        } else {
2995
            $orderby = '';
2996
        }
2997
2998
        // Actually retrieve the rows, with offset and limit
2999
        $rs     = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size);
3000
        $status = $this->endTransaction();
3001
        if ($status != 0) {
3002
            $this->rollbackTransaction();
3003
3004
            return -1;
3005
        }
3006
3007
        return $rs;
3008
    }
3009
3010
    /**
3011
     * Generates the SQL for the 'select' function.
3012
     *
3013
     * @param string $table   The table from which to select
3014
     * @param array  $show    An array of columns to show.  Empty array means all columns.
3015
     * @param array  $values  An array mapping columns to values
3016
     * @param array  $ops     An array of the operators to use
3017
     * @param array  $orderby (optional) An array of column numbers or names (one based)
3018
     *                        mapped to sort direction (asc or desc or '' or null) to order by
3019
     *
3020
     * @return string The SQL query
3021
     */
3022
    public function getSelectSQL($table, $show, $values, $ops, $orderby = [])
3023
    {
3024
        $this->fieldArrayClean($show);
3025
3026
        // If an empty array is passed in, then show all columns
3027
        if (sizeof($show) == 0) {
3028
            if ($this->hasObjectID($table)) {
3029
                $sql = "SELECT \"{$this->id}\", * FROM ";
3030
            } else {
3031
                $sql = 'SELECT * FROM ';
3032
            }
3033
        } else {
3034
            // Add oid column automatically to results for editing purposes
3035
            if (!in_array($this->id, $show, true) && $this->hasObjectID($table)) {
3036
                $sql = "SELECT \"{$this->id}\", \"";
3037
            } else {
3038
                $sql = 'SELECT "';
3039
            }
3040
3041
            $sql .= join('","', $show) . '" FROM ';
3042
        }
3043
3044
        $this->fieldClean($table);
3045
3046
        if (isset($_REQUEST['schema'])) {
3047
            $f_schema = $_REQUEST['schema'];
3048
            $this->fieldClean($f_schema);
3049
            $sql .= "\"{$f_schema}\".";
3050
        }
3051
        $sql .= "\"{$table}\"";
3052
3053
        // If we have values specified, add them to the WHERE clause
3054
        $first = true;
3055
        if (is_array($values) && sizeof($values) > 0) {
3056
            foreach ($values as $k => $v) {
3057
                if ($v != '' || $this->selectOps[$ops[$k]] == 'p') {
3058
                    $this->fieldClean($k);
3059
                    if ($first) {
3060
                        $sql .= ' WHERE ';
3061
                        $first = false;
3062
                    } else {
3063
                        $sql .= ' AND ';
3064
                    }
3065
                    // Different query format depending on operator type
3066
                    switch ($this->selectOps[$ops[$k]]) {
3067
                        case 'i':
3068
                            // Only clean the field for the inline case
3069
                            // this is because (x), subqueries need to
3070
                            // to allow 'a','b' as input.
3071
                            $this->clean($v);
3072
                            $sql .= "\"{$k}\" {$ops[$k]} '{$v}'";
3073
3074
                            break;
3075
                        case 'p':
3076
                            $sql .= "\"{$k}\" {$ops[$k]}";
3077
3078
                            break;
3079
                        case 'x':
3080
                            $sql .= "\"{$k}\" {$ops[$k]} ({$v})";
3081
3082
                            break;
3083
                        case 't':
3084
                            $sql .= "\"{$k}\" {$ops[$k]}('{$v}')";
3085
3086
                            break;
3087
                        default:
3088
                            // Shouldn't happen
3089
                    }
3090
                }
3091
            }
3092
        }
3093
3094
        // ORDER BY
3095
        if (is_array($orderby) && sizeof($orderby) > 0) {
3096
            $sql .= ' ORDER BY ';
3097
            $first = true;
3098
            foreach ($orderby as $k => $v) {
3099
                if ($first) {
3100
                    $first = false;
3101
                } else {
3102
                    $sql .= ', ';
3103
                }
3104
3105
                if (preg_match('/^[0-9]+$/', $k)) {
3106
                    $sql .= $k;
3107
                } else {
3108
                    $this->fieldClean($k);
3109
                    $sql .= '"' . $k . '"';
3110
                }
3111
                if (strtoupper($v) == 'DESC') {
3112
                    $sql .= ' DESC';
3113
                }
3114
            }
3115
        }
3116
3117
        return $sql;
3118
    }
3119
3120
    /**
3121
     * Finds the number of rows that would be returned by a
3122
     * query.
3123
     *
3124
     * @param string $count The count query
3125
     *
3126
     * @return int The count of rows or -1 of no rows are found
3127
     */
3128
    public function browseQueryCount($count)
3129
    {
3130
        return $this->selectField($count, 'total');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->selectField($count, 'total') also could return the type string which is incompatible with the documented return type integer.
Loading history...
3131
    }
3132
3133
    /**
3134
     * Returns a recordset of all columns in a table.
3135
     *
3136
     * @param string $table The name of a table
3137
     * @param array  $key   The associative array holding the key to retrieve
3138
     *
3139
     * @return \PHPPgAdmin\ADORecordSet A recordset
3140
     */
3141
    public function browseRow($table, $key)
3142
    {
3143
        $f_schema = $this->_schema;
3144
        $this->fieldClean($f_schema);
3145
        $this->fieldClean($table);
3146
3147
        $sql = "SELECT * FROM \"{$f_schema}\".\"{$table}\"";
3148
        if (is_array($key) && sizeof($key) > 0) {
3149
            $sql .= ' WHERE true';
3150
            foreach ($key as $k => $v) {
3151
                $this->fieldClean($k);
3152
                $this->clean($v);
3153
                $sql .= " AND \"{$k}\"='{$v}'";
3154
            }
3155
        }
3156
3157
        return $this->selectSet($sql);
3158
    }
3159
3160
    /**
3161
     * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false.
3162
     *
3163
     * @param null|bool|int|string $parameter the parameter
3164
     *
3165
     * @return string the parameter transformed to 't' of 'f'
3166
     */
3167
    public function dbBool(&$parameter)
3168
    {
3169
        if ($parameter) {
3170
            $parameter = 't';
3171
        } else {
3172
            $parameter = 'f';
3173
        }
3174
3175
        return $parameter;
3176
    }
3177
3178
    /**
3179
     * Fetches statistics for a database.
3180
     *
3181
     * @param string $database The database to fetch stats for
3182
     *
3183
     * @return \PHPPgAdmin\ADORecordSet A recordset
3184
     */
3185
    public function getStatsDatabase($database)
3186
    {
3187
        $this->clean($database);
3188
3189
        $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'";
3190
3191
        return $this->selectSet($sql);
3192
    }
3193
3194
    /**
3195
     * Fetches tuple statistics for a table.
3196
     *
3197
     * @param string $table The table to fetch stats for
3198
     *
3199
     * @return \PHPPgAdmin\ADORecordSet A recordset
3200
     */
3201
    public function getStatsTableTuples($table)
3202
    {
3203
        $c_schema = $this->_schema;
3204
        $this->clean($c_schema);
3205
        $this->clean($table);
3206
3207
        $sql = "SELECT * FROM pg_stat_all_tables
3208
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3209
3210
        return $this->selectSet($sql);
3211
    }
3212
3213
    /**
3214
     * Fetches I/0 statistics for a table.
3215
     *
3216
     * @param string $table The table to fetch stats for
3217
     *
3218
     * @return \PHPPgAdmin\ADORecordSet A recordset
3219
     */
3220
    public function getStatsTableIO($table)
3221
    {
3222
        $c_schema = $this->_schema;
3223
        $this->clean($c_schema);
3224
        $this->clean($table);
3225
3226
        $sql = "SELECT * FROM pg_statio_all_tables
3227
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3228
3229
        return $this->selectSet($sql);
3230
    }
3231
3232
    /**
3233
     * Fetches tuple statistics for all indexes on a table.
3234
     *
3235
     * @param string $table The table to fetch index stats for
3236
     *
3237
     * @return \PHPPgAdmin\ADORecordSet A recordset
3238
     */
3239
    public function getStatsIndexTuples($table)
3240
    {
3241
        $c_schema = $this->_schema;
3242
        $this->clean($c_schema);
3243
        $this->clean($table);
3244
3245
        $sql = "SELECT * FROM pg_stat_all_indexes
3246
			WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname";
3247
3248
        return $this->selectSet($sql);
3249
    }
3250
3251
    /**
3252
     * Fetches I/0 statistics for all indexes on a table.
3253
     *
3254
     * @param string $table The table to fetch index stats for
3255
     *
3256
     * @return \PHPPgAdmin\ADORecordSet A recordset
3257
     */
3258
    public function getStatsIndexIO($table)
3259
    {
3260
        $c_schema = $this->_schema;
3261
        $this->clean($c_schema);
3262
        $this->clean($table);
3263
3264
        $sql = "SELECT * FROM pg_statio_all_indexes
3265
			WHERE schemaname='{$c_schema}' AND relname='{$table}'
3266
			ORDER BY indexrelname";
3267
3268
        return $this->selectSet($sql);
3269
    }
3270
}
3271