Passed
Push — develop ( ce7da4...69770d )
by Felipe
05:27
created

Postgres::setPrivileges()   F

Complexity

Conditions 36
Paths 13839

Size

Total Lines 129
Code Lines 77

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 129
rs 2
c 0
b 0
f 0
cc 36
eloc 77
nc 13839
nop 10

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

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.40
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\HelperTrait;
1 ignored issue
show
introduced by
The trait PHPPgAdmin\HelperTrait requires some properties which are not provided by PHPPgAdmin\Database\Postgres: $responseobj, $requestobj
Loading history...
20
    use \PHPPgAdmin\Database\SequenceTrait;
21
    use \PHPPgAdmin\Database\ViewTrait;
22
    use \PHPPgAdmin\Database\IndexTrait;
1 ignored issue
show
Bug introduced by
The trait PHPPgAdmin\Database\IndexTrait requires the property $EOF which is not provided by PHPPgAdmin\Database\Postgres.
Loading history...
23
    use \PHPPgAdmin\Database\RoleTrait;
24
    use \PHPPgAdmin\Database\AggregateTrait;
25
    use \PHPPgAdmin\Database\TableTrait;
1 ignored issue
show
Bug introduced by
The trait PHPPgAdmin\Database\TableTrait requires the property $EOF which is not provided by PHPPgAdmin\Database\Postgres.
Loading history...
26
    use \PHPPgAdmin\Database\DomainTrait;
27
    use \PHPPgAdmin\Database\FtsTrait;
28
29
    public $lang;
30
    public $conf;
31
    protected $container;
32
33
    public function __construct(&$conn, $container)
34
    {
35
        //$this->prtrace('major_version :' . $this->major_version);
36
        $this->conn      = $conn;
37
        $this->container = $container;
38
39
        $this->lang = $container->get('lang');
40
        $this->conf = $container->get('conf');
41
    }
42
43
    /**
44
     * Fetch a URL (or array of URLs) for a given help page.
45
     *
46
     * @param $help
47
     *
48
     * @return null|array|string
49
     */
50
    public function getHelp($help)
51
    {
52
        $this->getHelpPages();
53
54
        if (isset($this->help_page[$help])) {
55
            if (is_array($this->help_page[$help])) {
56
                $urls = [];
57
                foreach ($this->help_page[$help] as $link) {
58
                    $urls[] = $this->help_base . $link;
59
                }
60
61
                return $urls;
62
            }
63
64
            return $this->help_base . $this->help_page[$help];
65
        }
66
67
        return null;
68
    }
69
70
    /**
71
     * Gets the help pages.
72
     * get help page by instancing the corresponding help class
73
     * if $this->help_page and $this->help_base are set, this function is a noop.
74
     */
75
    public function getHelpPages()
76
    {
77
        if ($this->help_page === null || $this->help_base === null) {
78
            $help_classname = '\PHPPgAdmin\Help\PostgresDoc' . str_replace('.', '', $this->major_version);
79
80
            $help_class = new $help_classname($this->conf, $this->major_version);
81
82
            $this->help_base = $help_class->getHelpBase();
83
        }
84
    }
85
86
    // Formatting functions
87
88
    /**
89
     * Outputs the HTML code for a particular field.
90
     *
91
     * @param       $name   The name to give the field
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...
92
     * @param       $value  The value of the field.  Note this could be 'numeric(7,2)' sort of thing...
93
     * @param       $type   The database type of the field
94
     * @param array $extras An array of attributes name as key and attributes' values as value
95
     */
96
    public function printField($name, $value, $type, $extras = [])
97
    {
98
        $lang = $this->lang;
99
100
        // Determine actions string
101
        $extra_str = '';
102
        foreach ($extras as $k => $v) {
103
            $extra_str .= " {$k}=\"" . htmlspecialchars($v) . '"';
104
        }
105
106
        switch (substr($type, 0, 9)) {
107
            case 'bool':
108
            case 'boolean':
109
                if ($value !== null && $value == '') {
110
                    $value = null;
111
                } elseif ($value == 'true') {
112
                    $value = 't';
113
                } elseif ($value == 'false') {
114
                    $value = 'f';
115
                }
116
117
                // If value is null, 't' or 'f'...
118
                if ($value === null || $value == 't' || $value == 'f') {
119
                    echo '<select name="', htmlspecialchars($name), "\"{$extra_str}>\n";
120
                    echo '<option value=""', ($value === null) ? ' selected="selected"' : '', "></option>\n";
121
                    echo '<option value="t"', ($value == 't') ? ' selected="selected"' : '', ">{$lang['strtrue']}</option>\n";
122
                    echo '<option value="f"', ($value == 'f') ? ' selected="selected"' : '', ">{$lang['strfalse']}</option>\n";
123
                    echo "</select>\n";
124
                } else {
125
                    echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
126
                }
127
128
                break;
129
            case 'bytea':
130
            case 'bytea[]':
131
                if (!is_null($value)) {
132
                    $value = $this->escapeBytea($value);
133
                }
134
            // no break
135
            case 'text':
136
            case 'text[]':
137
            case 'json':
138
            case 'jsonb':
139
            case 'xml':
140
            case 'xml[]':
141
                $n = substr_count($value, "\n");
142
                $n = $n < 5 ? max(2, $n) : $n;
143
                $n = $n > 20 ? 20 : $n;
144
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"85\"{$extra_str}>\n";
145
                echo htmlspecialchars($value);
146
                echo "</textarea>\n";
147
148
                break;
149
            case 'character':
150
            case 'character[]':
151
                $n = substr_count($value, "\n");
152
                $n = $n < 5 ? 5 : $n;
153
                $n = $n > 20 ? 20 : $n;
154
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"35\"{$extra_str}>\n";
155
                echo htmlspecialchars($value);
156
                echo "</textarea>\n";
157
158
                break;
159
            default:
160
                echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
161
162
                break;
163
        }
164
    }
165
166
    /**
167
     * Escapes bytea data for display on the screen.
168
     *
169
     * @param string $data The bytea data
170
     *
171
     * @return string Data formatted for on-screen display
172
     */
173
    public function escapeBytea($data)
174
    {
175
        return htmlentities($data, ENT_QUOTES, 'UTF-8');
176
    }
177
178
    /**
179
     * Return all information about a particular database.
180
     *
181
     * @param $database The name of the database to retrieve
182
     *
183
     * @return \PHPPgAdmin\ADORecordSet The database info
184
     */
185
    public function getDatabase($database)
186
    {
187
        $this->clean($database);
188
        $sql = "SELECT * FROM pg_database WHERE datname='{$database}'";
189
190
        return $this->selectSet($sql);
191
    }
192
193
    /**
194
     * Cleans (escapes) a string.
195
     *
196
     * @param string $str The string to clean, by reference
197
     *
198
     * @return string The cleaned string
199
     */
200
    public function clean(&$str)
201
    {
202
        if ($str === null) {
0 ignored issues
show
introduced by
The condition $str === null is always false.
Loading history...
203
            return null;
204
        }
205
206
        $str = str_replace("\r\n", "\n", $str);
207
        $str = pg_escape_string($str);
2 ignored issues
show
Bug introduced by
The call to pg_escape_string() has too few arguments starting with data. ( Ignorable by Annotation )

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

207
        $str = /** @scrutinizer ignore-call */ pg_escape_string($str);

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

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

Loading history...
Bug introduced by
$str of type string is incompatible with the type resource expected by parameter $connection of pg_escape_string(). ( Ignorable by Annotation )

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

207
        $str = pg_escape_string(/** @scrutinizer ignore-type */ $str);
Loading history...
208
209
        return $str;
210
    }
211
212
    /**
213
     * Return all database available on the server.
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\database 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...
214
     *
215
     * @param $currentdatabase database name that should be on top of the resultset
216
     *
217
     * @return \PHPPgAdmin\ADORecordSet A list of databases, sorted alphabetically
218
     */
219
    public function getDatabases($currentdatabase = null)
220
    {
221
        $conf        = $this->conf;
222
        $server_info = $this->server_info;
0 ignored issues
show
Bug Best Practice introduced by
The property server_info does not exist on PHPPgAdmin\Database\Postgres. Did you maybe forget to declare it?
Loading history...
223
224
        if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) {
225
            $username = $server_info['username'];
226
            $this->clean($username);
227
            $clause = " AND pr.rolname='{$username}'";
228
        } else {
229
            $clause = '';
230
        }
231
        if (isset($server_info['useonlydefaultdb']) && $server_info['useonlydefaultdb']) {
232
            $currentdatabase = $server_info['defaultdb'];
233
            $clause .= " AND pdb.datname = '{$currentdatabase}' ";
234
        }
235
236
        if (isset($server_info['hiddendbs']) && $server_info['hiddendbs']) {
237
            $hiddendbs = $server_info['hiddendbs'];
238
            $not_in    = "('" . implode("','", $hiddendbs) . "')";
239
            $clause .= " AND pdb.datname NOT IN {$not_in} ";
240
        }
241
242
        if ($currentdatabase != null) {
243
            $this->clean($currentdatabase);
244
            $orderby = "ORDER BY pdb.datname = '{$currentdatabase}' DESC, pdb.datname";
245
        } else {
246
            $orderby = 'ORDER BY pdb.datname';
247
        }
248
249
        if (!$conf['show_system']) {
250
            $where = ' AND NOT pdb.datistemplate';
251
        } else {
252
            $where = ' AND pdb.datallowconn';
253
        }
254
255
        $sql = "
256
			SELECT pdb.datname AS datname,
257
                    pr.rolname AS datowner,
258
                    pg_encoding_to_char(encoding) AS datencoding,
259
				    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pdb.oid=pd.objoid AND pd.classoid='pg_database'::regclass) AS datcomment,
260
				    (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace,
261
				CASE WHEN pg_catalog.has_database_privilege(current_user, pdb.oid, 'CONNECT')
262
					THEN pg_catalog.pg_database_size(pdb.oid)
263
					ELSE -1 -- set this magic value, which we will convert to no access later
264
				END as dbsize,
265
                pdb.datcollate,
266
                pdb.datctype
267
			FROM pg_catalog.pg_database pdb
268
            LEFT JOIN pg_catalog.pg_roles pr ON (pdb.datdba = pr.oid)
269
			WHERE true
270
				{$where}
271
				{$clause}
272
			{$orderby}";
273
274
        return $this->selectSet($sql);
275
    }
276
277
    /**
278
     * Determines whether or not a user is a super user.
279
     *
280
     * @param string $username The username of the user
281
     *
282
     * @return true if is a super user, false otherwise
283
     */
284
    public function isSuperUser($username = '')
285
    {
286
        $this->clean($username);
287
288
        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...
289
            $val = pg_parameter_status($this->conn->_connectionID, 'is_superuser');
290
            if ($val !== false) {
291
                return $val == 'on';
0 ignored issues
show
Bug Best Practice introduced by
The expression return $val == 'on' returns the type boolean which is incompatible with the documented return type true.
Loading history...
292
            }
293
        }
294
295
        $sql = "SELECT usesuper FROM pg_user WHERE usename='{$username}'";
296
297
        $usesuper = $this->selectField($sql, 'usesuper');
298
        if ($usesuper == -1) {
0 ignored issues
show
introduced by
The condition $usesuper == -1 is always false.
Loading history...
299
            return false;
300
        }
301
302
        return $usesuper == 't';
0 ignored issues
show
Bug Best Practice introduced by
The expression return $usesuper == 't' returns the type boolean which is incompatible with the documented return type true.
Loading history...
303
    }
304
305
    /**
306
     * Return the database comment of a db from the shared description table.
307
     *
308
     * @param string $database the name of the database to get the comment for
309
     *
310
     * @return \PHPPgAdmin\ADORecordSet recordset of the db comment info
311
     */
312
    public function getDatabaseComment($database)
313
    {
314
        $this->clean($database);
315
        $sql =
0 ignored issues
show
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
316
            "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}' ";
317
318
        return $this->selectSet($sql);
319
    }
320
321
    /**
322
     * Return the database owner of a db.
323
     *
324
     * @param string $database the name of the database to get the owner for
325
     *
326
     * @return \PHPPgAdmin\ADORecordSet recordset of the db owner info
327
     */
328
    public function getDatabaseOwner($database)
329
    {
330
        $this->clean($database);
331
        $sql = "SELECT usename FROM pg_user, pg_database WHERE pg_user.usesysid = pg_database.datdba AND pg_database.datname = '{$database}' ";
332
333
        return $this->selectSet($sql);
334
    }
335
336
    // Help functions
337
338
    // Database functions
339
340
    /**
341
     * Returns the current database encoding.
342
     *
343
     * @return string The encoding.  eg. SQL_ASCII, UTF-8, etc.
344
     */
345
    public function getDatabaseEncoding()
346
    {
347
        return pg_parameter_status($this->conn->_connectionID, 'server_encoding');
348
    }
349
350
    /**
351
     * Returns the current default_with_oids setting.
352
     *
353
     * @return string default_with_oids setting
354
     */
355
    public function getDefaultWithOid()
356
    {
357
        $sql = 'SHOW default_with_oids';
358
359
        return $this->selectField($sql, 'default_with_oids');
360
    }
361
362
    /**
363
     * Creates a database.
364
     *
365
     * @param        $database   The name of the database to create
366
     * @param        $encoding   Encoding of the database
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Encoding 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...
367
     * @param string $tablespace (optional) The tablespace name
368
     * @param string $comment
369
     * @param string $template
370
     * @param string $lc_collate
371
     * @param string $lc_ctype
372
     *
373
     * @return int 0 success
374
     */
375
    public function createDatabase(
376
        $database,
377
        $encoding,
378
        $tablespace = '',
379
        $comment = '',
380
        $template = 'template1',
381
        $lc_collate = '',
382
        $lc_ctype = ''
383
    ) {
384
        $this->fieldClean($database);
385
        $this->clean($encoding);
386
        $this->fieldClean($tablespace);
387
        $this->fieldClean($template);
388
        $this->clean($lc_collate);
389
        $this->clean($lc_ctype);
390
391
        $sql = "CREATE DATABASE \"{$database}\" WITH TEMPLATE=\"{$template}\"";
392
393
        if ($encoding != '') {
394
            $sql .= " ENCODING='{$encoding}'";
395
        }
396
397
        if ($lc_collate != '') {
398
            $sql .= " LC_COLLATE='{$lc_collate}'";
399
        }
400
401
        if ($lc_ctype != '') {
402
            $sql .= " LC_CTYPE='{$lc_ctype}'";
403
        }
404
405
        if ($tablespace != '' && $this->hasTablespaces()) {
406
            $sql .= " TABLESPACE \"{$tablespace}\"";
407
        }
408
409
        $status = $this->execute($sql);
410
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
411
            return -1;
412
        }
413
414
        if ($comment != '' && $this->hasSharedComments()) {
415
            $status = $this->setComment('DATABASE', $database, '', $comment);
416
            if ($status != 0) {
417
                return -2;
418
            }
419
        }
420
421
        return 0;
422
    }
423
424
    /**
425
     * Cleans (escapes) an object name (eg. table, field).
426
     *
427
     * @param string $str The string to clean, by reference
428
     *
429
     * @return string The cleaned string
430
     */
431
    public function fieldClean(&$str)
432
    {
433
        if ($str === null) {
0 ignored issues
show
introduced by
The condition $str === null is always false.
Loading history...
434
            return null;
435
        }
436
437
        $str = str_replace('"', '""', $str);
438
439
        return $str;
440
    }
441
442
    /**
443
     * Sets the comment for an object in the database.
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...
444
     *
445
     * @pre All parameters must already be cleaned
446
     *
447
     * @param      $obj_type One of 'TABLE' | 'COLUMN' | 'VIEW' | 'SCHEMA' | 'SEQUENCE' | 'TYPE' | 'FUNCTION' | 'AGGREGATE'
1 ignored issue
show
Bug introduced by
The type PHPPgAdmin\Database\One 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...
Coding Style introduced by
Parameter tags must be defined first in a doc comment
Loading history...
448
     * @param      $obj_name the name of the object for which to attach a comment
449
     * @param      $table    Name of table that $obj_name belongs to.  Ignored unless $obj_type is 'TABLE' or 'COLUMN'.
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Name 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...
Coding Style introduced by
Doc comment for parameter $obj_name does not match actual variable name $table
Loading history...
450
     * @param      $comment  the comment to add
451
     * @param null $basetype
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $basetype is correct as it would always require null to be passed?
Loading history...
452
     *
453
     * @return int 0 if operation was successful
454
     */
