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

Postgres::createTableLike()   D

Complexity

Conditions 10
Paths 33

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 25
nc 33
nop 6
dl 0
loc 43
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.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