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

Postgres::saveAutovacuum()   C

Complexity

Conditions 8
Paths 128

Size

Total Lines 48
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 27
nc 128
nop 8
dl 0
loc 48
rs 5.1851
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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