455
    public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null)
456
    {
457
        $sql      = "COMMENT ON {$obj_type} ";
458
        $f_schema = $this->_schema;
459
        $this->fieldClean($f_schema);
460
        $this->clean($comment); // Passing in an already cleaned comment will lead to double escaped data
461
        // So, while counter-intuitive, it is important to not clean comments before
462
        // calling setComment. We will clean it here instead.
463
        /*
464
        $this->fieldClean($table);
465
        $this->fieldClean($obj_name);
466
         */
467
468
        switch ($obj_type) {
469
            case 'TABLE':
470
                $sql .= "\"{$f_schema}\".\"{$table}\" IS ";
471
472
                break;
473
            case 'COLUMN':
474
                $sql .= "\"{$f_schema}\".\"{$table}\".\"{$obj_name}\" IS ";
475
476
                break;
477
            case 'SEQUENCE':
478
            case 'VIEW':
479
            case 'TEXT SEARCH CONFIGURATION':
480
            case 'TEXT SEARCH DICTIONARY':
481
            case 'TEXT SEARCH TEMPLATE':
482
            case 'TEXT SEARCH PARSER':
483
            case 'TYPE':
484
                $sql .= "\"{$f_schema}\".";
485
            // no break
486
            case 'DATABASE':
487
            case 'ROLE':
488
            case 'SCHEMA':
489
            case 'TABLESPACE':
490
                $sql .= "\"{$obj_name}\" IS ";
491
492
                break;
493
            case 'FUNCTION':
494
                $sql .= "\"{$f_schema}\".{$obj_name} IS ";
495
496
                break;
497
            case 'AGGREGATE':
498
                $sql .= "\"{$f_schema}\".\"{$obj_name}\" (\"{$basetype}\") IS ";
499
500
                break;
501
            default:
502
                // Unknown object type
503
                return -1;
504
        }
505
506
        if ($comment != '') {
507
            $sql .= "'{$comment}';";
508
        } else {
509
            $sql .= 'NULL;';
510
        }
511
512
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
513
    }
514
515
    /**
516
     * Drops a database.
517
     *
518
     * @param $database The name of the database to drop
519
     *
520
     * @return int 0 if operation was successful
521
     */
522
    public function dropDatabase($database)
523
    {
524
        $this->fieldClean($database);
525
        $sql = "DROP DATABASE \"{$database}\"";
526
527
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
528
    }
529
530
    /**
531
     * Alters a database
532
     * the multiple return vals are for postgres 8+ which support more functionality in alter database.
533
     *
534
     * @param        $dbName   The name of the database
535
     * @param        $newName  new name for the database
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\new 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...
536
     * @param string $newOwner The new owner for the database
537
     * @param string $comment
538
     *
539
     * @return bool|int 0 success
540
     */
541
    public function alterDatabase($dbName, $newName, $newOwner = '', $comment = '')
542
    {
543
        $status = $this->beginTransaction();
544
        if ($status != 0) {
545
            $this->rollbackTransaction();
546
547
            return -1;
548
        }
549
550
        if ($dbName != $newName) {
551
            $status = $this->alterDatabaseRename($dbName, $newName);
552
            if ($status != 0) {
553
                $this->rollbackTransaction();
554
555
                return -3;
556
            }
557
            $dbName = $newName;
558
        }
559
560
        if ($newOwner != '') {
561
            $status = $this->alterDatabaseOwner($newName, $newOwner);
562
            if ($status != 0) {
563
                $this->rollbackTransaction();
564
565
                return -2;
566
            }
567
        }
568
569
        $this->fieldClean($dbName);
570
        $status = $this->setComment('DATABASE', $dbName, '', $comment);
571
        if ($status != 0) {
572
            $this->rollbackTransaction();
573
574
            return -4;
575
        }
576
577
        return $this->endTransaction();
578
    }
579
580
    /**
581
     * Renames a database, note that this operation cannot be
582
     * performed on a database that is currently being connected to.
583
     *
584
     * @param string $oldName name of database to rename
585
     * @param string $newName new name of database
586
     *
587
     * @return int 0 on success
588
     */
589
    public function alterDatabaseRename($oldName, $newName)
590
    {
591
        $this->fieldClean($oldName);
592
        $this->fieldClean($newName);
593
594
        if ($oldName != $newName) {
595
            $sql = "ALTER DATABASE \"{$oldName}\" RENAME TO \"{$newName}\"";
596
597
            return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
598
        }
599
600
        return 0;
601
    }
602
603
    /**
604
     * Changes ownership of a database
605
     * This can only be done by a superuser or the owner of the database.
606
     *
607
     * @param string $dbName   database to change ownership of
608
     * @param string $newOwner user that will own the database
609
     *
610
     * @return int 0 on success
611
     */
612
    public function alterDatabaseOwner($dbName, $newOwner)
613
    {
614
        $this->fieldClean($dbName);
615
        $this->fieldClean($newOwner);
616
617
        $sql = "ALTER DATABASE \"{$dbName}\" OWNER TO \"{$newOwner}\"";
618
619
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
620
    }
621
622
    /**
623
     * Returns prepared transactions information.
624
     *
625
     * @param $database (optional) Find only prepared transactions executed in a specific database
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...
626
     *
627
     * @return \PHPPgAdmin\ADORecordSet A recordset
628
     */
629
    public function getPreparedXacts($database = null)
630
    {
631
        if ($database === null) {
632
            $sql = 'SELECT * FROM pg_prepared_xacts';
633
        } else {
634
            $this->clean($database);
635
            $sql = "SELECT transaction, gid, prepared, owner FROM pg_prepared_xacts
636
				WHERE database='{$database}' ORDER BY owner";
637
        }
638
639
        return $this->selectSet($sql);
640
    }
641
642
    /**
643
     * Searches all system catalogs to find objects that match a certain name.
644
     *
645
     * @param $term   The search term
646
     * @param $filter The object type to restrict to ('' means no restriction)
647
     *
648
     * @return \PHPPgAdmin\ADORecordSet A recordset
649
     */
650
    public function findObject($term, $filter)
651
    {
652
        $conf = $this->conf;
653
654
        /*about escaping:
655
         * SET standard_conforming_string is not available before 8.2
656
         * So we must use PostgreSQL specific notation :/
657
         * E'' notation is not available before 8.1
658
         * $$ is available since 8.0
659
         * Nothing specific from 7.4
660
         */
661
662
        // Escape search term for ILIKE match
663
        $this->clean($term);
664
        $this->clean($filter);
665
        $term = str_replace('_', '\_', $term);
666
        $term = str_replace('%', '\%', $term);
667
668
        // Exclude system relations if necessary
669
        if (!$conf['show_system']) {
670
            // XXX: The mention of information_schema here is in the wrong place, but
671
            // it's the quickest fix to exclude the info schema from 7.4
672
            $where     = " AND pn.nspname NOT LIKE \$_PATERN_\$pg\\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'";
673
            $lan_where = 'AND pl.lanispl';
674
        } else {
675
            $where     = '';
676
            $lan_where = '';
677
        }
678
679
        // Apply outer filter
680
        $sql = '';
681
        if ($filter != '') {
682
            $sql = 'SELECT * FROM (';
683
        }
684
685
        $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$";
686
687
        $sql .= "
688
			SELECT 'SCHEMA' AS type, oid, NULL AS schemaname, NULL AS relname, nspname AS name
689
				FROM pg_catalog.pg_namespace pn WHERE nspname ILIKE {$term} {$where}
690
			UNION ALL
691
			SELECT CASE WHEN relkind='r' THEN 'TABLE' WHEN relkind='v' THEN 'VIEW' WHEN relkind='S' THEN 'SEQUENCE' END, pc.oid,
692
				pn.nspname, NULL, pc.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn
693
				WHERE pc.relnamespace=pn.oid AND relkind IN ('r', 'v', 'S') AND relname ILIKE {$term} {$where}
694
			UNION ALL
695
			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,
696
				pg_catalog.pg_attribute pa WHERE pc.relnamespace=pn.oid AND pc.oid=pa.attrelid
697
				AND pa.attname ILIKE {$term} AND pa.attnum > 0 AND NOT pa.attisdropped AND pc.relkind IN ('r', 'v') {$where}
698
			UNION ALL
699
			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
700
				WHERE pp.pronamespace=pn.oid AND NOT pp.proisagg AND pp.proname ILIKE {$term} {$where}
701
			UNION ALL
702
			SELECT 'INDEX', NULL, pn.nspname, pc.relname, pc2.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
703
				pg_catalog.pg_index pi, pg_catalog.pg_class pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pi.indrelid
704
				AND pi.indexrelid=pc2.oid
705
				AND NOT EXISTS (
706
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
707
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
708
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
709
				)
710
				AND pc2.relname ILIKE {$term} {$where}
711
			UNION ALL
712
			SELECT 'CONSTRAINTTABLE', NULL, pn.nspname, pc.relname, pc2.conname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
713
				pg_catalog.pg_constraint pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pc2.conrelid AND pc2.conrelid != 0
714
				AND CASE WHEN pc2.contype IN ('f', 'c') THEN TRUE ELSE NOT EXISTS (
715
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
716
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
717
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
718
				) END
719
				AND pc2.conname ILIKE {$term} {$where}
720
			UNION ALL
721
			SELECT 'CONSTRAINTDOMAIN', pt.oid, pn.nspname, pt.typname, pc.conname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn,
722
				pg_catalog.pg_constraint pc WHERE pt.typnamespace=pn.oid AND pt.oid=pc.contypid AND pc.contypid != 0
723
				AND pc.conname ILIKE {$term} {$where}
724
			UNION ALL
725
			SELECT 'TRIGGER', NULL, pn.nspname, pc.relname, pt.tgname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
726
				pg_catalog.pg_trigger pt WHERE pc.relnamespace=pn.oid AND pc.oid=pt.tgrelid
727
					AND ( pt.tgconstraint = 0 OR NOT EXISTS
728
					(SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
729
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
730
					WHERE d.classid = pt.tableoid AND d.objid = pt.oid AND d.deptype = 'i' AND c.contype = 'f'))
731
				AND pt.tgname ILIKE {$term} {$where}
732
			UNION ALL
733
			SELECT 'RULETABLE', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
734
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
735
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
736
				WHERE c.relkind='r' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
737
			UNION ALL
738
			SELECT 'RULEVIEW', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
739
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
740
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
741
				WHERE c.relkind='v' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
742
		";
743
744
        // Add advanced objects if show_advanced is set
745
        if ($conf['show_advanced']) {
746
            $sql .= "
747
				UNION ALL
748
				SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL,
749
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
750
					WHERE pt.typnamespace=pn.oid AND typname ILIKE {$term}
751
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
752
					{$where}
753
			 	UNION ALL
754
				SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn
755
					WHERE po.oprnamespace=pn.oid AND oprname ILIKE {$term} {$where}
756
				UNION ALL
757
				SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc,
758
					pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE {$term} {$where}
759
				UNION ALL
760
				SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl
761
					WHERE lanname ILIKE {$term} {$lan_where}
762
				UNION ALL
763
				SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p
764
					LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid
765
					WHERE p.proisagg AND p.proname ILIKE {$term} {$where}
766
				UNION ALL
767
				SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po,
768
					pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid
769
					AND po.opcname ILIKE {$term} {$where}
770
			";
771
        } // Otherwise just add domains
772
        else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " // Otherwise just add domains\n else {\n"
Loading history...
773
            $sql .= "
774
				UNION ALL
775
				SELECT 'DOMAIN', pt.oid, pn.nspname, NULL,
776
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
777
					WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE {$term}
778
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
779
					{$where}
780
			";
781
        }
782
783
        if ($filter != '') {
784
            // We use like to make RULE, CONSTRAINT and COLUMN searches work
785
            $sql .= ") AS sub WHERE type LIKE '{$filter}%' ";
786
        }
787
788
        $sql .= 'ORDER BY type, schemaname, relname, name';
789
790
        return $this->selectSet($sql);
791
    }
792
793
    /**
794
     * Returns all available variable information.
795
     *
796
     * @return \PHPPgAdmin\ADORecordSet A recordset
797
     */
798
    public function getVariables()
799
    {
800
        $sql = 'SHOW ALL';
801
802
        return $this->selectSet($sql);
803
    }
804
805
    // Schema functons
806
807
    /**
808
     * Return all schemas in the current database.
809
     *
810
     * @return \PHPPgAdmin\ADORecordSet All schemas, sorted alphabetically
811
     */
812
    public function getSchemas()
813
    {
814
        $conf = $this->conf;
815
816
        if (!$conf['show_system']) {
817
            $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'";
818
        } else {
819
            $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
820
        }
821
822
        $sql = "
823
			SELECT pn.nspname,
824
                   pu.rolname AS nspowner,
825
				   pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment,
826
                   pg_size_pretty(SUM(pg_total_relation_size(pg_class.oid))) as schema_size
827
			FROM pg_catalog.pg_namespace pn
828
            LEFT JOIN pg_catalog.pg_class  ON relnamespace = pn.oid
829
			LEFT JOIN pg_catalog.pg_roles pu ON (pn.nspowner = pu.oid)
830
			{$where}
831
            GROUP BY pn.nspname, pu.rolname, pg_catalog.obj_description(pn.oid, 'pg_namespace')
832
			ORDER BY nspname";
833
834
        return $this->selectSet($sql);
835
    }
836
837
    /**
838
     * Sets the current working schema.  Will also set Class variable.
839
     *
840
     * @param $schema The the name of the schema to work in
841
     *
842
     * @return int 0 if operation was successful
843
     */
844
    public function setSchema($schema)
845
    {
846
        // Get the current schema search path, including 'pg_catalog'.
847
        $search_path = $this->getSearchPath();
848
        // Prepend $schema to search path
849
        array_unshift($search_path, $schema);
0 ignored issues
show
Bug introduced by
$search_path of type PHPPgAdmin\ADORecordSet is incompatible with the type array expected by parameter $array of array_unshift(). ( Ignorable by Annotation )

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

849
        array_unshift(/** @scrutinizer ignore-type */ $search_path, $schema);
Loading history...
850
        $status = $this->setSearchPath($search_path);
851
        if ($status == 0) {
852
            $this->_schema = $schema;
853
854
            return 0;
855
        }
856
857
        return $status;
858
    }
859
860
    /**
861
     * Return the current schema search path.
862
     *
863
     * @return \PHPPgAdmin\ADORecordSet array of schema names
864
     */
865
    public function getSearchPath()
866
    {
867
        $sql = 'SELECT current_schemas(false) AS search_path';
868
869
        return $this->phpArray($this->selectField($sql, 'search_path'));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->phpArray($...d($sql, 'search_path')) returns the type array which is incompatible with the documented return type PHPPgAdmin\ADORecordSet.
Loading history...
870
    }
871
872
    /**
873
     * Sets the current schema search path.
874
     *
875
     * @param $paths An array of schemas in required search order
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...
876
     *
877
     * @return int 0 if operation was successful
878
     */
879
    public function setSearchPath($paths)
880
    {
881
        if (!is_array($paths)) {
882
            return -1;
883
        }
884
885
        if (sizeof($paths) == 0) {
886
            return -2;
887
        }
888
        if (sizeof($paths) == 1 && $paths[0] == '') {
889
            // Need to handle empty paths in some cases
890
            $paths[0] = 'pg_catalog';
891
        }
892
893
        // Loop over all the paths to check that none are empty
894
        $temp = [];
895
        foreach ($paths as $schema) {
896
            if ($schema != '') {
897
                $temp[] = $schema;
898
            }
899
        }
900
        $this->fieldArrayClean($temp);
901
902
        $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"';
903
904
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
905
    }
906
907
    /**
908
     * Cleans (escapes) an array of field names.
909
     *
910
     * @param $arr The array to clean, by reference
911
     *
912
     * @return The cleaned array
913
     */
914
    public function fieldArrayClean(&$arr)
915
    {
916
        foreach ($arr as $k => $v) {
917
            if ($v === null) {
918
                continue;
919
            }
920
921
            $arr[$k] = str_replace('"', '""', $v);
922
        }
923
924
        return $arr;
925
    }
926
927
    /**
928
     * Creates a new schema.
929
     *
930
     * @param        $schemaname    The name of the schema to create
931
     * @param string $authorization (optional) The username to create the schema for
932
     * @param string $comment       (optional) If omitted, defaults to nothing
933
     *
934
     * @return bool|int 0 success
935
     */
