Passed
Push — develop ( f344af...62218e )
by Felipe
05:30
created

Postgres::dropTrigger()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 3
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.43
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;
1 ignored issue
show
introduced by
The trait PHPPgAdmin\Traits\HelperTrait requires some properties which are not provided by PHPPgAdmin\Database\Postgres: $responseobj, $requestobj
Loading history...
20
    use \PHPPgAdmin\Traits\SequenceTrait;
21
    use \PHPPgAdmin\Traits\ViewTrait;
22
    use \PHPPgAdmin\Traits\IndexTrait;
1 ignored issue
show
Bug introduced by
The trait PHPPgAdmin\Traits\IndexTrait requires the property $EOF which is not provided by PHPPgAdmin\Database\Postgres.
Loading history...
23
    use \PHPPgAdmin\Traits\RoleTrait;
24
    use \PHPPgAdmin\Traits\AggregateTrait;
25
    use \PHPPgAdmin\Traits\TableTrait;
1 ignored issue
show
Bug introduced by
The trait PHPPgAdmin\Traits\TableTrait requires the property $EOF which is not provided by PHPPgAdmin\Database\Postgres.
Loading history...
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
34
    public function __construct(&$conn, $container)
35
    {
36
        //$this->prtrace('major_version :' . $this->major_version);
37
        $this->conn      = $conn;
38
        $this->container = $container;
39
40
        $this->lang = $container->get('lang');
41
        $this->conf = $container->get('conf');
42
    }
43
44
    /**
45
     * Fetch a URL (or array of URLs) for a given help page.
46
     *
47
     * @param string $help
48
     *
49
     * @return null|array|string the help page or pages related to the $help topic, or null if none exists
50
     */
51
    public function getHelp($help)
52
    {
53
        $this->getHelpPages();
54
55
        if (isset($this->help_page[$help])) {
56
            if (is_array($this->help_page[$help])) {
57
                $urls = [];
58
                foreach ($this->help_page[$help] as $link) {
59
                    $urls[] = $this->help_base . $link;
60
                }
61
62
                return $urls;
63
            }
64
65
            return $this->help_base . $this->help_page[$help];
66
        }
67
68
        return null;
69
    }
70
71
    /**
72
     * Gets the help pages.
73
     * get help page by instancing the corresponding help class
74
     * if $this->help_page and $this->help_base are set, this function is a noop.
75
     */
76
    public function getHelpPages()
77
    {
78
        if ($this->help_page === null || $this->help_base === null) {
79
            $help_classname = '\PHPPgAdmin\Help\PostgresDoc' . str_replace('.', '', $this->major_version);
80
81
            $help_class = new $help_classname($this->conf, $this->major_version);
82
83
            $this->help_base = $help_class->getHelpBase();
84
        }
85
    }
86
87
    // Formatting functions
88
89
    /**
90
     * Outputs the HTML code for a particular field.
91
     *
92
     * @param string $name   The name to give the field
93
     * @param mixed  $value  The value of the field.  Note this could be 'numeric(7,2)' sort of thing...
94
     * @param string $type   The database type of the field
95
     * @param array $extras An array of attributes name as key and attributes' values as value
96
     */
97
    public function printField($name, $value, $type, $extras = [])
98
    {
99
        $lang = $this->lang;
100
101
        // Determine actions string
102
        $extra_str = '';
103
        foreach ($extras as $k => $v) {
104
            $extra_str .= " {$k}=\"" . htmlspecialchars($v) . '"';
105
        }
106
107
        switch (substr($type, 0, 9)) {
108
            case 'bool':
109
            case 'boolean':
110
                if ($value !== null && $value == '') {
111
                    $value = null;
112
                } elseif ($value == 'true') {
113
                    $value = 't';
114
                } elseif ($value == 'false') {
115
                    $value = 'f';
116
                }
117
118
                // If value is null, 't' or 'f'...
119
                if ($value === null || $value == 't' || $value == 'f') {
120
                    echo '<select name="', htmlspecialchars($name), "\"{$extra_str}>\n";
121
                    echo '<option value=""', ($value === null) ? ' selected="selected"' : '', "></option>\n";
122
                    echo '<option value="t"', ($value == 't') ? ' selected="selected"' : '', ">{$lang['strtrue']}</option>\n";
123
                    echo '<option value="f"', ($value == 'f') ? ' selected="selected"' : '', ">{$lang['strfalse']}</option>\n";
124
                    echo "</select>\n";
125
                } else {
126
                    echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
127
                }
128
129
                break;
130
            case 'bytea':
131
            case 'bytea[]':
132
                if (!is_null($value)) {
133
                    $value = $this->escapeBytea($value);
134
                }
135
            // no break
136
            case 'text':
137
            case 'text[]':
138
            case 'json':
139
            case 'jsonb':
140
            case 'xml':
141
            case 'xml[]':
142
                $n = substr_count($value, "\n");
143
                $n = $n < 5 ? max(2, $n) : $n;
144
                $n = $n > 20 ? 20 : $n;
145
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"85\"{$extra_str}>\n";
146
                echo htmlspecialchars($value);
147
                echo "</textarea>\n";
148
149
                break;
150
            case 'character':
151
            case 'character[]':
152
                $n = substr_count($value, "\n");
153
                $n = $n < 5 ? 5 : $n;
154
                $n = $n > 20 ? 20 : $n;
155
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"35\"{$extra_str}>\n";
156
                echo htmlspecialchars($value);
157
                echo "</textarea>\n";
158
159
                break;
160
            default:
161
                echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
162
163
                break;
164
        }
165
    }
166
167
    /**
168
     * Return all information about a particular database.
169
     *
170
     * @param string $database The name of the database to retrieve
171
     *
172
     * @return \PHPPgAdmin\ADORecordSet The database info
173
     */
174
    public function getDatabase($database)
175
    {
176
        $this->clean($database);
177
        $sql = "SELECT * FROM pg_database WHERE datname='{$database}'";
178
179
        return $this->selectSet($sql);
180
    }
181
182
    /**
183
     * Return all database available on the server.
184
     *
185
     * @param null|string $currentdatabase database name that should be on top of the resultset
186
     *
187
     * @return \PHPPgAdmin\ADORecordSet A list of databases, sorted alphabetically
188
     */
189
    public function getDatabases($currentdatabase = null)
190
    {
191
        $conf        = $this->conf;
192
        $server_info = $this->server_info;
193
194
        if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) {
195
            $username = $server_info['username'];
196
            $this->clean($username);
197
            $clause = " AND pr.rolname='{$username}'";
198
        } else {
199
            $clause = '';
200
        }
201
        if (isset($server_info['useonlydefaultdb']) && $server_info['useonlydefaultdb']) {
202
            $currentdatabase = $server_info['defaultdb'];
203
            $clause .= " AND pdb.datname = '{$currentdatabase}' ";
204
        }
205
206
        if (isset($server_info['hiddendbs']) && $server_info['hiddendbs']) {
207
            $hiddendbs = $server_info['hiddendbs'];
208
            $not_in    = "('" . implode("','", $hiddendbs) . "')";
209
            $clause .= " AND pdb.datname NOT IN {$not_in} ";
210
        }
211
212
        if ($currentdatabase != null) {
213
            $this->clean($currentdatabase);
214
            $orderby = "ORDER BY pdb.datname = '{$currentdatabase}' DESC, pdb.datname";
215
        } else {
216
            $orderby = 'ORDER BY pdb.datname';
217
        }
218
219
        if (!$conf['show_system']) {
220
            $where = ' AND NOT pdb.datistemplate';
221
        } else {
222
            $where = ' AND pdb.datallowconn';
223
        }
224
225
        $sql = "
226
			SELECT pdb.datname AS datname,
227
                    pr.rolname AS datowner,
228
                    pg_encoding_to_char(encoding) AS datencoding,
229
				    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pdb.oid=pd.objoid AND pd.classoid='pg_database'::regclass) AS datcomment,
230
				    (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace,
231
				CASE WHEN pg_catalog.has_database_privilege(current_user, pdb.oid, 'CONNECT')
232
					THEN pg_catalog.pg_database_size(pdb.oid)
233
					ELSE -1 -- set this magic value, which we will convert to no access later
234
				END as dbsize,
235
                pdb.datcollate,
236
                pdb.datctype
237
			FROM pg_catalog.pg_database pdb
238
            LEFT JOIN pg_catalog.pg_roles pr ON (pdb.datdba = pr.oid)
239
			WHERE true
240
				{$where}
241
				{$clause}
242
			{$orderby}";
243
244
        return $this->selectSet($sql);
245
    }
246
247
    /**
248
     * Determines whether or not a user is a super user.
249
     *
250
     * @param string $username The username of the user
251
     *
252
     * @return bool true if is a super user, false otherwise
253
     */
254
    public function isSuperUser($username = '')
255
    {
256
        $this->clean($username);
257
258
        if (empty($usename)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $usename does not exist. Did you maybe mean $username?
Loading history...
259
            $val = pg_parameter_status($this->conn->_connectionID, 'is_superuser');
260
            if ($val !== false) {
261
                return $val == 'on';
262
            }
263
        }
264
265
        $sql = "SELECT usesuper FROM pg_user WHERE usename='{$username}'";
266
267
        $usesuper = $this->selectField($sql, 'usesuper');
268
        if ($usesuper == -1) {
269
            return false;
270
        }
271
272
        return $usesuper == 't';
273
    }
274
275
    /**
276
     * Return the database comment of a db from the shared description table.
277
     *
278
     * @param string $database the name of the database to get the comment for
279
     *
280
     * @return \PHPPgAdmin\ADORecordSet recordset of the db comment info
281
     */
282
    public function getDatabaseComment($database)
283
    {
284
        $this->clean($database);
285
        $sql =
0 ignored issues
show
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
286
            "SELECT description FROM pg_catalog.pg_database JOIN pg_catalog.pg_shdescription ON (oid=objoid AND classoid='pg_database'::regclass) WHERE pg_database.datname = '{$database}' ";
287
288
        return $this->selectSet($sql);
289
    }
290
291
    /**
292
     * Return the database owner of a db.
293
     *
294
     * @param string $database the name of the database to get the owner for
295
     *
296
     * @return \PHPPgAdmin\ADORecordSet recordset of the db owner info
297
     */
298
    public function getDatabaseOwner($database)
299
    {
300
        $this->clean($database);
301
        $sql = "SELECT usename FROM pg_user, pg_database WHERE pg_user.usesysid = pg_database.datdba AND pg_database.datname = '{$database}' ";
302
303
        return $this->selectSet($sql);
304
    }
305
306
    // Help functions
307
308
    // Database functions
309
310
    /**
311
     * Returns the current database encoding.
312
     *
313
     * @return string The encoding.  eg. SQL_ASCII, UTF-8, etc.
314
     */
315
    public function getDatabaseEncoding()
316
    {
317
        return pg_parameter_status($this->conn->_connectionID, 'server_encoding');
318
    }
319
320
    /**
321
     * Returns the current default_with_oids setting.
322
     *
323
     * @return string default_with_oids setting
324
     */
325
    public function getDefaultWithOid()
326
    {
327
        $sql = 'SHOW default_with_oids';
328
329
        return $this->selectField($sql, 'default_with_oids');
330
    }
331
332
    /**
333
     * Creates a database.
334
     *
335
     * @param string $database   The name of the database to create
336
     * @param string $encoding   Encoding of the database
337
     * @param string $tablespace (optional) The tablespace name
338
     * @param string $comment
339
     * @param string $template
340
     * @param string $lc_collate
341
     * @param string $lc_ctype
342
     *
343
     * @return int 0 success
344
     */
345
    public function createDatabase(
346
        $database,
347
        $encoding,
348
        $tablespace = '',
349
        $comment = '',
350
        $template = 'template1',
351
        $lc_collate = '',
352
        $lc_ctype = ''
353
    ) {
354
        $this->fieldClean($database);
355
        $this->clean($encoding);
356
        $this->fieldClean($tablespace);
357
        $this->fieldClean($template);
358
        $this->clean($lc_collate);
359
        $this->clean($lc_ctype);
360
361
        $sql = "CREATE DATABASE \"{$database}\" WITH TEMPLATE=\"{$template}\"";
362
363
        if ($encoding != '') {
364
            $sql .= " ENCODING='{$encoding}'";
365
        }
366
367
        if ($lc_collate != '') {
368
            $sql .= " LC_COLLATE='{$lc_collate}'";
369
        }
370
371
        if ($lc_ctype != '') {
372
            $sql .= " LC_CTYPE='{$lc_ctype}'";
373
        }
374
375
        if ($tablespace != '' && $this->hasTablespaces()) {
376
            $sql .= " TABLESPACE \"{$tablespace}\"";
377
        }
378
379
        $status = $this->execute($sql);
380
        if ($status != 0) {
381
            return -1;
382
        }
383
384
        if ($comment != '' && $this->hasSharedComments()) {
385
            $status = $this->setComment('DATABASE', $database, '', $comment);
386
            if ($status != 0) {
387
                return -2;
388
            }
389
        }
390
391
        return 0;
392
    }
393
394
    /**
395
     * Cleans (escapes) an object name (eg. table, field).
396
     *
397
     * @param string $str The string to clean, by reference
398
     *
399
     * @return string The cleaned string
400
     */
401
    public function fieldClean(&$str)
402
    {
403
        if ($str === null) {
0 ignored issues
show
introduced by
The condition $str === null is always false.
Loading history...
404
            return null;
405
        }
406
407
        $str = str_replace('"', '""', $str);
408
409
        return $str;
410
    }
411
412
    /**
413
     * Drops a database.
414
     *
415
     * @param string $database The name of the database to drop
416
     *
417
     * @return int 0 if operation was successful
418
     */
419
    public function dropDatabase($database)
420
    {
421
        $this->fieldClean($database);
422
        $sql = "DROP DATABASE \"{$database}\"";
423
424
        return $this->execute($sql);
425
    }
426
427
    /**
428
     * Alters a database
429
     * the multiple return vals are for postgres 8+ which support more functionality in alter database.
430
     *
431
     * @param string $dbName   The name of the database
432
     * @param string $newName  new name for the database
433
     * @param string $newOwner The new owner for the database
434
     * @param string $comment
435
     *
436
     * @return bool|int 0 success
437
     */