936
    public function createSchema($schemaname, $authorization = '', $comment = '')
937
    {
938
        $this->fieldClean($schemaname);
939
        $this->fieldClean($authorization);
940
941
        $sql = "CREATE SCHEMA \"{$schemaname}\"";
942
        if ($authorization != '') {
943
            $sql .= " AUTHORIZATION \"{$authorization}\"";
944
        }
945
946
        if ($comment != '') {
947
            $status = $this->beginTransaction();
948
            if ($status != 0) {
949
                return -1;
950
            }
951
        }
952
953
        // Create the new schema
954
        $status = $this->execute($sql);
955
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
956
            $this->rollbackTransaction();
957
958
            return -1;
959
        }
960
961
        // Set the comment
962
        if ($comment != '') {
963
            $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
964
            if ($status != 0) {
965
                $this->rollbackTransaction();
966
967
                return -1;
968
            }
969
970
            return $this->endTransaction();
971
        }
972
973
        return 0;
974
    }
975
976
    /**
977
     * Updates a schema.
978
     *
979
     * @param $schemaname The name of the schema to drop
980
     * @param $comment    The new comment for this schema
981
     * @param $name
982
     * @param $owner      The new owner for this schema
983
     *
984
     * @return bool|int 0 success
985
     */
986
    public function updateSchema($schemaname, $comment, $name, $owner)
987
    {
988
        $this->fieldClean($schemaname);
989
        $this->fieldClean($name);
990
        $this->fieldClean($owner);
991
992
        $status = $this->beginTransaction();
993
        if ($status != 0) {
994
            $this->rollbackTransaction();
995
996
            return -1;
997
        }
998
999
        $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
1000
        if ($status != 0) {
1001
            $this->rollbackTransaction();
1002
1003
            return -1;
1004
        }
1005
1006
        $schema_rs = $this->getSchemaByName($schemaname);
1007
        /* Only if the owner change */
1008
        if ($schema_rs->fields['ownername'] != $owner) {
1009
            $sql    = "ALTER SCHEMA \"{$schemaname}\" OWNER TO \"{$owner}\"";
1010
            $status = $this->execute($sql);
1011
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
1012
                $this->rollbackTransaction();
1013
1014
                return -1;
1015
            }
1016
        }
1017
1018
        // Only if the name has changed
1019
        if ($name != $schemaname) {
1020
            $sql    = "ALTER SCHEMA \"{$schemaname}\" RENAME TO \"{$name}\"";
1021
            $status = $this->execute($sql);
1022
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
1023
                $this->rollbackTransaction();
1024
1025
                return -1;
1026
            }
1027
        }
1028
1029
        return $this->endTransaction();
1030
    }
1031
1032
    /**
1033
     * Return all information relating to a schema.
1034
     *
1035
     * @param $schema The name of the schema
1036
     *
1037
     * @return Schema information
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Schema 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...
1038
     */
1039
    public function getSchemaByName($schema)
1040
    {
1041
        $this->clean($schema);
1042
        $sql = "
1043
			SELECT nspname, nspowner, r.rolname AS ownername, nspacl,
1044
				pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment
1045
			FROM pg_catalog.pg_namespace pn
1046
				LEFT JOIN pg_roles as r ON pn.nspowner = r.oid
1047
			WHERE nspname='{$schema}'";
1048
1049
        return $this->selectSet($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->selectSet($sql) returns the type ADORecordSet which is incompatible with the documented return type PHPPgAdmin\Database\Schema.
Loading history...
1050
    }
1051
1052
    // Table functions
1053
1054
    /**
1055
     * Drops a schema.
1056
     *
1057
     * @param $schemaname The name of the schema to drop
1058
     * @param $cascade    True to cascade drop, false to restrict
1059
     *
1060
     * @return int 0 if operation was successful
1061
     */
1062
    public function dropSchema($schemaname, $cascade)
1063
    {
1064
        $this->fieldClean($schemaname);
1065
1066
        $sql = "DROP SCHEMA \"{$schemaname}\"";
1067
        if ($cascade) {
1068
            $sql .= ' CASCADE';
1069
        }
1070
1071
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
1072
    }
1073
1074
    /**
1075
     * Returns the SQL for changing the current user.
1076
     *
1077
     * @param $user The user to change to
1078
     *
1079
     * @return The SQL
1080
     */
1081
    public function getChangeUserSQL($user)
1082
    {
1083
        $this->clean($user);
1084
1085
        return "SET SESSION AUTHORIZATION '{$user}';";
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'SET SESSION AUTHORIZATION ''.$user.'';' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
1086
    }
1087
1088
    /**
1089
     * Change a parameter from 't' or 'f' to a boolean, (others evaluate to false).
1090
     *
1091
     * @param $parameter the parameter
1092
     *
1093
     * @return bool|\PHPPgAdmin\Database\the
1094
     */
1095
    public function phpBool($parameter)
1096
    {
1097
        $parameter = ($parameter == 't');
1098
1099
        return $parameter;
1100
    }
1101
1102
    /**
1103
     * Formats a type correctly for display.  Postgres 7.0 had no 'format_type'
1104
     * built-in function, and hence we need to do it manually.
1105
     *
1106
     * @param $typname The name of the type
1107
     * @param $typmod  The contents of the typmod field
1108
     *
1109
     * @return bool|string
1110
     */
1111
    public function formatType($typname, $typmod)
1112
    {
1113
        // This is a specific constant in the 7.0 source
1114
        $varhdrsz = 4;
1115
1116
        // If the first character is an underscore, it's an array type
1117
        $is_array = false;
1118
        if (substr($typname, 0, 1) == '_') {
1119
            $is_array = true;
1120
            $typname  = substr($typname, 1);
1121
        }
1122
1123
        // Show lengths on bpchar and varchar
1124
        if ($typname == 'bpchar') {
1125
            $len  = $typmod - $varhdrsz;
1126
            $temp = 'character';
1127
            if ($len > 1) {
1128
                $temp .= "({$len})";
1129
            }
1130
        } elseif ($typname == 'varchar') {
1131
            $temp = 'character varying';
1132
            if ($typmod != -1) {
1133
                $temp .= '(' . ($typmod - $varhdrsz) . ')';
1134
            }
1135
        } elseif ($typname == 'numeric') {
1136
            $temp = 'numeric';
1137
            if ($typmod != -1) {
1138
                $tmp_typmod = $typmod - $varhdrsz;
1139
                $precision  = ($tmp_typmod >> 16) & 0xffff;
1140
                $scale      = $tmp_typmod & 0xffff;
1141
                $temp .= "({$precision}, {$scale})";
1142
            }
1143
        } else {
1144
            $temp = $typname;
1145
        }
1146
1147
        // Add array qualifier if it's an array
1148
        if ($is_array) {
1149
            $temp .= '[]';
1150
        }
1151
1152
        return $temp;
1153
    }
1154
1155
    /**
1156
     * Given an array of attnums and a relation, returns an array mapping
1157
     * attribute number to attribute name.
1158
     *
1159
     * @param $table The table to get attributes for
1160
     * @param $atts  An array of attribute numbers
1161
     *
1162
     * @return An array mapping attnum to attname
1163
     * @return -1 $atts must be an array
0 ignored issues
show
Coding Style introduced by
Only 1 @return tag is allowed in a function comment
Loading history...
1164
     * @return -2 wrong number of attributes found
1165
     */
1166
    public function getAttributeNames($table, $atts)
1167
    {
1168
        $c_schema = $this->_schema;
1169
        $this->clean($c_schema);
1170
        $this->clean($table);
1171
        $this->arrayClean($atts);
1172
1173
        if (!is_array($atts)) {
1174
            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 PHPPgAdmin\Database\An.
Loading history...
1175
        }
1176
1177
        if (sizeof($atts) == 0) {
1178
            return [];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array() returns the type array which is incompatible with the documented return type PHPPgAdmin\Database\An.
Loading history...
1179
        }
1180
1181
        $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
1182
			attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
1183
			relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
1184
			AND attnum IN ('" . join("','", $atts) . "')";
1185
1186
        $rs = $this->selectSet($sql);
1187
        if ($rs->recordCount() != sizeof($atts)) {
1188
            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 PHPPgAdmin\Database\An.
Loading history...
1189
        }
1190
1191
        $temp = [];
1192
        while (!$rs->EOF) {
1193
            $temp[$rs->fields['attnum']] = $rs->fields['attname'];
1194
            $rs->moveNext();
1195
        }
1196
1197
        return $temp;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $temp returns the type array which is incompatible with the documented return type PHPPgAdmin\Database\An.
Loading history...
1198
    }
1199
1200
    /**
1201
     * Cleans (escapes) an array.
1202
     *
1203
     * @param $arr The array to clean, by reference
1204
     *
1205
     * @return The cleaned array
1206
     */
1207
    public function arrayClean(&$arr)
1208
    {
1209
        foreach ($arr as $k => $v) {
1210
            if ($v === null) {
1211
                continue;
1212
            }
1213
1214
            $arr[$k] = pg_escape_string($v);
1 ignored issue
show
Bug introduced by
The call to pg_escape_string() has too few arguments starting with data. ( Ignorable by Annotation )

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

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

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

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

Loading history...
1215
        }
1216
1217
        return $arr;
1218
    }
1219
1220
    /**
1221
     * Grabs an array of users and their privileges for an object,
1222
     * given its type.
1223
     *
1224
     * @param $object The name of the object whose privileges are to be retrieved
1225
     * @param $type   The type of the object (eg. database, schema, relation, function or language)
1226
     * @param $table  Optional, column's table if type = column
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...
1227
     *
1228
     * @return Privileges array
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Privileges 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...
1229
     * @return -1         invalid type
0 ignored issues
show
Coding Style introduced by
Only 1 @return tag is allowed in a function comment
Loading history...
1230
     * @return -2         object not found
1231
     * @return -3         unknown privilege type
1232
     */
1233
    public function getPrivileges($object, $type, $table = null)
1234
    {
1235
        $c_schema = $this->_schema;
1236
        $this->clean($c_schema);
1237
        $this->clean($object);
1238
1239
        switch ($type) {
1240
            case 'column':
1241
                $this->clean($table);
1242
                $sql = "
1243
					SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl
1244
					FROM pg_catalog.pg_attribute a
1245
						LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid)
1246
						LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid)
1247
					WHERE n.nspname='{$c_schema}'
1248
						AND c.relname='{$table}'
1249
						AND a.attname='{$object}'";
1250
1251
                break;
1252
            case 'table':
1253
            case 'view':
1254
            case 'sequence':
1255
                $sql = "
1256
					SELECT relacl AS acl FROM pg_catalog.pg_class
1257
					WHERE relname='{$object}'
1258
						AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace
1259
							WHERE nspname='{$c_schema}')";
1260
1261
                break;
1262
            case 'database':
1263
                $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'";
1264
1265
                break;
1266
            case 'function':
1267
                // Since we fetch functions by oid, they are already constrained to
1268
                // the current schema.
1269
                $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'";
1270
1271
                break;
1272
            case 'language':
1273
                $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'";
1274
1275
                break;
1276
            case 'schema':
1277
                $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'";
1278
1279
                break;
1280
            case 'tablespace':
1281
                $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'";
1282
1283
                break;
1284
            default:
1285
                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 PHPPgAdmin\Database\Privileges.
Loading history...
1286
        }
1287
1288
        // Fetch the ACL for object
1289
        $acl = $this->selectField($sql, 'acl');
1290
        if ($acl == -1) {
0 ignored issues
show
introduced by
The condition $acl == -1 is always false.
Loading history...
1291
            return -2;
1292
        }
1293
1294
        if ($acl == '' || $acl == null) {
1295
            return [];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array() returns the type array which is incompatible with the documented return type PHPPgAdmin\Database\Privileges.
Loading history...
1296
        }
1297
1298
        return $this->_parseACL($acl);
1299
    }
1300
1301
    /**
1302
     * Internal function used for parsing ACLs.
1303
     *
1304
     * @param $acl The ACL to parse (of type aclitem[])
1305
     *
1306
     * @return Privileges array
1307
     */
1308
    public function _parseACL($acl)
1 ignored issue
show
Coding Style introduced by
Public method name "Postgres::_parseACL" must not be prefixed with an underscore
Loading history...
1309
    {
1310
        // Take off the first and last characters (the braces)
1311
        $acl = substr($acl, 1, strlen($acl) - 2);
1312
1313
        // Pick out individual ACE's by carefully parsing.  This is necessary in order
1314
        // to cope with usernames and stuff that contain commas
1315
        $aces      = [];
1316
        $i         = $j         = 0;
1317
        $in_quotes = false;
1318
        while ($i < strlen($acl)) {
1319
            // If current char is a double quote and it's not escaped, then
1320
            // enter quoted bit
1321
            $char = substr($acl, $i, 1);
1322
            if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) {
1323
                $in_quotes = !$in_quotes;
1 ignored issue
show
introduced by
$in_quotes is of type mixed, thus it always evaluated to false.
Loading history...
1324
            } elseif ($char == ',' && !$in_quotes) {
1325
                // Add text so far to the array
1326
                $aces[] = substr($acl, $j, $i - $j);
1327
                $j      = $i + 1;
1328
            }
1329
            ++$i;
1330
        }
1331
        // Add final text to the array
1332
        $aces[] = substr($acl, $j);
1333
1334
        // Create the array to be returned
1335
        $temp = [];
1336
1337
        // For each ACE, generate an entry in $temp
1338
        foreach ($aces as $v) {
1339
            // If the ACE begins with a double quote, strip them off both ends
1340
            // and unescape backslashes and double quotes
1341
            $unquote = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $unquote is dead and can be removed.
Loading history...
1342
            if (strpos($v, '"') === 0) {
1343
                $v = substr($v, 1, strlen($v) - 2);
1344
                $v = str_replace('\\"', '"', $v);
1345
                $v = str_replace('\\\\', '\\', $v);
1346
            }
1347
1348
            // Figure out type of ACE (public, user or group)
1349
            if (strpos($v, '=') === 0) {
1350
                $atype = 'public';
1351
            } else {
1352
                if ($this->hasRoles()) {
1353
                    $atype = 'role';
1354
                } else {
1355
                    if (strpos($v, 'group ') === 0) {
1356
                        $atype = 'group';
1357
                        // Tear off 'group' prefix
1358
                        $v = substr($v, 6);
1359
                    } else {
1360
                        $atype = 'user';
1361
                    }
1362
                }
1363
            }
1364
1365
            // Break on unquoted equals sign...
1366
            $i         = 0;
1367
            $in_quotes = false;
1368
            $entity    = null;
1369
            $chars     = null;
1370
            while ($i < strlen($v)) {
1371
                // If current char is a double quote and it's not escaped, then
1372
                // enter quoted bit
1373
                $char      = substr($v, $i, 1);
1374
                $next_char = substr($v, $i + 1, 1);
1375
                if ($char == '"' && ($i == 0 || $next_char != '"')) {
1376
                    $in_quotes = !$in_quotes;
1 ignored issue
show
introduced by
The condition $in_quotes is always false.
Loading history...
1377
                } // Skip over escaped double quotes
1378
                elseif ($char == '"' && $next_char == '"') {
0 ignored issues
show
Coding Style introduced by
Expected "} elseif (...) \n"; found " // Skip over escaped double quotes\n elseif (...) {\n"
Loading history...
1379
                    ++$i;
1380
                } elseif ($char == '=' && !$in_quotes) {
1381
                    // Split on current equals sign
1382
                    $entity = substr($v, 0, $i);
1383
                    $chars  = substr($v, $i + 1);
1384
1385
                    break;
1386
                }
1387
                ++$i;
1388
            }
1389
1390
            // Check for quoting on entity name, and unescape if necessary
1391
            if (strpos($entity, '"') === 0) {
1392
                $entity = substr($entity, 1, strlen($entity) - 2);
1393
                $entity = str_replace('""', '"', $entity);
1394
            }
1395
1396
            // New row to be added to $temp
1397
            // (type, grantee, privileges, grantor, grant option?
1398
            $row = [$atype, $entity, [], '', []];
1399
1400
            // Loop over chars and add privs to $row
1401
            for ($i = 0; $i < strlen($chars); ++$i) {
1402
                // Append to row's privs list the string representing
1403
                // the privilege
1404
                $char = substr($chars, $i, 1);
1405
                if ($char == '*') {
1406
                    $row[4][] = $this->privmap[substr($chars, $i - 1, 1)];
1407
                } elseif ($char == '/') {
1408
                    $grantor = substr($chars, $i + 1);
1409
                    // Check for quoting
1410
                    if (strpos($grantor, '"') === 0) {
1411
                        $grantor = substr($grantor, 1, strlen($grantor) - 2);
1412
                        $grantor = str_replace('""', '"', $grantor);
1413
                    }
1414
                    $row[3] = $grantor;
1415
1416
                    break;
1417
                } else {
1418
                    if (!isset($this->privmap[$char])) {
1419
                        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 PHPPgAdmin\Database\Privileges.
Loading history...
1420
                    }
1421
1422
                    $row[2][] = $this->privmap[$char];
1423
                }
1424
            }
1425
1426
            // Append row to temp
1427
            $temp[] = $row;
1428
        }
1429
1430
        return $temp;
1431
    }
1432
1433
    // Rule functions
1434
1435
    /**
1436
     * Returns an array containing a function's properties.
1437
     *
1438
     * @param array $f The array of data for the function
1439
     *
1440
     * @return array An array containing the properties
1441
     */
1442
    public function getFunctionProperties($f)
1443
    {
1444
        $temp = [];
1445
1446
        // Volatility
1447
        if ($f['provolatile'] == 'v') {
1448
            $temp[] = 'VOLATILE';
1449
        } elseif ($f['provolatile'] == 'i') {
1450
            $temp[] = 'IMMUTABLE';
1451
        } elseif ($f['provolatile'] == 's') {
1452
            $temp[] = 'STABLE';
1453
        } else {
1454
            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...
1455
        }
1456
1457
        // Null handling
1458
        $f['proisstrict'] = $this->phpBool($f['proisstrict']);
1459
        if ($f['proisstrict']) {
1460
            $temp[] = 'RETURNS NULL ON NULL INPUT';
1461
        } else {
1462
            $temp[] = 'CALLED ON NULL INPUT';
1463
        }
1464
1465
        // Security
1466
        $f['prosecdef'] = $this->phpBool($f['prosecdef']);
1467
        if ($f['prosecdef']) {
1468
            $temp[] = 'SECURITY DEFINER';
1469
        } else {
1470
            $temp[] = 'SECURITY INVOKER';
1471
        }
1472
1473
        return $temp;
1474
    }
1475
1476
    /**
1477
     * Updates (replaces) a function.
1478
     *
1479
     * @param int    $function_oid The OID of the function
1480
     * @param string $funcname     The name of the function to create
1481
     * @param string $newname      The new name for the function
1482
     * @param array  $args         The array of argument types
1483
     * @param string $returns      The return type
1484
     * @param string $definition   The definition for the new function
1485
     * @param string $language     The language the function is written for
1486
     * @param array  $flags        An array of optional flags
1487
     * @param bool   $setof        True if returns a set, false otherwise
1488
     * @param string $funcown
1489
     * @param string $newown
1490
     * @param string $funcschema
1491
     * @param string $newschema
1492
     * @param float  $cost
1493
     * @param int    $rows
1494
     * @param string $comment      The comment on the function
1495
     *
1496
     * @return bool|int 0 success
1497
     */
1498
    public function setFunction(
1499
        $function_oid,
0 ignored issues
show
Unused Code introduced by
The parameter $function_oid 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

1499
        /** @scrutinizer ignore-unused */ $function_oid,

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...
1500
        $funcname,
1501
        $newname,
1502
        $args,
1503
        $returns,
1504
        $definition,
1505
        $language,
1506
        $flags,
1507
        $setof,
1508
        $funcown,
1509
        $newown,
1510
        $funcschema,
1511
        $newschema,
1512
        $cost,
1513
        $rows,
1514
        $comment
1515
    ) {
1516
        // Begin a transaction
1517
        $status = $this->beginTransaction();
1518
        if ($status != 0) {
1519
            $this->rollbackTransaction();
1520
1521
            return -1;
1522
        }
1523
1524
        // Replace the existing function
1525
        $status = $this->createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, true);
0 ignored issues
show
Bug introduced by
$args of type array is incompatible with the type string expected by parameter $args of PHPPgAdmin\Database\Postgres::createFunction(). ( Ignorable by Annotation )

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

1525
        $status = $this->createFunction($funcname, /** @scrutinizer ignore-type */ $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, true);
Loading history...
1526
        if ($status != 0) {
1527
            $this->rollbackTransaction();
1528
1529
            return $status;
1530
        }
1531
1532
        $f_schema = $this->_schema;
1533
        $this->fieldClean($f_schema);
1534
1535
        // Rename the function, if necessary
1536
        $this->fieldClean($newname);
1537
        /* $funcname is escaped in createFunction */
1538
        if ($funcname != $newname) {
1539
            $sql    = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) RENAME TO \"{$newname}\"";
1540
            $status = $this->execute($sql);
1541
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
1542
                $this->rollbackTransaction();
1543
1544
                return -5;
1545
            }
1546
1547
            $funcname = $newname;
1548
        }
1549
1550
        // Alter the owner, if necessary
1551
        if ($this->hasFunctionAlterOwner()) {
1552
            $this->fieldClean($newown);
1553
            if ($funcown != $newown) {
1554
                $sql    = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) OWNER TO \"{$newown}\"";
1555
                $status = $this->execute($sql);
1556
                if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
1557
                    $this->rollbackTransaction();
1558
1559
                    return -6;
1560
                }
1561
            }
1562
        }
1563
1564
        // Alter the schema, if necessary
1565
        if ($this->hasFunctionAlterSchema()) {
1566
            $this->fieldClean($newschema);
1567
            /* $funcschema is escaped in createFunction */
1568
            if ($funcschema != $newschema) {
1569
                $sql    = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) SET SCHEMA \"{$newschema}\"";
1570
                $status = $this->execute($sql);
1571
                if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
1572
                    $this->rollbackTransaction();
1573
1574
                    return -7;
1575
                }
1576
            }
1577
        }
1578
1579
        return $this->endTransaction();
1580
    }
1581
1582
    /**
1583
     * Creates a new function.
1584
     *
1585
     * @param string $funcname   The name of the function to create
1586
     * @param string $args       A comma separated string of types
1587
     * @param string $returns    The return type
1588
     * @param string $definition The definition for the new function
1589
     * @param string $language   The language the function is written for
1590
     * @param array  $flags      An array of optional flags
1591
     * @param bool   $setof      True if it returns a set, false otherwise
1592
     * @param string $cost       cost the planner should use in the function  execution step
1593
     * @param int    $rows       number of rows planner should estimate will be returned
1594
     * @param string $comment    Comment for the function
1595
     * @param bool   $replace    (optional) True if OR REPLACE, false for
1596
     *                           normal
1597
     *
1598
     * @return bool|int 0 success
1599
     */
1600
    public function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, $replace = false)
1601
    {
1602
        // Begin a transaction
1603
        $status = $this->beginTransaction();
1604
        if ($status != 0) {
1605
            $this->rollbackTransaction();
1606
1607
            return -1;
1608
        }
1609
1610
        $this->fieldClean($funcname);
1611
        $this->clean($args);
1612
        $this->fieldClean($language);
1613
        $this->arrayClean($flags);
1614
        $this->clean($cost);
1615
        $this->clean($rows);
1616
        $f_schema = $this->_schema;
1617
        $this->fieldClean($f_schema);
1618
1619
        $sql = 'CREATE';
1620
        if ($replace) {
1621
            $sql .= ' OR REPLACE';
1622
        }
1623
1624
        $sql .= " FUNCTION \"{$f_schema}\".\"{$funcname}\" (";
1625
1626
        if ($args != '') {
1627
            $sql .= $args;
1628
        }
1629
1630
        // For some reason, the returns field cannot have quotes...
1631
        $sql .= ') RETURNS ';
1632
        if ($setof) {
1633
            $sql .= 'SETOF ';
1634
        }
1635
1636
        $sql .= "{$returns} AS ";
1637
1638
        if (is_array($definition)) {
1 ignored issue
show
introduced by
The condition is_array($definition) is always false.
Loading history...
1639
            $this->arrayClean($definition);
1640
            $sql .= "'" . $definition[0] . "'";
1641
            if ($definition[1]) {
1642
                $sql .= ",'" . $definition[1] . "'";
1643
            }
1644
        } else {
1645
            $this->clean($definition);
1646
            $sql .= "'" . $definition . "'";
1647
        }
1648
1649
        $sql .= " LANGUAGE \"{$language}\"";
1650
1651
        // Add costs
1652
        if (!empty($cost)) {
1653
            $sql .= " COST {$cost}";
1654
        }
1655
1656
        if ($rows != 0) {
1657
            $sql .= " ROWS {$rows}";
1658
        }
1659
1660
        // Add flags
1661
        foreach ($flags as $v) {
1662
            // Skip default flags
1663
            if ($v == '') {
1664
                continue;
1665
            }
1666
1667
            $sql .= "\n{$v}";
1668
        }
1669
1670
        $status = $this->execute($sql);
1671
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
1672
            $this->rollbackTransaction();
1673
1674
            return -3;
1675
        }
1676
1677
        /* set the comment */
1678
        $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment);
1679
        if ($status != 0) {
1680
            $this->rollbackTransaction();
1681
1682
            return -4;
1683
        }
1684
1685
        return $this->endTransaction();
1686
    }
1687
1688
    /**
1689
     * Drops a function.
1690
     *
1691
     * @param int  $function_oid The OID of the function to drop
1692
     * @param bool $cascade      True to cascade drop, false to restrict
1693
     *
1694
     * @return int 0 if operation was successful
1695
     */
1696
    public function dropFunction($function_oid, $cascade)
1697
    {
1698
        // Function comes in with $object as function OID
1699
        $fn       = $this->getFunction($function_oid);
1700
        $f_schema = $this->_schema;
1701
        $this->fieldClean($f_schema);
1702
        $this->fieldClean($fn->fields['proname']);
1703
1704
        $sql = "DROP FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})";
1705
        if ($cascade) {
1706
            $sql .= ' CASCADE';
1707
        }
1708
1709
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
1710
    }
1711
1712
    /**
1713
     * Returns all details for a particular function.
1714
     *
1715
     * @param int $function_oid
1716
     *
1717
     * @return \PHPPgAdmin\ADORecordSet Function info
1718
     *
1719
     * @internal param string The $func name of the function to retrieve
1720
     */
1721
    public function getFunction($function_oid)
1722
    {
1723
        $this->clean($function_oid);
1724
1725
        $sql = "
1726
			SELECT
1727
				pc.oid AS prooid, proname,
1728
				pg_catalog.pg_get_userbyid(proowner) AS proowner,
1729
				nspname as proschema, lanname as prolanguage, procost, prorows,
1730
				pg_catalog.format_type(prorettype, NULL) as proresult, prosrc,
1731
				probin, proretset, proisstrict, provolatile, prosecdef,
1732
				pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments,
1733
				proargnames AS proargnames,
1734
				pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment,
1735
				proconfig,
1736
				(select array_agg( (select typname from pg_type pt
1737
					where pt.oid = p.oid) ) from unnest(proallargtypes) p)
1738
				AS proallarguments,
1739
				proargmodes
1740
			FROM
1741
				pg_catalog.pg_proc pc, pg_catalog.pg_language pl,
1742
				pg_catalog.pg_namespace pn
1743
			WHERE
1744
				pc.oid = '{$function_oid}'::oid AND pc.prolang = pl.oid
1745
				AND pc.pronamespace = pn.oid
1746
			";
1747
1748
        return $this->selectSet($sql);
1749
    }
1750
1751
    /**
1752
     * Returns all details for a particular type.
1753
     *
1754
     * @param string $typname The name of the view to retrieve
1755
     *
1756
     * @return \PHPPgAdmin\ADORecordSet info
1757
     */
1758
    public function getType($typname)
1759
    {
1760
        $this->clean($typname);
1761
1762
        $sql = "SELECT typtype, typbyval, typname, typinput AS typin, typoutput AS typout, typlen, typalign
1763
			FROM pg_type WHERE typname='{$typname}'";
1764
1765
        return $this->selectSet($sql);
1766
    }
1767
1768
    /**
1769
     * Returns a list of all types in the database.
1770
     *
1771
     * @param bool $all        If true, will find all available types, if false just those in search path
1772
     * @param bool $tabletypes If true, will include table types
1773
     * @param bool $domains    If true, will include domains
1774
     *
1775
     * @return \PHPPgAdmin\ADORecordSet A recordset
1776
     */
1777
    public function getTypes($all = false, $tabletypes = false, $domains = false)
1778
    {
1779
        if ($all) {
1780
            $where = '1 = 1';
1781
        } else {
1782
            $c_schema = $this->_schema;
1783
            $this->clean($c_schema);
1784
            $where = "n.nspname = '{$c_schema}'";
1785
        }
1786
        // Never show system table types
1787
        $where2 = "AND c.relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname LIKE 'pg@_%' ESCAPE '@')";
1788
1789
        // Create type filter
1790
        $tqry = "'c'";
1791
        if ($tabletypes) {
1792
            $tqry .= ", 'r', 'v'";
1793
        }
1794
1795
        // Create domain filter
1796
        if (!$domains) {
1797
            $where .= " AND t.typtype != 'd'";
1798
        }
1799
1800
        $sql = "SELECT
1801
				t.typname AS basename,
1802
				pg_catalog.format_type(t.oid, NULL) AS typname,
1803
				pu.usename AS typowner,
1804
				t.typtype,
1805
				pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment
1806
			FROM (pg_catalog.pg_type t
1807
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace)
1808
				LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid
1809
			WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2}))
1810
			AND t.typname !~ '^_'
1811
			AND {$where}
1812
			ORDER BY typname
1813
		";
1814
1815
        return $this->selectSet($sql);
1816
    }
1817
1818
    /**
1819
     * Creates a new type.
1820
     *
1821
     * @param string $typname
1822
     * @param string $typin
1823
     * @param string $typout
1824
     * @param string $typlen
1825
     * @param string $typdef
1826
     * @param string $typelem
1827
     * @param string $typdelim
1828
     * @param string $typbyval
1829
     * @param string $typalign
1830
     * @param string $typstorage
1831
     *
1832
     * @return int 0 if operation was successful
1833
     *
1834
     * @internal param $ ...
1835
     */
1836
    public function createType(
1837
        $typname,
1838
        $typin,
1839
        $typout,
1840
        $typlen,
1841
        $typdef,
1842
        $typelem,
1843
        $typdelim,
1844
        $typbyval,
1845
        $typalign,
1846
        $typstorage
1847
    ) {
1848
        $f_schema = $this->_schema;
1849
        $this->fieldClean($f_schema);
1850
        $this->fieldClean($typname);
1851
        $this->fieldClean($typin);
1852
        $this->fieldClean($typout);
1853
1854
        $sql = "
1855
			CREATE TYPE \"{$f_schema}\".\"{$typname}\" (
1856
				INPUT = \"{$typin}\",
1857
				OUTPUT = \"{$typout}\",
1858
				INTERNALLENGTH = {$typlen}";
1859
        if ($typdef != '') {
1860
            $sql .= ", DEFAULT = {$typdef}";
1861
        }
1862
1863
        if ($typelem != '') {
1864
            $sql .= ", ELEMENT = {$typelem}";
1865
        }
1866
1867
        if ($typdelim != '') {
1868
            $sql .= ", DELIMITER = {$typdelim}";
1869
        }
1870
1871
        if ($typbyval) {
1872
            $sql .= ', PASSEDBYVALUE, ';
1873
        }
1874
1875
        if ($typalign != '') {
1876
            $sql .= ", ALIGNMENT = {$typalign}";
1877
        }
1878
1879
        if ($typstorage != '') {
1880
            $sql .= ", STORAGE = {$typstorage}";
1881
        }
1882
1883
        $sql .= ')';
1884
1885
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
1886
    }
1887
1888
    /**
1889
     * Drops a type.
1890
     *
1891
     * @param $typname The name of the type to drop
1892
     * @param $cascade True to cascade drop, false to restrict
1893
     *
1894
     * @return int 0 if operation was successful
1895
     */
1896
    public function dropType($typname, $cascade)