438
    public function alterDatabase($dbName, $newName, $newOwner = '', $comment = '')
439
    {
440
        $status = $this->beginTransaction();
441
        if ($status != 0) {
442
            $this->rollbackTransaction();
443
444
            return -1;
445
        }
446
447
        if ($dbName != $newName) {
448
            $status = $this->alterDatabaseRename($dbName, $newName);
449
            if ($status != 0) {
450
                $this->rollbackTransaction();
451
452
                return -3;
453
            }
454
            $dbName = $newName;
455
        }
456
457
        if ($newOwner != '') {
458
            $status = $this->alterDatabaseOwner($newName, $newOwner);
459
            if ($status != 0) {
460
                $this->rollbackTransaction();
461
462
                return -2;
463
            }
464
        }
465
466
        $this->fieldClean($dbName);
467
        $status = $this->setComment('DATABASE', $dbName, '', $comment);
468
        if ($status != 0) {
469
            $this->rollbackTransaction();
470
471
            return -4;
472
        }
473
474
        return $this->endTransaction();
475
    }
476
477
    /**
478
     * Renames a database, note that this operation cannot be
479
     * performed on a database that is currently being connected to.
480
     *
481
     * @param string $oldName name of database to rename
482
     * @param string $newName new name of database
483
     *
484
     * @return int 0 on success
485
     */
486
    public function alterDatabaseRename($oldName, $newName)
487
    {
488
        $this->fieldClean($oldName);
489
        $this->fieldClean($newName);
490
491
        if ($oldName != $newName) {
492
            $sql = "ALTER DATABASE \"{$oldName}\" RENAME TO \"{$newName}\"";
493
494
            return $this->execute($sql);
495
        }
496
497
        return 0;
498
    }
499
500
    /**
501
     * Changes ownership of a database
502
     * This can only be done by a superuser or the owner of the database.
503
     *
504
     * @param string $dbName   database to change ownership of
505
     * @param string $newOwner user that will own the database
506
     *
507
     * @return int 0 on success
508
     */
509
    public function alterDatabaseOwner($dbName, $newOwner)
510
    {
511
        $this->fieldClean($dbName);
512
        $this->fieldClean($newOwner);
513
514
        $sql = "ALTER DATABASE \"{$dbName}\" OWNER TO \"{$newOwner}\"";
515
516
        return $this->execute($sql);
517
    }
518
519
    /**
520
     * Returns prepared transactions information.
521
     *
522
     * @param string|null $database (optional) Find only prepared transactions executed in a specific database
523
     *
524
     * @return \PHPPgAdmin\ADORecordSet A recordset
525
     */
526
    public function getPreparedXacts($database = null)
527
    {
528
        if ($database === null) {
529
            $sql = 'SELECT * FROM pg_prepared_xacts';
530
        } else {
531
            $this->clean($database);
532
            $sql = "SELECT transaction, gid, prepared, owner FROM pg_prepared_xacts
533
				WHERE database='{$database}' ORDER BY owner";
534
        }
535
536
        return $this->selectSet($sql);
537
    }
538
539
    /**
540
     * Searches all system catalogs to find objects that match a certain name.
541
     *
542
     * @param string $term   The search term
543
     * @param string $filter The object type to restrict to ('' means no restriction)
544
     *
545
     * @return \PHPPgAdmin\ADORecordSet A recordset
546
     */
547
    public function findObject($term, $filter)
548
    {
549
        $conf = $this->conf;
550
551
        /*about escaping:
552
         * SET standard_conforming_string is not available before 8.2
553
         * So we must use PostgreSQL specific notation :/
554
         * E'' notation is not available before 8.1
555
         * $$ is available since 8.0
556
         * Nothing specific from 7.4
557
         */
558
559
        // Escape search term for ILIKE match
560
        $this->clean($term);
561
        $this->clean($filter);
562
        $term = str_replace('_', '\_', $term);
563
        $term = str_replace('%', '\%', $term);
564
565
        // Exclude system relations if necessary
566
        if (!$conf['show_system']) {
567
            // XXX: The mention of information_schema here is in the wrong place, but
568
            // it's the quickest fix to exclude the info schema from 7.4
569
            $where     = " AND pn.nspname NOT LIKE \$_PATERN_\$pg\\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'";
570
            $lan_where = 'AND pl.lanispl';
571
        } else {
572
            $where     = '';
573
            $lan_where = '';
574
        }
575
576
        // Apply outer filter
577
        $sql = '';
578
        if ($filter != '') {
579
            $sql = 'SELECT * FROM (';
580
        }
581
582
        $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$";
583
584
        $sql .= "
585
			SELECT 'SCHEMA' AS type, oid, NULL AS schemaname, NULL AS relname, nspname AS name
586
				FROM pg_catalog.pg_namespace pn WHERE nspname ILIKE {$term} {$where}
587
			UNION ALL
588
			SELECT CASE WHEN relkind='r' THEN 'TABLE' WHEN relkind='v' THEN 'VIEW' WHEN relkind='S' THEN 'SEQUENCE' END, pc.oid,
589
				pn.nspname, NULL, pc.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn
590
				WHERE pc.relnamespace=pn.oid AND relkind IN ('r', 'v', 'S') AND relname ILIKE {$term} {$where}
591
			UNION ALL
592
			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,
593
				pg_catalog.pg_attribute pa WHERE pc.relnamespace=pn.oid AND pc.oid=pa.attrelid
594
				AND pa.attname ILIKE {$term} AND pa.attnum > 0 AND NOT pa.attisdropped AND pc.relkind IN ('r', 'v') {$where}
595
			UNION ALL
596
			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
597
				WHERE pp.pronamespace=pn.oid AND NOT pp.proisagg AND pp.proname ILIKE {$term} {$where}
598
			UNION ALL
599
			SELECT 'INDEX', NULL, pn.nspname, pc.relname, pc2.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
600
				pg_catalog.pg_index pi, pg_catalog.pg_class pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pi.indrelid
601
				AND pi.indexrelid=pc2.oid
602
				AND NOT EXISTS (
603
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
604
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
605
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
606
				)
607
				AND pc2.relname ILIKE {$term} {$where}
608
			UNION ALL
609
			SELECT 'CONSTRAINTTABLE', NULL, pn.nspname, pc.relname, pc2.conname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
610
				pg_catalog.pg_constraint pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pc2.conrelid AND pc2.conrelid != 0
611
				AND CASE WHEN pc2.contype IN ('f', 'c') THEN TRUE ELSE NOT EXISTS (
612
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
613
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
614
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
615
				) END
616
				AND pc2.conname ILIKE {$term} {$where}
617
			UNION ALL
618
			SELECT 'CONSTRAINTDOMAIN', pt.oid, pn.nspname, pt.typname, pc.conname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn,
619
				pg_catalog.pg_constraint pc WHERE pt.typnamespace=pn.oid AND pt.oid=pc.contypid AND pc.contypid != 0
620
				AND pc.conname ILIKE {$term} {$where}
621
			UNION ALL
622
			SELECT 'TRIGGER', NULL, pn.nspname, pc.relname, pt.tgname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
623
				pg_catalog.pg_trigger pt WHERE pc.relnamespace=pn.oid AND pc.oid=pt.tgrelid
624
					AND ( pt.tgconstraint = 0 OR NOT EXISTS
625
					(SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
626
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
627
					WHERE d.classid = pt.tableoid AND d.objid = pt.oid AND d.deptype = 'i' AND c.contype = 'f'))
628
				AND pt.tgname ILIKE {$term} {$where}
629
			UNION ALL
630
			SELECT 'RULETABLE', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
631
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
632
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
633
				WHERE c.relkind='r' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
634
			UNION ALL
635
			SELECT 'RULEVIEW', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
636
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
637
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
638
				WHERE c.relkind='v' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
639
		";
640
641
        // Add advanced objects if show_advanced is set
642
        if ($conf['show_advanced']) {
643
            $sql .= "
644
				UNION ALL
645
				SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL,
646
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
647
					WHERE pt.typnamespace=pn.oid AND typname ILIKE {$term}
648
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
649
					{$where}
650
			 	UNION ALL
651
				SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn
652
					WHERE po.oprnamespace=pn.oid AND oprname ILIKE {$term} {$where}
653
				UNION ALL
654
				SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc,
655
					pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE {$term} {$where}
656
				UNION ALL
657
				SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl
658
					WHERE lanname ILIKE {$term} {$lan_where}
659
				UNION ALL
660
				SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p
661
					LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid
662
					WHERE p.proisagg AND p.proname ILIKE {$term} {$where}
663
				UNION ALL
664
				SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po,
665
					pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid
666
					AND po.opcname ILIKE {$term} {$where}
667
			";
668
        } else {
669
            // Otherwise just add domains
670
            $sql .= "
671
				UNION ALL
672
				SELECT 'DOMAIN', pt.oid, pn.nspname, NULL,
673
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
674
					WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE {$term}
675
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
676
					{$where}
677
			";
678
        }
679
680
        if ($filter != '') {
681
            // We use like to make RULE, CONSTRAINT and COLUMN searches work
682
            $sql .= ") AS sub WHERE type LIKE '{$filter}%' ";
683
        }
684
685
        $sql .= 'ORDER BY type, schemaname, relname, name';
686
687
        return $this->selectSet($sql);
688
    }
689
690
    /**
691
     * Returns all available variable information.
692
     *
693
     * @return \PHPPgAdmin\ADORecordSet A recordset
694
     */
695
    public function getVariables()
696
    {
697
        $sql = 'SHOW ALL';
698
699
        return $this->selectSet($sql);
700
    }
701
702
    // Schema functons
703
704
    /**
705
     * Return all schemas in the current database.
706
     *
707
     * @return \PHPPgAdmin\ADORecordSet All schemas, sorted alphabetically
708
     */
709
    public function getSchemas()
710
    {
711
        $conf = $this->conf;
712
713
        if (!$conf['show_system']) {
714
            $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'";
715
        } else {
716
            $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
717
        }
718
719
        $sql = "
720
			SELECT pn.nspname,
721
                   pu.rolname AS nspowner,
722
				   pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment,
723
                   pg_size_pretty(SUM(pg_total_relation_size(pg_class.oid))) as schema_size
724
			FROM pg_catalog.pg_namespace pn
725
            LEFT JOIN pg_catalog.pg_class  ON relnamespace = pn.oid
726
			LEFT JOIN pg_catalog.pg_roles pu ON (pn.nspowner = pu.oid)
727
			{$where}
728
            GROUP BY pn.nspname, pu.rolname, pg_catalog.obj_description(pn.oid, 'pg_namespace')
729
			ORDER BY nspname";
730
731
        return $this->selectSet($sql);
732
    }
733
734
    /**
735
     * Sets the current working schema.  Will also set Class variable.
736
     *
737
     * @param string $schema The the name of the schema to work in
738
     *
739
     * @return int 0 if operation was successful
740
     */
741
    public function setSchema($schema)
742
    {
743
        // Get the current schema search path, including 'pg_catalog'.
744
        $search_path = $this->getSearchPath();
745
        // Prepend $schema to search path
746
        array_unshift($search_path, $schema);
747
        $status = $this->setSearchPath($search_path);
748
        if ($status == 0) {
749
            $this->_schema = $schema;
750
751
            return 0;
752
        }
753
754
        return $status;
755
    }
756
757
    /**
758
     * Return the current schema search path.
759
     *
760
     * @return array array of schema names
761
     */
762
    public function getSearchPath()
763
    {
764
        $sql = 'SELECT current_schemas(false) AS search_path';
765
766
        return $this->phpArray($this->selectField($sql, 'search_path'));
767
    }
768
769
    /**
770
     * Sets the current schema search path.
771
     *
772
     * @param array $paths An array of schemas in required search order
773
     *
774
     * @return int 0 if operation was successful
775
     */
776
    public function setSearchPath($paths)
777
    {
778
        if (!is_array($paths)) {
0 ignored issues
show
introduced by
The condition is_array($paths) is always true.
Loading history...
779
            return -1;
780
        }
781
782
        if (sizeof($paths) == 0) {
783
            return -2;
784
        }
785
        if (sizeof($paths) == 1 && $paths[0] == '') {
786
            // Need to handle empty paths in some cases
787
            $paths[0] = 'pg_catalog';
788
        }
789
790
        // Loop over all the paths to check that none are empty
791
        $temp = [];
792
        foreach ($paths as $schema) {
793
            if ($schema != '') {
794
                $temp[] = $schema;
795
            }
796
        }
797
        $this->fieldArrayClean($temp);
798
799
        $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"';
800
801
        return $this->execute($sql);
802
    }
803
804
    /**
805
     * Creates a new schema.
806
     *
807
     * @param string $schemaname    The name of the schema to create
808
     * @param string $authorization (optional) The username to create the schema for
809
     * @param string $comment       (optional) If omitted, defaults to nothing
810
     *
811
     * @return bool|int 0 success
812
     */
813
    public function createSchema($schemaname, $authorization = '', $comment = '')
814
    {
815
        $this->fieldClean($schemaname);
816
        $this->fieldClean($authorization);
817
818
        $sql = "CREATE SCHEMA \"{$schemaname}\"";
819
        if ($authorization != '') {
820
            $sql .= " AUTHORIZATION \"{$authorization}\"";
821
        }
822
823
        if ($comment != '') {
824
            $status = $this->beginTransaction();
825
            if ($status != 0) {
826
                return -1;
827
            }
828
        }
829
830
        // Create the new schema
831
        $status = $this->execute($sql);
832
        if ($status != 0) {
833
            $this->rollbackTransaction();
834
835
            return -1;
836
        }
837
838
        // Set the comment
839
        if ($comment != '') {
840
            $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
841
            if ($status != 0) {
842
                $this->rollbackTransaction();
843
844
                return -1;
845
            }
846
847
            return $this->endTransaction();
848
        }
849
850
        return 0;
851
    }
852
853
    /**
854
     * Updates a schema.
855
     *
856
     * @param string $schemaname The name of the schema to drop
857
     * @param string $comment    The new comment for this schema
858
     * @param string $name       new name for this schema
859
     * @param string $owner      The new owner for this schema
860
     *
861
     * @return bool|int 0 success
862
     */