1897
    {
1898
        $f_schema = $this->_schema;
1899
        $this->fieldClean($f_schema);
1900
        $this->fieldClean($typname);
1901
1902
        $sql = "DROP TYPE \"{$f_schema}\".\"{$typname}\"";
1903
        if ($cascade) {
1904
            $sql .= ' CASCADE';
1905
        }
1906
1907
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
1908
    }
1909
1910
    /**
1911
     * Creates a new enum type in the database.
1912
     *
1913
     * @param $name       The name of the type
1914
     * @param $values     An array of values
1915
     * @param $typcomment Type comment
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Type 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...
1916
     *
1917
     * @return bool|int 0 success
1918
     */
1919
    public function createEnumType($name, $values, $typcomment)
1920
    {
1921
        $f_schema = $this->_schema;
1922
        $this->fieldClean($f_schema);
1923
        $this->fieldClean($name);
1924
1925
        if (empty($values)) {
1926
            return -2;
1927
        }
1928
1929
        $status = $this->beginTransaction();
1930
        if ($status != 0) {
1931
            return -1;
1932
        }
1933
1934
        $values = array_unique($values);
1935
1936
        $nbval = count($values);
1937
1938
        for ($i = 0; $i < $nbval; ++$i) {
1939
            $this->clean($values[$i]);
1940
        }
1941
1942
        $sql = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS ENUM ('";
1943
        $sql .= implode("','", $values);
1944
        $sql .= "')";
1945
1946
        $status = $this->execute($sql);
1947
        if ($status) {
0 ignored issues
show
introduced by
$status is of type ADORecordSet, thus it always evaluated to true.
Loading history...
1948
            $this->rollbackTransaction();
1949
1950
            return -1;
1951
        }
1952
1953
        if ($typcomment != '') {
1954
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
1955
            if ($status) {
1956
                $this->rollbackTransaction();
1957
1958
                return -1;
1959
            }
1960
        }
1961
1962
        return $this->endTransaction();
1963
    }
1964
1965
    /**
1966
     * Get defined values for a given enum.
1967
     *
1968
     * @param $name
1969
     *
1970
     * @return \PHPPgAdmin\ADORecordSet A recordset
1971
     */
1972
    public function getEnumValues($name)
1973
    {
1974
        $this->clean($name);
1975
1976
        $sql = "SELECT enumlabel AS enumval
1977
		FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON (t.oid=e.enumtypid)
1978
		WHERE t.typname = '{$name}' ORDER BY e.oid";
1979
1980
        return $this->selectSet($sql);
1981
    }
1982
1983
    // Operator functions
1984
1985
    /**
1986
     * Creates a new composite type in the database.
1987
     *
1988
     * @param $name       The name of the type
1989
     * @param $fields     The number of fields
1990
     * @param $field      An array of field names
1991
     * @param $type       An array of field types
1992
     * @param $array      An array of '' or '[]' for each type if it's an array or not
1993
     * @param $length     An array of field lengths
1994
     * @param $colcomment An array of comments
1995
     * @param $typcomment Type comment
1996
     *
1997
     * @return bool|int 0 success
1998
     */
1999
    public function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment)
2000
    {
2001
        $f_schema = $this->_schema;
2002
        $this->fieldClean($f_schema);
2003
        $this->fieldClean($name);
2004
2005
        $status = $this->beginTransaction();
2006
        if ($status != 0) {
2007
            return -1;
2008
        }
2009
2010
        $found       = false;
2011
        $first       = true;
2012
        $comment_sql = ''; // Accumulate comments for the columns
2013
        $sql         = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS (";
2014
        for ($i = 0; $i < $fields; ++$i) {
2015
            $this->fieldClean($field[$i]);
2016
            $this->clean($type[$i]);
2017
            $this->clean($length[$i]);
2018
            $this->clean($colcomment[$i]);
2019
2020
            // Skip blank columns - for user convenience
2021
            if ($field[$i] == '' || $type[$i] == '') {
2022
                continue;
2023
            }
2024
2025
            // If not the first column, add a comma
2026
            if (!$first) {
2027
                $sql .= ', ';
2028
            } else {
2029
                $first = false;
2030
            }
2031
2032
            switch ($type[$i]) {
2033
                // Have to account for weird placing of length for with/without
2034
                // time zone types
2035
                case 'timestamp with time zone':
2036
                case 'timestamp without time zone':
2037
                    $qual = substr($type[$i], 9);
2038
                    $sql .= "\"{$field[$i]}\" timestamp";
2039
                    if ($length[$i] != '') {
2040
                        $sql .= "({$length[$i]})";
2041
                    }
2042
2043
                    $sql .= $qual;
2044
2045
                    break;
2046
                case 'time with time zone':
2047
                case 'time without time zone':
2048
                    $qual = substr($type[$i], 4);
2049
                    $sql .= "\"{$field[$i]}\" time";
2050
                    if ($length[$i] != '') {
2051
                        $sql .= "({$length[$i]})";
2052
                    }
2053
2054
                    $sql .= $qual;
2055
2056
                    break;
2057
                default:
2058
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
2059
                    if ($length[$i] != '') {
2060
                        $sql .= "({$length[$i]})";
2061
                    }
2062
            }
2063
            // Add array qualifier if necessary
2064
            if ($array[$i] == '[]') {
2065
                $sql .= '[]';
2066
            }
2067
2068
            if ($colcomment[$i] != '') {
2069
                $comment_sql .= "COMMENT ON COLUMN \"{$f_schema}\".\"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
2070
            }
2071
2072
            $found = true;
2073
        }
2074
2075
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
2076
            return -1;
2077
        }
2078
2079
        $sql .= ')';
2080
2081
        $status = $this->execute($sql);
2082
        if ($status) {
2083
            $this->rollbackTransaction();
2084
2085
            return -1;
2086
        }
2087
2088
        if ($typcomment != '') {
2089
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
2090
            if ($status) {
2091
                $this->rollbackTransaction();
2092
2093
                return -1;
2094
            }
2095
        }
2096
2097
        if ($comment_sql != '') {
2098
            $status = $this->execute($comment_sql);
2099
            if ($status) {
2100
                $this->rollbackTransaction();
2101
2102
                return -1;
2103
            }
2104
        }
2105
2106
        return $this->endTransaction();
2107
    }
2108
2109
    /**
2110
     * Returns a list of all casts in the database.
2111
     *
2112
     * @return \PHPPgAdmin\ADORecordSet All casts
2113
     */
2114
    public function getCasts()
2115
    {
2116
        $conf = $this->conf;
2117
2118
        if ($conf['show_system']) {
2119
            $where = '';
2120
        } else {
2121
            $where = '
2122
				AND n1.nspname NOT LIKE $$pg\_%$$
2123
				AND n2.nspname NOT LIKE $$pg\_%$$
2124
				AND n3.nspname NOT LIKE $$pg\_%$$
2125
			';
2126
        }
2127
2128
        $sql = "
2129
			SELECT
2130
				c.castsource::pg_catalog.regtype AS castsource,
2131
				c.casttarget::pg_catalog.regtype AS casttarget,
2132
				CASE WHEN c.castfunc=0 THEN NULL
2133
				ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc,
2134
				c.castcontext,
2135
				obj_description(c.oid, 'pg_cast') as castcomment
2136
			FROM
2137
				(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),
2138
				pg_catalog.pg_type t1,
2139
				pg_catalog.pg_type t2,
2140
				pg_catalog.pg_namespace n1,
2141
				pg_catalog.pg_namespace n2
2142
			WHERE
2143
				c.castsource=t1.oid
2144
				AND c.casttarget=t2.oid
2145
				AND t1.typnamespace=n1.oid
2146
				AND t2.typnamespace=n2.oid
2147
				{$where}
2148
			ORDER BY 1, 2
2149
		";
2150
2151
        return $this->selectSet($sql);
2152
    }
2153
2154
    /**
2155
     * Returns a list of all conversions in the database.
2156
     *
2157
     * @return \PHPPgAdmin\ADORecordSet All conversions
2158
     */
2159
    public function getConversions()
2160
    {
2161
        $c_schema = $this->_schema;
2162
        $this->clean($c_schema);
2163
        $sql = "
2164
			SELECT
2165
			       c.conname,
2166
			       pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding,
2167
			       pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding,
2168
			       c.condefault,
2169
			       pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment
2170
			FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n
2171
			WHERE n.oid = c.connamespace
2172
			      AND n.nspname='{$c_schema}'
2173
			ORDER BY 1;
2174
		";
2175
2176
        return $this->selectSet($sql);
2177
    }
2178
2179
    // Operator Class functions
2180
2181
    /**
2182
     * Edits a rule on a table OR view.
2183
     *
2184
     * @param $name    The name of the new rule
2185
     * @param $event   SELECT, INSERT, UPDATE or DELETE
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\SELECT 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...
2186
     * @param $table   Table on which to create the rule
2187
     * @param $where   When to execute the rule, '' indicates always
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\When 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...
2188
     * @param $instead True if an INSTEAD rule, false otherwise
2189
     * @param $type    NOTHING for a do nothing rule, SOMETHING to use given action
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\NOTHING 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...
2190
     * @param $action  The action to take
2191
     *
2192
     * @return int 0 if operation was successful
2193
     */
2194
    public function setRule($name, $event, $table, $where, $instead, $type, $action)
2195
    {
2196
        return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true);
2197
    }
2198
2199
    // FTS functions
2200
2201
    /**
2202
     * Creates a rule.
2203
     *
2204
     * @param string $name    The name of the new rule
2205
     * @param string $event   SELECT, INSERT, UPDATE or DELETE
2206
     * @param string $table   Table on which to create the rule
2207
     * @param string $where   When to execute the rule, '' indicates always
2208
     * @param bool   $instead True if an INSTEAD rule, false otherwise
2209
     * @param string $type    NOTHING for a do nothing rule, SOMETHING to use given action
2210
     * @param string $action  The action to take
2211
     * @param bool   $replace (optional) True to replace existing rule, false
2212
     *                        otherwise
2213
     *
2214
     * @return int 0 if operation was successful
2215
     */
2216
    public function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false)
2217
    {
2218
        $f_schema = $this->_schema;
2219
        $this->fieldClean($f_schema);
2220
        $this->fieldClean($name);
2221
        $this->fieldClean($table);
2222
        if (!in_array($event, $this->rule_events, true)) {
2223
            return -1;
2224
        }
2225
2226
        $sql = 'CREATE';
2227
        if ($replace) {
2228
            $sql .= ' OR REPLACE';
2229
        }
2230
2231
        $sql .= " RULE \"{$name}\" AS ON {$event} TO \"{$f_schema}\".\"{$table}\"";
2232
        // Can't escape WHERE clause
2233
        if ($where != '') {
2234
            $sql .= " WHERE {$where}";
2235
        }
2236
2237
        $sql .= ' DO';
2238
        if ($instead) {
2239
            $sql .= ' INSTEAD';
2240
        }
2241
2242
        if ($type == 'NOTHING') {
2243
            $sql .= ' NOTHING';
2244
        } else {
2245
            $sql .= " ({$action})";
2246
        }
2247
2248
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2249
    }
2250
2251
    /**
2252
     * Removes a rule from a table OR view.
2253
     *
2254
     * @param string $rule     The rule to drop
2255
     * @param string $relation The relation from which to drop
2256
     * @param string $cascade  True to cascade drop, false to restrict
2257
     *
2258
     * @return int 0 if operation was successful
2259
     */
2260
    public function dropRule($rule, $relation, $cascade)
2261
    {
2262
        $f_schema = $this->_schema;
2263
        $this->fieldClean($f_schema);
2264
        $this->fieldClean($rule);
2265
        $this->fieldClean($relation);
2266
2267
        $sql = "DROP RULE \"{$rule}\" ON \"{$f_schema}\".\"{$relation}\"";
2268
        if ($cascade) {
2269
            $sql .= ' CASCADE';
2270
        }
2271
2272
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2273
    }
2274
2275
    /**
2276
     * Grabs a single trigger.
2277
     *
2278
     * @param string $table   The name of a table whose triggers to retrieve
2279
     * @param string $trigger The name of the trigger to retrieve
2280
     *
2281
     * @return \PHPPgAdmin\ADORecordSet A recordset
2282
     */
2283
    public function getTrigger($table, $trigger)
2284
    {
2285
        $c_schema = $this->_schema;
2286
        $this->clean($c_schema);
2287
        $this->clean($table);
2288
        $this->clean($trigger);
2289
2290
        $sql = "
2291
			SELECT * FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c
2292
			WHERE t.tgrelid=c.oid AND c.relname='{$table}' AND t.tgname='{$trigger}'
2293
				AND c.relnamespace=(
2294
					SELECT oid FROM pg_catalog.pg_namespace
2295
					WHERE nspname='{$c_schema}')";
2296
2297
        return $this->selectSet($sql);
2298
    }
2299
2300
    /**
2301
     * A helper function for getTriggers that translates
2302
     * an array of attribute numbers to an array of field names.
2303
     * Note: Only needed for pre-7.4 servers, this function is deprecated.
2304
     *
2305
     * @param string $trigger An array containing fields from the trigger table
2306
     *
2307
     * @return string The trigger definition string
2308
     */
2309
    public function getTriggerDef($trigger)
2310
    {
2311
        $this->fieldArrayClean($trigger);
2312
        // Constants to figure out tgtype
2313
        if (!defined('TRIGGER_TYPE_ROW')) {
2314
            define('TRIGGER_TYPE_ROW', 1 << 0);
2315
        }
2316
2317
        if (!defined('TRIGGER_TYPE_BEFORE')) {
2318
            define('TRIGGER_TYPE_BEFORE', 1 << 1);
2319
        }
2320
2321
        if (!defined('TRIGGER_TYPE_INSERT')) {
2322
            define('TRIGGER_TYPE_INSERT', 1 << 2);
2323
        }
2324
2325
        if (!defined('TRIGGER_TYPE_DELETE')) {
2326
            define('TRIGGER_TYPE_DELETE', 1 << 3);
2327
        }
2328
2329
        if (!defined('TRIGGER_TYPE_UPDATE')) {
2330
            define('TRIGGER_TYPE_UPDATE', 1 << 4);
2331
        }
2332
2333
        $trigger['tgisconstraint'] = $this->phpBool($trigger['tgisconstraint']);
2334
        $trigger['tgdeferrable']   = $this->phpBool($trigger['tgdeferrable']);
2335
        $trigger['tginitdeferred'] = $this->phpBool($trigger['tginitdeferred']);
2336
2337
        // Constraint trigger or normal trigger
2338
        if ($trigger['tgisconstraint']) {
2339
            $tgdef = 'CREATE CONSTRAINT TRIGGER ';
2340
        } else {
2341
            $tgdef = 'CREATE TRIGGER ';
2342
        }
2343
2344
        $tgdef .= "\"{$trigger['tgname']}\" ";
2345
2346
        // Trigger type
2347
        $findx = 0;
2348
        if (($trigger['tgtype'] & TRIGGER_TYPE_BEFORE) == TRIGGER_TYPE_BEFORE) {
2349
            $tgdef .= 'BEFORE';
2350
        } else {
2351
            $tgdef .= 'AFTER';
2352
        }
2353
2354
        if (($trigger['tgtype'] & TRIGGER_TYPE_INSERT) == TRIGGER_TYPE_INSERT) {
2355
            $tgdef .= ' INSERT';
2356
            ++$findx;
2357
        }
2358
        if (($trigger['tgtype'] & TRIGGER_TYPE_DELETE) == TRIGGER_TYPE_DELETE) {
2359
            if ($findx > 0) {
2360
                $tgdef .= ' OR DELETE';
2361
            } else {
2362
                $tgdef .= ' DELETE';
2363
                ++$findx;
2364
            }
2365
        }
2366
        if (($trigger['tgtype'] & TRIGGER_TYPE_UPDATE) == TRIGGER_TYPE_UPDATE) {
2367
            if ($findx > 0) {
2368
                $tgdef .= ' OR UPDATE';
2369
            } else {
2370
                $tgdef .= ' UPDATE';
2371
            }
2372
        }
2373
2374
        $f_schema = $this->_schema;
2375
        $this->fieldClean($f_schema);
2376
        // Table name
2377
        $tgdef .= " ON \"{$f_schema}\".\"{$trigger['relname']}\" ";
2378
2379
        // Deferrability
2380
        if ($trigger['tgisconstraint']) {
2381
            if ($trigger['tgconstrrelid'] != 0) {
2382
                // Assume constrelname is not null
2383
                $tgdef .= " FROM \"{$trigger['tgconstrrelname']}\" ";
2384
            }
2385
            if (!$trigger['tgdeferrable']) {
2386
                $tgdef .= 'NOT ';
2387
            }
2388
2389
            $tgdef .= 'DEFERRABLE INITIALLY ';
2390
            if ($trigger['tginitdeferred']) {
2391
                $tgdef .= 'DEFERRED ';
2392
            } else {
2393
                $tgdef .= 'IMMEDIATE ';
2394
            }
2395
        }
2396
2397
        // Row or statement
2398
        if ($trigger['tgtype'] & TRIGGER_TYPE_ROW == TRIGGER_TYPE_ROW) {
2399
            $tgdef .= 'FOR EACH ROW ';
2400
        } else {
2401
            $tgdef .= 'FOR EACH STATEMENT ';
2402
        }
2403
2404
        // Execute procedure
2405
        $tgdef .= "EXECUTE PROCEDURE \"{$trigger['tgfname']}\"(";
2406
2407
        // Parameters
2408
        // Escape null characters
2409
        $v = addcslashes($trigger['tgargs'], "\0");
2410
        // Split on escaped null characters
2411
        $params = explode('\\000', $v);
2412
        for ($findx = 0; $findx < $trigger['tgnargs']; ++$findx) {
2413
            $param = "'" . str_replace('\'', '\\\'', $params[$findx]) . "'";
2414
            $tgdef .= $param;
2415
            if ($findx < ($trigger['tgnargs'] - 1)) {
2416
                $tgdef .= ', ';
2417
            }
2418
        }
2419
2420
        // Finish it off
2421
        $tgdef .= ')';
2422
2423
        return $tgdef;
2424
    }
2425
2426
    /**
2427
     * Returns a list of all functions that can be used in triggers.
2428
     */
2429
    public function getTriggerFunctions()
2430
    {
2431
        return $this->getFunctions(true, 'trigger');
2432
    }
2433
2434
    /**
2435
     * Returns a list of all functions in the database.
2436
     *
2437
     * @param bool $all  If true, will find all available functions, if false just those in search path
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\If 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...
2438
     * @param      $type If not null, will find all functions with return value = type
2439
     *
2440
     * @return \PHPPgAdmin\ADORecordSet All functions
2441
     */
2442
    public function getFunctions($all = false, $type = null)
2443
    {
2444
        if ($all) {
2445
            $where    = 'pg_catalog.pg_function_is_visible(p.oid)';
2446
            $distinct = 'DISTINCT ON (p.proname)';
2447
2448
            if ($type) {
2449
                $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') ";
2450
            }
2451
        } else {
2452
            $c_schema = $this->_schema;
2453
            $this->clean($c_schema);
2454
            $where    = "n.nspname = '{$c_schema}'";
2455
            $distinct = '';
2456
        }
2457
2458
        $sql = "
2459
			SELECT
2460
				{$distinct}
2461
				p.oid AS prooid,
2462
				p.proname,
2463
				p.proretset,
2464
				pg_catalog.format_type(p.prorettype, NULL) AS proresult,
2465
				pg_catalog.oidvectortypes(p.proargtypes) AS proarguments,
2466
				pl.lanname AS prolanguage,
2467
				pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment,
2468
				p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
2469
				CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns,
2470
				coalesce(u.usename::text,p.proowner::text) AS proowner
2471
2472
			FROM pg_catalog.pg_proc p
2473
				INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
2474
				INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang
2475
				LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner
2476
			WHERE NOT p.proisagg
2477
				AND {$where}
2478
			ORDER BY p.proname, proresult
2479
			";
2480
2481
        return $this->selectSet($sql);
2482
    }
2483
2484
    /**
2485
     * Creates a trigger.
2486
     *
2487
     * @param $tgname  The name of the trigger to create
2488
     * @param $table   The name of the table
2489
     * @param $tgproc  The function to execute
2490
     * @param $tgtime  BEFORE or AFTER
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\BEFORE 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...
2491
     * @param $tgevent Event
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Event 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...
2492
     * @param $tgfrequency
2493
     * @param $tgargs  The function arguments
2494
     *
2495
     * @return int 0 if operation was successful
2496
     */
2497
    public function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs)
2498
    {
2499
        $f_schema = $this->_schema;
2500
        $this->fieldClean($f_schema);
2501
        $this->fieldClean($tgname);
2502
        $this->fieldClean($table);
2503
        $this->fieldClean($tgproc);
2504
2505
        /* No Statement Level Triggers in PostgreSQL (by now) */
2506
        $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime}
2507
				{$tgevent} ON \"{$f_schema}\".\"{$table}\"
2508
				FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})";
2509
2510
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2511
    }
2512
2513
    /**
2514
     * Alters a trigger.
2515
     *
2516
     * @param $table   The name of the table containing the trigger
2517
     * @param $trigger The name of the trigger to alter
2518
     * @param $name    The new name for the trigger
2519
     *
2520
     * @return int 0 if operation was successful
2521
     */
2522
    public function alterTrigger($table, $trigger, $name)
2523
    {
2524
        $f_schema = $this->_schema;
2525
        $this->fieldClean($f_schema);
2526
        $this->fieldClean($table);
2527
        $this->fieldClean($trigger);
2528
        $this->fieldClean($name);
2529
2530
        $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_schema}\".\"{$table}\" RENAME TO \"{$name}\"";
2531
2532
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2533
    }
2534
2535
    /**
2536
     * Drops a trigger.
2537
     *
2538
     * @param $tgname  The name of the trigger to drop
2539
     * @param $table   The table from which to drop the trigger
2540
     * @param $cascade True to cascade drop, false to restrict
2541
     *
2542
     * @return int 0 if operation was successful
2543
     */
2544
    public function dropTrigger($tgname, $table, $cascade)
2545
    {
2546
        $f_schema = $this->_schema;
2547
        $this->fieldClean($f_schema);
2548
        $this->fieldClean($tgname);
2549
        $this->fieldClean($table);
2550
2551
        $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\"";
2552
        if ($cascade) {
2553
            $sql .= ' CASCADE';
2554
        }
2555
2556
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2557
    }
2558
2559
    /**
2560
     * Enables a trigger.
2561
     *
2562
     * @param $tgname The name of the trigger to enable
2563
     * @param $table  The table in which to enable the trigger
2564
     *
2565
     * @return int 0 if operation was successful
2566
     */
2567
    public function enableTrigger($tgname, $table)
2568
    {
2569
        $f_schema = $this->_schema;
2570
        $this->fieldClean($f_schema);
2571
        $this->fieldClean($tgname);
2572
        $this->fieldClean($table);
2573
2574
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\"";
2575
2576
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2577
    }
2578
2579
    /**
2580
     * Disables a trigger.
2581
     *
2582
     * @param $tgname The name of the trigger to disable
2583
     * @param $table  The table in which to disable the trigger
2584
     *
2585
     * @return int 0 if operation was successful
2586
     */
2587
    public function disableTrigger($tgname, $table)
2588
    {
2589
        $f_schema = $this->_schema;
2590
        $this->fieldClean($f_schema);
2591
        $this->fieldClean($tgname);
2592
        $this->fieldClean($table);
2593
2594
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\"";
2595
2596
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2597
    }
2598
2599
    /**
2600
     * Returns a list of all operators in the database.
2601
     *
2602
     * @return \PHPPgAdmin\ADORecordSet All operators
2603
     */
2604
    public function getOperators()
2605
    {
2606
        $c_schema = $this->_schema;
2607
        $this->clean($c_schema);
2608
        // We stick with the subselects here, as you cannot ORDER BY a regtype
2609
        $sql = "
2610
			SELECT
2611
            	po.oid,	po.oprname,
2612
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname,
2613
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname,
2614
				po.oprresult::pg_catalog.regtype AS resultname,
2615
		        pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment
2616
			FROM
2617
				pg_catalog.pg_operator po
2618
			WHERE
2619
				po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')
2620
			ORDER BY
2621
				po.oprname, oprleftname, oprrightname
2622
		";
2623
2624
        return $this->selectSet($sql);
2625
    }
2626
2627
    /**
2628
     * Drops an operator.
2629
     *
2630
     * @param $operator_oid The OID of the operator to drop
2631
     * @param $cascade      True to cascade drop, false to restrict
2632
     *
2633
     * @return int 0 if operation was successful
2634
     */
2635
    public function dropOperator($operator_oid, $cascade)
2636
    {
2637
        // Function comes in with $object as operator OID
2638
        $opr      = $this->getOperator($operator_oid);
2639
        $f_schema = $this->_schema;
2640
        $this->fieldClean($f_schema);
2641
        $this->fieldClean($opr->fields['oprname']);
2642
2643
        $sql = "DROP OPERATOR \"{$f_schema}\".{$opr->fields['oprname']} (";
2644
        // Quoting or formatting here???
2645
        if ($opr->fields['oprleftname'] !== null) {
2646
            $sql .= $opr->fields['oprleftname'] . ', ';
2647
        } else {
2648
            $sql .= 'NONE, ';
2649
        }
2650
2651
        if ($opr->fields['oprrightname'] !== null) {
2652
            $sql .= $opr->fields['oprrightname'] . ')';
2653
        } else {
2654
            $sql .= 'NONE)';
2655
        }
2656
2657
        if ($cascade) {
2658
            $sql .= ' CASCADE';
2659
        }
2660
2661
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2662
    }
2663
2664
    /**
2665
     * Returns all details for a particular operator.
2666
     *
2667
     * @param $operator_oid The oid of the operator
2668
     *
2669
     * @return Function info
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...
2670
     */
2671
    public function getOperator($operator_oid)
2672
    {
2673
        $this->clean($operator_oid);
2674
2675
        $sql = "
2676
			SELECT
2677
            	po.oid, po.oprname,
2678
				oprleft::pg_catalog.regtype AS oprleftname,
2679
				oprright::pg_catalog.regtype AS oprrightname,
2680
				oprresult::pg_catalog.regtype AS resultname,
2681
				po.oprcanhash,
2682
				oprcanmerge,
2683
				oprcom::pg_catalog.regoperator AS oprcom,
2684
				oprnegate::pg_catalog.regoperator AS oprnegate,
2685
				po.oprcode::pg_catalog.regproc AS oprcode,
2686
				po.oprrest::pg_catalog.regproc AS oprrest,
2687
				po.oprjoin::pg_catalog.regproc AS oprjoin
2688
			FROM
2689
				pg_catalog.pg_operator po
2690
			WHERE
2691
				po.oid='{$operator_oid}'
2692
		";
2693
2694
        return $this->selectSet($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->selectSet($sql) returns the type ADORecordSet which is incompatible with the documented return type PHPPgAdmin\Database\Function.
Loading history...
2695
    }
2696
2697
    /**
2698
     *  Gets all opclasses.
2699
     *
2700
     * @return \PHPPgAdmin\ADORecordSet A recordset
2701
     */
2702
    public function getOpClasses()
2703
    {
2704
        $c_schema = $this->_schema;
2705
        $this->clean($c_schema);
2706
        $sql = "
2707
			SELECT
2708
				pa.amname, po.opcname,
2709
				po.opcintype::pg_catalog.regtype AS opcintype,
2710
				po.opcdefault,
2711
				pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment
2712
			FROM
2713
				pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn
2714
			WHERE
2715
				po.opcmethod=pa.oid
2716
				AND po.opcnamespace=pn.oid
2717
				AND pn.nspname='{$c_schema}'
2718
			ORDER BY 1,2
2719
			";
2720
2721
        return $this->selectSet($sql);
2722
    }
2723
2724
    /**
2725
     * Gets all languages.
2726
     *
2727
     * @param bool|true $all True to get all languages, regardless of show_system
2728
     *
2729
     * @return \PHPPgAdmin\ADORecordSet A recordset
2730
     */
2731
    public function getLanguages($all = false)
2732
    {
2733
        $conf = $this->conf;
2734
2735
        if ($conf['show_system'] || $all) {
2736
            $where = '';
2737
        } else {
2738
            $where = 'WHERE lanispl';
2739
        }
2740
2741
        $sql = "
2742
			SELECT
2743
				lanname, lanpltrusted,
2744
				lanplcallfoid::pg_catalog.regproc AS lanplcallf
2745
			FROM
2746
				pg_catalog.pg_language
2747
			{$where}
2748
			ORDER BY lanname
2749
		";
2750
2751
        return $this->selectSet($sql);
2752
    }
2753
2754
    /**
2755
     * Retrieves information for all tablespaces.
2756
     *
2757
     * @param bool $all Include all tablespaces (necessary when moving objects back to the default space)
2758
     *
2759
     * @return \PHPPgAdmin\ADORecordSet A recordset
2760
     */
2761
    public function getTablespaces($all = false)
2762
    {
2763
        $conf = $this->conf;
2764
2765
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2766
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2767
					FROM pg_catalog.pg_tablespace";
2768
2769
        if (!$conf['show_system'] && !$all) {
2770
            $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$';
2771
        }
2772
2773
        $sql .= ' ORDER BY spcname';
2774
2775
        return $this->selectSet($sql);
2776
    }
2777
2778
    // Misc functions
2779
2780
    /**
2781
     * Retrieves a tablespace's information.
2782
     *
2783
     * @param $spcname
2784
     *
2785
     * @return \PHPPgAdmin\ADORecordSet A recordset
2786
     */
2787
    public function getTablespace($spcname)
2788
    {
2789
        $this->clean($spcname);
2790
2791
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2792
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2793
					FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'";
2794
2795
        return $this->selectSet($sql);
2796
    }
2797
2798
    /**
2799
     * Creates a tablespace.
2800
     *
2801
     * @param        $spcname  The name of the tablespace to create
2802
     * @param        $spcowner The owner of the tablespace. '' for current
2803
     * @param        $spcloc   The directory in which to create the tablespace
2804
     * @param string $comment
2805
     *
2806
     * @return int 0 success
2807
     */
2808
    public function createTablespace($spcname, $spcowner, $spcloc, $comment = '')
2809
    {
2810
        $this->fieldClean($spcname);
2811
        $this->clean($spcloc);
2812
2813
        $sql = "CREATE TABLESPACE \"{$spcname}\"";
2814
2815
        if ($spcowner != '') {
2816
            $this->fieldClean($spcowner);
2817
            $sql .= " OWNER \"{$spcowner}\"";
2818
        }
2819
2820
        $sql .= " LOCATION '{$spcloc}'";
2821
2822
        $status = $this->execute($sql);
2823
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
2824
            return -1;
2825
        }
2826
2827
        if ($comment != '' && $this->hasSharedComments()) {
2828
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2829
            if ($status != 0) {
2830
                return -2;
2831
            }
2832
        }
2833
2834
        return 0;
2835
    }
2836
2837
    /**
2838
     * Alters a tablespace.
2839
     *
2840
     * @param        $spcname The name of the tablespace
2841
     * @param        $name    The new name for the tablespace
2842
     * @param        $owner   The new owner for the tablespace
2843
     * @param string $comment
2844
     *
2845
     * @return bool|int 0 success
2846
     */
2847
    public function alterTablespace($spcname, $name, $owner, $comment = '')
2848
    {
2849
        $this->fieldClean($spcname);
2850
        $this->fieldClean($name);
2851
        $this->fieldClean($owner);
2852
2853
        // Begin transaction
2854
        $status = $this->beginTransaction();
2855
        if ($status != 0) {
2856
            return -1;
2857
        }
2858
2859
        // Owner
2860
        $sql    = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\"";
2861
        $status = $this->execute($sql);
2862
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
2863
            $this->rollbackTransaction();
2864
2865
            return -2;
2866
        }
2867
2868
        // Rename (only if name has changed)
2869
        if ($name != $spcname) {
2870
            $sql    = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\"";
2871
            $status = $this->execute($sql);
2872
            if ($status != 0) {
2873
                $this->rollbackTransaction();
2874
2875
                return -3;
2876
            }
2877
2878
            $spcname = $name;
2879
        }
2880
2881
        // Set comment if it has changed
2882
        if (trim($comment) != '' && $this->hasSharedComments()) {
2883
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2884
            if ($status != 0) {
2885
                return -4;
2886
            }
2887
        }
2888
2889
        return $this->endTransaction();
2890
    }
2891
2892
    /**
2893
     * Drops a tablespace.
2894
     *
2895
     * @param $spcname The name of the domain to drop
2896
     *
2897
     * @return int 0 if operation was successful
2898
     */