863
    public function updateSchema($schemaname, $comment, $name, $owner)
864
    {
865
        $this->fieldClean($schemaname);
866
        $this->fieldClean($name);
867
        $this->fieldClean($owner);
868
869
        $status = $this->beginTransaction();
870
        if ($status != 0) {
871
            $this->rollbackTransaction();
872
873
            return -1;
874
        }
875
876
        $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
877
        if ($status != 0) {
878
            $this->rollbackTransaction();
879
880
            return -1;
881
        }
882
883
        $schema_rs = $this->getSchemaByName($schemaname);
884
        /* Only if the owner change */
885
        if ($schema_rs->fields['ownername'] != $owner) {
886
            $sql    = "ALTER SCHEMA \"{$schemaname}\" OWNER TO \"{$owner}\"";
887
            $status = $this->execute($sql);
888
            if ($status != 0) {
889
                $this->rollbackTransaction();
890
891
                return -1;
892
            }
893
        }
894
895
        // Only if the name has changed
896
        if ($name != $schemaname) {
897
            $sql    = "ALTER SCHEMA \"{$schemaname}\" RENAME TO \"{$name}\"";
898
            $status = $this->execute($sql);
899
            if ($status != 0) {
900
                $this->rollbackTransaction();
901
902
                return -1;
903
            }
904
        }
905
906
        return $this->endTransaction();
907
    }
908
909
    /**
910
     * Return all information relating to a schema.
911
     *
912
     * @param string $schema The name of the schema
913
     *
914
     * @return \PHPPgAdmin\ADORecordSet Schema information
915
     */
916
    public function getSchemaByName($schema)
917
    {
918
        $this->clean($schema);
919
        $sql = "
920
			SELECT nspname, nspowner, r.rolname AS ownername, nspacl,
921
				pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment
922
			FROM pg_catalog.pg_namespace pn
923
				LEFT JOIN pg_roles as r ON pn.nspowner = r.oid
924
			WHERE nspname='{$schema}'";
925
926
        return $this->selectSet($sql);
927
    }
928
929
    // Table functions
930
931
    /**
932
     * Drops a schema.
933
     *
934
     * @param string $schemaname The name of the schema to drop
935
     * @param bool $cascade    True to cascade drop, false to restrict
936
     *
937
     * @return int 0 if operation was successful
938
     */
939
    public function dropSchema($schemaname, $cascade)
940
    {
941
        $this->fieldClean($schemaname);
942
943
        $sql = "DROP SCHEMA \"{$schemaname}\"";
944
        if ($cascade) {
945
            $sql .= ' CASCADE';
946
        }
947
948
        return $this->execute($sql);
949
    }
950
951
    /**
952
     * Formats a type correctly for display.  Postgres 7.0 had no 'format_type'
953
     * built-in function, and hence we need to do it manually.
954
     *
955
     * @param string $typname The name of the type
956
     * @param string $typmod  The contents of the typmod field
957
     *
958
     * @return bool|string
959
     */
960
    public function formatType($typname, $typmod)
961
    {
962
        // This is a specific constant in the 7.0 source
963
        $varhdrsz = 4;
964
965
        // If the first character is an underscore, it's an array type
966
        $is_array = false;
967
        if (substr($typname, 0, 1) == '_') {
968
            $is_array = true;
969
            $typname  = substr($typname, 1);
970
        }
971
972
        // Show lengths on bpchar and varchar
973
        if ($typname == 'bpchar') {
974
            $len  = $typmod - $varhdrsz;
975
            $temp = 'character';
976
            if ($len > 1) {
977
                $temp .= "({$len})";
978
            }
979
        } elseif ($typname == 'varchar') {
980
            $temp = 'character varying';
981
            if ($typmod != -1) {
982
                $temp .= '(' . ($typmod - $varhdrsz) . ')';
983
            }
984
        } elseif ($typname == 'numeric') {
985
            $temp = 'numeric';
986
            if ($typmod != -1) {
987
                $tmp_typmod = $typmod - $varhdrsz;
988
                $precision  = ($tmp_typmod >> 16) & 0xffff;
989
                $scale      = $tmp_typmod & 0xffff;
990
                $temp .= "({$precision}, {$scale})";
991
            }
992
        } else {
993
            $temp = $typname;
994
        }
995
996
        // Add array qualifier if it's an array
997
        if ($is_array) {
998
            $temp .= '[]';
999
        }
1000
1001
        return $temp;
1002
    }
1003
1004
    /**
1005
     * Given an array of attnums and a relation, returns an array mapping
1006
     * attribute number to attribute name.
1007
     *
1008
     * @param string $table The table to get attributes for
1009
     * @param array $atts  An array of attribute numbers
1010
     *
1011
     * @return array An array mapping attnum to attname or error code
1012
     *              - -1 $atts must be an array
1013
     *              - -2 wrong number of attributes found
1014
     */
1015
    public function getAttributeNames($table, $atts)
1016
    {
1017
        $c_schema = $this->_schema;
1018
        $this->clean($c_schema);
1019
        $this->clean($table);
1020
        $this->arrayClean($atts);
1021
1022
        if (!is_array($atts)) {
1023
            return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type array.
Loading history...
1024
        }
1025
1026
        if (sizeof($atts) == 0) {
1027
            return [];
1028
        }
1029
1030
        $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
1031
			attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
1032
			relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
1033
			AND attnum IN ('" . join("','", $atts) . "')";
1034
1035
        $rs = $this->selectSet($sql);
1036
        if ($rs->recordCount() != sizeof($atts)) {
1037
            return -2;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -2 returns the type integer which is incompatible with the documented return type array.
Loading history...
1038
        }
1039
1040
        $temp = [];
1041
        while (!$rs->EOF) {
1042
            $temp[$rs->fields['attnum']] = $rs->fields['attname'];
1043
            $rs->moveNext();
1044
        }
1045
1046
        return $temp;
1047
    }
1048
1049
    /**
1050
     * Cleans (escapes) an array.
1051
     *
1052
     * @param array $arr The array to clean, by reference
1053
     *
1054
     * @return array The cleaned array
1055
     */
1056
    public function arrayClean(&$arr)
1057
    {
1058
        foreach ($arr as $k => $v) {
1059
            if ($v === null) {
1060
                continue;
1061
            }
1062
1063
            $arr[$k] = pg_escape_string($v);
1064
        }
1065
1066
        return $arr;
1067
    }
1068
1069
    /**
1070
     * Grabs an array of users and their privileges for an object,
1071
     * given its type.
1072
     *
1073
     * @param string $object The name of the object whose privileges are to be retrieved
1074
     * @param string $type   The type of the object (eg. database, schema, relation, function or language)
1075
     * @param null|string $table  Optional, column's table if type = column
1076
     *
1077
     * @return array|int Privileges array or error code
1078
     *                   - -1         invalid type
1079
     *                   - -2         object not found
1080
     *                   - -3         unknown privilege type
1081
     */
1082
    public function getPrivileges($object, $type, $table = null)
1083
    {
1084
        $c_schema = $this->_schema;
1085
        $this->clean($c_schema);
1086
        $this->clean($object);
1087
1088
        switch ($type) {
1089
            case 'column':
1090
                $this->clean($table);
1091
                $sql = "
1092
					SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl
1093
					FROM pg_catalog.pg_attribute a
1094
						LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid)
1095
						LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid)
1096
					WHERE n.nspname='{$c_schema}'
1097
						AND c.relname='{$table}'
1098
						AND a.attname='{$object}'";
1099
1100
                break;
1101
            case 'table':
1102
            case 'view':
1103
            case 'sequence':
1104
                $sql = "
1105
					SELECT relacl AS acl FROM pg_catalog.pg_class
1106
					WHERE relname='{$object}'
1107
						AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace
1108
							WHERE nspname='{$c_schema}')";
1109
1110
                break;
1111
            case 'database':
1112
                $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'";
1113
1114
                break;
1115
            case 'function':
1116
                // Since we fetch functions by oid, they are already constrained to
1117
                // the current schema.
1118
                $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'";
1119
1120
                break;
1121
            case 'language':
1122
                $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'";
1123
1124
                break;
1125
            case 'schema':
1126
                $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'";
1127
1128
                break;
1129
            case 'tablespace':
1130
                $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'";
1131
1132
                break;
1133
            default:
1134
                return -1;
1135
        }
1136
1137
        // Fetch the ACL for object
1138
        $acl = $this->selectField($sql, 'acl');
1139
        if ($acl == -1) {
1140
            return -2;
1141
        }
1142
1143
        if ($acl == '' || $acl === null || !(bool) $acl) {
1144
            return [];
1145
        }
1146
1147
        return $this->_parseACL($acl);
1148
    }
1149
1150
    /**
1151
     * Internal function used for parsing ACLs.
1152
     *
1153
     * @param string  $acl The ACL to parse (of type aclitem[])
1154
     *
1155
     * @return array Privileges array
1156
     * @internal bool $in_quotes toggles acl in_quotes attribute
1157
     */
1158
    protected function _parseACL($acl)
1 ignored issue
show
Coding Style introduced by
Protected method name "Postgres::_parseACL" must not be prefixed with an underscore
Loading history...
1159
    {
1160
        // Take off the first and last characters (the braces)
1161
        $acl = substr($acl, 1, strlen($acl) - 2);
1162
1163
        // Pick out individual ACE's by carefully parsing.  This is necessary in order
1164
        // to cope with usernames and stuff that contain commas
1165
        $aces      = [];
1166
        $i         = $j         = 0;
1167
        $in_quotes = false;
1168
        while ($i < strlen($acl)) {
1169
            // If current char is a double quote and it's not escaped, then
1170
            // enter quoted bit
1171
            $char = substr($acl, $i, 1);
1172
            if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) {
1173
                $in_quotes = !$in_quotes;
1 ignored issue
show
introduced by
$in_quotes is of type mixed, thus it always evaluated to false.
Loading history...
1174
            } elseif ($char == ',' && !$in_quotes) {
1175
                // Add text so far to the array
1176
                $aces[] = substr($acl, $j, $i - $j);
1177
                $j      = $i + 1;
1178
            }
1179
            ++$i;
1180
        }
1181
        // Add final text to the array
1182
        $aces[] = substr($acl, $j);
1183
1184
        // Create the array to be returned
1185
        $temp = [];
1186
1187
        // For each ACE, generate an entry in $temp
1188
        foreach ($aces as $v) {
1189
            // If the ACE begins with a double quote, strip them off both ends
1190
            // and unescape backslashes and double quotes
1191
            $unquote = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $unquote is dead and can be removed.
Loading history...
1192
            if (strpos($v, '"') === 0) {
1193
                $v = substr($v, 1, strlen($v) - 2);
1194
                $v = str_replace('\\"', '"', $v);
1195
                $v = str_replace('\\\\', '\\', $v);
1196
            }
1197
1198
            // Figure out type of ACE (public, user or group)
1199
            if (strpos($v, '=') === 0) {
1200
                $atype = 'public';
1201
            } else {
1202
                if ($this->hasRoles()) {
1203
                    $atype = 'role';
1204
                } else {
1205
                    if (strpos($v, 'group ') === 0) {
1206
                        $atype = 'group';
1207
                        // Tear off 'group' prefix
1208
                        $v = substr($v, 6);
1209
                    } else {
1210
                        $atype = 'user';
1211
                    }
1212
                }
1213
            }
1214
1215
            // Break on unquoted equals sign...
1216
            $i         = 0;
1217
            $in_quotes = false;
1218
            $entity    = null;
1219
            $chars     = null;
1220
            while ($i < strlen($v)) {
1221
                // If current char is a double quote and it's not escaped, then
1222
                // enter quoted bit
1223
                $char      = substr($v, $i, 1);
1224
                $next_char = substr($v, $i + 1, 1);
1225
                if ($char == '"' && ($i == 0 || $next_char != '"')) {
1226
                    $in_quotes = !$in_quotes;
1 ignored issue
show
introduced by
The condition $in_quotes is always false.
Loading history...
1227
                } elseif ($char == '"' && $next_char == '"') {
1228
                    // Skip over escaped double quotes
1229
                    ++$i;
1230
                } elseif ($char == '=' && !$in_quotes) {
1231
                    // Split on current equals sign
1232
                    $entity = substr($v, 0, $i);
1233
                    $chars  = substr($v, $i + 1);
1234
1235
                    break;
1236
                }
1237
                ++$i;
1238
            }
1239
1240
            // Check for quoting on entity name, and unescape if necessary
1241
            if (strpos($entity, '"') === 0) {
1242
                $entity = substr($entity, 1, strlen($entity) - 2);
1243
                $entity = str_replace('""', '"', $entity);
1244
            }
1245
1246
            // New row to be added to $temp
1247
            // (type, grantee, privileges, grantor, grant option?
1248
            $row = [$atype, $entity, [], '', []];
1249
1250
            // Loop over chars and add privs to $row
1251
            for ($i = 0; $i < strlen($chars); ++$i) {
1252
                // Append to row's privs list the string representing
1253
                // the privilege
1254
                $char = substr($chars, $i, 1);
1255
                if ($char == '*') {
1256
                    $row[4][] = $this->privmap[substr($chars, $i - 1, 1)];
1257
                } elseif ($char == '/') {
1258
                    $grantor = substr($chars, $i + 1);
1259
                    // Check for quoting
1260
                    if (strpos($grantor, '"') === 0) {
1261
                        $grantor = substr($grantor, 1, strlen($grantor) - 2);
1262
                        $grantor = str_replace('""', '"', $grantor);
1263
                    }
1264
                    $row[3] = $grantor;
1265
1266
                    break;
1267
                } else {
1268
                    if (!isset($this->privmap[$char])) {
1269
                        return -3;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -3 returns the type integer which is incompatible with the documented return type array.
Loading history...
1270
                    }
1271
1272
                    $row[2][] = $this->privmap[$char];
1273
                }
1274
            }
1275
1276
            // Append row to temp
1277
            $temp[] = $row;
1278
        }
1279
1280
        return $temp;
1281
    }
1282
1283
    // Rule functions
1284
1285
    /**
1286
     * Returns all details for a particular type.
1287
     *
1288
     * @param string $typname The name of the view to retrieve
1289
     *
1290
     * @return \PHPPgAdmin\ADORecordSet type info
1291
     */