2899
    public function dropTablespace($spcname)
2900
    {
2901
        $this->fieldClean($spcname);
2902
2903
        $sql = "DROP TABLESPACE \"{$spcname}\"";
2904
2905
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2906
    }
2907
2908
    /**
2909
     * Analyze a database.
2910
     *
2911
     * @param string $table (optional) The table to analyze
2912
     *
2913
     * @return bool 0 if successful
2914
     */
2915
    public function analyzeDB($table = '')
2916
    {
2917
        if ($table != '') {
2918
            $f_schema = $this->_schema;
2919
            $this->fieldClean($f_schema);
2920
            $this->fieldClean($table);
2921
2922
            $sql = "ANALYZE \"{$f_schema}\".\"{$table}\"";
2923
        } else {
2924
            $sql = 'ANALYZE';
2925
        }
2926
2927
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type boolean.
Loading history...
2928
    }
2929
2930
    /**
2931
     * Vacuums a database.
2932
     *
2933
     * @param string $table   The table to vacuum
2934
     * @param bool   $analyze If true, also does analyze
2935
     * @param bool   $full    If true, selects "full" vacuum
2936
     * @param bool   $freeze  If true, selects aggressive "freezing" of tuples
2937
     *
2938
     * @return bool 0 if successful
2939
     */
2940
    public function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false)
2941
    {
2942
        $sql = 'VACUUM';
2943
        if ($full) {
2944
            $sql .= ' FULL';
2945
        }
2946
2947
        if ($freeze) {
2948
            $sql .= ' FREEZE';
2949
        }
2950
2951
        if ($analyze) {
2952
            $sql .= ' ANALYZE';
2953
        }
2954
2955
        if ($table != '') {
2956
            $f_schema = $this->_schema;
2957
            $this->fieldClean($f_schema);
2958
            $this->fieldClean($table);
2959
            $sql .= " \"{$f_schema}\".\"{$table}\"";
2960
        }
2961
2962
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type boolean.
Loading history...
2963
    }
2964
2965
    /**
2966
     * Returns all autovacuum global configuration.
2967
     *
2968
     * @return array associative array array( param => value, ...)
2969
     */
2970
    public function getAutovacuum()
2971
    {
2972
        $_defaults = $this->selectSet(
2973
            "SELECT name, setting
2974
			FROM pg_catalog.pg_settings
2975
			WHERE
2976
				name = 'autovacuum'
2977
				OR name = 'autovacuum_vacuum_threshold'
2978
				OR name = 'autovacuum_vacuum_scale_factor'
2979
				OR name = 'autovacuum_analyze_threshold'
2980
				OR name = 'autovacuum_analyze_scale_factor'
2981
				OR name = 'autovacuum_vacuum_cost_delay'
2982
				OR name = 'autovacuum_vacuum_cost_limit'
2983
				OR name = 'vacuum_freeze_min_age'
2984
				OR name = 'autovacuum_freeze_max_age'
2985
			"
2986
        );
2987
2988
        $ret = [];
2989
        while (!$_defaults->EOF) {
2990
            $ret[$_defaults->fields['name']] = $_defaults->fields['setting'];
2991
            $_defaults->moveNext();
2992
        }
2993
2994
        return $ret;
2995
    }
2996
2997
    /**
2998
     * Returns all available autovacuum per table information.
2999
     *
3000
     * @param string $table
3001
     * @param bool   $vacenabled
3002
     * @param int    $vacthreshold
3003
     * @param int    $vacscalefactor
3004
     * @param int    $anathresold
3005
     * @param int    $anascalefactor
3006
     * @param int    $vaccostdelay
3007
     * @param int    $vaccostlimit
3008
     *
3009
     * @return bool 0 if successful
3010
     */
3011
    public function saveAutovacuum(
3012
        $table,
3013
        $vacenabled,
3014
        $vacthreshold,
3015
        $vacscalefactor,
3016
        $anathresold,
3017
        $anascalefactor,
3018
        $vaccostdelay,
3019
        $vaccostlimit
3020
    ) {
3021
        $f_schema = $this->_schema;
3022
        $this->fieldClean($f_schema);
3023
        $this->fieldClean($table);
3024
3025
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
3026
3027
        if (!empty($vacenabled)) {
3028
            $this->clean($vacenabled);
3029
            $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...
3030
        }
3031
        if (!empty($vacthreshold)) {
3032
            $this->clean($vacthreshold);
3033
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
3034
        }
3035
        if (!empty($vacscalefactor)) {
3036
            $this->clean($vacscalefactor);
3037
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
3038
        }
3039
        if (!empty($anathresold)) {
3040
            $this->clean($anathresold);
3041
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
3042
        }
3043
        if (!empty($anascalefactor)) {
3044
            $this->clean($anascalefactor);
3045
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
3046
        }
3047
        if (!empty($vaccostdelay)) {
3048
            $this->clean($vaccostdelay);
3049
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
3050
        }
3051
        if (!empty($vaccostlimit)) {
3052
            $this->clean($vaccostlimit);
3053
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
3054
        }
3055
3056
        $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...
3057
3058
        return $this->execute($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute($sql) returns the type ADORecordSet which is incompatible with the documented return type boolean.
Loading history...
3059
    }
3060
3061
    // Type conversion routines
3062
3063
    /**
3064
     * Drops autovacuum config for a table.
3065
     *
3066
     * @param string $table The table
3067
     *
3068
     * @return bool 0 if successful
3069
     */
3070
    public function dropAutovacuum($table)
3071
    {
3072
        $f_schema = $this->_schema;
3073
        $this->fieldClean($f_schema);
3074
        $this->fieldClean($table);
3075
3076
        return $this->execute(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute(' ...cuum_cost_limit );') returns the type ADORecordSet which is incompatible with the documented return type boolean.
Loading history...
3077
            "
3078
			ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
3079
				autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
3080
				autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
3081
			);"
3082
        );
3083
    }
3084
3085
    /**
3086
     * Returns all available process information.
3087
     *
3088
     * @param $database (optional) Find only connections to specified database
3089
     *
3090
     * @return \PHPPgAdmin\ADORecordSet A recordset
3091
     */
3092
    public function getProcesses($database = null)
3093
    {
3094
        if ($database === null) {
3095
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
3096
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
3097
				FROM pg_catalog.pg_stat_activity
3098
				ORDER BY datname, usename, pid";
3099
        } else {
3100
            $this->clean($database);
3101
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
3102
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
3103
				FROM pg_catalog.pg_stat_activity
3104
				WHERE datname='{$database}'
3105
				ORDER BY usename, pid";
3106
        }
3107
3108
        return $this->selectSet($sql);
3109
    }
3110
3111
    // interfaces Statistics collector functions
3112
3113
    /**
3114
     * Returns table locks information in the current database.
3115
     *
3116
     * @return \PHPPgAdmin\ADORecordSet A recordset
3117
     */
3118
    public function getLocks()
3119
    {
3120
        $conf = $this->conf;
3121
3122
        if (!$conf['show_system']) {
3123
            $where = 'AND pn.nspname NOT LIKE $$pg\_%$$';
3124
        } else {
3125
            $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
3126
        }
3127
3128
        $sql = "
3129
			SELECT
3130
				pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction,
3131
				(select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid'
3132
					and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction
3133
			FROM
3134
				pg_catalog.pg_locks pl,
3135
				pg_catalog.pg_class pc,
3136
				pg_catalog.pg_namespace pn
3137
			WHERE
3138
				pl.relation = pc.oid AND pc.relnamespace=pn.oid
3139
			{$where}
3140
			ORDER BY pid,nspname,tablename";
3141
3142
        return $this->selectSet($sql);
3143
    }
3144
3145
    /**
3146
     * Sends a cancel or kill command to a process.
3147
     *
3148
     * @param $pid    The ID of the backend process
3149
     * @param $signal 'CANCEL'
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'CANCEL' at position 0 could not be parsed: Unknown type name ''CANCEL'' at position 0 in 'CANCEL'.
Loading history...
3150
     *
3151
     * @return int 0 success
3152
     */
3153
    public function sendSignal($pid, $signal)
3154
    {
3155
        // Clean
3156
        $pid = (int) $pid;
3157
3158
        if ($signal == 'CANCEL') {
3159
            $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val";
3160
        } elseif ($signal == 'KILL') {
3161
            $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val";
3162
        } else {
3163
            return -1;
3164
        }
3165
3166
        // Execute the query
3167
        $val = $this->selectField($sql, 'val');
3168
3169
        if ($val === 'f') {
0 ignored issues
show
introduced by
The condition $val === 'f' is always false.
Loading history...
3170
            return -1;
3171
        }
3172
3173
        if ($val === 't') {
0 ignored issues
show
introduced by
The condition $val === 't' is always false.
Loading history...
3174
            return 0;
3175
        }
3176
3177
        return -1;
3178
    }
3179
3180
    /**
3181
     * Executes an SQL script as a series of SQL statements.  Returns
3182
     * the result of the final step.  This is a very complicated lexer
3183
     * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in
3184
     * the PostgreSQL source code.
3185
     * XXX: It does not handle multibyte languages properly.
3186
     *
3187
     * @param string        $name     Entry in $_FILES to use
3188
     * @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...
3189
     *
3190
     * @return bool true for general success, false on any failure
3191
     */
3192
    public function executeScript($name, $callback = null)
3193
    {
3194
        // This whole function isn't very encapsulated, but hey...
3195
        $conn = $this->conn->_connectionID;
3196
        if (!is_uploaded_file($_FILES[$name]['tmp_name'])) {
3197
            return false;
3198
        }
3199
3200
        $fd = fopen($_FILES[$name]['tmp_name'], 'rb');
3201
        if (!$fd) {
0 ignored issues
show
introduced by
$fd is of type resource|false, thus it always evaluated to false.
Loading history...
3202
            return false;
3203
        }
3204
3205
        // Build up each SQL statement, they can be multiline
3206
        $query_buf    = null;
3207
        $query_start  = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $query_start is dead and can be removed.
Loading history...
3208
        $in_quote     = 0;
3209
        $in_xcomment  = 0;
3210
        $bslash_count = 0;
3211
        $dol_quote    = null;
3212
        $paren_level  = 0;
3213
        $len          = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $len is dead and can be removed.
Loading history...
3214
        $i            = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $i is dead and can be removed.
Loading history...
3215
        $prevlen      = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $prevlen is dead and can be removed.
Loading history...
3216
        $thislen      = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $thislen is dead and can be removed.
Loading history...
3217
        $lineno       = 0;
3218
3219
        // Loop over each line in the file
3220
        while (!feof($fd)) {
3221
            $line = fgets($fd);
3222
            ++$lineno;
3223
3224
            // Nothing left on line? Then ignore...
3225
            if (trim($line) == '') {
3226
                continue;
3227
            }
3228
3229
            $len         = strlen($line);
3230
            $query_start = 0;
3231
3232
            /*
3233
             * Parse line, looking for command separators.
3234
             *
3235
             * The current character is at line[i], the prior character at line[i
3236
             * - prevlen], the next character at line[i + thislen].
3237
             */
3238
            $prevlen = 0;
3239
            $thislen = ($len > 0) ? 1 : 0;
3240
3241
            for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) {
3242
                /* was the previous character a backslash? */
3243
                if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') {
3244
                    ++$bslash_count;
3245
                } else {
3246
                    $bslash_count = 0;
3247
                }
3248
3249
                /*
3250
                 * It is important to place the in_* test routines before the
3251
                 * in_* detection routines. i.e. we have to test if we are in
3252
                 * a quote before testing for comments.
3253
                 */
3254
3255
                /* in quote? */
3256
                if ($in_quote !== 0) {
3257
                    /*
3258
                     * end of quote if matching non-backslashed character.
3259
                     * backslashes don't count for double quotes, though.
3260
                     */
3261
                    if (substr($line, $i, 1) == $in_quote &&
3262
                        ($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...
3263
                        $in_quote = 0;
3264
                    }
3265
                } /* in or end of $foo$ type quote? */
3266
                else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* in or end of $foo$ type quote? */\n else {\n"
Loading history...
3267
                    if ($dol_quote) {
3268
                        if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) {
3269
                            $this->advance_1($i, $prevlen, $thislen);
3270
                            while (substr($line, $i, 1) != '$') {
3271
                                $this->advance_1($i, $prevlen, $thislen);
3272
                            }
3273
3274
                            $dol_quote = null;
3275
                        }
3276
                    } /* start of extended comment? */
3277
                    else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* start of extended comment? */\n else {\n"
Loading history...
3278
                        if (substr($line, $i, 2) == '/*') {
3279
                            ++$in_xcomment;
3280
                            if ($in_xcomment == 1) {
3281
                                $this->advance_1($i, $prevlen, $thislen);
3282
                            }
3283
                        } /* in or end of extended comment? */
3284
                        else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* in or end of extended comment? */\n else {\n"
Loading history...
3285
                            if ($in_xcomment) {
3286
                                if (substr($line, $i, 2) == '*/' && !--$in_xcomment) {
3287
                                    $this->advance_1($i, $prevlen, $thislen);
3288
                                }
3289
                            } /* start of quote? */
3290
                            else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* start of quote? */\n else {\n"
Loading history...
3291
                                if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
3292
                                    $in_quote = substr($line, $i, 1);
3293
                                } /*
3294
                                 * start of $foo$ type quote?
3295
                                 */
3296
                                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...
3297
                                    if (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
3298
                                        $dol_end   = strpos(substr($line, $i + 1), '$');
3299
                                        $dol_quote = substr($line, $i, $dol_end + 1);
3300
                                        $this->advance_1($i, $prevlen, $thislen);
3301
                                        while (substr($line, $i, 1) != '$') {
3302
                                            $this->advance_1($i, $prevlen, $thislen);
3303
                                        }
3304
                                    } /* single-line comment? truncate line */
3305
                                    else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* single-line comment? truncate line */\n else {\n"
Loading history...
3306
                                        if (substr($line, $i, 2) == '--') {
3307
                                            $line = substr($line, 0, $i); /* remove comment */
3308
                                            break;
3309
                                        } /* count nested parentheses */
3310
3311
                                        if (substr($line, $i, 1) == '(') {
3312
                                            ++$paren_level;
3313
                                        } else {
3314
                                            if (substr($line, $i, 1) == ')' && $paren_level > 0) {
3315
                                                --$paren_level;
3316
                                            } /* semicolon? then send query */
3317
                                            else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* semicolon? then send query */\n else {\n"
Loading history...
3318
                                                if (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) {
3319
                                                    $subline = substr(substr($line, 0, $i), $query_start);
3320
                                                    /*
3321
                                                     * insert a cosmetic newline, if this is not the first
3322
                                                     * line in the buffer
3323
                                                     */
3324
                                                    if (strlen($query_buf) > 0) {
3325
                                                        $query_buf .= "\n";
3326
                                                    }
3327
3328
                                                    /* append the line to the query buffer */
3329
                                                    $query_buf .= $subline;
3330
                                                    /* is there anything in the query_buf? */
3331
                                                    if (trim($query_buf)) {
3332
                                                        $query_buf .= ';';
3333
3334
                                                        // Execute the query. PHP cannot execute
3335
                                                        // empty queries, unlike libpq
3336
                                                        $res = @pg_query($conn, $query_buf);
3337
3338
                                                        // Call the callback function for display
3339
                                                        if ($callback !== null) {
3340
                                                            $callback($query_buf, $res, $lineno);
3341
                                                        }
3342
3343
                                                        // Check for COPY request
3344
                                                        if (pg_result_status($res) == 4) {
3345
                                                            // 4 == PGSQL_COPY_FROM
3346
                                                            while (!feof($fd)) {
3347
                                                                $copy = fgets($fd, 32768);
3348
                                                                ++$lineno;
3349
                                                                pg_put_line($conn, $copy);
3350
                                                                if ($copy == "\\.\n" || $copy == "\\.\r\n") {
3351
                                                                    pg_end_copy($conn);
3352
3353
                                                                    break;
3354
                                                                }
3355
                                                            }
3356
                                                        }
3357
                                                    }
3358
                                                    $query_buf   = null;
3359
                                                    $query_start = $i + $thislen;
3360
                                                }
3361
3362
                                                /*
3363
                                                 * keyword or identifier?
3364
                                                 * We grab the whole string so that we don't
3365
                                                 * mistakenly see $foo$ inside an identifier as the start
3366
                                                 * of a dollar quote.
3367
                                                 */
3368
                                                // XXX: multibyte here
3369
                                                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...
3370
                                                    if (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) {
3371
                                                        $sub = substr($line, $i, $thislen);
3372
                                                        while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) {
3373
                                                            /* keep going while we still have identifier chars */
3374
                                                            $this->advance_1($i, $prevlen, $thislen);
3375
                                                            $sub = substr($line, $i, $thislen);
3376
                                                        }
3377
                                                        // Since we're now over the next character to be examined, it is necessary
3378
                                                        // to move back one space.
3379
                                                        $i -= $prevlen;
3380
                                                    }
3381
                                                }
3382
                                            }
3383
                                        }
3384
                                    }
3385
                                }
3386
                            }
3387
                        }
3388
                    }
3389
                }
3390
            } // end for
3391
3392
            /* Put the rest of the line in the query buffer. */
3393
            $subline = substr($line, $query_start);
3394
            if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) {
3395
                if (strlen($query_buf) > 0) {
3396
                    $query_buf .= "\n";
3397
                }
3398
3399
                $query_buf .= $subline;
3400
            }
3401
3402
            $line = null;
1 ignored issue
show
Unused Code introduced by
The assignment to $line is dead and can be removed.
Loading history...
3403
        } // end while
3404
3405
        /*
3406
         * Process query at the end of file without a semicolon, so long as
3407
         * it's non-empty.
3408
         */
3409
        if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) {
3410
            // Execute the query
3411
            $res = @pg_query($conn, $query_buf);
3412
3413
            // Call the callback function for display
3414
            if ($callback !== null) {
3415
                $callback($query_buf, $res, $lineno);
3416
            }
3417
3418
            // Check for COPY request
3419
            if (pg_result_status($res) == 4) {
3420
                // 4 == PGSQL_COPY_FROM
3421
                while (!feof($fd)) {
3422
                    $copy = fgets($fd, 32768);
3423
                    ++$lineno;
3424
                    pg_put_line($conn, $copy);
3425
                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
3426
                        pg_end_copy($conn);
3427
3428
                        break;
3429
                    }
3430
                }
3431
            }
3432
        }
3433
3434
        fclose($fd);
3435
3436
        return true;
3437
    }
3438
3439
    /**
3440
     * A private helper method for executeScript that advances the
3441
     * character by 1.  In psql this is careful to take into account
3442
     * multibyte languages, but we don't at the moment, so this function
3443
     * is someone redundant, since it will always advance by 1.
3444
     *
3445
     * @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...
3446
     * @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...
3447
     * @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...
3448
     */
3449
    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...
3450
    {
3451
        $prevlen = $thislen;
3452
        $i += $thislen;
3453
        $thislen = 1;
3454
    }
3455
3456
    /**
3457
     * Private helper method to detect a valid $foo$ quote delimiter at
3458
     * the start of the parameter dquote.
3459
     *
3460
     * @param string $dquote
3461
     *
3462
     * @return true if valid, false otherwise
3463
     */
3464
    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...
3465
    {
3466
        // XXX: support multibyte
3467
        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...
3468
    }
3469
3470
    // Capabilities
3471
3472
    /**
3473
     * Returns a recordset of all columns in a query.  Supports paging.
3474
     *
3475
     * @param string $type       Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier,
3476
     *                           or 'SELECT" if it's a select query
3477
     * @param string $table      The base table of the query.  NULL for no table.
3478
     * @param string $query      The query that is being executed.  NULL for no query.
3479
     * @param string $sortkey    The column number to sort by, or '' or null for no sorting
3480
     * @param string $sortdir    The direction in which to sort the specified column ('asc' or 'desc')
3481
     * @param int    $page       The page of the relation to retrieve
3482
     * @param int    $page_size  The number of rows per page
3483
     * @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...
3484
     *
3485
     * @return A  recordset on success
3486
     * @return -1 transaction error
0 ignored issues
show
Coding Style introduced by
Only 1 @return tag is allowed in a function comment
Loading history...
3487
     * @return -2 counting error
3488
     * @return -3 page or page_size invalid
3489
     * @return -4 unknown type
3490
     * @return -5 failed setting transaction read only
3491
     */
3492
    public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages)
3493
    {
3494
        // Check that we're not going to divide by zero
3495
        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...
3496
            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 PHPPgAdmin\Database\A.
Loading history...
3497
        }
3498
3499
        // If $type is TABLE, then generate the query
3500
        switch ($type) {
3501
            case 'TABLE':
3502
                if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
3503
                    $orderby = [$sortkey => $sortdir];
3504
                } else {
3505
                    $orderby = [];
3506
                }
3507
3508
                $query = $this->getSelectSQL($table, [], [], [], $orderby);
3509
3510
                break;
3511
            case 'QUERY':
3512
            case 'SELECT':
3513
                // Trim query
3514
                $query = trim($query);
3515
                // Trim off trailing semi-colon if there is one
3516
                if (substr($query, strlen($query) - 1, 1) == ';') {
3517
                    $query = substr($query, 0, strlen($query) - 1);
3518
                }
3519
3520
                break;
3521
            default:
3522
                return -4;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -4 returns the type integer which is incompatible with the documented return type PHPPgAdmin\Database\A.
Loading history...
3523
        }
3524
3525
        // Generate count query
3526
        $count = "SELECT COUNT(*) AS total FROM (${query}) AS sub";
3527
3528
        // Open a transaction
3529
        $status = $this->beginTransaction();
3530
        if ($status != 0) {
3531
            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 PHPPgAdmin\Database\A.
Loading history...
3532
        }
3533
3534
        // If backend supports read only queries, then specify read only mode
3535
        // to avoid side effects from repeating queries that do writes.
3536
        if ($this->hasReadOnlyQueries()) {
3537
            $status = $this->execute('SET TRANSACTION READ ONLY');
3538
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
3539
                $this->rollbackTransaction();
3540
3541
                return -5;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -5 returns the type integer which is incompatible with the documented return type PHPPgAdmin\Database\A.
Loading history...
3542
            }
3543
        }
3544
3545
        // Count the number of rows
3546
        $total = $this->browseQueryCount($query, $count);
3547
        if ($total < 0) {
3548
            $this->rollbackTransaction();
3549
3550
            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 PHPPgAdmin\Database\A.
Loading history...
3551
        }
3552
3553
        // Calculate max pages
3554
        $max_pages = ceil($total / $page_size);
3555
3556
        // Check that page is less than or equal to max pages
3557
        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...
3558
            $this->rollbackTransaction();
3559
3560
            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 PHPPgAdmin\Database\A.
Loading history...
3561
        }
3562
3563
        // Set fetch mode to NUM so that duplicate field names are properly returned
3564
        // for non-table queries.  Since the SELECT feature only allows selecting one
3565
        // table, duplicate fields shouldn't appear.
3566
        if ($type == 'QUERY') {
3567
            $this->conn->setFetchMode(ADODB_FETCH_NUM);
3568
        }
3569
3570
        // Figure out ORDER BY.  Sort key is always the column number (based from one)
3571
        // of the column to order by.  Only need to do this for non-TABLE queries
3572
        if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
3573
            $orderby = " ORDER BY {$sortkey}";
3574
            // Add sort order
3575
            if ($sortdir == 'desc') {
3576
                $orderby .= ' DESC';
3577
            } else {
3578
                $orderby .= ' ASC';
3579
            }
3580
        } else {
3581
            $orderby = '';
3582
        }
3583
3584
        // Actually retrieve the rows, with offset and limit
3585
        $rs     = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size);
3586
        $status = $this->endTransaction();
3587
        if ($status != 0) {
3588
            $this->rollbackTransaction();
3589
3590
            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 PHPPgAdmin\Database\A.
Loading history...
3591
        }
3592
3593
        return $rs;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $rs returns the type ADORecordSet which is incompatible with the documented return type PHPPgAdmin\Database\A.
Loading history...
3594
    }
3595
3596
    /**
3597
     * Generates the SQL for the 'select' function.
3598
     *
3599
     * @param $table   The table from which to select
3600
     * @param $show    An array of columns to show.  Empty array means all columns.
3601
     * @param $values  An array mapping columns to values
3602
     * @param $ops     An array of the operators to use
3603
     * @param $orderby (optional) An array of column numbers or names (one based)
3604
     *                 mapped to sort direction (asc or desc or '' or null) to order by
3605
     *
3606
     * @return The SQL query
3607
     */
3608
    public function getSelectSQL($table, $show, $values, $ops, $orderby = [])
3609
    {
3610
        $this->fieldArrayClean($show);
3611
3612
        // If an empty array is passed in, then show all columns
3613
        if (sizeof($show) == 0) {
3614
            if ($this->hasObjectID($table)) {
3615
                $sql = "SELECT \"{$this->id}\", * FROM ";
3616
            } else {
3617
                $sql = 'SELECT * FROM ';
3618
            }
3619
        } else {
3620
            // Add oid column automatically to results for editing purposes
3621
            if (!in_array($this->id, $show, true) && $this->hasObjectID($table)) {
3622
                $sql = "SELECT \"{$this->id}\", \"";
3623
            } else {
3624
                $sql = 'SELECT "';
3625
            }
3626
3627
            $sql .= join('","', $show) . '" FROM ';
3628
        }
3629
3630
        $this->fieldClean($table);
3631
3632
        if (isset($_REQUEST['schema'])) {
3633
            $f_schema = $_REQUEST['schema'];
3634
            $this->fieldClean($f_schema);
3635
            $sql .= "\"{$f_schema}\".";
3636
        }
3637
        $sql .= "\"{$table}\"";
3638
3639
        // If we have values specified, add them to the WHERE clause
3640
        $first = true;
3641
        if (is_array($values) && sizeof($values) > 0) {
3642
            foreach ($values as $k => $v) {
3643
                if ($v != '' || $this->selectOps[$ops[$k]] == 'p') {
3644
                    $this->fieldClean($k);
3645
                    if ($first) {
3646
                        $sql .= ' WHERE ';
3647
                        $first = false;
3648
                    } else {
3649
                        $sql .= ' AND ';
3650
                    }
3651
                    // Different query format depending on operator type
3652
                    switch ($this->selectOps[$ops[$k]]) {
3653
                        case 'i':
3654
                            // Only clean the field for the inline case
3655
                            // this is because (x), subqueries need to
3656
                            // to allow 'a','b' as input.
3657
                            $this->clean($v);
3658
                            $sql .= "\"{$k}\" {$ops[$k]} '{$v}'";
3659
3660
                            break;
3661
                        case 'p':
3662
                            $sql .= "\"{$k}\" {$ops[$k]}";
3663
3664
                            break;
3665
                        case 'x':
3666
                            $sql .= "\"{$k}\" {$ops[$k]} ({$v})";
3667
3668
                            break;
3669
                        case 't':
3670
                            $sql .= "\"{$k}\" {$ops[$k]}('{$v}')";
3671
3672
                            break;
3673
                        default:
3674
                            // Shouldn't happen
3675
                    }
3676
                }
3677
            }
3678
        }
3679
3680
        // ORDER BY
3681
        if (is_array($orderby) && sizeof($orderby) > 0) {
3682
            $sql .= ' ORDER BY ';
3683
            $first = true;
3684
            foreach ($orderby as $k => $v) {
3685
                if ($first) {
3686
                    $first = false;
3687
                } else {
3688
                    $sql .= ', ';
3689
                }
3690
3691
                if (preg_match('/^[0-9]+$/', $k)) {
3692
                    $sql .= $k;
3693
                } else {
3694
                    $this->fieldClean($k);
3695
                    $sql .= '"' . $k . '"';
3696
                }
3697
                if (strtoupper($v) == 'DESC') {
3698
                    $sql .= ' DESC';
3699
                }
3700
            }
3701
        }
3702
3703
        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...
3704
    }
3705
3706
    /**
3707
     * Finds the number of rows that would be returned by a
3708
     * query.
3709
     *
3710
     * @param $query The SQL query
3711
     * @param $count The count query
3712
     *
3713
     * @return The count of rows
3714
     * @return -1  error
0 ignored issues
show
Coding Style introduced by
Only 1 @return tag is allowed in a function comment
Loading history...
3715
     */
3716
    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

3716
    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...
3717
    {
3718
        return $this->selectField($count, 'total');
3719
    }
3720
3721
    /**
3722
     * Returns a recordset of all columns in a table.
3723
     *
3724
     * @param $table The name of a table
3725
     * @param $key   The associative array holding the key to retrieve
3726
     *
3727
     * @return \PHPPgAdmin\ADORecordSet A recordset
3728
     */
3729
    public function browseRow($table, $key)
3730
    {
3731
        $f_schema = $this->_schema;
3732
        $this->fieldClean($f_schema);
3733
        $this->fieldClean($table);
3734
3735
        $sql = "SELECT * FROM \"{$f_schema}\".\"{$table}\"";
3736
        if (is_array($key) && sizeof($key) > 0) {
3737
            $sql .= ' WHERE true';
3738
            foreach ($key as $k => $v) {
3739
                $this->fieldClean($k);
3740
                $this->clean($v);
3741
                $sql .= " AND \"{$k}\"='{$v}'";
3742
            }
3743
        }
3744
3745
        return $this->selectSet($sql);
3746
    }
3747
3748
    /**
3749
     * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false.
3750
     *
3751
     * @param $parameter the parameter
3752
     *
3753
     * @return string
3754
     */
3755
    public function dbBool(&$parameter)
3756
    {
3757
        if ($parameter) {
3758
            $parameter = 't';
3759
        } else {
3760
            $parameter = 'f';
3761
        }
3762
3763
        return $parameter;
3764
    }
3765
3766
    /**
3767
     * Fetches statistics for a database.
3768
     *
3769
     * @param $database The database to fetch stats for
3770
     *
3771
     * @return \PHPPgAdmin\ADORecordSet A recordset
3772
     */
3773
    public function getStatsDatabase($database)
3774
    {
3775
        $this->clean($database);
3776
3777
        $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'";
3778
3779
        return $this->selectSet($sql);
3780
    }
3781
3782
    /**
3783
     * Fetches tuple statistics for a table.
3784
     *
3785
     * @param $table The table to fetch stats for
3786
     *
3787
     * @return \PHPPgAdmin\ADORecordSet A recordset
3788
     */
3789
    public function getStatsTableTuples($table)
3790
    {
3791
        $c_schema = $this->_schema;
3792
        $this->clean($c_schema);
3793
        $this->clean($table);
3794
3795
        $sql = "SELECT * FROM pg_stat_all_tables
3796
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3797
3798
        return $this->selectSet($sql);
3799
    }
3800
3801
    /**
3802
     * Fetches I/0 statistics for a table.
3803
     *
3804
     * @param $table The table to fetch stats for
3805
     *
3806
     * @return \PHPPgAdmin\ADORecordSet A recordset
3807
     */
3808
    public function getStatsTableIO($table)
3809
    {
3810
        $c_schema = $this->_schema;
3811
        $this->clean($c_schema);
3812
        $this->clean($table);
3813
3814
        $sql = "SELECT * FROM pg_statio_all_tables
3815
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3816
3817
        return $this->selectSet($sql);
3818
    }
3819
3820
    /**
3821
     * Fetches tuple statistics for all indexes on a table.
3822
     *
3823
     * @param $table The table to fetch index stats for
3824
     *
3825
     * @return \PHPPgAdmin\ADORecordSet A recordset
3826
     */
3827
    public function getStatsIndexTuples($table)
3828
    {
3829
        $c_schema = $this->_schema;
3830
        $this->clean($c_schema);
3831
        $this->clean($table);
3832
3833
        $sql = "SELECT * FROM pg_stat_all_indexes
3834
			WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname";
3835
3836
        return $this->selectSet($sql);
3837
    }
3838
3839
    /**
3840
     * Fetches I/0 statistics for all indexes on a table.
3841
     *
3842
     * @param $table The table to fetch index stats for
3843
     *
3844
     * @return \PHPPgAdmin\ADORecordSet A recordset
3845
     */
3846
    public function getStatsIndexIO($table)
3847
    {
3848
        $c_schema = $this->_schema;
3849
        $this->clean($c_schema);
3850
        $this->clean($table);
3851
3852
        $sql = "SELECT * FROM pg_statio_all_indexes
3853
			WHERE schemaname='{$c_schema}' AND relname='{$table}'
3854
			ORDER BY indexrelname";
3855
3856
        return $this->selectSet($sql);
3857
    }
3858
}
3859