1292
    public function getType($typname)
1293
    {
1294
        $this->clean($typname);
1295
1296
        $sql = "SELECT typtype, typbyval, typname, typinput AS typin, typoutput AS typout, typlen, typalign
1297
			FROM pg_type WHERE typname='{$typname}'";
1298
1299
        return $this->selectSet($sql);
1300
    }
1301
1302
    /**
1303
     * Returns a list of all types in the database.
1304
     *
1305
     * @param bool $all        If true, will find all available types, if false just those in search path
1306
     * @param bool $tabletypes If true, will include table types
1307
     * @param bool $domains    If true, will include domains
1308
     *
1309
     * @return \PHPPgAdmin\ADORecordSet A recordset
1310
     */
1311
    public function getTypes($all = false, $tabletypes = false, $domains = false)
1312
    {
1313
        if ($all) {
1314
            $where = '1 = 1';
1315
        } else {
1316
            $c_schema = $this->_schema;
1317
            $this->clean($c_schema);
1318
            $where = "n.nspname = '{$c_schema}'";
1319
        }
1320
        // Never show system table types
1321
        $where2 = "AND c.relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname LIKE 'pg@_%' ESCAPE '@')";
1322
1323
        // Create type filter
1324
        $tqry = "'c'";
1325
        if ($tabletypes) {
1326
            $tqry .= ", 'r', 'v'";
1327
        }
1328
1329
        // Create domain filter
1330
        if (!$domains) {
1331
            $where .= " AND t.typtype != 'd'";
1332
        }
1333
1334
        $sql = "SELECT
1335
				t.typname AS basename,
1336
				pg_catalog.format_type(t.oid, NULL) AS typname,
1337
				pu.usename AS typowner,
1338
				t.typtype,
1339
				pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment
1340
			FROM (pg_catalog.pg_type t
1341
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace)
1342
				LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid
1343
			WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2}))
1344
			AND t.typname !~ '^_'
1345
			AND {$where}
1346
			ORDER BY typname
1347
		";
1348
1349
        return $this->selectSet($sql);
1350
    }
1351
1352
    /**
1353
     * Creates a new type.
1354
     *
1355
     * @param string $typname
1356
     * @param string $typin
1357
     * @param string $typout
1358
     * @param string $typlen
1359
     * @param string $typdef
1360
     * @param string $typelem
1361
     * @param string $typdelim
1362
     * @param string $typbyval
1363
     * @param string $typalign
1364
     * @param string $typstorage
1365
     *
1366
     * @return int 0 if operation was successful
1367
     *
1368
     * @internal param $ ...
1369
     */
1370
    public function createType(
1371
        $typname,
1372
        $typin,
1373
        $typout,
1374
        $typlen,
1375
        $typdef,
1376
        $typelem,
1377
        $typdelim,
1378
        $typbyval,
1379
        $typalign,
1380
        $typstorage
1381
    ) {
1382
        $f_schema = $this->_schema;
1383
        $this->fieldClean($f_schema);
1384
        $this->fieldClean($typname);
1385
        $this->fieldClean($typin);
1386
        $this->fieldClean($typout);
1387
1388
        $sql = "
1389
			CREATE TYPE \"{$f_schema}\".\"{$typname}\" (
1390
				INPUT = \"{$typin}\",
1391
				OUTPUT = \"{$typout}\",
1392
				INTERNALLENGTH = {$typlen}";
1393
        if ($typdef != '') {
1394
            $sql .= ", DEFAULT = {$typdef}";
1395
        }
1396
1397
        if ($typelem != '') {
1398
            $sql .= ", ELEMENT = {$typelem}";
1399
        }
1400
1401
        if ($typdelim != '') {
1402
            $sql .= ", DELIMITER = {$typdelim}";
1403
        }
1404
1405
        if ($typbyval) {
1406
            $sql .= ', PASSEDBYVALUE, ';
1407
        }
1408
1409
        if ($typalign != '') {
1410
            $sql .= ", ALIGNMENT = {$typalign}";
1411
        }
1412
1413
        if ($typstorage != '') {
1414
            $sql .= ", STORAGE = {$typstorage}";
1415
        }
1416
1417
        $sql .= ')';
1418
1419
        return $this->execute($sql);
1420
    }
1421
1422
    /**
1423
     * Drops a type.
1424
     *
1425
     * @param string $typname The name of the type to drop
1426
     * @param bool $cascade True to cascade drop, false to restrict
1427
     *
1428
     * @return int 0 if operation was successful
1429
     */
1430
    public function dropType($typname, $cascade)
1431
    {
1432
        $f_schema = $this->_schema;
1433
        $this->fieldClean($f_schema);
1434
        $this->fieldClean($typname);
1435
1436
        $sql = "DROP TYPE \"{$f_schema}\".\"{$typname}\"";
1437
        if ($cascade) {
1438
            $sql .= ' CASCADE';
1439
        }
1440
1441
        return $this->execute($sql);
1442
    }
1443
1444
    /**
1445
     * Creates a new enum type in the database.
1446
     *
1447
     * @param string $name       The name of the type
1448
     * @param array  $values     An array of values
1449
     * @param string $typcomment Type comment
1450
     *
1451
     * @return bool|int 0 success
1452
     */
1453
    public function createEnumType($name, $values, $typcomment)
1454
    {
1455
        $f_schema = $this->_schema;
1456
        $this->fieldClean($f_schema);
1457
        $this->fieldClean($name);
1458
1459
        if (empty($values)) {
1460
            return -2;
1461
        }
1462
1463
        $status = $this->beginTransaction();
1464
        if ($status != 0) {
1465
            return -1;
1466
        }
1467
1468
        $values = array_unique($values);
1469
1470
        $nbval = count($values);
1471
1472
        for ($i = 0; $i < $nbval; ++$i) {
1473
            $this->clean($values[$i]);
1474
        }
1475
1476
        $sql = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS ENUM ('";
1477
        $sql .= implode("','", $values);
1478
        $sql .= "')";
1479
1480
        $status = $this->execute($sql);
1481
        if ($status) {
1482
            $this->rollbackTransaction();
1483
1484
            return -1;
1485
        }
1486
1487
        if ($typcomment != '') {
1488
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
1489
            if ($status) {
1490
                $this->rollbackTransaction();
1491
1492
                return -1;
1493
            }
1494
        }
1495
1496
        return $this->endTransaction();
1497
    }
1498
1499
    /**
1500
     * Get defined values for a given enum.
1501
     *
1502
     * @param string $name
1503
     *
1504
     * @return \PHPPgAdmin\ADORecordSet A recordset
1505
     */
1506
    public function getEnumValues($name)
1507
    {
1508
        $this->clean($name);
1509
1510
        $sql = "SELECT enumlabel AS enumval
1511
		FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON (t.oid=e.enumtypid)
1512
		WHERE t.typname = '{$name}' ORDER BY e.oid";
1513
1514
        return $this->selectSet($sql);
1515
    }
1516
1517
    // Operator functions
1518
1519
    /**
1520
     * Creates a new composite type in the database.
1521
     *
1522
     * @param string $name       The name of the type
1523
     * @param int    $fields     The number of fields
1524
     * @param array  $field      An array of field names
1525
     * @param array  $type       An array of field types
1526
     * @param array  $array      An array of '' or '[]' for each type if it's an array or not
1527
     * @param array  $length     An array of field lengths
1528
     * @param array  $colcomment An array of comments
1529
     * @param string $typcomment Type comment
1530
     *
1531
     * @return bool|int 0 success
1532
     */
1533
    public function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment)
1534
    {
1535
        $f_schema = $this->_schema;
1536
        $this->fieldClean($f_schema);
1537
        $this->fieldClean($name);
1538
1539
        $status = $this->beginTransaction();
1540
        if ($status != 0) {
1541
            return -1;
1542
        }
1543
1544
        $found       = false;
1545
        $first       = true;
1546
        $comment_sql = ''; // Accumulate comments for the columns
1547
        $sql         = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS (";
1548
        for ($i = 0; $i < $fields; ++$i) {
1549
            $this->fieldClean($field[$i]);
1550
            $this->clean($type[$i]);
1551
            $this->clean($length[$i]);
1552
            $this->clean($colcomment[$i]);
1553
1554
            // Skip blank columns - for user convenience
1555
            if ($field[$i] == '' || $type[$i] == '') {
1556
                continue;
1557
            }
1558
1559
            // If not the first column, add a comma
1560
            if (!$first) {
1561
                $sql .= ', ';
1562
            } else {
1563
                $first = false;
1564
            }
1565
1566
            switch ($type[$i]) {
1567
                // Have to account for weird placing of length for with/without
1568
                // time zone types
1569
                case 'timestamp with time zone':
1570
                case 'timestamp without time zone':
1571
                    $qual = substr($type[$i], 9);
1572
                    $sql .= "\"{$field[$i]}\" timestamp";
1573
                    if ($length[$i] != '') {
1574
                        $sql .= "({$length[$i]})";
1575
                    }
1576
1577
                    $sql .= $qual;
1578
1579
                    break;
1580
                case 'time with time zone':
1581
                case 'time without time zone':
1582
                    $qual = substr($type[$i], 4);
1583
                    $sql .= "\"{$field[$i]}\" time";
1584
                    if ($length[$i] != '') {
1585
                        $sql .= "({$length[$i]})";
1586
                    }
1587
1588
                    $sql .= $qual;
1589
1590
                    break;
1591
                default:
1592
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
1593
                    if ($length[$i] != '') {
1594
                        $sql .= "({$length[$i]})";
1595
                    }
1596
            }
1597
            // Add array qualifier if necessary
1598
            if ($array[$i] == '[]') {
1599
                $sql .= '[]';
1600
            }
1601
1602
            if ($colcomment[$i] != '') {
1603
                $comment_sql .= "COMMENT ON COLUMN \"{$f_schema}\".\"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
1604
            }
1605
1606
            $found = true;
1607
        }
1608
1609
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
1610
            return -1;
1611
        }
1612
1613
        $sql .= ')';
1614
1615
        $status = $this->execute($sql);
1616
        if ($status) {
1617
            $this->rollbackTransaction();
1618
1619
            return -1;
1620
        }
1621
1622
        if ($typcomment != '') {
1623
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
1624
            if ($status) {
1625
                $this->rollbackTransaction();
1626
1627
                return -1;
1628
            }
1629
        }
1630
1631
        if ($comment_sql != '') {
1632
            $status = $this->execute($comment_sql);
1633
            if ($status) {
1634
                $this->rollbackTransaction();
1635
1636
                return -1;
1637
            }
1638
        }
1639
1640
        return $this->endTransaction();
1641
    }
1642
1643
    /**
1644
     * Returns a list of all casts in the database.
1645
     *
1646
     * @return \PHPPgAdmin\ADORecordSet All casts
1647
     */
1648
    public function getCasts()
1649
    {
1650
        $conf = $this->conf;
1651
1652
        if ($conf['show_system']) {
1653
            $where = '';
1654
        } else {
1655
            $where = '
1656
				AND n1.nspname NOT LIKE $$pg\_%$$
1657
				AND n2.nspname NOT LIKE $$pg\_%$$
1658
				AND n3.nspname NOT LIKE $$pg\_%$$
1659
			';
1660
        }
1661
1662
        $sql = "
1663
			SELECT
1664
				c.castsource::pg_catalog.regtype AS castsource,
1665
				c.casttarget::pg_catalog.regtype AS casttarget,
1666
				CASE WHEN c.castfunc=0 THEN NULL
1667
				ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc,
1668
				c.castcontext,
1669
				obj_description(c.oid, 'pg_cast') as castcomment
1670
			FROM
1671
				(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),
1672
				pg_catalog.pg_type t1,
1673
				pg_catalog.pg_type t2,
1674
				pg_catalog.pg_namespace n1,
1675
				pg_catalog.pg_namespace n2
1676
			WHERE
1677
				c.castsource=t1.oid
1678
				AND c.casttarget=t2.oid
1679
				AND t1.typnamespace=n1.oid
1680
				AND t2.typnamespace=n2.oid
1681
				{$where}
1682
			ORDER BY 1, 2
1683
		";
1684
1685
        return $this->selectSet($sql);
1686
    }
1687
1688
    /**
1689
     * Returns a list of all conversions in the database.
1690
     *
1691
     * @return \PHPPgAdmin\ADORecordSet All conversions
1692
     */
1693
    public function getConversions()
1694
    {
1695
        $c_schema = $this->_schema;
1696
        $this->clean($c_schema);
1697
        $sql = "
1698
			SELECT
1699
			       c.conname,
1700
			       pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding,
1701
			       pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding,
1702
			       c.condefault,
1703
			       pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment
1704
			FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n
1705
			WHERE n.oid = c.connamespace
1706
			      AND n.nspname='{$c_schema}'
1707
			ORDER BY 1;
1708
		";
1709
1710
        return $this->selectSet($sql);
1711
    }
1712
1713
    // Operator Class functions
1714
1715
    /**
1716
     * Edits a rule on a table OR view.
1717
     *
1718
     * @param string $name The name of the new rule
1719
     * @param string $event   SELECT, INSERT, UPDATE or DELETE
1720
     * @param string $table   Table on which to create the rule
1721
     * @param string $where   Where to execute the rule, '' indicates always
1722
     * @param bool $instead True if an INSTEAD rule, false otherwise
1723
     * @param string $type    NOTHING for a do nothing rule, SOMETHING to use given action
1724
     * @param string $action  The action to take
1725
     *
1726
     * @return int 0 if operation was successful
1727
     */
1728
    public function setRule($name, $event, $table, $where, $instead, $type, $action)
1729
    {
1730
        return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true);
1731
    }
1732
1733
    // FTS functions
1734
1735
    /**
1736
     * Creates a rule.
1737
     *
1738
     * @param string $name    The name of the new rule
1739
     * @param string $event   SELECT, INSERT, UPDATE or DELETE
1740
     * @param string $table   Table on which to create the rule
1741
     * @param string $where   When to execute the rule, '' indicates always
1742
     * @param bool   $instead True if an INSTEAD rule, false otherwise
1743
     * @param string $type    NOTHING for a do nothing rule, SOMETHING to use given action
1744
     * @param string $action  The action to take
1745
     * @param bool   $replace (optional) True to replace existing rule, false
1746
     *                        otherwise
1747
     *
1748
     * @return int 0 if operation was successful
1749
     */
1750
    public function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false)
1751
    {
1752
        $f_schema = $this->_schema;
1753
        $this->fieldClean($f_schema);
1754
        $this->fieldClean($name);
1755
        $this->fieldClean($table);
1756
        if (!in_array($event, $this->rule_events, true)) {
1757
            return -1;
1758
        }
1759
1760
        $sql = 'CREATE';
1761
        if ($replace) {
1762
            $sql .= ' OR REPLACE';
1763
        }
1764
1765
        $sql .= " RULE \"{$name}\" AS ON {$event} TO \"{$f_schema}\".\"{$table}\"";
1766
        // Can't escape WHERE clause
1767
        if ($where != '') {
1768
            $sql .= " WHERE {$where}";
1769
        }
1770
1771
        $sql .= ' DO';
1772
        if ($instead) {
1773
            $sql .= ' INSTEAD';
1774
        }
1775
1776
        if ($type == 'NOTHING') {
1777
            $sql .= ' NOTHING';
1778
        } else {
1779
            $sql .= " ({$action})";
1780
        }
1781
1782
        return $this->execute($sql);
1783
    }
1784
1785
    /**
1786
     * Removes a rule from a table OR view.
1787
     *
1788
     * @param string $rule     The rule to drop
1789
     * @param string $relation The relation from which to drop
1790
     * @param string $cascade  True to cascade drop, false to restrict
1791
     *
1792
     * @return int 0 if operation was successful
1793
     */
1794
    public function dropRule($rule, $relation, $cascade)
1795
    {
1796
        $f_schema = $this->_schema;
1797
        $this->fieldClean($f_schema);
1798
        $this->fieldClean($rule);
1799
        $this->fieldClean($relation);
1800
1801
        $sql = "DROP RULE \"{$rule}\" ON \"{$f_schema}\".\"{$relation}\"";
1802
        if ($cascade) {
1803
            $sql .= ' CASCADE';
1804
        }
1805
1806
        return $this->execute($sql);
1807
    }
1808
1809
    /**
1810
     * Grabs a single trigger.
1811
     *
1812
     * @param string $table   The name of a table whose triggers to retrieve
1813
     * @param string $trigger The name of the trigger to retrieve
1814
     *
1815
     * @return \PHPPgAdmin\ADORecordSet A recordset
1816
     */
1817
    public function getTrigger($table, $trigger)
1818
    {
1819
        $c_schema = $this->_schema;
1820
        $this->clean($c_schema);
1821
        $this->clean($table);
1822
        $this->clean($trigger);
1823
1824
        $sql = "
1825
			SELECT * FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c
1826
			WHERE t.tgrelid=c.oid AND c.relname='{$table}' AND t.tgname='{$trigger}'
1827
				AND c.relnamespace=(
1828
					SELECT oid FROM pg_catalog.pg_namespace
1829
					WHERE nspname='{$c_schema}')";
1830
1831
        return $this->selectSet($sql);
1832
    }
1833
1834
    /**
1835
     * A helper function for getTriggers that translates
1836
     * an array of attribute numbers to an array of field names.
1837
     * Note: Only needed for pre-7.4 servers, this function is deprecated.
1838
     *
1839
     * @param string $trigger An array containing fields from the trigger table
1840
     *
1841
     * @return string The trigger definition string
1842
     */
1843
    public function getTriggerDef($trigger)
1844
    {
1845
        $this->fieldArrayClean($trigger);
1846
        // Constants to figure out tgtype
1847
        if (!defined('TRIGGER_TYPE_ROW')) {
1848
            define('TRIGGER_TYPE_ROW', 1 << 0);
1849
        }
1850
1851
        if (!defined('TRIGGER_TYPE_BEFORE')) {
1852
            define('TRIGGER_TYPE_BEFORE', 1 << 1);
1853
        }
1854
1855
        if (!defined('TRIGGER_TYPE_INSERT')) {
1856
            define('TRIGGER_TYPE_INSERT', 1 << 2);
1857
        }
1858
1859
        if (!defined('TRIGGER_TYPE_DELETE')) {
1860
            define('TRIGGER_TYPE_DELETE', 1 << 3);
1861
        }
1862
1863
        if (!defined('TRIGGER_TYPE_UPDATE')) {
1864
            define('TRIGGER_TYPE_UPDATE', 1 << 4);
1865
        }
1866
1867
        $trigger['tgisconstraint'] = $this->phpBool($trigger['tgisconstraint']);
1868
        $trigger['tgdeferrable']   = $this->phpBool($trigger['tgdeferrable']);
1869
        $trigger['tginitdeferred'] = $this->phpBool($trigger['tginitdeferred']);
1870
1871
        // Constraint trigger or normal trigger
1872
        if ($trigger['tgisconstraint']) {
1873
            $tgdef = 'CREATE CONSTRAINT TRIGGER ';
1874
        } else {
1875
            $tgdef = 'CREATE TRIGGER ';
1876
        }
1877
1878
        $tgdef .= "\"{$trigger['tgname']}\" ";
1879
1880
        // Trigger type
1881
        $findx = 0;
1882
        if (($trigger['tgtype'] & TRIGGER_TYPE_BEFORE) == TRIGGER_TYPE_BEFORE) {
1883
            $tgdef .= 'BEFORE';
1884
        } else {
1885
            $tgdef .= 'AFTER';
1886
        }
1887
1888
        if (($trigger['tgtype'] & TRIGGER_TYPE_INSERT) == TRIGGER_TYPE_INSERT) {
1889
            $tgdef .= ' INSERT';
1890
            ++$findx;
1891
        }
1892
        if (($trigger['tgtype'] & TRIGGER_TYPE_DELETE) == TRIGGER_TYPE_DELETE) {
1893
            if ($findx > 0) {
1894
                $tgdef .= ' OR DELETE';
1895
            } else {
1896
                $tgdef .= ' DELETE';
1897
                ++$findx;
1898
            }
1899
        }
1900
        if (($trigger['tgtype'] & TRIGGER_TYPE_UPDATE) == TRIGGER_TYPE_UPDATE) {
1901
            if ($findx > 0) {
1902
                $tgdef .= ' OR UPDATE';
1903
            } else {
1904
                $tgdef .= ' UPDATE';
1905
            }
1906
        }
1907
1908
        $f_schema = $this->_schema;
1909
        $this->fieldClean($f_schema);
1910
        // Table name
1911
        $tgdef .= " ON \"{$f_schema}\".\"{$trigger['relname']}\" ";
1912
1913
        // Deferrability
1914
        if ($trigger['tgisconstraint']) {
1915
            if ($trigger['tgconstrrelid'] != 0) {
1916
                // Assume constrelname is not null
1917
                $tgdef .= " FROM \"{$trigger['tgconstrrelname']}\" ";
1918
            }
1919
            if (!$trigger['tgdeferrable']) {
1920
                $tgdef .= 'NOT ';
1921
            }
1922
1923
            $tgdef .= 'DEFERRABLE INITIALLY ';
1924
            if ($trigger['tginitdeferred']) {
1925
                $tgdef .= 'DEFERRED ';
1926
            } else {
1927
                $tgdef .= 'IMMEDIATE ';
1928
            }
1929
        }
1930
1931
        // Row or statement
1932
        if ($trigger['tgtype'] & TRIGGER_TYPE_ROW == TRIGGER_TYPE_ROW) {
1933
            $tgdef .= 'FOR EACH ROW ';
1934
        } else {
1935
            $tgdef .= 'FOR EACH STATEMENT ';
1936
        }
1937
1938
        // Execute procedure
1939
        $tgdef .= "EXECUTE PROCEDURE \"{$trigger['tgfname']}\"(";
1940
1941
        // Parameters
1942
        // Escape null characters
1943
        $v = addcslashes($trigger['tgargs'], "\0");
1944
        // Split on escaped null characters
1945
        $params = explode('\\000', $v);
1946
        for ($findx = 0; $findx < $trigger['tgnargs']; ++$findx) {
1947
            $param = "'" . str_replace('\'', '\\\'', $params[$findx]) . "'";
1948
            $tgdef .= $param;
1949
            if ($findx < ($trigger['tgnargs'] - 1)) {
1950
                $tgdef .= ', ';
1951
            }
1952
        }
1953
1954
        // Finish it off
1955
        $tgdef .= ')';
1956
1957
        return $tgdef;
1958
    }
1959
1960
    /**
1961
     * Returns a list of all functions that can be used in triggers.
1962
     *
1963
     * @return \PHPPgAdmin\ADORecordSet All functions that can be used in triggers.
1964
     */
1965
    public function getTriggerFunctions()
1966
    {
1967
        return $this->getFunctions(true, 'trigger');
1968
    }
1969
1970
    /**
1971
     * Returns a list of all functions in the database.
1972
     *
1973
     * @param bool $all  If true, will find all available functions, if false just those in search path
1974
     * @param string|null  $type If not null, will find all functions with return value = type
1975
     *
1976
     * @return \PHPPgAdmin\ADORecordSet All functions
1977
     */
1978
    public function getFunctions($all = false, $type = null)
1979
    {
1980
        if ($all) {
1981
            $where    = 'pg_catalog.pg_function_is_visible(p.oid)';
1982
            $distinct = 'DISTINCT ON (p.proname)';
1983
1984
            if ($type) {
1985
                $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') ";
1986
            }
1987
        } else {
1988
            $c_schema = $this->_schema;
1989
            $this->clean($c_schema);
1990
            $where    = "n.nspname = '{$c_schema}'";
1991
            $distinct = '';
1992
        }
1993
1994
        $sql = "
1995
			SELECT
1996
				{$distinct}
1997
				p.oid AS prooid,
1998
				p.proname,
1999
				p.proretset,
2000
				pg_catalog.format_type(p.prorettype, NULL) AS proresult,
2001
				pg_catalog.oidvectortypes(p.proargtypes) AS proarguments,
2002
				pl.lanname AS prolanguage,
2003
				pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment,
2004
				p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
2005
				CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns,
2006
				coalesce(u.usename::text,p.proowner::text) AS proowner
2007
2008
			FROM pg_catalog.pg_proc p
2009
				INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
2010
				INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang
2011
				LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner
2012
			WHERE NOT p.proisagg
2013
				AND {$where}
2014
			ORDER BY p.proname, proresult
2015
			";
2016
2017
        return $this->selectSet($sql);
2018
    }
2019
2020
    /**
2021
     * Creates a trigger.
2022
     *
2023
     * @param string $tgname  The name of the trigger to create
2024
     * @param string $table   The name of the table
2025
     * @param string $tgproc  The function to execute
2026
     * @param string $tgtime  BEFORE or AFTER
2027
     * @param string $tgevent Event
2028
     * @param string $tgfrequency
2029
     * @param string $tgargs  The function arguments
2030
     *
2031
     * @return int 0 if operation was successful
2032
     */
2033
    public function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs)
2034
    {
2035
        $f_schema = $this->_schema;
2036
        $this->fieldClean($f_schema);
2037
        $this->fieldClean($tgname);
2038
        $this->fieldClean($table);
2039
        $this->fieldClean($tgproc);
2040
2041
        /* No Statement Level Triggers in PostgreSQL (by now) */
2042
        $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime}
2043
				{$tgevent} ON \"{$f_schema}\".\"{$table}\"
2044
				FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})";
2045
2046
        return $this->execute($sql);
2047
    }
2048
2049
    /**
2050
     * Alters a trigger.
2051
     *
2052
     * @param string $table   The name of the table containing the trigger
2053
     * @param string $trigger The name of the trigger to alter
2054
     * @param string $name    The new name for the trigger
2055
     *
2056
     * @return int 0 if operation was successful
2057
     */
2058
    public function alterTrigger($table, $trigger, $name)
2059
    {
2060
        $f_schema = $this->_schema;
2061
        $this->fieldClean($f_schema);
2062
        $this->fieldClean($table);
2063
        $this->fieldClean($trigger);
2064
        $this->fieldClean($name);
2065
2066
        $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_schema}\".\"{$table}\" RENAME TO \"{$name}\"";
2067
2068
        return $this->execute($sql);
2069
    }
2070
2071
    /**
2072
     * Drops a trigger.
2073
     *
2074
     * @param string $tgname  The name of the trigger to drop
2075
     * @param string $table   The table from which to drop the trigger
2076
     * @param bool $cascade True to cascade drop, false to restrict
2077
     *
2078
     * @return int 0 if operation was successful
2079
     */
2080
    public function dropTrigger($tgname, $table, $cascade)
2081
    {
2082
        $f_schema = $this->_schema;
2083
        $this->fieldClean($f_schema);
2084
        $this->fieldClean($tgname);
2085
        $this->fieldClean($table);
2086
2087
        $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\"";
2088
        if ($cascade) {
2089
            $sql .= ' CASCADE';
2090
        }
2091
2092
        return $this->execute($sql);
2093
    }
2094
2095
    /**
2096
     * Enables a trigger.
2097
     *
2098
     * @param string $tgname The name of the trigger to enable
2099
     * @param string $table  The table in which to enable the trigger
2100
     *
2101
     * @return int 0 if operation was successful
2102
     */
2103
    public function enableTrigger($tgname, $table)
2104
    {
2105
        $f_schema = $this->_schema;
2106
        $this->fieldClean($f_schema);
2107
        $this->fieldClean($tgname);
2108
        $this->fieldClean($table);
2109
2110
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\"";
2111
2112
        return $this->execute($sql);
2113
    }
2114
2115
    /**
2116
     * Disables a trigger.
2117
     *
2118
     * @param string $tgname The name of the trigger to disable
2119
     * @param string $table  The table in which to disable the trigger
2120
     *
2121
     * @return int 0 if operation was successful
2122
     */
2123
    public function disableTrigger($tgname, $table)
2124
    {
2125
        $f_schema = $this->_schema;
2126
        $this->fieldClean($f_schema);
2127
        $this->fieldClean($tgname);
2128
        $this->fieldClean($table);
2129
2130
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\"";
2131
2132
        return $this->execute($sql);
2133
    }
2134
2135
    /**
2136
     * Returns a list of all operators in the database.
2137
     *
2138
     * @return \PHPPgAdmin\ADORecordSet All operators
2139
     */
2140
    public function getOperators()
2141
    {
2142
        $c_schema = $this->_schema;
2143
        $this->clean($c_schema);
2144
        // We stick with the subselects here, as you cannot ORDER BY a regtype
2145
        $sql = "
2146
			SELECT
2147
            	po.oid,	po.oprname,
2148
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname,
2149
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname,
2150
				po.oprresult::pg_catalog.regtype AS resultname,
2151
		        pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment
2152
			FROM
2153
				pg_catalog.pg_operator po
2154
			WHERE
2155
				po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')
2156
			ORDER BY
2157
				po.oprname, oprleftname, oprrightname
2158
		";
2159
2160
        return $this->selectSet($sql);
2161
    }
2162
2163
    /**
2164
     * Drops an operator.
2165
     *
2166
     * @param mixed $operator_oid The OID of the operator to drop
2167
     * @param bool $cascade      True to cascade drop, false to restrict
2168
     *
2169
     * @return int 0 if operation was successful
2170
     */
2171
    public function dropOperator($operator_oid, $cascade)
2172
    {
2173
        // Function comes in with $object as operator OID
2174
        $opr      = $this->getOperator($operator_oid);
2175
        $f_schema = $this->_schema;
2176
        $this->fieldClean($f_schema);
2177
        $this->fieldClean($opr->fields['oprname']);
2178
2179
        $sql = "DROP OPERATOR \"{$f_schema}\".{$opr->fields['oprname']} (";
2180
        // Quoting or formatting here???
2181
        if ($opr->fields['oprleftname'] !== null) {
2182
            $sql .= $opr->fields['oprleftname'] . ', ';
2183
        } else {
2184
            $sql .= 'NONE, ';
2185
        }
2186
2187
        if ($opr->fields['oprrightname'] !== null) {
2188
            $sql .= $opr->fields['oprrightname'] . ')';
2189
        } else {
2190
            $sql .= 'NONE)';
2191
        }
2192
2193
        if ($cascade) {
2194
            $sql .= ' CASCADE';
2195
        }
2196
2197
        return $this->execute($sql);
2198
    }
2199
2200
    /**
2201
     * Returns all details for a particular operator.
2202
     *
2203
     * @param mixed $operator_oid The oid of the operator
2204
     *
2205
     * @return \PHPPgAdmin\ADORecordSet Function info
2206
     */
2207
    public function getOperator($operator_oid)
2208
    {
2209
        $this->clean($operator_oid);
2210
2211
        $sql = "
2212
			SELECT
2213
            	po.oid, po.oprname,
2214
				oprleft::pg_catalog.regtype AS oprleftname,
2215
				oprright::pg_catalog.regtype AS oprrightname,
2216
				oprresult::pg_catalog.regtype AS resultname,
2217
				po.oprcanhash,
2218
				oprcanmerge,
2219
				oprcom::pg_catalog.regoperator AS oprcom,
2220
				oprnegate::pg_catalog.regoperator AS oprnegate,
2221
				po.oprcode::pg_catalog.regproc AS oprcode,
2222
				po.oprrest::pg_catalog.regproc AS oprrest,
2223
				po.oprjoin::pg_catalog.regproc AS oprjoin
2224
			FROM
2225
				pg_catalog.pg_operator po
2226
			WHERE
2227
				po.oid='{$operator_oid}'
2228
		";
2229
2230
        return $this->selectSet($sql);
2231
    }
2232
2233
    /**
2234
     *  Gets all opclasses.
2235
     *
2236
     * @return \PHPPgAdmin\ADORecordSet A recordset
2237
     */
2238
    public function getOpClasses()
2239
    {
2240
        $c_schema = $this->_schema;
2241
        $this->clean($c_schema);
2242
        $sql = "
2243
			SELECT
2244
				pa.amname, po.opcname,
2245
				po.opcintype::pg_catalog.regtype AS opcintype,
2246
				po.opcdefault,
2247
				pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment
2248
			FROM
2249
				pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn
2250
			WHERE
2251
				po.opcmethod=pa.oid
2252
				AND po.opcnamespace=pn.oid
2253
				AND pn.nspname='{$c_schema}'
2254
			ORDER BY 1,2
2255
			";
2256
2257
        return $this->selectSet($sql);
2258
    }
2259
2260
    /**
2261
     * Gets all languages.
2262
     *
2263
     * @param bool $all True to get all languages, regardless of show_system
2264
     *
2265
     * @return \PHPPgAdmin\ADORecordSet A recordset
2266
     */
2267
    public function getLanguages($all = false)
2268
    {
2269
        $conf = $this->conf;
2270
2271
        if ($conf['show_system'] || $all) {
2272
            $where = '';
2273
        } else {
2274
            $where = 'WHERE lanispl';
2275
        }
2276
2277
        $sql = "
2278
			SELECT
2279
				lanname, lanpltrusted,
2280
				lanplcallfoid::pg_catalog.regproc AS lanplcallf
2281
			FROM
2282
				pg_catalog.pg_language
2283
			{$where}
2284
			ORDER BY lanname
2285
		";
2286
2287
        return $this->selectSet($sql);
2288
    }
2289
2290
    /**
2291
     * Retrieves information for all tablespaces.
2292
     *
2293
     * @param bool $all Include all tablespaces (necessary when moving objects back to the default space)
2294
     *
2295
     * @return \PHPPgAdmin\ADORecordSet A recordset
2296
     */
2297
    public function getTablespaces($all = false)
2298
    {
2299
        $conf = $this->conf;
2300
2301
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2302
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2303
					FROM pg_catalog.pg_tablespace";
2304
2305
        if (!$conf['show_system'] && !$all) {
2306
            $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$';
2307
        }
2308
2309
        $sql .= ' ORDER BY spcname';
2310
2311
        return $this->selectSet($sql);
2312
    }
2313
2314
    // Misc functions
2315
2316
    /**
2317
     * Retrieves a tablespace's information.
2318
     *
2319
     * @param string $spcname
2320
     *
2321
     * @return \PHPPgAdmin\ADORecordSet A recordset
2322
     */
2323
    public function getTablespace($spcname)
2324
    {
2325
        $this->clean($spcname);
2326
2327
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2328
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2329
					FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'";
2330
2331
        return $this->selectSet($sql);
2332
    }
2333
2334
    /**
2335
     * Creates a tablespace.
2336
     *
2337
     * @param string $spcname  The name of the tablespace to create
2338
     * @param string $spcowner The owner of the tablespace. '' for current
2339
     * @param string $spcloc   The directory in which to create the tablespace
2340
     * @param string $comment
2341
     *
2342
     * @return int 0 success
2343
     */
2344
    public function createTablespace($spcname, $spcowner, $spcloc, $comment = '')
2345
    {
2346
        $this->fieldClean($spcname);
2347
        $this->clean($spcloc);
2348
2349
        $sql = "CREATE TABLESPACE \"{$spcname}\"";
2350
2351
        if ($spcowner != '') {
2352
            $this->fieldClean($spcowner);
2353
            $sql .= " OWNER \"{$spcowner}\"";
2354
        }
2355
2356
        $sql .= " LOCATION '{$spcloc}'";
2357
2358
        $status = $this->execute($sql);
2359
        if ($status != 0) {
2360
            return -1;
2361
        }
2362
2363
        if ($comment != '' && $this->hasSharedComments()) {
2364
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2365
            if ($status != 0) {
2366
                return -2;
2367
            }
2368
        }
2369
2370
        return 0;
2371
    }
2372
2373
    /**
2374
     * Alters a tablespace.
2375
     *
2376
     * @param string $spcname The name of the tablespace
2377
     * @param string $name    The new name for the tablespace
2378
     * @param string $owner   The new owner for the tablespace
2379
     * @param string $comment
2380
     *
2381
     * @return bool|int 0 success
2382
     */
2383
    public function alterTablespace($spcname, $name, $owner, $comment = '')
2384
    {
2385
        $this->fieldClean($spcname);
2386
        $this->fieldClean($name);
2387
        $this->fieldClean($owner);
2388
2389
        // Begin transaction
2390
        $status = $this->beginTransaction();
2391
        if ($status != 0) {
2392
            return -1;
2393
        }
2394
2395
        // Owner
2396
        $sql    = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\"";
2397
        $status = $this->execute($sql);
2398
        if ($status != 0) {
2399
            $this->rollbackTransaction();
2400
2401
            return -2;
2402
        }
2403
2404
        // Rename (only if name has changed)
2405
        if ($name != $spcname) {
2406
            $sql    = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\"";
2407
            $status = $this->execute($sql);
2408
            if ($status != 0) {
2409
                $this->rollbackTransaction();
2410
2411
                return -3;
2412
            }
2413
2414
            $spcname = $name;
2415
        }
2416
2417
        // Set comment if it has changed
2418
        if (trim($comment) != '' && $this->hasSharedComments()) {
2419
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2420
            if ($status != 0) {
2421
                return -4;
2422
            }
2423
        }
2424
2425
        return $this->endTransaction();
2426
    }
2427
2428
    /**
2429
     * Drops a tablespace.
2430
     *
2431
     * @param string $spcname The name of the domain to drop
2432
     *
2433
     * @return int 0 if operation was successful
2434
     */
2435
    public function dropTablespace($spcname)
2436
    {
2437
        $this->fieldClean($spcname);
2438
2439
        $sql = "DROP TABLESPACE \"{$spcname}\"";
2440
2441
        return $this->execute($sql);
2442
    }
2443
2444
    /**
2445
     * Analyze a database.
2446
     *
2447
     * @param string $table (optional) The table to analyze
2448
     *
2449
     * @return bool 0 if successful
2450
     */
2451
    public function analyzeDB($table = '')
2452
    {
2453
        if ($table != '') {
2454
            $f_schema = $this->_schema;
2455
            $this->fieldClean($f_schema);
2456
            $this->fieldClean($table);
2457
2458
            $sql = "ANALYZE \"{$f_schema}\".\"{$table}\"";
2459
        } else {
2460
            $sql = 'ANALYZE';
2461
        }
2462
2463
        return $this->execute($sql);
2464
    }
2465
2466
    /**
2467
     * Vacuums a database.
2468
     *
2469
     * @param string $table   The table to vacuum
2470
     * @param bool   $analyze If true, also does analyze
2471
     * @param bool   $full    If true, selects "full" vacuum
2472
     * @param bool   $freeze  If true, selects aggressive "freezing" of tuples
2473
     *
2474
     * @return bool 0 if successful
2475
     */
2476
    public function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false)
2477
    {
2478
        $sql = 'VACUUM';
2479
        if ($full) {
2480
            $sql .= ' FULL';
2481
        }
2482
2483
        if ($freeze) {
2484
            $sql .= ' FREEZE';
2485
        }
2486
2487
        if ($analyze) {
2488
            $sql .= ' ANALYZE';
2489
        }
2490
2491
        if ($table != '') {
2492
            $f_schema = $this->_schema;
2493
            $this->fieldClean($f_schema);
2494
            $this->fieldClean($table);
2495
            $sql .= " \"{$f_schema}\".\"{$table}\"";
2496
        }
2497
2498
        return $this->execute($sql);
2499
    }
2500
2501
    /**
2502
     * Returns all autovacuum global configuration.
2503
     *
2504
     * @return array associative array array( param => value, ...)
2505
     */
2506
    public function getAutovacuum()
2507
    {
2508
        $_defaults = $this->selectSet(
2509
            "SELECT name, setting
2510
			FROM pg_catalog.pg_settings
2511
			WHERE
2512
				name = 'autovacuum'
2513
				OR name = 'autovacuum_vacuum_threshold'
2514
				OR name = 'autovacuum_vacuum_scale_factor'
2515
				OR name = 'autovacuum_analyze_threshold'
2516
				OR name = 'autovacuum_analyze_scale_factor'
2517
				OR name = 'autovacuum_vacuum_cost_delay'
2518
				OR name = 'autovacuum_vacuum_cost_limit'
2519
				OR name = 'vacuum_freeze_min_age'
2520
				OR name = 'autovacuum_freeze_max_age'
2521
			"
2522
        );
2523
2524
        $ret = [];
2525
        while (!$_defaults->EOF) {
2526
            $ret[$_defaults->fields['name']] = $_defaults->fields['setting'];
2527
            $_defaults->moveNext();
2528
        }
2529
2530
        return $ret;
2531
    }
2532
2533
    /**
2534
     * Returns all available autovacuum per table information.
2535
     *
2536
     * @param string  $table table name
2537
     * @param bool    $vacenabled true if vacuum is enabled
2538
     * @param int     $vacthreshold vacuum threshold
2539
     * @param int     $vacscalefactor vacuum scalefactor
2540
     * @param int     $anathresold analyze threshold
2541
     * @param int     $anascalefactor analyze scale factor
2542
     * @param int     $vaccostdelay vacuum cost delay
2543
     * @param int     $vaccostlimit vacuum cost limit
2544
     *
2545
     * @return bool  0 if successful
2546
     */
2547
    public function saveAutovacuum(
2548
        $table,
2549
        $vacenabled,
2550
        $vacthreshold,
2551
        $vacscalefactor,
2552
        $anathresold,
2553
        $anascalefactor,
2554
        $vaccostdelay,
2555
        $vaccostlimit
2556
    ) {
2557
        $f_schema = $this->_schema;
2558
        $this->fieldClean($f_schema);
2559
        $this->fieldClean($table);
2560
2561
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
2562
2563
        if (!empty($vacenabled)) {
2564
            $this->clean($vacenabled);
2565
            $params[] = "autovacuum_enabled='{$vacenabled}'";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
2566
        }
2567
        if (!empty($vacthreshold)) {
2568
            $this->clean($vacthreshold);
2569
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
2570
        }
2571
        if (!empty($vacscalefactor)) {
2572
            $this->clean($vacscalefactor);
2573
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
2574
        }
2575
        if (!empty($anathresold)) {
2576
            $this->clean($anathresold);
2577
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
2578
        }
2579
        if (!empty($anascalefactor)) {
2580
            $this->clean($anascalefactor);
2581
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
2582
        }
2583
        if (!empty($vaccostdelay)) {
2584
            $this->clean($vaccostdelay);
2585
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
2586
        }
2587
        if (!empty($vaccostlimit)) {
2588
            $this->clean($vaccostlimit);
2589
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
2590
        }
2591
2592
        $sql = $sql . implode(',', $params) . ');';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $params does not seem to be defined for all execution paths leading up to this point.
Loading history...
2593
2594
        return $this->execute($sql);
2595
    }
2596
2597
    // Type conversion routines
2598
2599
    /**
2600
     * Drops autovacuum config for a table.
2601
     *
2602
     * @param string $table The table
2603
     *
2604
     * @return bool 0 if successful
2605
     */
2606
    public function dropAutovacuum($table)
2607
    {
2608
        $f_schema = $this->_schema;
2609
        $this->fieldClean($f_schema);
2610
        $this->fieldClean($table);
2611
2612
        return $this->execute(
2613
            "
2614
			ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
2615
				autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
2616
				autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
2617
			);"
2618
        );
2619
    }
2620
2621
    /**
2622
     * Returns all available process information.
2623
     *
2624
     * @param string|null $database (optional) Find only connections to specified database
2625
     *
2626
     * @return \PHPPgAdmin\ADORecordSet A recordset
2627
     */
2628
    public function getProcesses($database = null)
2629
    {
2630
        if ($database === null) {
2631
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
2632
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
2633
				FROM pg_catalog.pg_stat_activity
2634
				ORDER BY datname, usename, pid";
2635
        } else {
2636
            $this->clean($database);
2637
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
2638
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
2639
				FROM pg_catalog.pg_stat_activity
2640
				WHERE datname='{$database}'
2641
				ORDER BY usename, pid";
2642
        }
2643
2644
        return $this->selectSet($sql);
2645
    }
2646
2647
    // interfaces Statistics collector functions
2648
2649
    /**
2650
     * Returns table locks information in the current database.
2651
     *
2652
     * @return \PHPPgAdmin\ADORecordSet A recordset
2653
     */
2654
    public function getLocks()
2655
    {
2656
        $conf = $this->conf;
2657
2658
        if (!$conf['show_system']) {
2659
            $where = 'AND pn.nspname NOT LIKE $$pg\_%$$';
2660
        } else {
2661
            $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
2662
        }
2663
2664
        $sql = "
2665
			SELECT
2666
				pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction,
2667
				(select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid'
2668
					and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction
2669
			FROM
2670
				pg_catalog.pg_locks pl,
2671
				pg_catalog.pg_class pc,
2672
				pg_catalog.pg_namespace pn
2673
			WHERE
2674
				pl.relation = pc.oid AND pc.relnamespace=pn.oid
2675
			{$where}
2676
			ORDER BY pid,nspname,tablename";
2677
2678
        return $this->selectSet($sql);
2679
    }
2680
2681
    /**
2682
     * Sends a cancel or kill command to a process.
2683
     *
2684
     * @param int $pid    The ID of the backend process
2685
     * @param string $signal 'CANCEL' or 'KILL'
2686
     *
2687
     * @return int 0 success
2688
     */
2689
    public function sendSignal($pid, $signal)
2690
    {
2691
        // Clean
2692
        $pid = (int) $pid;
2693
2694
        if ($signal == 'CANCEL') {
2695
            $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val";
2696
        } elseif ($signal == 'KILL') {
2697
            $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val";
2698
        } else {
2699
            return -1;
2700
        }
2701
2702
        // Execute the query
2703
        $val = $this->selectField($sql, 'val');
2704
2705
        if ($val === 'f') {
2706
            return -1;
2707
        }
2708
2709
        if ($val === 't') {
2710
            return 0;
2711
        }
2712
2713
        return -1;
2714
    }
2715
2716
    /**
2717
     * Executes an SQL script as a series of SQL statements.  Returns
2718
     * the result of the final step.  This is a very complicated lexer
2719
     * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in
2720
     * the PostgreSQL source code.
2721
     * XXX: It does not handle multibyte languages properly.
2722
     *
2723
     * @param string        $name     Entry in $_FILES to use
2724
     * @param null|function $callback (optional) Callback function to call with each query, its result and line number
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\function was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2725
     *
2726
     * @return bool true for general success, false on any failure
2727
     */
2728
    public function executeScript($name, $callback = null)
2729
    {
2730
        // This whole function isn't very encapsulated, but hey...
2731
        $conn = $this->conn->_connectionID;
2732
        if (!is_uploaded_file($_FILES[$name]['tmp_name'])) {
2733
            return false;
2734
        }
2735
2736
        $fd = fopen($_FILES[$name]['tmp_name'], 'rb');
2737
        if (!$fd) {
0 ignored issues
show
introduced by
$fd is of type resource|false, thus it always evaluated to false.
Loading history...
2738
            return false;
2739
        }
2740
2741
        // Build up each SQL statement, they can be multiline
2742
        $query_buf    = null;
2743
        $query_start  = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $query_start is dead and can be removed.
Loading history...
2744
        $in_quote     = 0;
2745
        $in_xcomment  = 0;
2746
        $bslash_count = 0;
2747
        $dol_quote    = null;
2748
        $paren_level  = 0;
2749
        $len          = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $len is dead and can be removed.
Loading history...
2750
        $i            = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $i is dead and can be removed.
Loading history...
2751
        $prevlen      = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $prevlen is dead and can be removed.
Loading history...
2752
        $thislen      = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $thislen is dead and can be removed.
Loading history...
2753
        $lineno       = 0;
2754
2755
        // Loop over each line in the file
2756
        while (!feof($fd)) {
2757
            $line = fgets($fd);
2758
            ++$lineno;
2759
2760
            // Nothing left on line? Then ignore...
2761
            if (trim($line) == '') {
2762
                continue;
2763
            }
2764
2765
            $len         = strlen($line);
2766
            $query_start = 0;
2767
2768
            /**
2769
             * Parse line, looking for command separators.
2770
             *
2771
             * The current character is at line[i], the prior character at line[i
2772
             * - prevlen], the next character at line[i + thislen].
2773
             */
2774
            $prevlen = 0;
2775
            $thislen = ($len > 0) ? 1 : 0;
2776
2777
            for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) {
2778
                /* was the previous character a backslash? */
2779
                if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') {
2780
                    ++$bslash_count;
2781
                } else {
2782
                    $bslash_count = 0;
2783
                }
2784
2785
                /*
2786
                 * It is important to place the in_* test routines before the
2787
                 * in_* detection routines. i.e. we have to test if we are in
2788
                 * a quote before testing for comments.
2789
                 */
2790
2791
                /* in quote? */
2792
                if ($in_quote !== 0) {
2793
                    /*
2794
                     * end of quote if matching non-backslashed character.
2795
                     * backslashes don't count for double quotes, though.
2796
                     */
2797
                    if (substr($line, $i, 1) == $in_quote &&
2798
                        ($bslash_count % 2 == 0 || $in_quote == '"')) {
2 ignored issues
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
2799
                        $in_quote = 0;
2800
                    }
2801
                } else {
2802
                    if ($dol_quote) {
2803
                        if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) {
2804
                            $this->advance_1($i, $prevlen, $thislen);
2805
                            while (substr($line, $i, 1) != '$') {
2806
                                $this->advance_1($i, $prevlen, $thislen);
2807
                            }
2808
2809
                            $dol_quote = null;
2810
                        }
2811
                    } else {
2812
                        if (substr($line, $i, 2) == '/*') {
2813
                            ++$in_xcomment;
2814
                            if ($in_xcomment == 1) {
2815
                                $this->advance_1($i, $prevlen, $thislen);
2816
                            }
2817
                        } else {
2818
                            if ($in_xcomment) {
2819
                                if (substr($line, $i, 2) == '*/' && !--$in_xcomment) {
2820
                                    $this->advance_1($i, $prevlen, $thislen);
2821
                                }
2822
                            } else {
2823
                                if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
2824
                                    $in_quote = substr($line, $i, 1);
2825
                                } /*
2826
                                 * start of $foo$ type quote?
2827
                                 */
2828
                                else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /*\n * start of $foo$ type quote?\n */\n else {\n"
Loading history...
2829
                                    if (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
2830
                                        $dol_end   = strpos(substr($line, $i + 1), '$');
2831
                                        $dol_quote = substr($line, $i, $dol_end + 1);
2832
                                        $this->advance_1($i, $prevlen, $thislen);
2833
                                        while (substr($line, $i, 1) != '$') {
2834
                                            $this->advance_1($i, $prevlen, $thislen);
2835
                                        }
2836
                                    } else {
2837
                                        if (substr($line, $i, 2) == '--') {
2838
                                            $line = substr($line, 0, $i); /* remove comment */
2839
                                            break;
2840
                                        } /* count nested parentheses */
2841
2842
                                        if (substr($line, $i, 1) == '(') {
2843
                                            ++$paren_level;
2844
                                        } else {
2845
                                            if (substr($line, $i, 1) == ')' && $paren_level > 0) {
2846
                                                --$paren_level;
2847
                                            } else {
2848
                                                if (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) {
2849
                                                    $subline = substr(substr($line, 0, $i), $query_start);
2850
                                                    /*
2851
                                                     * insert a cosmetic newline, if this is not the first
2852
                                                     * line in the buffer
2853
                                                     */
2854
                                                    if (strlen($query_buf) > 0) {
2855
                                                        $query_buf .= "\n";
2856
                                                    }
2857
2858
                                                    /* append the line to the query buffer */
2859
                                                    $query_buf .= $subline;
2860
                                                    /* is there anything in the query_buf? */
2861
                                                    if (trim($query_buf)) {
2862
                                                        $query_buf .= ';';
2863
2864
                                                        // Execute the query. PHP cannot execute
2865
                                                        // empty queries, unlike libpq
2866
                                                        $res = @pg_query($conn, $query_buf);
2867
2868
                                                        // Call the callback function for display
2869
                                                        if ($callback !== null) {
2870
                                                            $callback($query_buf, $res, $lineno);
2871
                                                        }
2872
2873
                                                        // Check for COPY request
2874
                                                        if (pg_result_status($res) == 4) {
2875
                                                            // 4 == PGSQL_COPY_FROM
2876
                                                            while (!feof($fd)) {
2877
                                                                $copy = fgets($fd, 32768);
2878
                                                                ++$lineno;
2879
                                                                pg_put_line($conn, $copy);
2880
                                                                if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2881
                                                                    pg_end_copy($conn);
2882
2883
                                                                    break;
2884
                                                                }
2885
                                                            }
2886
                                                        }
2887
                                                    }
2888
                                                    $query_buf   = null;
2889
                                                    $query_start = $i + $thislen;
2890
                                                }
2891
2892
                                                /*
2893
                                                 * keyword or identifier?
2894
                                                 * We grab the whole string so that we don't
2895
                                                 * mistakenly see $foo$ inside an identifier as the start
2896
                                                 * of a dollar quote.
2897
                                                 */
2898
                                                // XXX: multibyte here
2899
                                                else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found "\n\n /*\n * keyword or identifier?\n * We grab the whole string so that we don't\n * mistakenly see $foo$ inside an identifier as the start\n * of a dollar quote.\n */\n // XXX: multibyte here\n else {\n"
Loading history...
2900
                                                    if (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) {
2901
                                                        $sub = substr($line, $i, $thislen);
2902
                                                        while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) {
2903
                                                            /* keep going while we still have identifier chars */
2904
                                                            $this->advance_1($i, $prevlen, $thislen);
2905
                                                            $sub = substr($line, $i, $thislen);
2906
                                                        }
2907
                                                        // Since we're now over the next character to be examined, it is necessary
2908
                                                        // to move back one space.
2909
                                                        $i -= $prevlen;
2910
                                                    }
2911
                                                }
2912
                                            }
2913
                                        }
2914
                                    }
2915
                                }
2916
                            }
2917
                        }
2918
                    }
2919
                }
2920
            } // end for
2921
2922
            /* Put the rest of the line in the query buffer. */
2923
            $subline = substr($line, $query_start);
2924
            if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) {
2925
                if (strlen($query_buf) > 0) {
2926
                    $query_buf .= "\n";
2927
                }
2928
2929
                $query_buf .= $subline;
2930
            }
2931
2932
            $line = null;
1 ignored issue
show
Unused Code introduced by
The assignment to $line is dead and can be removed.
Loading history...
2933
        } // end while
2934
2935
        /*
2936
         * Process query at the end of file without a semicolon, so long as
2937
         * it's non-empty.
2938
         */
2939
        if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) {
2940
            // Execute the query
2941
            $res = @pg_query($conn, $query_buf);
2942
2943
            // Call the callback function for display
2944
            if ($callback !== null) {
2945
                $callback($query_buf, $res, $lineno);
2946
            }
2947
2948
            // Check for COPY request
2949
            if (pg_result_status($res) == 4) {
2950
                // 4 == PGSQL_COPY_FROM
2951
                while (!feof($fd)) {
2952
                    $copy = fgets($fd, 32768);
2953
                    ++$lineno;
2954
                    pg_put_line($conn, $copy);
2955
                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2956
                        pg_end_copy($conn);
2957
2958
                        break;
2959
                    }
2960
                }
2961
            }
2962
        }
2963
2964
        fclose($fd);
2965
2966
        return true;
2967
    }
2968
2969
    /**
2970
     * A private helper method for executeScript that advances the
2971
     * character by 1.  In psql this is careful to take into account
2972
     * multibyte languages, but we don't at the moment, so this function
2973
     * is someone redundant, since it will always advance by 1.
2974
     *
2975
     * @param int &$i       The current character position in the line
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter &$i does not match actual variable name $i
Loading history...
2976
     * @param int &$prevlen Length of previous character (ie. 1)
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter &$prevlen does not match actual variable name $prevlen
Loading history...
2977
     * @param int &$thislen Length of current character (ie. 1)
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter &$thislen does not match actual variable name $thislen
Loading history...
2978
     */
2979
    private function advance_1(&$i, &$prevlen, &$thislen)
1 ignored issue
show
Coding Style introduced by
Private method name "Postgres::advance_1" must be prefixed with an underscore
Loading history...
2980
    {
2981
        $prevlen = $thislen;
2982
        $i += $thislen;
2983
        $thislen = 1;
2984
    }
2985
2986
    /**
2987
     * Private helper method to detect a valid $foo$ quote delimiter at
2988
     * the start of the parameter dquote.
2989
     *
2990
     * @param string $dquote
2991
     *
2992
     * @return true if valid, false otherwise
2993
     */
2994
    private function valid_dolquote($dquote)
1 ignored issue
show
Coding Style introduced by
Private method name "Postgres::valid_dolquote" must be prefixed with an underscore
Loading history...
2995
    {
2996
        // XXX: support multibyte
2997
        return preg_match('/^[$][$]/', $dquote) || preg_match('/^[$][_[:alpha:]][_[:alnum:]]*[$]/', $dquote);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('/^[$]...lnum:]]*[$]/', $dquote) returns the type boolean which is incompatible with the documented return type true.
Loading history...
2998
    }
2999
3000
    // Capabilities
3001
3002
    /**
3003
     * Returns a recordset of all columns in a query.  Supports paging.
3004
     *
3005
     * @param string $type       Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier,
3006
     *                           or 'SELECT" if it's a select query
3007
     * @param string $table      The base table of the query.  NULL for no table.
3008
     * @param string $query      The query that is being executed.  NULL for no query.
3009
     * @param string $sortkey    The column number to sort by, or '' or null for no sorting
3010
     * @param string $sortdir    The direction in which to sort the specified column ('asc' or 'desc')
3011
     * @param int    $page       The page of the relation to retrieve
3012
     * @param int    $page_size  The number of rows per page
3013
     * @param int    &$max_pages (return-by-ref) The max number of pages in the relation
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter &$max_pages does not match actual variable name $max_pages
Loading history...
3014
     *
3015
     * @return \PHPPgAdmin\ADORecordSet|int A  recordset on success or an int with error code
3016
     *          - -1 transaction error
3017
     *          - -2 counting error
3018
     *          - -3 page or page_size invalid
3019
     *          - -4 unknown type
3020
     *          - -5 failed setting transaction read only
3021
     */
3022
    public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages)
3023
    {
3024
        // Check that we're not going to divide by zero
3025
        if (!is_numeric($page_size) || $page_size != (int) $page_size || $page_size <= 0) {
0 ignored issues
show
introduced by
The condition is_numeric($page_size) is always true.
Loading history...
3026
            return -3;
3027
        }
3028
3029
        // If $type is TABLE, then generate the query
3030
        switch ($type) {
3031
            case 'TABLE':
3032
                if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
3033
                    $orderby = [$sortkey => $sortdir];
3034
                } else {
3035
                    $orderby = [];
3036
                }
3037
3038
                $query = $this->getSelectSQL($table, [], [], [], $orderby);
3039
3040
                break;
3041
            case 'QUERY':
3042
            case 'SELECT':
3043
                // Trim query
3044
                $query = trim($query);
3045
                // Trim off trailing semi-colon if there is one
3046
                if (substr($query, strlen($query) - 1, 1) == ';') {
3047
                    $query = substr($query, 0, strlen($query) - 1);
3048
                }
3049
3050
                break;
3051
            default:
3052
                return -4;
3053
        }
3054
3055
        // Generate count query
3056
        $count = "SELECT COUNT(*) AS total FROM (${query}) AS sub";
3057
3058
        // Open a transaction
3059
        $status = $this->beginTransaction();
3060
        if ($status != 0) {
3061
            return -1;
3062
        }
3063
3064
        // If backend supports read only queries, then specify read only mode
3065
        // to avoid side effects from repeating queries that do writes.
3066
        if ($this->hasReadOnlyQueries()) {
3067
            $status = $this->execute('SET TRANSACTION READ ONLY');
3068
            if ($status != 0) {
3069
                $this->rollbackTransaction();
3070
3071
                return -5;
3072
            }
3073
        }
3074
3075
        // Count the number of rows
3076
        $total = $this->browseQueryCount($query, $count);
3077
        if ($total < 0) {
3078
            $this->rollbackTransaction();
3079
3080
            return -2;
3081
        }
3082
3083
        // Calculate max pages
3084
        $max_pages = ceil($total / $page_size);
3085
3086
        // Check that page is less than or equal to max pages
3087
        if (!is_numeric($page) || $page != (int) $page || $page > $max_pages || $page < 1) {
0 ignored issues
show
introduced by
The condition is_numeric($page) is always true.
Loading history...
3088
            $this->rollbackTransaction();
3089
3090
            return -3;
3091
        }
3092
3093
        // Set fetch mode to NUM so that duplicate field names are properly returned
3094
        // for non-table queries.  Since the SELECT feature only allows selecting one
3095
        // table, duplicate fields shouldn't appear.
3096
        if ($type == 'QUERY') {
3097
            $this->conn->setFetchMode(ADODB_FETCH_NUM);
3098
        }
3099
3100
        // Figure out ORDER BY.  Sort key is always the column number (based from one)
3101
        // of the column to order by.  Only need to do this for non-TABLE queries
3102
        if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
3103
            $orderby = " ORDER BY {$sortkey}";
3104
            // Add sort order
3105
            if ($sortdir == 'desc') {
3106
                $orderby .= ' DESC';
3107
            } else {
3108
                $orderby .= ' ASC';
3109
            }
3110
        } else {
3111
            $orderby = '';
3112
        }
3113
3114
        // Actually retrieve the rows, with offset and limit
3115
        $rs     = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size);
3116
        $status = $this->endTransaction();
3117
        if ($status != 0) {
3118
            $this->rollbackTransaction();
3119
3120
            return -1;
3121
        }
3122
3123
        return $rs;
3124
    }
3125
3126
    /**
3127
     * Generates the SQL for the 'select' function.
3128
     *
3129
     * @param string $table   The table from which to select
3130
     * @param $show    An array of columns to show.  Empty array means all columns.
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\An was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
3131
     * @param $values  An array mapping columns to values
3132
     * @param $ops     An array of the operators to use
3133
     * @param $orderby (optional) An array of column numbers or names (one based)
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\optional was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
3134
     *                 mapped to sort direction (asc or desc or '' or null) to order by
3135
     *
3136
     * @return The SQL query
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
3137
     */
3138
    public function getSelectSQL($table, $show, $values, $ops, $orderby = [])
3139
    {
3140
        $this->fieldArrayClean($show);
3141
3142
        // If an empty array is passed in, then show all columns
3143
        if (sizeof($show) == 0) {
3144
            if ($this->hasObjectID($table)) {
3145
                $sql = "SELECT \"{$this->id}\", * FROM ";
3146
            } else {
3147
                $sql = 'SELECT * FROM ';
3148
            }
3149
        } else {
3150
            // Add oid column automatically to results for editing purposes
3151
            if (!in_array($this->id, $show, true) && $this->hasObjectID($table)) {
3152
                $sql = "SELECT \"{$this->id}\", \"";
3153
            } else {
3154
                $sql = 'SELECT "';
3155
            }
3156
3157
            $sql .= join('","', $show) . '" FROM ';
3158
        }
3159
3160
        $this->fieldClean($table);
3161
3162
        if (isset($_REQUEST['schema'])) {
3163
            $f_schema = $_REQUEST['schema'];
3164
            $this->fieldClean($f_schema);
3165
            $sql .= "\"{$f_schema}\".";
3166
        }
3167
        $sql .= "\"{$table}\"";
3168
3169
        // If we have values specified, add them to the WHERE clause
3170
        $first = true;
3171
        if (is_array($values) && sizeof($values) > 0) {
3172
            foreach ($values as $k => $v) {
3173
                if ($v != '' || $this->selectOps[$ops[$k]] == 'p') {
3174
                    $this->fieldClean($k);
3175
                    if ($first) {
3176
                        $sql .= ' WHERE ';
3177
                        $first = false;
3178
                    } else {
3179
                        $sql .= ' AND ';
3180
                    }
3181
                    // Different query format depending on operator type
3182
                    switch ($this->selectOps[$ops[$k]]) {
3183
                        case 'i':
3184
                            // Only clean the field for the inline case
3185
                            // this is because (x), subqueries need to
3186
                            // to allow 'a','b' as input.
3187
                            $this->clean($v);
3188
                            $sql .= "\"{$k}\" {$ops[$k]} '{$v}'";
3189
3190
                            break;
3191
                        case 'p':
3192
                            $sql .= "\"{$k}\" {$ops[$k]}";
3193
3194
                            break;
3195
                        case 'x':
3196
                            $sql .= "\"{$k}\" {$ops[$k]} ({$v})";
3197
3198
                            break;
3199
                        case 't':
3200
                            $sql .= "\"{$k}\" {$ops[$k]}('{$v}')";
3201
3202
                            break;
3203
                        default:
3204
                            // Shouldn't happen
3205
                    }
3206
                }
3207
            }
3208
        }
3209
3210
        // ORDER BY
3211
        if (is_array($orderby) && sizeof($orderby) > 0) {
3212
            $sql .= ' ORDER BY ';
3213
            $first = true;
3214
            foreach ($orderby as $k => $v) {
3215
                if ($first) {
3216
                    $first = false;
3217
                } else {
3218
                    $sql .= ', ';
3219
                }
3220
3221
                if (preg_match('/^[0-9]+$/', $k)) {
3222
                    $sql .= $k;
3223
                } else {
3224
                    $this->fieldClean($k);
3225
                    $sql .= '"' . $k . '"';
3226
                }
3227
                if (strtoupper($v) == 'DESC') {
3228
                    $sql .= ' DESC';
3229
                }
3230
            }
3231
        }
3232
3233
        return $sql;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sql returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
3234
    }
3235
3236
    /**
3237
     * Finds the number of rows that would be returned by a
3238
     * query.
3239
     *
3240
     * @param $query The SQL query
3241
     * @param $count The count query
3242
     *
3243
     * @return int The count of rows or -1 of no rows are found
3244
     */
3245
    public function browseQueryCount($query, $count)
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed. ( Ignorable by Annotation )

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

3245
    public function browseQueryCount(/** @scrutinizer ignore-unused */ $query, $count)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
3246
    {
3247
        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...
3248
    }
3249
3250
    /**
3251
     * Returns a recordset of all columns in a table.
3252
     *
3253
     * @param string $table The name of a table
3254
     * @param $key   The associative array holding the key to retrieve
3255
     *
3256
     * @return \PHPPgAdmin\ADORecordSet A recordset
3257
     */
3258
    public function browseRow($table, $key)
3259
    {
3260
        $f_schema = $this->_schema;
3261
        $this->fieldClean($f_schema);
3262
        $this->fieldClean($table);
3263
3264
        $sql = "SELECT * FROM \"{$f_schema}\".\"{$table}\"";
3265
        if (is_array($key) && sizeof($key) > 0) {
3266
            $sql .= ' WHERE true';
3267
            foreach ($key as $k => $v) {
3268
                $this->fieldClean($k);
3269
                $this->clean($v);
3270
                $sql .= " AND \"{$k}\"='{$v}'";
3271
            }
3272
        }
3273
3274
        return $this->selectSet($sql);
3275
    }
3276
3277
    /**
3278
     * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false.
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\the was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
3279
     *
3280
     * @param $parameter the parameter
3281
     *
3282
     * @return string
3283
     */
3284
    public function dbBool(&$parameter)
3285
    {
3286
        if ($parameter) {
3287
            $parameter = 't';
3288
        } else {
3289
            $parameter = 'f';
3290
        }
3291
3292
        return $parameter;
3293
    }
3294
3295
    /**
3296
     * Fetches statistics for a database.
3297
     *
3298
     * @param string $database The database to fetch stats for
3299
     *
3300
     * @return \PHPPgAdmin\ADORecordSet A recordset
3301
     */
3302
    public function getStatsDatabase($database)
3303
    {
3304
        $this->clean($database);
3305
3306
        $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'";
3307
3308
        return $this->selectSet($sql);
3309
    }
3310
3311
    /**
3312
     * Fetches tuple statistics for a table.
3313
     *
3314
     * @param string $table The table to fetch stats for
3315
     *
3316
     * @return \PHPPgAdmin\ADORecordSet A recordset
3317
     */
3318
    public function getStatsTableTuples($table)
3319
    {
3320
        $c_schema = $this->_schema;
3321
        $this->clean($c_schema);
3322
        $this->clean($table);
3323
3324
        $sql = "SELECT * FROM pg_stat_all_tables
3325
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3326
3327
        return $this->selectSet($sql);
3328
    }
3329
3330
    /**
3331
     * Fetches I/0 statistics for a table.
3332
     *
3333
     * @param string $table The table to fetch stats for
3334
     *
3335
     * @return \PHPPgAdmin\ADORecordSet A recordset
3336
     */
3337
    public function getStatsTableIO($table)
3338
    {
3339
        $c_schema = $this->_schema;
3340
        $this->clean($c_schema);
3341
        $this->clean($table);
3342
3343
        $sql = "SELECT * FROM pg_statio_all_tables
3344
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3345
3346
        return $this->selectSet($sql);
3347
    }
3348
3349
    /**
3350
     * Fetches tuple statistics for all indexes on a table.
3351
     *
3352
     * @param string $table The table to fetch index stats for
3353
     *
3354
     * @return \PHPPgAdmin\ADORecordSet A recordset
3355
     */
3356
    public function getStatsIndexTuples($table)
3357
    {
3358
        $c_schema = $this->_schema;
3359
        $this->clean($c_schema);
3360
        $this->clean($table);
3361
3362
        $sql = "SELECT * FROM pg_stat_all_indexes
3363
			WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname";
3364
3365
        return $this->selectSet($sql);
3366
    }
3367
3368
    /**
3369
     * Fetches I/0 statistics for all indexes on a table.
3370
     *
3371
     * @param string $table The table to fetch index stats for
3372
     *
3373
     * @return \PHPPgAdmin\ADORecordSet A recordset
3374
     */
3375
    public function getStatsIndexIO($table)
3376
    {
3377
        $c_schema = $this->_schema;
3378
        $this->clean($c_schema);
3379
        $this->clean($table);
3380
3381
        $sql = "SELECT * FROM pg_statio_all_indexes
3382
			WHERE schemaname='{$c_schema}' AND relname='{$table}'
3383
			ORDER BY indexrelname";
3384
3385
        return $this->selectSet($sql);
3386
    }
3387
}
3388