Passed
Push — master ( 2856b4...cf0423 )
by Felipe
10:02 queued 05:52
created

Postgres::getTableDefSuffix()   C

Complexity

Conditions 10
Paths 15

Size

Total Lines 57
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 30
nc 15
nop 1
dl 0
loc 57
rs 6.7123
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

193
        $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

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

835
        array_unshift(/** @scrutinizer ignore-type */ $search_path, $schema);
Loading history...
836
        $status = $this->setSearchPath($search_path);
837
        if ($status == 0) {
838
            $this->_schema = $schema;
839
840
            return 0;
841
        }
842
843
        return $status;
844
    }
845
846
    /**
847
     * Return the current schema search path.
848
     *
849
     * @return \PHPPgAdmin\ADORecordSet array of schema names
850
     */
851
    public function getSearchPath()
852
    {
853
        $sql = 'SELECT current_schemas(false) AS search_path';
854
855
        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...
856
    }
857
858
    /**
859
     * Sets the current schema search path.
860
     *
861
     * @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...
862
     *
863
     * @return int 0 if operation was successful
864
     */
865
    public function setSearchPath($paths)
866
    {
867
        if (!is_array($paths)) {
868
            return -1;
869
        }
870
871
        if (sizeof($paths) == 0) {
872
            return -2;
873
        }
874
        if (sizeof($paths) == 1 && $paths[0] == '') {
875
            // Need to handle empty paths in some cases
876
            $paths[0] = 'pg_catalog';
877
        }
878
879
        // Loop over all the paths to check that none are empty
880
        $temp = [];
881
        foreach ($paths as $schema) {
882
            if ($schema != '') {
883
                $temp[] = $schema;
884
            }
885
        }
886
        $this->fieldArrayClean($temp);
887
888
        $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"';
889
890
        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...
891
    }
892
893
    /**
894
     * Cleans (escapes) an array of field names.
895
     *
896
     * @param $arr The array to clean, by reference
897
     *
898
     * @return The cleaned array
899
     */
900
    public function fieldArrayClean(&$arr)
901
    {
902
        foreach ($arr as $k => $v) {
903
            if ($v === null) {
904
                continue;
905
            }
906
907
            $arr[$k] = str_replace('"', '""', $v);
908
        }
909
910
        return $arr;
911
    }
912
913
    /**
914
     * Creates a new schema.
915
     *
916
     * @param        $schemaname    The name of the schema to create
917
     * @param string $authorization (optional) The username to create the schema for
918
     * @param string $comment       (optional) If omitted, defaults to nothing
919
     *
920
     * @return bool|int 0 success
921
     */
922
    public function createSchema($schemaname, $authorization = '', $comment = '')
923
    {
924
        $this->fieldClean($schemaname);
925
        $this->fieldClean($authorization);
926
927
        $sql = "CREATE SCHEMA \"{$schemaname}\"";
928
        if ($authorization != '') {
929
            $sql .= " AUTHORIZATION \"{$authorization}\"";
930
        }
931
932
        if ($comment != '') {
933
            $status = $this->beginTransaction();
934
            if ($status != 0) {
935
                return -1;
936
            }
937
        }
938
939
        // Create the new schema
940
        $status = $this->execute($sql);
941
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
942
            $this->rollbackTransaction();
943
944
            return -1;
945
        }
946
947
        // Set the comment
948
        if ($comment != '') {
949
            $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
950
            if ($status != 0) {
951
                $this->rollbackTransaction();
952
953
                return -1;
954
            }
955
956
            return $this->endTransaction();
957
        }
958
959
        return 0;
960
    }
961
962
    /**
963
     * Updates a schema.
964
     *
965
     * @param $schemaname The name of the schema to drop
966
     * @param $comment    The new comment for this schema
967
     * @param $name
968
     * @param $owner      The new owner for this schema
969
     *
970
     * @return bool|int 0 success
971
     */
972
    public function updateSchema($schemaname, $comment, $name, $owner)
973
    {
974
        $this->fieldClean($schemaname);
975
        $this->fieldClean($name);
976
        $this->fieldClean($owner);
977
978
        $status = $this->beginTransaction();
979
        if ($status != 0) {
980
            $this->rollbackTransaction();
981
982
            return -1;
983
        }
984
985
        $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
986
        if ($status != 0) {
987
            $this->rollbackTransaction();
988
989
            return -1;
990
        }
991
992
        $schema_rs = $this->getSchemaByName($schemaname);
993
        /* Only if the owner change */
994
        if ($schema_rs->fields['ownername'] != $owner) {
995
            $sql    = "ALTER SCHEMA \"{$schemaname}\" OWNER TO \"{$owner}\"";
996
            $status = $this->execute($sql);
997
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
998
                $this->rollbackTransaction();
999
1000
                return -1;
1001
            }
1002
        }
1003
1004
        // Only if the name has changed
1005
        if ($name != $schemaname) {
1006
            $sql    = "ALTER SCHEMA \"{$schemaname}\" RENAME TO \"{$name}\"";
1007
            $status = $this->execute($sql);
1008
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
1009
                $this->rollbackTransaction();
1010
1011
                return -1;
1012
            }
1013
        }
1014
1015
        return $this->endTransaction();
1016
    }
1017
1018
    /**
1019
     * Return all information relating to a schema.
1020
     *
1021
     * @param $schema The name of the schema
1022
     *
1023
     * @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...
1024
     */
1025
    public function getSchemaByName($schema)
1026
    {
1027
        $this->clean($schema);
1028
        $sql = "
1029
			SELECT nspname, nspowner, r.rolname AS ownername, nspacl,
1030
				pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment
1031
			FROM pg_catalog.pg_namespace pn
1032
				LEFT JOIN pg_roles as r ON pn.nspowner = r.oid
1033
			WHERE nspname='{$schema}'";
1034
1035
        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...
1036
    }
1037
1038
    // Table functions
1039
1040
    /**
1041
     * Drops a schema.
1042
     *
1043
     * @param $schemaname The name of the schema to drop
1044
     * @param $cascade    True to cascade drop, false to restrict
1045
     *
1046
     * @return integer 0 if operation was successful
1047
     */
1048
    public function dropSchema($schemaname, $cascade)
1049
    {
1050
        $this->fieldClean($schemaname);
1051
1052
        $sql = "DROP SCHEMA \"{$schemaname}\"";
1053
        if ($cascade) {
1054
            $sql .= ' CASCADE';
1055
        }
1056
1057
        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...
1058
    }
1059
1060
    /**
1061
     * Return all tables in current database (and schema).
1062
     *
1063
     * @param bool|true $all True to fetch all tables, false for just in current schema
1064
     *
1065
     * @return \PHPPgAdmin\ADORecordSet All tables, sorted alphabetically
1066
     */
1067
    public function getTables($all = false)
1068
    {
1069
        $c_schema = $this->_schema;
1070
        $this->clean($c_schema);
1071
        if ($all) {
1072
            // Exclude pg_catalog and information_schema tables
1073
            $sql = "SELECT
1074
                        schemaname AS nspname,
1075
                        tablename AS relname,
1076
                        tableowner AS relowner
1077
					FROM pg_catalog.pg_tables
1078
					WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
1079
					ORDER BY schemaname, tablename";
1080
        } else {
1081
            $sql = "
1082
                SELECT c.relname,
1083
                    pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
1084
                    pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
1085
                    reltuples::bigint,
1086
                    pt.spcname as tablespace,
1087
                    pg_size_pretty(pg_total_relation_size(c.oid)) as table_size
1088
                FROM pg_catalog.pg_class c
1089
                LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1090
                LEFT JOIN  pg_catalog.pg_tablespace pt ON  pt.oid=c.reltablespace
1091
                WHERE c.relkind = 'r'
1092
                AND nspname='{$c_schema}'
1093
                ORDER BY c.relname";
1094
        }
1095
1096
        return $this->selectSet($sql);
1097
    }
1098
1099
    /**
1100
     * Finds the names and schemas of parent tables (in order).
1101
     *
1102
     * @param $table The table to find the parents for
1103
     *
1104
     * @return \PHPPgAdmin\ADORecordSet A recordset
1105
     */
1106
    public function getTableParents($table)
1107
    {
1108
        $c_schema = $this->_schema;
1109
        $this->clean($c_schema);
1110
        $this->clean($table);
1111
1112
        $sql = "
1113
			SELECT
1114
				pn.nspname, relname
1115
			FROM
1116
				pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
1117
			WHERE
1118
				pc.oid=pi.inhparent
1119
				AND pc.relnamespace=pn.oid
1120
				AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
1121
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
1122
			ORDER BY
1123
				pi.inhseqno
1124
		";
1125
1126
        return $this->selectSet($sql);
1127
    }
1128
1129
    /**
1130
     * Finds the names and schemas of child tables.
1131
     *
1132
     * @param $table The table to find the children for
1133
     *
1134
     * @return \PHPPgAdmin\ADORecordSet A recordset
1135
     */
1136
    public function getTableChildren($table)
1137
    {
1138
        $c_schema = $this->_schema;
1139
        $this->clean($c_schema);
1140
        $this->clean($table);
1141
1142
        $sql = "
1143
			SELECT
1144
				pn.nspname, relname
1145
			FROM
1146
				pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
1147
			WHERE
1148
				pc.oid=pi.inhrelid
1149
				AND pc.relnamespace=pn.oid
1150
				AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
1151
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
1152
		";
1153
1154
        return $this->selectSet($sql);
1155
    }
1156
1157
    /**
1158
     * Returns the SQL definition for the table.
1159
     *
1160
     * @pre MUST be run within a transaction
1161
     *
1162
     * @param           $table The table to define
1 ignored issue
show
Coding Style introduced by
Parameter tags must be defined first in a doc comment
Loading history...
1163
     * @param bool|true $clean True to issue drop command, false otherwise
1164
     *
1165
     * @return string A string containing the formatted SQL code
1166
     */
1167
    public function getTableDefPrefix($table, $clean = false)
1168
    {
1169
        // Fetch table
1170
        $t = $this->getTable($table);
1171
        if (!is_object($t) || $t->recordCount() != 1) {
0 ignored issues
show
introduced by
The condition $t->recordCount() != 1 is always true.
Loading history...
1172
            $this->rollbackTransaction();
1173
1174
            return null;
1175
        }
1176
        $this->fieldClean($t->fields['relname']);
1177
        $this->fieldClean($t->fields['nspname']);
1178
1179
        // Fetch attributes
1180
        $atts = $this->getTableAttributes($table);
1181
        if (!is_object($atts)) {
1182
            $this->rollbackTransaction();
1183
1184
            return null;
1185
        }
1186
1187
        // Fetch constraints
1188
        $cons = $this->getConstraints($table);
1189
        if (!is_object($cons)) {
1190
            $this->rollbackTransaction();
1191
1192
            return null;
1193
        }
1194
1195
        // Output a reconnect command to create the table as the correct user
1196
        $sql = $this->getChangeUserSQL($t->fields['relowner']) . "\n\n";
1197
1198
        // Set schema search path
1199
        $sql .= "SET search_path = \"{$t->fields['nspname']}\", pg_catalog;\n\n";
1200
1201
        // Begin CREATE TABLE definition
1202
        $sql .= "-- Definition\n\n";
1203
        // DROP TABLE must be fully qualified in case a table with the same name exists
1204
        // in pg_catalog.
1205
        if (!$clean) {
1206
            $sql .= '-- ';
1207
        }
1208
1209
        $sql .= 'DROP TABLE ';
1210
        $sql .= "\"{$t->fields['nspname']}\".\"{$t->fields['relname']}\";\n";
1211
        $sql .= "CREATE TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" (\n";
1212
1213
        // Output all table columns
1214
        $col_comments_sql = ''; // Accumulate comments on columns
1215
        $num              = $atts->recordCount() + $cons->recordCount();
1216
        $i                = 1;
1217
        while (!$atts->EOF) {
1218
            $this->fieldClean($atts->fields['attname']);
1219
            $sql .= "    \"{$atts->fields['attname']}\"";
1220
            // Dump SERIAL and BIGSERIAL columns correctly
1221
            if ($this->phpBool($atts->fields['attisserial']) &&
1222
                ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) {
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...
1223
                if ($atts->fields['type'] == 'integer') {
1224
                    $sql .= ' SERIAL';
1225
                } else {
1226
                    $sql .= ' BIGSERIAL';
1227
                }
1228
            } else {
1229
                $sql .= ' ' . $this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
1230
1231
                // Add NOT NULL if necessary
1232
                if ($this->phpBool($atts->fields['attnotnull'])) {
1233
                    $sql .= ' NOT NULL';
1234
                }
1235
1236
                // Add default if necessary
1237
                if ($atts->fields['adsrc'] !== null) {
1238
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
1239
                }
1240
            }
1241
1242
            // Output comma or not
1243
            if ($i < $num) {
1244
                $sql .= ",\n";
1245
            } else {
1246
                $sql .= "\n";
1247
            }
1248
1249
            // Does this column have a comment?
1250
            if ($atts->fields['comment'] !== null) {
1251
                $this->clean($atts->fields['comment']);
1252
                $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
1253
            }
1254
1255
            $atts->moveNext();
1256
            ++$i;
1257
        }
1258
        // Output all table constraints
1259
        while (!$cons->EOF) {
1260
            $this->fieldClean($cons->fields['conname']);
1261
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
1262
            // Nasty hack to support pre-7.4 PostgreSQL
1263
            if ($cons->fields['consrc'] !== null) {
1264
                $sql .= $cons->fields['consrc'];
1265
            } else {
1266
                switch ($cons->fields['contype']) {
1267
                    case 'p':
1268
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
1269
                        $sql .= 'PRIMARY KEY (' . join(',', $keys) . ')';
1270
1271
                        break;
1272
                    case 'u':
1273
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
1274
                        $sql .= 'UNIQUE (' . join(',', $keys) . ')';
1275
1276
                        break;
1277
                    default:
1278
                        // Unrecognised constraint
1279
                        $this->rollbackTransaction();
1280
1281
                        return null;
1282
                }
1283
            }
1284
1285
            // Output comma or not
1286
            if ($i < $num) {
1287
                $sql .= ",\n";
1288
            } else {
1289
                $sql .= "\n";
1290
            }
1291
1292
            $cons->moveNext();
1293
            ++$i;
1294
        }
1295
1296
        $sql .= ')';
1297
1298
        // @@@@ DUMP CLUSTERING INFORMATION
1299
1300
        // Inherits
1301
        /*
1302
         * XXX: This is currently commented out as handling inheritance isn't this simple.
1303
         * You also need to make sure you don't dump inherited columns and defaults, as well
1304
         * as inherited NOT NULL and CHECK constraints.  So for the time being, we just do
1305
         * not claim to support inheritance.
1306
        $parents = $this->getTableParents($table);
1307
        if ($parents->recordCount() > 0) {
1308
        $sql .= " INHERITS (";
1309
        while (!$parents->EOF) {
1310
        $this->fieldClean($parents->fields['relname']);
1311
        // Qualify the parent table if it's in another schema
1312
        if ($parents->fields['schemaname'] != $this->_schema) {
1313
        $this->fieldClean($parents->fields['schemaname']);
1314
        $sql .= "\"{$parents->fields['schemaname']}\".";
1315
        }
1316
        $sql .= "\"{$parents->fields['relname']}\"";
1317
1318
        $parents->moveNext();
1319
        if (!$parents->EOF) $sql .= ', ';
1320
        }
1321
        $sql .= ")";
1322
        }
1323
         */
1324
1325
        // Handle WITHOUT OIDS
1326
        if ($this->hasObjectID($table)) {
1327
            $sql .= ' WITH OIDS';
1328
        } else {
1329
            $sql .= ' WITHOUT OIDS';
1330
        }
1331
1332
        $sql .= ";\n";
1333
1334
        // Column storage and statistics
1335
        $atts->moveFirst();
1336
        $first = true;
1337
        while (!$atts->EOF) {
1338
            $this->fieldClean($atts->fields['attname']);
1339
            // Statistics first
1340
            if ($atts->fields['attstattarget'] >= 0) {
1341
                if ($first) {
1342
                    $sql .= "\n";
1343
                    $first = false;
1344
                }
1345
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
1346
            }
1347
            // Then storage
1348
            if ($atts->fields['attstorage'] != $atts->fields['typstorage']) {
1349
                switch ($atts->fields['attstorage']) {
1350
                    case 'p':
1351
                        $storage = 'PLAIN';
1352
1353
                        break;
1354
                    case 'e':
1355
                        $storage = 'EXTERNAL';
1356
1357
                        break;
1358
                    case 'm':
1359
                        $storage = 'MAIN';
1360
1361
                        break;
1362
                    case 'x':
1363
                        $storage = 'EXTENDED';
1364
1365
                        break;
1366
                    default:
1367
                        // Unknown storage type
1368
                        $this->rollbackTransaction();
1369
1370
                        return null;
1371
                }
1372
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
1373
            }
1374
1375
            $atts->moveNext();
1376
        }
1377
1378
        // Comment
1379
        if ($t->fields['relcomment'] !== null) {
1380
            $this->clean($t->fields['relcomment']);
1381
            $sql .= "\n-- Comment\n\n";
1382
            $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n";
1383
        }
1384
1385
        // Add comments on columns, if any
1386
        if ($col_comments_sql != '') {
1387
            $sql .= $col_comments_sql;
1388
        }
1389
1390
        // Privileges
1391
        $privs = $this->getPrivileges($table, 'table');
1392
        if (!is_array($privs)) {
1393
            $this->rollbackTransaction();
1394
1395
            return null;
1396
        }
1397
1398
        if (sizeof($privs) > 0) {
1399
            $sql .= "\n-- Privileges\n\n";
1400
            /*
1401
             * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
1402
             * wire-in knowledge about the default public privileges for different
1403
             * kinds of objects.
1404
             */
1405
            $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n";
1406
            foreach ($privs as $v) {
1407
                // Get non-GRANT OPTION privs
1408
                $nongrant = array_diff($v[2], $v[4]);
1409
1410
                // Skip empty or owner ACEs
1411
                if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) {
1412
                    continue;
1413
                }
1414
1415
                // Change user if necessary
1416
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
1417
                    $grantor = $v[3];
1418
                    $this->clean($grantor);
1419
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
1420
                }
1421
1422
                // Output privileges with no GRANT OPTION
1423
                $sql .= 'GRANT ' . join(', ', $nongrant) . " ON TABLE \"{$t->fields['relname']}\" TO ";
1424
                switch ($v[0]) {
1425
                    case 'public':
1426
                        $sql .= "PUBLIC;\n";
1427
1428
                        break;
1429
                    case 'user':
1430
                        $this->fieldClean($v[1]);
1431
                        $sql .= "\"{$v[1]}\";\n";
1432
1433
                        break;
1434
                    case 'group':
1435
                        $this->fieldClean($v[1]);
1436
                        $sql .= "GROUP \"{$v[1]}\";\n";
1437
1438
                        break;
1439
                    default:
1440
                        // Unknown privilege type - fail
1441
                        $this->rollbackTransaction();
1442
1443
                        return null;
1444
                }
1445
1446
                // Reset user if necessary
1447
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
1448
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
1449
                }
1450
1451
                // Output privileges with GRANT OPTION
1452
1453
                // Skip empty or owner ACEs
1454
                if (!$this->hasGrantOption() || sizeof($v[4]) == 0) {
1455
                    continue;
1456
                }
1457
1458
                // Change user if necessary
1459
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
1460
                    $grantor = $v[3];
1461
                    $this->clean($grantor);
1462
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
1463
                }
1464
1465
                $sql .= 'GRANT ' . join(', ', $v[4]) . " ON \"{$t->fields['relname']}\" TO ";
1466
                switch ($v[0]) {
1467
                    case 'public':
1468
                        $sql .= 'PUBLIC';
1469
1470
                        break;
1471
                    case 'user':
1472
                        $this->fieldClean($v[1]);
1473
                        $sql .= "\"{$v[1]}\"";
1474
1475
                        break;
1476
                    case 'group':
1477
                        $this->fieldClean($v[1]);
1478
                        $sql .= "GROUP \"{$v[1]}\"";
1479
1480
                        break;
1481
                    default:
1482
                        // Unknown privilege type - fail
1483
                        return null;
1484
                }
1485
                $sql .= " WITH GRANT OPTION;\n";
1486
1487
                // Reset user if necessary
1488
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
1489
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
1490
                }
1491
            }
1492
        }
1493
1494
        // Add a newline to separate data that follows (if any)
1495
        $sql .= "\n";
1496
1497
        return $sql;
1498
    }
1499
1500
    /**
1501
     * Returns table information.
1502
     *
1503
     * @param $table The name of the table
1504
     *
1505
     * @return \PHPPgAdmin\ADORecordSet A recordset
1506
     */
1507
    public function getTable($table)
1508
    {
1509
        $c_schema = $this->_schema;
1510
        $this->clean($c_schema);
1511
        $this->clean($table);
1512
1513
        $sql = "
1514
			SELECT
1515
			  c.relname, n.nspname, u.usename AS relowner,
1516
			  pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
1517
			  (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
1518
			FROM pg_catalog.pg_class c
1519
			     LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
1520
			     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1521
			WHERE c.relkind = 'r'
1522
			      AND n.nspname = '{$c_schema}'
1523
			      AND n.oid = c.relnamespace
1524
			      AND c.relname = '{$table}'";
1525
1526
        return $this->selectSet($sql);
1527
    }
1528
1529
    /**
1530
     * Retrieve the attribute definition of a table.
1531
     *
1532
     * @param $table The name of the table
1533
     * @param $field (optional) The name of a field to return
1534
     *
1535
     * @return All attributes in order
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\All 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...
1536
     */
1537
    public function getTableAttributes($table, $field = '')
1538
    {
1539
        $c_schema = $this->_schema;
1540
        $this->clean($c_schema);
1541
        $this->clean($table);
1542
        $this->clean($field);
1543
1544
        if ($field == '') {
1545
            // This query is made much more complex by the addition of the 'attisserial' field.
1546
            // The subquery to get that field checks to see if there is an internally dependent
1547
            // sequence on the field.
1548
            $sql = "
1549
				SELECT
1550
					a.attname, a.attnum,
1551
					pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
1552
					a.atttypmod,
1553
					a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
1554
					a.attstattarget, a.attstorage, t.typstorage,
1555
					(
1556
						SELECT 1 FROM pg_catalog.pg_depend pd, pg_catalog.pg_class pc
1557
						WHERE pd.objid=pc.oid
1558
						AND pd.classid=pc.tableoid
1559
						AND pd.refclassid=pc.tableoid
1560
						AND pd.refobjid=a.attrelid
1561
						AND pd.refobjsubid=a.attnum
1562
						AND pd.deptype='i'
1563
						AND pc.relkind='S'
1564
					) IS NOT NULL AS attisserial,
1565
					pg_catalog.col_description(a.attrelid, a.attnum) AS comment
1566
				FROM
1567
					pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
1568
					ON a.attrelid=adef.adrelid
1569
					AND a.attnum=adef.adnum
1570
					LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
1571
				WHERE
1572
					a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
1573
						AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
1574
						nspname = '{$c_schema}'))
1575
					AND a.attnum > 0 AND NOT a.attisdropped
1576
				ORDER BY a.attnum";
1577
        } else {
1578
            $sql = "
1579
				SELECT
1580
					a.attname, a.attnum,
1581
					pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
1582
					pg_catalog.format_type(a.atttypid, NULL) as base_type,
1583
					a.atttypmod,
1584
					a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
1585
					a.attstattarget, a.attstorage, t.typstorage,
1586
					pg_catalog.col_description(a.attrelid, a.attnum) AS comment
1587
				FROM
1588
					pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
1589
					ON a.attrelid=adef.adrelid
1590
					AND a.attnum=adef.adnum
1591
					LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
1592
				WHERE
1593
					a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
1594
						AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
1595
						nspname = '{$c_schema}'))
1596
					AND a.attname = '{$field}'";
1597
        }
1598
1599
        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\All.
Loading history...
1600
    }
1601
1602
    /**
1603
     * Returns a list of all constraints on a table.
1604
     *
1605
     * @param $table The table to find rules for
1606
     *
1607
     * @return \PHPPgAdmin\ADORecordSet A recordset
1608
     */
1609
    public function getConstraints($table)
1610
    {
1611
        $c_schema = $this->_schema;
1612
        $this->clean($c_schema);
1613
        $this->clean($table);
1614
1615
        // This SQL is greatly complicated by the need to retrieve
1616
        // index clustering information for primary and unique constraints
1617
        $sql = "SELECT
1618
				pc.conname,
1619
				pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc,
1620
				pc.contype,
1621
				CASE WHEN pc.contype='u' OR pc.contype='p' THEN (
1622
					SELECT
1623
						indisclustered
1624
					FROM
1625
						pg_catalog.pg_depend pd,
1626
						pg_catalog.pg_class pl,
1627
						pg_catalog.pg_index pi
1628
					WHERE
1629
						pd.refclassid=pc.tableoid
1630
						AND pd.refobjid=pc.oid
1631
						AND pd.objid=pl.oid
1632
						AND pl.oid=pi.indexrelid
1633
				) ELSE
1634
					NULL
1635
				END AS indisclustered
1636
			FROM
1637
				pg_catalog.pg_constraint pc
1638
			WHERE
1639
				pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
1640
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
1641
					WHERE nspname='{$c_schema}'))
1642
			ORDER BY
1643
				1
1644
		";
1645
1646
        return $this->selectSet($sql);
1647
    }
1648
1649
    /**
1650
     * Returns the SQL for changing the current user.
1651
     *
1652
     * @param $user The user to change to
1653
     *
1654
     * @return The SQL
1655
     */
1656
    public function getChangeUserSQL($user)
1657
    {
1658
        $this->clean($user);
1659
1660
        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...
1661
    }
1662
1663
    /**
1664
     * Change a parameter from 't' or 'f' to a boolean, (others evaluate to false).
1665
     *
1666
     * @param $parameter the parameter
1667
     *
1668
     * @return bool|\PHPPgAdmin\Database\the
1669
     */
1670
    public function phpBool($parameter)
1671
    {
1672
        $parameter = ($parameter == 't');
1673
1674
        return $parameter;
1675
    }
1676
1677
    /**
1678
     * Formats a type correctly for display.  Postgres 7.0 had no 'format_type'
1679
     * built-in function, and hence we need to do it manually.
1680
     *
1681
     * @param $typname The name of the type
1682
     * @param $typmod  The contents of the typmod field
1683
     *
1684
     * @return bool|string
1685
     */
1686
    public function formatType($typname, $typmod)
1687
    {
1688
        // This is a specific constant in the 7.0 source
1689
        $varhdrsz = 4;
1690
1691
        // If the first character is an underscore, it's an array type
1692
        $is_array = false;
1693
        if (substr($typname, 0, 1) == '_') {
1694
            $is_array = true;
1695
            $typname  = substr($typname, 1);
1696
        }
1697
1698
        // Show lengths on bpchar and varchar
1699
        if ($typname == 'bpchar') {
1700
            $len  = $typmod - $varhdrsz;
1701
            $temp = 'character';
1702
            if ($len > 1) {
1703
                $temp .= "({$len})";
1704
            }
1705
        } elseif ($typname == 'varchar') {
1706
            $temp = 'character varying';
1707
            if ($typmod != -1) {
1708
                $temp .= '(' . ($typmod - $varhdrsz) . ')';
1709
            }
1710
        } elseif ($typname == 'numeric') {
1711
            $temp = 'numeric';
1712
            if ($typmod != -1) {
1713
                $tmp_typmod = $typmod - $varhdrsz;
1714
                $precision  = ($tmp_typmod >> 16) & 0xffff;
1715
                $scale      = $tmp_typmod & 0xffff;
1716
                $temp .= "({$precision}, {$scale})";
1717
            }
1718
        } else {
1719
            $temp = $typname;
1720
        }
1721
1722
        // Add array qualifier if it's an array
1723
        if ($is_array) {
1724
            $temp .= '[]';
1725
        }
1726
1727
        return $temp;
1728
    }
1729
1730
    /**
1731
     * Given an array of attnums and a relation, returns an array mapping
1732
     * attribute number to attribute name.
1733
     *
1734
     * @param $table The table to get attributes for
1735
     * @param $atts  An array of attribute numbers
1736
     *
1737
     * @return An array mapping attnum to attname
1738
     * @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...
1739
     * @return -2 wrong number of attributes found
1740
     */
1741
    public function getAttributeNames($table, $atts)
1742
    {
1743
        $c_schema = $this->_schema;
1744
        $this->clean($c_schema);
1745
        $this->clean($table);
1746
        $this->arrayClean($atts);
1747
1748
        if (!is_array($atts)) {
1749
            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...
1750
        }
1751
1752
        if (sizeof($atts) == 0) {
1753
            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...
1754
        }
1755
1756
        $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
1757
			attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
1758
			relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
1759
			AND attnum IN ('" . join("','", $atts) . "')";
1760
1761
        $rs = $this->selectSet($sql);
1762
        if ($rs->recordCount() != sizeof($atts)) {
1763
            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...
1764
        }
1765
1766
        $temp = [];
1767
        while (!$rs->EOF) {
1768
            $temp[$rs->fields['attnum']] = $rs->fields['attname'];
1769
            $rs->moveNext();
1770
        }
1771
1772
        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...
1773
    }
1774
1775
    /**
1776
     * Cleans (escapes) an array.
1777
     *
1778
     * @param $arr The array to clean, by reference
1779
     *
1780
     * @return The cleaned array
1781
     */
1782
    public function arrayClean(&$arr)
1783
    {
1784
        foreach ($arr as $k => $v) {
1785
            if ($v === null) {
1786
                continue;
1787
            }
1788
1789
            $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

1789
            $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...
1790
        }
1791
1792
        return $arr;
1793
    }
1794
1795
    /**
1796
     * Checks to see whether or not a table has a unique id column.
1797
     *
1798
     * @param $table The table name
1799
     *
1800
     * @return true if it has a unique id, false otherwise
1801
     */
1802
    public function hasObjectID($table)
1803
    {
1804
        $c_schema = $this->_schema;
1805
        $this->clean($c_schema);
1806
        $this->clean($table);
1807
1808
        $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}'
1809
			AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')";
1810
1811
        $rs = $this->selectSet($sql);
1812
        if ($rs->recordCount() != 1) {
0 ignored issues
show
introduced by
The condition $rs->recordCount() != 1 is always true.
Loading history...
1813
            return null;
1814
        }
1815
1816
        $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']);
1817
1818
        return $rs->fields['relhasoids'];
1819
    }
1820
1821
    /**
1822
     * Grabs an array of users and their privileges for an object,
1823
     * given its type.
1824
     *
1825
     * @param $object The name of the object whose privileges are to be retrieved
1826
     * @param $type   The type of the object (eg. database, schema, relation, function or language)
1827
     * @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...
1828
     *
1829
     * @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...
1830
     * @return -1         invalid type
0 ignored issues
show
Coding Style introduced by
Only 1 @return tag is allowed in a function comment
Loading history...
1831
     * @return -2         object not found
1832
     * @return -3         unknown privilege type
1833
     */
1834
    public function getPrivileges($object, $type, $table = null)
1835
    {
1836
        $c_schema = $this->_schema;
1837
        $this->clean($c_schema);
1838
        $this->clean($object);
1839
1840
        switch ($type) {
1841
            case 'column':
1842
                $this->clean($table);
1843
                $sql = "
1844
					SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl
1845
					FROM pg_catalog.pg_attribute a
1846
						LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid)
1847
						LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid)
1848
					WHERE n.nspname='{$c_schema}'
1849
						AND c.relname='{$table}'
1850
						AND a.attname='{$object}'";
1851
1852
                break;
1853
            case 'table':
1854
            case 'view':
1855
            case 'sequence':
1856
                $sql = "
1857
					SELECT relacl AS acl FROM pg_catalog.pg_class
1858
					WHERE relname='{$object}'
1859
						AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace
1860
							WHERE nspname='{$c_schema}')";
1861
1862
                break;
1863
            case 'database':
1864
                $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'";
1865
1866
                break;
1867
            case 'function':
1868
                // Since we fetch functions by oid, they are already constrained to
1869
                // the current schema.
1870
                $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'";
1871
1872
                break;
1873
            case 'language':
1874
                $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'";
1875
1876
                break;
1877
            case 'schema':
1878
                $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'";
1879
1880
                break;
1881
            case 'tablespace':
1882
                $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'";
1883
1884
                break;
1885
            default:
1886
                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...
1887
        }
1888
1889
        // Fetch the ACL for object
1890
        $acl = $this->selectField($sql, 'acl');
1891
        if ($acl == -1) {
0 ignored issues
show
introduced by
The condition $acl == -1 is always false.
Loading history...
1892
            return -2;
1893
        }
1894
1895
        if ($acl == '' || $acl == null) {
1896
            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...
1897
        }
1898
1899
        return $this->_parseACL($acl);
1900
    }
1901
1902
    /**
1903
     * Internal function used for parsing ACLs.
1904
     *
1905
     * @param $acl The ACL to parse (of type aclitem[])
1906
     *
1907
     * @return Privileges array
1908
     */
1909
    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...
1910
    {
1911
        // Take off the first and last characters (the braces)
1912
        $acl = substr($acl, 1, strlen($acl) - 2);
1913
1914
        // Pick out individual ACE's by carefully parsing.  This is necessary in order
1915
        // to cope with usernames and stuff that contain commas
1916
        $aces      = [];
1917
        $i         = $j         = 0;
1918
        $in_quotes = false;
1919
        while ($i < strlen($acl)) {
1920
            // If current char is a double quote and it's not escaped, then
1921
            // enter quoted bit
1922
            $char = substr($acl, $i, 1);
1923
            if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) {
1924
                $in_quotes = !$in_quotes;
1 ignored issue
show
introduced by
The condition $in_quotes is always false.
Loading history...
1925
            } elseif ($char == ',' && !$in_quotes) {
1926
                // Add text so far to the array
1927
                $aces[] = substr($acl, $j, $i - $j);
1928
                $j      = $i + 1;
1929
            }
1930
            ++$i;
1931
        }
1932
        // Add final text to the array
1933
        $aces[] = substr($acl, $j);
1934
1935
        // Create the array to be returned
1936
        $temp = [];
1937
1938
        // For each ACE, generate an entry in $temp
1939
        foreach ($aces as $v) {
1940
            // If the ACE begins with a double quote, strip them off both ends
1941
            // and unescape backslashes and double quotes
1942
            $unquote = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $unquote is dead and can be removed.
Loading history...
1943
            if (strpos($v, '"') === 0) {
1944
                $v = substr($v, 1, strlen($v) - 2);
1945
                $v = str_replace('\\"', '"', $v);
1946
                $v = str_replace('\\\\', '\\', $v);
1947
            }
1948
1949
            // Figure out type of ACE (public, user or group)
1950
            if (strpos($v, '=') === 0) {
1951
                $atype = 'public';
1952
            } else {
1953
                if ($this->hasRoles()) {
1954
                    $atype = 'role';
1955
                } else {
1956
                    if (strpos($v, 'group ') === 0) {
1957
                        $atype = 'group';
1958
                        // Tear off 'group' prefix
1959
                        $v = substr($v, 6);
1960
                    } else {
1961
                        $atype = 'user';
1962
                    }
1963
                }
1964
            }
1965
1966
            // Break on unquoted equals sign...
1967
            $i         = 0;
1968
            $in_quotes = false;
1969
            $entity    = null;
1970
            $chars     = null;
1971
            while ($i < strlen($v)) {
1972
                // If current char is a double quote and it's not escaped, then
1973
                // enter quoted bit
1974
                $char      = substr($v, $i, 1);
1975
                $next_char = substr($v, $i + 1, 1);
1976
                if ($char == '"' && ($i == 0 || $next_char != '"')) {
1977
                    $in_quotes = !$in_quotes;
1 ignored issue
show
introduced by
The condition $in_quotes is always false.
Loading history...
1978
                } // Skip over escaped double quotes
1979
                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...
1980
                    ++$i;
1981
                } elseif ($char == '=' && !$in_quotes) {
1982
                    // Split on current equals sign
1983
                    $entity = substr($v, 0, $i);
1984
                    $chars  = substr($v, $i + 1);
1985
1986
                    break;
1987
                }
1988
                ++$i;
1989
            }
1990
1991
            // Check for quoting on entity name, and unescape if necessary
1992
            if (strpos($entity, '"') === 0) {
1993
                $entity = substr($entity, 1, strlen($entity) - 2);
1994
                $entity = str_replace('""', '"', $entity);
1995
            }
1996
1997
            // New row to be added to $temp
1998
            // (type, grantee, privileges, grantor, grant option?
1999
            $row = [$atype, $entity, [], '', []];
2000
2001
            // Loop over chars and add privs to $row
2002
            for ($i = 0; $i < strlen($chars); ++$i) {
2003
                // Append to row's privs list the string representing
2004
                // the privilege
2005
                $char = substr($chars, $i, 1);
2006
                if ($char == '*') {
2007
                    $row[4][] = $this->privmap[substr($chars, $i - 1, 1)];
2008
                } elseif ($char == '/') {
2009
                    $grantor = substr($chars, $i + 1);
2010
                    // Check for quoting
2011
                    if (strpos($grantor, '"') === 0) {
2012
                        $grantor = substr($grantor, 1, strlen($grantor) - 2);
2013
                        $grantor = str_replace('""', '"', $grantor);
2014
                    }
2015
                    $row[3] = $grantor;
2016
2017
                    break;
2018
                } else {
2019
                    if (!isset($this->privmap[$char])) {
2020
                        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...
2021
                    }
2022
2023
                    $row[2][] = $this->privmap[$char];
2024
                }
2025
            }
2026
2027
            // Append row to temp
2028
            $temp[] = $row;
2029
        }
2030
2031
        return $temp;
2032
    }
2033
2034
    /**
2035
     * Returns extra table definition information that is most usefully
2036
     * dumped after the table contents for speed and efficiency reasons.
2037
     *
2038
     * @param $table The table to define
2039
     *
2040
     * @return A string containing the formatted SQL code
2041
     */
2042
    public function getTableDefSuffix($table)
2043
    {
2044
        $sql = '';
2045
2046
        // Indexes
2047
        $indexes = $this->getIndexes($table);
2048
        if (!is_object($indexes)) {
2049
            $this->rollbackTransaction();
2050
2051
            return null;
2052
        }
2053
2054
        if ($indexes->recordCount() > 0) {
2055
            $sql .= "\n-- Indexes\n\n";
2056
            while (!$indexes->EOF) {
2057
                $sql .= $indexes->fields['inddef'] . ";\n";
2058
2059
                $indexes->moveNext();
2060
            }
2061
        }
2062
2063
        // Triggers
2064
        $triggers = $this->getTriggers($table);
2065
        if (!is_object($triggers)) {
2066
            $this->rollbackTransaction();
2067
2068
            return null;
2069
        }
2070
2071
        if ($triggers->recordCount() > 0) {
2072
            $sql .= "\n-- Triggers\n\n";
2073
            while (!$triggers->EOF) {
2074
                $sql .= $triggers->fields['tgdef'];
2075
                $sql .= ";\n";
2076
2077
                $triggers->moveNext();
2078
            }
2079
        }
2080
2081
        // Rules
2082
        $rules = $this->getRules($table);
2083
        if (!is_object($rules)) {
2084
            $this->rollbackTransaction();
2085
2086
            return null;
2087
        }
2088
2089
        if ($rules->recordCount() > 0) {
2090
            $sql .= "\n-- Rules\n\n";
2091
            while (!$rules->EOF) {
2092
                $sql .= $rules->fields['definition'] . "\n";
2093
2094
                $rules->moveNext();
2095
            }
2096
        }
2097
2098
        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\A.
Loading history...
2099
    }
2100
2101
    /**
2102
     * Grabs a list of indexes for a table.
2103
     *
2104
     * @param string $table  The name of a table whose indexes to retrieve
2105
     * @param bool|\PHPPgAdmin\Database\Only  $unique Only get unique/pk indexes
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Only 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...
2106
     *
2107
     * @return \PHPPgAdmin\ADORecordSet A recordset
2108
     */
2109
    public function getIndexes($table = '', $unique = false)
2110
    {
2111
        $this->clean($table);
2112
2113
        $sql = "
2114
			SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
2115
				pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef
2116
			FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
2117
			WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid)
2118
				AND c.oid = i.indrelid AND i.indexrelid = c2.oid
2119
		";
2120
        if ($unique) {
2121
            $sql .= ' AND i.indisunique ';
2122
        }
2123
2124
        $sql .= ' ORDER BY c2.relname';
2125
2126
        return $this->selectSet($sql);
2127
    }
2128
2129
    /**
2130
     * Grabs a list of triggers on a table.
2131
     *
2132
     * @param string $table The name of a table whose triggers to retrieve
2133
     *
2134
     * @return \PHPPgAdmin\ADORecordSet A recordset
2135
     */
2136
    public function getTriggers($table = '')
2137
    {
2138
        $c_schema = $this->_schema;
2139
        $this->clean($c_schema);
2140
        $this->clean($table);
2141
2142
        $sql = "SELECT
2143
				t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef,
2144
				CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid,
2145
				p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
2146
				ns.nspname AS pronamespace
2147
			FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns
2148
			WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
2149
				AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
2150
				AND ( tgconstraint = 0 OR NOT EXISTS
2151
						(SELECT 1 FROM pg_catalog.pg_depend d    JOIN pg_catalog.pg_constraint c
2152
							ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
2153
						WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))
2154
				AND p.oid=t.tgfoid
2155
				AND p.pronamespace = ns.oid";
2156
2157
        return $this->selectSet($sql);
2158
    }
2159
2160
    /**
2161
     * Returns a list of all rules on a table OR view.
2162
     *
2163
     * @param $table The table to find rules for
2164
     *
2165
     * @return \PHPPgAdmin\ADORecordSet A recordset
2166
     */
2167
    public function getRules($table)
2168
    {
2169
        $c_schema = $this->_schema;
2170
        $this->clean($c_schema);
2171
        $this->clean($table);
2172
2173
        $sql = "
2174
			SELECT *
2175
			FROM pg_catalog.pg_rules
2176
			WHERE
2177
				schemaname='{$c_schema}' AND tablename='{$table}'
2178
			ORDER BY rulename
2179
		";
2180
2181
        return $this->selectSet($sql);
2182
    }
2183
2184
    /**
2185
     * Creates a new table in the database.
2186
     *
2187
     * @param $name        The name of the table
2188
     * @param $fields      The number of fields
2189
     * @param $field       An array of field names
2190
     * @param $type        An array of field types
2191
     * @param $array       An array of '' or '[]' for each type if it's an array or not
2192
     * @param $length      An array of field lengths
2193
     * @param $notnull     An array of not null
2194
     * @param $default     An array of default values
2195
     * @param $withoutoids True if WITHOUT OIDS, false otherwise
2196
     * @param $colcomment  An array of comments
2197
     * @param $tblcomment
2198
     * @param $tablespace  The tablespace name ('' means none/default)
2199
     * @param $uniquekey   An Array indicating the fields that are unique (those indexes that are set)
2200
     * @param $primarykey  An Array indicating the field used for the primarykey (those indexes that are set)
2201
     *
2202
     * @return bool|int 0 success
2203
     *
2204
     * @internal param \PHPPgAdmin\Database\Table $comment comment
2205
     */
2206
    public function createTable(
2207
        $name,
2208
        $fields,
2209
        $field,
2210
        $type,
2211
        $array,
2212
        $length,
2213
        $notnull,
2214
        $default,
2215
        $withoutoids,
2216
        $colcomment,
2217
        $tblcomment,
2218
        $tablespace,
2219
        $uniquekey,
2220
        $primarykey
2221
    ) {
2222
        $f_schema = $this->_schema;
2223
        $this->fieldClean($f_schema);
2224
        $this->fieldClean($name);
2225
2226
        $status = $this->beginTransaction();
2227
        if ($status != 0) {
2228
            return -1;
2229
        }
2230
2231
        $found       = false;
2232
        $first       = true;
2233
        $comment_sql = ''; //Accumulate comments for the columns
2234
        $sql         = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (";
2235
        for ($i = 0; $i < $fields; ++$i) {
2236
            $this->fieldClean($field[$i]);
2237
            $this->clean($type[$i]);
2238
            $this->clean($length[$i]);
2239
            $this->clean($colcomment[$i]);
2240
2241
            // Skip blank columns - for user convenience
2242
            if ($field[$i] == '' || $type[$i] == '') {
2243
                continue;
2244
            }
2245
2246
            // If not the first column, add a comma
2247
            if (!$first) {
2248
                $sql .= ', ';
2249
            } else {
2250
                $first = false;
2251
            }
2252
2253
            switch ($type[$i]) {
2254
                // Have to account for weird placing of length for with/without
2255
                // time zone types
2256
                case 'timestamp with time zone':
2257
                case 'timestamp without time zone':
2258
                    $qual = substr($type[$i], 9);
2259
                    $sql .= "\"{$field[$i]}\" timestamp";
2260
                    if ($length[$i] != '') {
2261
                        $sql .= "({$length[$i]})";
2262
                    }
2263
2264
                    $sql .= $qual;
2265
2266
                    break;
2267
                case 'time with time zone':
2268
                case 'time without time zone':
2269
                    $qual = substr($type[$i], 4);
2270
                    $sql .= "\"{$field[$i]}\" time";
2271
                    if ($length[$i] != '') {
2272
                        $sql .= "({$length[$i]})";
2273
                    }
2274
2275
                    $sql .= $qual;
2276
2277
                    break;
2278
                default:
2279
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
2280
                    if ($length[$i] != '') {
2281
                        $sql .= "({$length[$i]})";
2282
                    }
2283
            }
2284
            // Add array qualifier if necessary
2285
            if ($array[$i] == '[]') {
2286
                $sql .= '[]';
2287
            }
2288
2289
            // Add other qualifiers
2290
            if (!isset($primarykey[$i])) {
2291
                if (isset($uniquekey[$i])) {
2292
                    $sql .= ' UNIQUE';
2293
                }
2294
2295
                if (isset($notnull[$i])) {
2296
                    $sql .= ' NOT NULL';
2297
                }
2298
            }
2299
            if ($default[$i] != '') {
2300
                $sql .= " DEFAULT {$default[$i]}";
2301
            }
2302
2303
            if ($colcomment[$i] != '') {
2304
                $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
2305
            }
2306
2307
            $found = true;
2308
        }
2309
2310
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
2311
            return -1;
2312
        }
2313
2314
        // PRIMARY KEY
2315
        $primarykeycolumns = [];
2316
        for ($i = 0; $i < $fields; ++$i) {
2317
            if (isset($primarykey[$i])) {
2318
                $primarykeycolumns[] = "\"{$field[$i]}\"";
2319
            }
2320
        }
2321
        if (count($primarykeycolumns) > 0) {
2322
            $sql .= ', PRIMARY KEY (' . implode(', ', $primarykeycolumns) . ')';
2323
        }
2324
2325
        $sql .= ')';
2326
2327
        // WITHOUT OIDS
2328
        if ($withoutoids) {
2329
            $sql .= ' WITHOUT OIDS';
2330
        } else {
2331
            $sql .= ' WITH OIDS';
2332
        }
2333
2334
        // Tablespace
2335
        if ($this->hasTablespaces() && $tablespace != '') {
2336
            $this->fieldClean($tablespace);
2337
            $sql .= " TABLESPACE \"{$tablespace}\"";
2338
        }
2339
2340
        $status = $this->execute($sql);
2341
        if ($status) {
2342
            $this->rollbackTransaction();
2343
2344
            return -1;
2345
        }
2346
2347
        if ($tblcomment != '') {
2348
            $status = $this->setComment('TABLE', '', $name, $tblcomment, true);
2349
            if ($status) {
2350
                $this->rollbackTransaction();
2351
2352
                return -1;
2353
            }
2354
        }
2355
2356
        if ($comment_sql != '') {
2357
            $status = $this->execute($comment_sql);
2358
            if ($status) {
2359
                $this->rollbackTransaction();
2360
2361
                return -1;
2362
            }
2363
        }
2364
2365
        return $this->endTransaction();
2366
    }
2367
2368
    /**
2369
     * Creates a new table in the database copying attribs and other properties from another table.
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...
2370
     *
2371
     * @param                                 $name        The name of the table
2372
     * @param                                 $like        an array giving the schema ans the name of the table from which attribs are copying
2373
     *                                                     from: array(
2374
     *                                                     'table' => table name,
2375
     *                                                     'schema' => the schema name,
2376
     *                                                     )
2377
     * @param bool    $defaults    if true, copy the defaults values as well
2378
     * @param bool    $constraints if true, copy the constraints as well (CHECK on table & attr)
2379
     * @param bool                            $idx
2380
     * @param string $tablespace  The tablespace name ('' means none/default)
2381
     *
2382
     * @return bool|int
2383
     */
2384
    public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '')
2385
    {
2386
        $f_schema = $this->_schema;
2387
        $this->fieldClean($f_schema);
2388
        $this->fieldClean($name);
2389
        $this->fieldClean($like['schema']);
2390
        $this->fieldClean($like['table']);
2391
        $like = "\"{$like['schema']}\".\"{$like['table']}\"";
2392
2393
        $status = $this->beginTransaction();
2394
        if ($status != 0) {
2395
            return -1;
2396
        }
2397
2398
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}";
2399
2400
        if ($defaults) {
2401
            $sql .= ' INCLUDING DEFAULTS';
2402
        }
2403
2404
        if ($this->hasCreateTableLikeWithConstraints() && $constraints) {
2405
            $sql .= ' INCLUDING CONSTRAINTS';
2406
        }
2407
2408
        if ($this->hasCreateTableLikeWithIndexes() && $idx) {
2409
            $sql .= ' INCLUDING INDEXES';
2410
        }
2411
2412
        $sql .= ')';
2413
2414
        if ($this->hasTablespaces() && $tablespace != '') {
2415
            $this->fieldClean($tablespace);
2416
            $sql .= " TABLESPACE \"{$tablespace}\"";
2417
        }
2418
2419
        $status = $this->execute($sql);
2420
        if ($status) {
0 ignored issues
show
introduced by
$status is of type ADORecordSet, thus it always evaluated to true.
Loading history...
2421
            $this->rollbackTransaction();
2422
2423
            return -1;
2424
        }
2425
2426
        return $this->endTransaction();
2427
    }
2428
2429
    /**
2430
     * Alter table properties.
2431
     *
2432
     * @param $table      The name of the table
2433
     * @param $name       The new name for the table
2434
     * @param $owner      The new owner for the table
2435
     * @param $schema     The new schema for the table
2436
     * @param $comment    The comment on the table
2437
     * @param $tablespace The new tablespace for the table ('' means leave as is)
2438
     *
2439
     * @return bool|int 0 success
2440
     */
2441
    public function alterTable($table, $name, $owner, $schema, $comment, $tablespace)
2442
    {
2443
        $data = $this->getTable($table);
2444
2445
        if ($data->recordCount() != 1) {
0 ignored issues
show
introduced by
The condition $data->recordCount() != 1 is always true.
Loading history...
2446
            return -2;
2447
        }
2448
2449
        $status = $this->beginTransaction();
2450
        if ($status != 0) {
2451
            $this->rollbackTransaction();
2452
2453
            return -1;
2454
        }
2455
2456
        $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace);
2457
2458
        if ($status != 0) {
2459
            $this->rollbackTransaction();
2460
2461
            return $status;
2462
        }
2463
2464
        return $this->endTransaction();
2465
    }
2466
2467
    /**
2468
     * Protected method which alter a table
2469
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION.
2470
     *
2471
     * @param $tblrs      The table recordSet returned by getTable()
2472
     * @param $name       The new name for the table
2473
     * @param $owner      The new owner for the table
2474
     * @param $schema     The new schema for the table
2475
     * @param $comment    The comment on the table
2476
     * @param $tablespace The new tablespace for the table ('' means leave as is)
2477
     *
2478
     * @return int 0 success
2479
     */
2480
    protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace)
1 ignored issue
show
Coding Style introduced by
Protected method name "Postgres::_alterTable" must not be prefixed with an underscore
Loading history...
2481
    {
2482
        $this->fieldArrayClean($tblrs->fields);
2483
2484
        // Comment
2485
        $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment);
2486
        if ($status != 0) {
2487
            return -4;
2488
        }
2489
2490
        // Owner
2491
        $this->fieldClean($owner);
2492
        $status = $this->alterTableOwner($tblrs, $owner);
2493
        if ($status != 0) {
2494
            return -5;
2495
        }
2496
2497
        // Tablespace
2498
        $this->fieldClean($tablespace);
2499
        $status = $this->alterTableTablespace($tblrs, $tablespace);
2500
        if ($status != 0) {
2501
            return -6;
2502
        }
2503
2504
        // Rename
2505
        $this->fieldClean($name);
2506
        $status = $this->alterTableName($tblrs, $name);
2507
        if ($status != 0) {
2508
            return -3;
2509
        }
2510
2511
        // Schema
2512
        $this->fieldClean($schema);
2513
        $status = $this->alterTableSchema($tblrs, $schema);
2514
        if ($status != 0) {
2515
            return -7;
2516
        }
2517
2518
        return 0;
2519
    }
2520
2521
    /**
2522
     * Alter a table's owner
2523
     * /!\ this function is called from _alterTable which take care of escaping fields.
2524
     *
2525
     * @param      $tblrs The table RecordSet returned by getTable()
2526
     * @param null $owner
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $owner is correct as it would always require null to be passed?
Loading history...
2527
     *
2528
     * @return int 0 if operation was successful
2529
     *
2530
     * @internal param \PHPPgAdmin\Database\The $name new table's owner
2531
     */
2532
    public function alterTableOwner($tblrs, $owner = null)
2533
    {
2534
        /* vars cleaned in _alterTable */
2535
        if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) {
2536
            $f_schema = $this->_schema;
2537
            $this->fieldClean($f_schema);
2538
            // If owner has been changed, then do the alteration.  We are
2539
            // careful to avoid this generally as changing owner is a
2540
            // superuser only function.
2541
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\"";
2542
2543
            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...
2544
        }
2545
2546
        return 0;
2547
    }
2548
2549
    /**
2550
     * Alter a table's tablespace
2551
     * /!\ this function is called from _alterTable which take care of escaping fields.
2552
     *
2553
     * @param      $tblrs      The table RecordSet returned by getTable()
2554
     * @param null $tablespace
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $tablespace is correct as it would always require null to be passed?
Loading history...
2555
     *
2556
     * @return int 0 if operation was successful
2557
     *
2558
     * @internal param \PHPPgAdmin\Database\The $name new table's tablespace
2559
     */
2560
    public function alterTableTablespace($tblrs, $tablespace = null)
2561
    {
2562
        /* vars cleaned in _alterTable */
2563
        if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) {
2564
            $f_schema = $this->_schema;
2565
            $this->fieldClean($f_schema);
2566
2567
            // If tablespace has been changed, then do the alteration.  We
2568
            // don't want to do this unnecessarily.
2569
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\"";
2570
2571
            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...
2572
        }
2573
2574
        return 0;
2575
    }
2576
2577
    /**
2578
     * Alter a table's name
2579
     * /!\ this function is called from _alterTable which take care of escaping fields.
2580
     *
2581
     * @param $tblrs The table RecordSet returned by getTable()
2582
     * @param $name  The new table's name
2583
     *
2584
     * @return int 0 if operation was successful
2585
     */
2586
    public function alterTableName($tblrs, $name = null)
2587
    {
2588
        /* vars cleaned in _alterTable */
2589
        // Rename (only if name has changed)
2590
        if (!empty($name) && ($name != $tblrs->fields['relname'])) {
2591
            $f_schema = $this->_schema;
2592
            $this->fieldClean($f_schema);
2593
2594
            $sql    = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" RENAME TO \"{$name}\"";
2595
            $status = $this->execute($sql);
2596
            if ($status == 0) {
0 ignored issues
show
introduced by
The condition $status == 0 is always false.
Loading history...
2597
                $tblrs->fields['relname'] = $name;
2598
            } else {
2599
                return $status;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $status returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
2600
            }
2601
        }
2602
2603
        return 0;
2604
    }
2605
2606
    // Row functions
2607
2608
    /**
2609
     * Alter a table's schema
2610
     * /!\ this function is called from _alterTable which take care of escaping fields.
2611
     *
2612
     * @param      $tblrs  The table RecordSet returned by getTable()
2613
     * @param null $schema
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $schema is correct as it would always require null to be passed?
Loading history...
2614
     *
2615
     * @return int 0 if operation was successful
2616
     *
2617
     * @internal param \PHPPgAdmin\Database\The $name new table's schema
2618
     */
2619
    public function alterTableSchema($tblrs, $schema = null)
2620
    {
2621
        /* vars cleaned in _alterTable */
2622
        if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) {
2623
            $f_schema = $this->_schema;
2624
            $this->fieldClean($f_schema);
2625
            // If tablespace has been changed, then do the alteration.  We
2626
            // don't want to do this unnecessarily.
2627
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET SCHEMA \"{$schema}\"";
2628
2629
            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...
2630
        }
2631
2632
        return 0;
2633
    }
2634
2635
    /**
2636
     * Empties a table in the database.
2637
     *
2638
     * @param $table The table to be emptied
2639
     * @param $cascade True to cascade truncate, false to restrict
2640
     *
2641
     * @return integer 0 if operation was successful
2642
     */
2643
    public function emptyTable($table, $cascade)
2644
    {
2645
        $f_schema = $this->_schema;
2646
        $this->fieldClean($f_schema);
2647
        $this->fieldClean($table);
2648
2649
        $sql = "TRUNCATE TABLE \"{$f_schema}\".\"{$table}\" ";
2650
        if ($cascade) {
2651
            $sql = $sql . ' CASCADE';
2652
        }
2653
2654
        $status = $this->execute($sql);
2655
2656
        return [$status, $sql];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($status, $sql) returns the type array<integer,string|ADORecordSet> which is incompatible with the documented return type integer.
Loading history...
2657
    }
2658
2659
    /**
2660
     * Removes a table from the database.
2661
     *
2662
     * @param $table   The table to drop
2663
     * @param $cascade True to cascade drop, false to restrict
2664
     *
2665
     * @return integer 0 if operation was successful
2666
     */
2667
    public function dropTable($table, $cascade)
2668
    {
2669
        $f_schema = $this->_schema;
2670
        $this->fieldClean($f_schema);
2671
        $this->fieldClean($table);
2672
2673
        $sql = "DROP TABLE \"{$f_schema}\".\"{$table}\"";
2674
        if ($cascade) {
2675
            $sql .= ' CASCADE';
2676
        }
2677
2678
        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...
2679
    }
2680
2681
    /**
2682
     * Add a new column to a table.
2683
     *
2684
     * @param $table   The table to add to
2685
     * @param $column  The name of the new column
2686
     * @param $type    The type of the column
2687
     * @param $array   True if array type, false otherwise
2688
     * @param $length  The optional size of the column (ie. 30 for varchar(30))
2689
     * @param $notnull True if NOT NULL, false otherwise
2690
     * @param $default The default for the column.  '' for none.
2691
     * @param $comment
2692
     *
2693
     * @return bool|int 0 success
2694
     */
2695
    public function addColumn($table, $column, $type, $array, $length, $notnull, $default, $comment)
2696
    {
2697
        $f_schema = $this->_schema;
2698
        $this->fieldClean($f_schema);
2699
        $this->fieldClean($table);
2700
        $this->fieldClean($column);
2701
        $this->clean($type);
2702
        $this->clean($length);
2703
2704
        if ($length == '') {
2705
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}";
2706
        } else {
2707
            switch ($type) {
2708
                // Have to account for weird placing of length for with/without
2709
                // time zone types
2710
                case 'timestamp with time zone':
2711
                case 'timestamp without time zone':
2712
                    $qual = substr($type, 9);
2713
                    $sql  = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" timestamp({$length}){$qual}";
2714
2715
                    break;
2716
                case 'time with time zone':
2717
                case 'time without time zone':
2718
                    $qual = substr($type, 4);
2719
                    $sql  = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" time({$length}){$qual}";
2720
2721
                    break;
2722
                default:
2723
                    $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}({$length})";
2724
            }
2725
        }
2726
2727
        // Add array qualifier, if requested
2728
        if ($array) {
2729
            $sql .= '[]';
2730
        }
2731
2732
        // If we have advanced column adding, add the extra qualifiers
2733
        if ($this->hasCreateFieldWithConstraints()) {
2734
            // NOT NULL clause
2735
            if ($notnull) {
2736
                $sql .= ' NOT NULL';
2737
            }
2738
2739
            // DEFAULT clause
2740
            if ($default != '') {
2741
                $sql .= ' DEFAULT ' . $default;
2742
            }
2743
        }
2744
2745
        $status = $this->beginTransaction();
2746
        if ($status != 0) {
2747
            return -1;
2748
        }
2749
2750
        $status = $this->execute($sql);
2751
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
2752
            $this->rollbackTransaction();
2753
2754
            return -1;
2755
        }
2756
2757
        $status = $this->setComment('COLUMN', $column, $table, $comment);
2758
        if ($status != 0) {
2759
            $this->rollbackTransaction();
2760
2761
            return -1;
2762
        }
2763
2764
        return $this->endTransaction();
2765
    }
2766
2767
    /**
2768
     * Alters a column in a table.
2769
     *
2770
     * @param $table      The table in which the column resides
2771
     * @param $column     The column to alter
2772
     * @param $name       The new name for the column
2773
     * @param $notnull    (boolean) True if not null, false otherwise
2774
     * @param $oldnotnull (boolean) True if column is already not null, false otherwise
2775
     * @param $default    The new default for the column
2776
     * @param $olddefault The old default for the column
2777
     * @param $type       The new type for the column
2778
     * @param $length     The optional size of the column (ie. 30 for varchar(30))
2779
     * @param $array      True if array type, false otherwise
2780
     * @param $oldtype    The old type for the column
2781
     * @param $comment    Comment for the column
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Comment 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...
2782
     *
2783
     * @return array 0 success
2784
     */
2785
    public function alterColumn(
2786
        $table,
2787
        $column,
2788
        $name,
2789
        $notnull,
2790
        $oldnotnull,
2791
        $default,
2792
        $olddefault,
2793
        $type,
2794
        $length,
2795
        $array,
2796
        $oldtype,
2797
        $comment
2798
    ) {
2799
        // Begin transaction
2800
        $status = $this->beginTransaction();
2801
        $sql    = '';
2802
        if ($status != 0) {
2803
            $this->rollbackTransaction();
2804
2805
            return [-6, $sql];
2806
        }
2807
2808
        // Rename the column, if it has been changed
2809
        if ($column != $name) {
2810
            $status = $this->renameColumn($table, $column, $name);
2811
            if ($status != 0) {
2812
                $this->rollbackTransaction();
2813
2814
                return [-4, $sql];
2815
            }
2816
        }
2817
2818
        $f_schema = $this->_schema;
2819
        $this->fieldClean($f_schema);
2820
        $this->fieldClean($name);
2821
        $this->fieldClean($table);
2822
        $this->fieldClean($column);
2823
2824
        $toAlter = [];
2825
        // Create the command for changing nullability
2826
        if ($notnull != $oldnotnull) {
2827
            $toAlter[] = "ALTER COLUMN \"{$name}\" " . ($notnull ? 'SET' : 'DROP') . ' NOT NULL';
2828
        }
2829
2830
        // Add default, if it has changed
2831
        if ($default != $olddefault) {
2832
            if ($default == '') {
2833
                $toAlter[] = "ALTER COLUMN \"{$name}\" DROP DEFAULT";
2834
            } else {
2835
                $toAlter[] = "ALTER COLUMN \"{$name}\" SET DEFAULT {$default}";
2836
            }
2837
        }
2838
2839
        // Add type, if it has changed
2840
        if ($length == '') {
2841
            $ftype = $type;
2842
        } else {
2843
            switch ($type) {
2844
                // Have to account for weird placing of length for with/without
2845
                // time zone types
2846
                case 'timestamp with time zone':
2847
                case 'timestamp without time zone':
2848
                    $qual  = substr($type, 9);
2849
                    $ftype = "timestamp({$length}){$qual}";
2850
2851
                    break;
2852
                case 'time with time zone':
2853
                case 'time without time zone':
2854
                    $qual  = substr($type, 4);
2855
                    $ftype = "time({$length}){$qual}";
2856
2857
                    break;
2858
                default:
2859
                    $ftype = "{$type}({$length})";
2860
            }
2861
        }
2862
2863
        // Add array qualifier, if requested
2864
        if ($array) {
2865
            $ftype .= '[]';
2866
        }
2867
2868
        if ($ftype != $oldtype) {
2869
            $toAlter[] = "ALTER COLUMN \"{$name}\" TYPE {$ftype}";
2870
        }
2871
2872
        // Attempt to process the batch alteration, if anything has been changed
2873
        if (!empty($toAlter)) {
2874
            // Initialise an empty SQL string
2875
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" "
2876
            . implode(',', $toAlter);
2877
2878
            $status = $this->execute($sql);
2879
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
2880
                $this->rollbackTransaction();
2881
2882
                return [-1, $sql];
2883
            }
2884
        }
2885
2886
        // Update the comment on the column
2887
        $status = $this->setComment('COLUMN', $name, $table, $comment);
2888
        if ($status != 0) {
2889
            $this->rollbackTransaction();
2890
2891
            return [-5, $sql];
2892
        }
2893
2894
        return [$this->endTransaction(), $sql];
2895
    }
2896
2897
    /**
2898
     * Renames a column in a table.
2899
     *
2900
     * @param $table   The table containing the column to be renamed
2901
     * @param $column  The column to be renamed
2902
     * @param $newName The new name for the column
2903
     *
2904
     * @return integer 0 if operation was successful
2905
     */
2906
    public function renameColumn($table, $column, $newName)
2907
    {
2908
        $f_schema = $this->_schema;
2909
        $this->fieldClean($f_schema);
2910
        $this->fieldClean($table);
2911
        $this->fieldClean($column);
2912
        $this->fieldClean($newName);
2913
2914
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" RENAME COLUMN \"{$column}\" TO \"{$newName}\"";
2915
2916
        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...
2917
    }
2918
2919
    /**
2920
     * Sets default value of a column.
2921
     *
2922
     * @param $table   The table from which to drop
2923
     * @param $column  The column name to set
2924
     * @param $default The new default value
2925
     *
2926
     * @return integer 0 if operation was successful
2927
     */
2928
    public function setColumnDefault($table, $column, $default)
2929
    {
2930
        $f_schema = $this->_schema;
2931
        $this->fieldClean($f_schema);
2932
        $this->fieldClean($table);
2933
        $this->fieldClean($column);
2934
2935
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" SET DEFAULT {$default}";
2936
2937
        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...
2938
    }
2939
2940
    /**
2941
     * Sets whether or not a column can contain NULLs.
2942
     *
2943
     * @param $table  The table that contains the column
2944
     * @param $column The column to alter
2945
     * @param $state  True to set null, false to set not null
2946
     *
2947
     * @return integer 0 if operation was successful
2948
     */
2949
    public function setColumnNull($table, $column, $state)
2950
    {
2951
        $f_schema = $this->_schema;
2952
        $this->fieldClean($f_schema);
2953
        $this->fieldClean($table);
2954
        $this->fieldClean($column);
2955
2956
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . ($state ? 'DROP' : 'SET') . ' NOT NULL';
2957
2958
        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...
2959
    }
2960
2961
    /**
2962
     * Drops a column from a table.
2963
     *
2964
     * @param $table   The table from which to drop a column
2965
     * @param $column  The column to be dropped
2966
     * @param $cascade True to cascade drop, false to restrict
2967
     *
2968
     * @return integer 0 if operation was successful
2969
     */
2970
    public function dropColumn($table, $column, $cascade)
2971
    {
2972
        $f_schema = $this->_schema;
2973
        $this->fieldClean($f_schema);
2974
        $this->fieldClean($table);
2975
        $this->fieldClean($column);
2976
2977
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DROP COLUMN \"{$column}\"";
2978
        if ($cascade) {
2979
            $sql .= ' CASCADE';
2980
        }
2981
2982
        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...
2983
    }
2984
2985
    /**
2986
     * Drops default value of a column.
2987
     *
2988
     * @param $table  The table from which to drop
2989
     * @param $column The column name to drop default
2990
     *
2991
     * @return integer 0 if operation was successful
2992
     */
2993
    public function dropColumnDefault($table, $column)
2994
    {
2995
        $f_schema = $this->_schema;
2996
        $this->fieldClean($f_schema);
2997
        $this->fieldClean($table);
2998
        $this->fieldClean($column);
2999
3000
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT";
3001
3002
        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...
3003
    }
3004
3005
    /**
3006
     * Sets up the data object for a dump.  eg. Starts the appropriate
3007
     * transaction, sets variables, etc.
3008
     *
3009
     * @return int 0 success
3010
     */
3011
    public function beginDump()
3012
    {
3013
        // Begin serializable transaction (to dump consistent data)
3014
        $status = $this->beginTransaction();
3015
        if ($status != 0) {
3016
            return -1;
3017
        }
3018
3019
        // Set serializable
3020
        $sql    = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE';
3021
        $status = $this->execute($sql);
3022
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
3023
            $this->rollbackTransaction();
3024
3025
            return -1;
3026
        }
3027
3028
        // Set datestyle to ISO
3029
        $sql    = 'SET DATESTYLE = ISO';
3030
        $status = $this->execute($sql);
3031
        if ($status != 0) {
3032
            $this->rollbackTransaction();
3033
3034
            return -1;
3035
        }
3036
3037
        // Set extra_float_digits to 2
3038
        $sql    = 'SET extra_float_digits TO 2';
3039
        $status = $this->execute($sql);
3040
        if ($status != 0) {
3041
            $this->rollbackTransaction();
3042
3043
            return -1;
3044
        }
3045
3046
        return 0;
3047
    }
3048
3049
    /**
3050
     * Ends the data object for a dump.
3051
     *
3052
     * @return bool 0 success
3053
     */
3054
    public function endDump()
3055
    {
3056
        return $this->endTransaction();
3057
    }
3058
3059
    /**
3060
     * Returns a recordset of all columns in a relation.  Used for data export.
3061
     *
3062
     * @@ Note: Really needs to use a cursor
3063
     *
3064
     * @param $relation The name of a relation
1 ignored issue
show
Coding Style introduced by
Parameter tags must be defined first in a doc comment
Loading history...
3065
     * @param $oids
3066
     *
3067
     * @return \PHPPgAdmin\ADORecordSet A recordset on success
3068
     */
3069
    public function dumpRelation($relation, $oids)
3070
    {
3071
        $this->fieldClean($relation);
3072
3073
        // Actually retrieve the rows
3074
        if ($oids) {
3075
            $oid_str = $this->id . ', ';
3076
        } else {
3077
            $oid_str = '';
3078
        }
3079
3080
        return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\"");
3081
    }
3082
3083
    /**
3084
     * Returns all available autovacuum per table information.
3085
     *
3086
     * @param \PHPPgAdmin\Database\if|string $table if given, return autovacuum info for the given table or return all informations for all table
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...
3087
     *
3088
     * @return \PHPPgAdmin\ADORecordSet A recordset
3089
     */
3090
    public function getTableAutovacuum($table = '')
3091
    {
3092
        $sql = '';
3093
3094
        if ($table !== '') {
3095
            $this->clean($table);
3096
            $c_schema = $this->_schema;
3097
            $this->clean($c_schema);
3098
3099
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
3100
				FROM pg_class c
3101
					LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
3102
				WHERE c.relkind = 'r'::\"char\"
3103
					AND n.nspname NOT IN ('pg_catalog','information_schema')
3104
					AND c.reloptions IS NOT NULL
3105
					AND c.relname = '{$table}' AND n.nspname = '{$c_schema}'
3106
				ORDER BY nspname, relname";
3107
        } else {
3108
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
3109
				FROM pg_class c
3110
					LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
3111
				WHERE c.relkind = 'r'::\"char\"
3112
					AND n.nspname NOT IN ('pg_catalog','information_schema')
3113
					AND c.reloptions IS NOT NULL
3114
				ORDER BY nspname, relname";
3115
        }
3116
3117
        /* tmp var to parse the results */
3118
        $_autovacs = $this->selectSet($sql);
3119
3120
        /* result aray to return as RS */
3121
        $autovacs = [];
3122
        while (!$_autovacs->EOF) {
3123
            $_ = [
3124
                'nspname' => $_autovacs->fields['nspname'],
3125
                'relname' => $_autovacs->fields['relname'],
3126
            ];
3127
3128
            foreach (explode(',', $_autovacs->fields['reloptions']) as $var) {
3129
                list($o, $v) = explode('=', $var);
3130
                $_[$o]       = $v;
3131
            }
3132
3133
            $autovacs[] = $_;
3134
3135
            $_autovacs->moveNext();
3136
        }
3137
3138
        return new \PHPPgAdmin\ArrayRecordSet($autovacs);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new PHPPgAdmin\ArrayRecordSet($autovacs) returns the type PHPPgAdmin\ArrayRecordSet which is incompatible with the documented return type PHPPgAdmin\ADORecordSet.
Loading history...
3139
    }
3140
3141
    /**
3142
     * Get the fields for uniquely identifying a row in a table.
3143
     *
3144
     * @param $table The table for which to retrieve the identifier
3145
     *
3146
     * @return An array mapping attribute number to attribute name, empty for no identifiers
3147
     * @return -1 error
0 ignored issues
show
Coding Style introduced by
Only 1 @return tag is allowed in a function comment
Loading history...
3148
     */
3149
    public function getRowIdentifier($table)
3150
    {
3151
        $oldtable = $table;
3152
        $c_schema = $this->_schema;
3153
        $this->clean($c_schema);
3154
        $this->clean($table);
3155
3156
        $status = $this->beginTransaction();
3157
        if ($status != 0) {
3158
            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...
3159
        }
3160
3161
        // Get the first primary or unique index (sorting primary keys first) that
3162
        // is NOT a partial index.
3163
        $sql = "
3164
			SELECT indrelid, indkey
3165
			FROM pg_catalog.pg_index
3166
			WHERE indisunique AND indrelid=(
3167
				SELECT oid FROM pg_catalog.pg_class
3168
				WHERE relname='{$table}' AND relnamespace=(
3169
					SELECT oid FROM pg_catalog.pg_namespace
3170
					WHERE nspname='{$c_schema}'
3171
				)
3172
			) AND indpred IS NULL AND indexprs IS NULL
3173
			ORDER BY indisprimary DESC LIMIT 1";
3174
        $rs = $this->selectSet($sql);
3175
3176
        // If none, check for an OID column.  Even though OIDs can be duplicated, the edit and delete row
3177
        // functions check that they're only modiying a single row.  Otherwise, return empty array.
3178
        if ($rs->recordCount() == 0) {
0 ignored issues
show
introduced by
The condition $rs->recordCount() == 0 is always false.
Loading history...
3179
            // Check for OID column
3180
            $temp = [];
3181
            if ($this->hasObjectID($table)) {
3182
                $temp = ['oid'];
3183
            }
3184
            $this->endTransaction();
3185
3186
            return $temp;
3187
        } // Otherwise find the names of the keys
3188
3189
        $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey']));
3190
        if (!is_array($attnames)) {
0 ignored issues
show
introduced by
The condition is_array($attnames) is always false.
Loading history...
3191
            $this->rollbackTransaction();
3192
3193
            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...
3194
        }
3195
3196
        $this->endTransaction();
3197
3198
        return $attnames;
3199
    }
3200
3201
    /**
3202
     * Adds a new row to a table.
3203
     *
3204
     * @param $table  The table in which to insert
3205
     * @param $fields Array of given field in values
3206
     * @param $values Array of new values for the row
3207
     * @param $nulls  An array mapping column => something if it is to be null
3208
     * @param $format An array of the data type (VALUE or EXPRESSION)
3209
     * @param $types  An array of field types
3210
     *
3211
     * @return int 0 if operation was successful
3212
     */
3213
    public function insertRow($table, $fields, $values, $nulls, $format, $types)
3214
    {
3215
        if (!is_array($fields) || !is_array($values) || !is_array($nulls)
3216
            || !is_array($format) || !is_array($types)
3217
            || (count($fields) != count($values))
3218
        ) {
3219
            return -1;
3220
        }
3221
3222
        // Build clause
3223
        if (count($values) > 0) {
3224
            // Escape all field names
3225
            $fields   = array_map(['\PHPPgAdmin\Database\Postgres', 'fieldClean'], $fields);
3226
            $f_schema = $this->_schema;
3227
            $this->fieldClean($table);
3228
            $this->fieldClean($f_schema);
3229
3230
            $sql = '';
3231
            foreach ($values as $i => $value) {
3232
                // Handle NULL values
3233
                if (isset($nulls[$i])) {
3234
                    $sql .= ',NULL';
3235
                } else {
3236
                    $sql .= ',' . $this->formatValue($types[$i], $format[$i], $value);
3237
                }
3238
            }
3239
3240
            $sql = "INSERT INTO \"{$f_schema}\".\"{$table}\" (\"" . implode('","', $fields) . '")
3241
                VALUES (' . substr($sql, 1) . ')';
3242
3243
            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...
3244
        }
3245
3246
        return -1;
3247
    }
3248
3249
    /**
3250
     * Formats a value or expression for sql purposes.
3251
     *
3252
     * @param $type   The type of the field
3253
     * @param $format VALUE or EXPRESSION
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\VALUE 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...
3254
     * @param $value  The actual value entered in the field.  Can be NULL
3255
     *
3256
     * @return The suitably quoted and escaped value
3257
     */
3258
    public function formatValue($type, $format, $value)
3259
    {
3260
        switch ($type) {
3261
            case 'bool':
3262
            case 'boolean':
3263
                if ($value == 't') {
3264
                    return 'TRUE';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'TRUE' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
3265
                }
3266
3267
                if ($value == 'f') {
3268
                    return 'FALSE';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'FALSE' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
3269
                }
3270
                if ($value == '') {
3271
                    return 'NULL';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'NULL' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
3272
                }
3273
3274
                return $value;
3275
                break;
1 ignored issue
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
3276
            default:
3277
                // Checking variable fields is difficult as there might be a size
3278
                // attribute...
3279
                if (strpos($type, 'time') === 0) {
3280
                    // Assume it's one of the time types...
3281
                    if ($value == '') {
3282
                        return "''";
0 ignored issues
show
Bug Best Practice introduced by
The expression return '''' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
3283
                    }
3284
3285
                    if (strcasecmp($value, 'CURRENT_TIMESTAMP') == 0
3286
                        || strcasecmp($value, 'CURRENT_TIME') == 0
3287
                        || strcasecmp($value, 'CURRENT_DATE') == 0
3288
                        || strcasecmp($value, 'LOCALTIME') == 0
3289
                        || strcasecmp($value, 'LOCALTIMESTAMP') == 0) {
1 ignored issue
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
3290
                        return $value;
3291
                    }
3292
                    if ($format == 'EXPRESSION') {
3293
                        return $value;
3294
                    }
3295
                    $this->clean($value);
3296
3297
                    return "'{$value}'";
0 ignored issues
show
Bug Best Practice introduced by
The expression return '''.$value.''' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
3298
                }
3299
                if ($format == 'VALUE') {
3300
                    $this->clean($value);
3301
3302
                    return "'{$value}'";
0 ignored issues
show
Bug Best Practice introduced by
The expression return '''.$value.''' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
3303
                }
3304
3305
                return $value;
3306
        }
3307
    }
3308
3309
    // View functions
3310
3311
    /**
3312
     * Updates a row in a table.
3313
     *
3314
     * @param $table  The table in which to update
3315
     * @param $vars   An array mapping new values for the row
3316
     * @param $nulls  An array mapping column => something if it is to be null
3317
     * @param $format An array of the data type (VALUE or EXPRESSION)
3318
     * @param $types  An array of field types
3319
     * @param $keyarr An array mapping column => value to update
3320
     *
3321
     * @return bool|int 0 success
3322
     */
3323
    public function editRow($table, $vars, $nulls, $format, $types, $keyarr)
3324
    {
3325
        if (!is_array($vars) || !is_array($nulls) || !is_array($format) || !is_array($types)) {
3326
            return -1;
3327
        }
3328
3329
        $f_schema = $this->_schema;
3330
        $this->fieldClean($f_schema);
3331
        $this->fieldClean($table);
3332
3333
        // Build clause
3334
        if (sizeof($vars) > 0) {
3335
            foreach ($vars as $key => $value) {
3336
                $this->fieldClean($key);
3337
3338
                // Handle NULL values
3339
                if (isset($nulls[$key])) {
3340
                    $tmp = 'NULL';
3341
                } else {
3342
                    $tmp = $this->formatValue($types[$key], $format[$key], $value);
3343
                }
3344
3345
                if (isset($sql)) {
3346
                    $sql .= ", \"{$key}\"={$tmp}";
3347
                } else {
3348
                    $sql = "UPDATE \"{$f_schema}\".\"{$table}\" SET \"{$key}\"={$tmp}";
3349
                }
3350
            }
3351
            $first = true;
3352
            foreach ($keyarr as $k => $v) {
3353
                $this->fieldClean($k);
3354
                $this->clean($v);
3355
                if ($first) {
3356
                    $sql .= " WHERE \"{$k}\"='{$v}'";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sql does not seem to be defined for all execution paths leading up to this point.
Loading history...
3357
                    $first = false;
3358
                } else {
3359
                    $sql .= " AND \"{$k}\"='{$v}'";
3360
                }
3361
            }
3362
        }
3363
3364
        // Begin transaction.  We do this so that we can ensure only one row is
3365
        // edited
3366
        $status = $this->beginTransaction();
3367
        if ($status != 0) {
3368
            $this->rollbackTransaction();
3369
3370
            return -1;
3371
        }
3372
3373
        $status = $this->execute($sql);
3374
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
3375
            // update failed
3376
            $this->rollbackTransaction();
3377
3378
            return -1;
3379
        }
3380
3381
        if ($this->conn->Affected_Rows() != 1) {
3382
            // more than one row could be updated
3383
            $this->rollbackTransaction();
3384
3385
            return -2;
3386
        }
3387
3388
        // End transaction
3389
        return $this->endTransaction();
3390
    }
3391
3392
    /**
3393
     * Delete a row from a table.
3394
     *
3395
     * @param      $table  The table from which to delete
3396
     * @param      $key    An array mapping column => value to delete
3397
     * @param bool $schema
3398
     *
3399
     * @return bool|int 0 success
3400
     */
3401
    public function deleteRow($table, $key, $schema = false)
3402
    {
3403
        if (!is_array($key)) {
3404
            return -1;
3405
        }
3406
3407
        // Begin transaction.  We do this so that we can ensure only one row is
3408
        // deleted
3409
        $status = $this->beginTransaction();
3410
        if ($status != 0) {
3411
            $this->rollbackTransaction();
3412
3413
            return -1;
3414
        }
3415
3416
        if ($schema === false) {
3417
            $schema = $this->_schema;
3418
        }
3419
3420
        $status = $this->delete($table, $key, $schema);
3421
        if ($status != 0 || $this->conn->Affected_Rows() != 1) {
3422
            $this->rollbackTransaction();
3423
3424
            return -2;
3425
        }
3426
3427
        // End transaction
3428
        return $this->endTransaction();
3429
    }
3430
3431
    /**
3432
     * Returns all sequences in the current database.
3433
     *
3434
     * @param bool $all
3435
     *
3436
     * @return \PHPPgAdmin\ADORecordSet A recordset
3437
     */
3438
    public function getSequences($all = false)
3439
    {
3440
        if ($all) {
3441
            // Exclude pg_catalog and information_schema tables
3442
            $sql = "SELECT n.nspname, c.relname AS seqname, u.usename AS seqowner
3443
				FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
3444
				WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
3445
				AND c.relkind = 'S'
3446
				AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
3447
				ORDER BY nspname, seqname";
3448
        } else {
3449
            $c_schema = $this->_schema;
3450
            $this->clean($c_schema);
3451
            $sql = "SELECT c.relname AS seqname, u.usename AS seqowner, pg_catalog.obj_description(c.oid, 'pg_class') AS seqcomment,
3452
				(SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
3453
				FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
3454
				WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
3455
				AND c.relkind = 'S' AND n.nspname='{$c_schema}' ORDER BY seqname";
3456
        }
3457
3458
        return $this->selectSet($sql);
3459
    }
3460
3461
    /**
3462
     * Execute nextval on a given sequence.
3463
     *
3464
     * @param $sequence Sequence name
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Sequence 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...
3465
     *
3466
     * @return integer 0 if operation was successful
3467
     */
3468
    public function nextvalSequence($sequence)
3469
    {
3470
        /* This double-cleaning is deliberate */
3471
        $f_schema = $this->_schema;
3472
        $this->fieldClean($f_schema);
3473
        $this->clean($f_schema);
3474
        $this->fieldClean($sequence);
3475
        $this->clean($sequence);
3476
3477
        $sql = "SELECT pg_catalog.NEXTVAL('\"{$f_schema}\".\"{$sequence}\"')";
3478
3479
        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...
3480
    }
3481
3482
    /**
3483
     * Execute setval on a given sequence.
3484
     *
3485
     * @param $sequence  Sequence name
3486
     * @param $nextvalue The next value
3487
     *
3488
     * @return integer 0 if operation was successful
3489
     */
3490
    public function setvalSequence($sequence, $nextvalue)
3491
    {
3492
        /* This double-cleaning is deliberate */
3493
        $f_schema = $this->_schema;
3494
        $this->fieldClean($f_schema);
3495
        $this->clean($f_schema);
3496
        $this->fieldClean($sequence);
3497
        $this->clean($sequence);
3498
        $this->clean($nextvalue);
3499
3500
        $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', '{$nextvalue}')";
3501
3502
        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...
3503
    }
3504
3505
    /**
3506
     * Restart a given sequence to its start value.
3507
     *
3508
     * @param $sequence Sequence name
3509
     *
3510
     * @return integer 0 if operation was successful
3511
     */
3512
    public function restartSequence($sequence)
3513
    {
3514
        $f_schema = $this->_schema;
3515
        $this->fieldClean($f_schema);
3516
        $this->fieldClean($sequence);
3517
3518
        $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$sequence}\" RESTART;";
3519
3520
        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...
3521
    }
3522
3523
    /**
3524
     * Resets a given sequence to min value of sequence.
3525
     *
3526
     * @param $sequence Sequence name
3527
     *
3528
     * @return int 0 if operation was successful
3529
     */
3530
    public function resetSequence($sequence)
3531
    {
3532
        // Get the minimum value of the sequence
3533
        $seq = $this->getSequence($sequence);
3534
        if ($seq->recordCount() != 1) {
0 ignored issues
show
introduced by
The condition $seq->recordCount() != 1 is always true.
Loading history...
3535
            return -1;
3536
        }
3537
3538
        $minvalue = $seq->fields['min_value'];
3539
3540
        $f_schema = $this->_schema;
3541
        $this->fieldClean($f_schema);
3542
        /* This double-cleaning is deliberate */
3543
        $this->fieldClean($sequence);
3544
        $this->clean($sequence);
3545
3546
        $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', {$minvalue})";
3547
3548
        return $this->execute($sql);
3549
    }
3550
3551
    /**
3552
     * Returns properties of a single sequence.
3553
     *
3554
     * @param $sequence Sequence name
3555
     *
3556
     * @return \PHPPgAdmin\ADORecordSet A recordset
3557
     */
3558
    public function getSequence($sequence)
3559
    {
3560
        $c_schema = $this->_schema;
3561
        $this->clean($c_schema);
3562
        $c_sequence = $sequence;
3563
        $this->fieldClean($sequence);
3564
        $this->clean($c_sequence);
3565
3566
        $sql = "
3567
			SELECT c.relname AS seqname, s.*,
3568
				pg_catalog.obj_description(s.tableoid, 'pg_class') AS seqcomment,
3569
				u.usename AS seqowner, n.nspname
3570
			FROM \"{$sequence}\" AS s, pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
3571
			WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
3572
				AND c.relname = '{$c_sequence}' AND c.relkind = 'S' AND n.nspname='{$c_schema}'
3573
				AND n.oid = c.relnamespace";
3574
3575
        return $this->selectSet($sql);
3576
    }
3577
3578
    /**
3579
     * Creates a new sequence.
3580
     *
3581
     * @param $sequence    Sequence name
3582
     * @param $increment   The increment
3583
     * @param $minvalue    The min value
3584
     * @param $maxvalue    The max value
3585
     * @param $startvalue  The starting value
3586
     * @param $cachevalue  The cache value
3587
     * @param $cycledvalue True if cycled, false otherwise
3588
     *
3589
     * @return integer 0 if operation was successful
3590
     */
3591
    public function createSequence(
3592
        $sequence,
3593
        $increment,
3594
        $minvalue,
3595
        $maxvalue,
3596
        $startvalue,
3597
        $cachevalue,
3598
        $cycledvalue
3599
    ) {
3600
        $f_schema = $this->_schema;
3601
        $this->fieldClean($f_schema);
3602
        $this->fieldClean($sequence);
3603
        $this->clean($increment);
3604
        $this->clean($minvalue);
3605
        $this->clean($maxvalue);
3606
        $this->clean($startvalue);
3607
        $this->clean($cachevalue);
3608
3609
        $sql = "CREATE SEQUENCE \"{$f_schema}\".\"{$sequence}\"";
3610
        if ($increment != '') {
3611
            $sql .= " INCREMENT {$increment}";
3612
        }
3613
3614
        if ($minvalue != '') {
3615
            $sql .= " MINVALUE {$minvalue}";
3616
        }
3617
3618
        if ($maxvalue != '') {
3619
            $sql .= " MAXVALUE {$maxvalue}";
3620
        }
3621
3622
        if ($startvalue != '') {
3623
            $sql .= " START {$startvalue}";
3624
        }
3625
3626
        if ($cachevalue != '') {
3627
            $sql .= " CACHE {$cachevalue}";
3628
        }
3629
3630
        if ($cycledvalue) {
3631
            $sql .= ' CYCLE';
3632
        }
3633
3634
        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...
3635
    }
3636
3637
    /**
3638
     * Alters a sequence.
3639
     *
3640
     * @param $sequence     The name of the sequence
3641
     * @param $name         The new name for the sequence
3642
     * @param $comment      The comment on the sequence
3643
     * @param $owner        The new owner for the sequence
3644
     * @param $schema       The new schema for the sequence
3645
     * @param $increment    The increment
3646
     * @param $minvalue     The min value
3647
     * @param $maxvalue     The max value
3648
     * @param $restartvalue The starting value
3649
     * @param $cachevalue   The cache value
3650
     * @param $cycledvalue  True if cycled, false otherwise
3651
     * @param $startvalue   The sequence start value when issueing a restart
3652
     *
3653
     * @return bool|int 0 success
3654
     */
3655
    public function alterSequence(
3656
        $sequence,
3657
        $name,
3658
        $comment,
3659
        $owner = null,
3660
        $schema = null,
3661
        $increment = null,
3662
        $minvalue = null,
3663
        $maxvalue = null,
3664
        $restartvalue = null,
3665
        $cachevalue = null,
3666
        $cycledvalue = null,
3667
        $startvalue = null
3668
    ) {
3669
        $this->fieldClean($sequence);
3670
3671
        $data = $this->getSequence($sequence);
3672
3673
        if ($data->recordCount() != 1) {
0 ignored issues
show
introduced by
The condition $data->recordCount() != 1 is always true.
Loading history...
3674
            return -2;
3675
        }
3676
3677
        $status = $this->beginTransaction();
3678
        if ($status != 0) {
3679
            $this->rollbackTransaction();
3680
3681
            return -1;
3682
        }
3683
3684
        $status = $this->_alterSequence(
3685
            $data,
3686
            $name,
3687
            $comment,
3688
            $owner,
3689
            $schema,
3690
            $increment,
3691
            $minvalue,
3692
            $maxvalue,
3693
            $restartvalue,
3694
            $cachevalue,
3695
            $cycledvalue,
3696
            $startvalue
3697
        );
3698
3699
        if ($status != 0) {
3700
            $this->rollbackTransaction();
3701
3702
            return $status;
3703
        }
3704
3705
        return $this->endTransaction();
3706
    }
3707
3708
    /**
3709
     * Protected method which alter a sequence
3710
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION.
3711
     *
3712
     * @param $seqrs        The sequence recordSet returned by getSequence()
3713
     * @param $name         The new name for the sequence
3714
     * @param $comment      The comment on the sequence
3715
     * @param $owner        The new owner for the sequence
3716
     * @param $schema       The new schema for the sequence
3717
     * @param $increment    The increment
3718
     * @param $minvalue     The min value
3719
     * @param $maxvalue     The max value
3720
     * @param $restartvalue The starting value
3721
     * @param $cachevalue   The cache value
3722
     * @param $cycledvalue  True if cycled, false otherwise
3723
     * @param $startvalue   The sequence start value when issueing a restart
3724
     *
3725
     * @return int 0 success
3726
     */
3727
    protected function _alterSequence(
1 ignored issue
show
Coding Style introduced by
Protected method name "Postgres::_alterSequence" must not be prefixed with an underscore
Loading history...
3728
        $seqrs,
3729
        $name,
3730
        $comment,
3731
        $owner,
3732
        $schema,
3733
        $increment,
3734
        $minvalue,
3735
        $maxvalue,
3736
        $restartvalue,
3737
        $cachevalue,
3738
        $cycledvalue,
3739
        $startvalue
3740
    ) {
3741
        $this->fieldArrayClean($seqrs->fields);
3742
3743
        // Comment
3744
        $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment);
3745
        if ($status != 0) {
3746
            return -4;
3747
        }
3748
3749
        // Owner
3750
        $this->fieldClean($owner);
3751
        $status = $this->alterSequenceOwner($seqrs, $owner);
3752
        if ($status != 0) {
3753
            return -5;
3754
        }
3755
3756
        // Props
3757
        $this->clean($increment);
3758
        $this->clean($minvalue);
3759
        $this->clean($maxvalue);
3760
        $this->clean($restartvalue);
3761
        $this->clean($cachevalue);
3762
        $this->clean($cycledvalue);
3763
        $this->clean($startvalue);
3764
        $status = $this->alterSequenceProps(
3765
            $seqrs,
3766
            $increment,
3767
            $minvalue,
3768
            $maxvalue,
3769
            $restartvalue,
3770
            $cachevalue,
3771
            $cycledvalue,
3772
            $startvalue
3773
        );
3774
        if ($status != 0) {
3775
            return -6;
3776
        }
3777
3778
        // Rename
3779
        $this->fieldClean($name);
3780
        $status = $this->alterSequenceName($seqrs, $name);
3781
        if ($status != 0) {
3782
            return -3;
3783
        }
3784
3785
        // Schema
3786
        $this->clean($schema);
3787
        $status = $this->alterSequenceSchema($seqrs, $schema);
3788
        if ($status != 0) {
3789
            return -7;
3790
        }
3791
3792
        return 0;
3793
    }
3794
3795
    // Index functions
3796
3797
    /**
3798
     * Alter a sequence's owner.
3799
     *
3800
     * @param $seqrs The sequence RecordSet returned by getSequence()
3801
     * @param $owner
3802
     *
3803
     * @return int 0 if operation was successful
3804
     *
3805
     * @internal param \PHPPgAdmin\Database\The $name new owner for the sequence
3806
     */
3807
    public function alterSequenceOwner($seqrs, $owner)
3808
    {
3809
        // If owner has been changed, then do the alteration.  We are
3810
        // careful to avoid this generally as changing owner is a
3811
        // superuser only function.
3812
        /* vars are cleaned in _alterSequence */
3813
        if (!empty($owner) && ($seqrs->fields['seqowner'] != $owner)) {
3814
            $f_schema = $this->_schema;
3815
            $this->fieldClean($f_schema);
3816
            $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" OWNER TO \"{$owner}\"";
3817
3818
            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...
3819
        }
3820
3821
        return 0;
3822
    }
3823
3824
    /**
3825
     * Alter a sequence's properties.
3826
     *
3827
     * @param $seqrs        The sequence RecordSet returned by getSequence()
3828
     * @param $increment    The sequence incremental value
3829
     * @param $minvalue     The sequence minimum value
3830
     * @param $maxvalue     The sequence maximum value
3831
     * @param $restartvalue The sequence current value
3832
     * @param $cachevalue   The sequence cache value
3833
     * @param $cycledvalue  Sequence can cycle ?
3834
     * @param $startvalue   The sequence start value when issueing a restart
3835
     *
3836
     * @return int 0 if operation was successful
3837
     */
3838
    public function alterSequenceProps(
3839
        $seqrs,
3840
        $increment,
3841
        $minvalue,
3842
        $maxvalue,
3843
        $restartvalue,
3844
        $cachevalue,
3845
        $cycledvalue,
3846
        $startvalue
3847
    ) {
3848
        $sql = '';
3849
        /* vars are cleaned in _alterSequence */
3850
        if (!empty($increment) && ($increment != $seqrs->fields['increment_by'])) {
3851
            $sql .= " INCREMENT {$increment}";
3852
        }
3853
3854
        if (!empty($minvalue) && ($minvalue != $seqrs->fields['min_value'])) {
3855
            $sql .= " MINVALUE {$minvalue}";
3856
        }
3857
3858
        if (!empty($maxvalue) && ($maxvalue != $seqrs->fields['max_value'])) {
3859
            $sql .= " MAXVALUE {$maxvalue}";
3860
        }
3861
3862
        if (!empty($restartvalue) && ($restartvalue != $seqrs->fields['last_value'])) {
3863
            $sql .= " RESTART {$restartvalue}";
3864
        }
3865
3866
        if (!empty($cachevalue) && ($cachevalue != $seqrs->fields['cache_value'])) {
3867
            $sql .= " CACHE {$cachevalue}";
3868
        }
3869
3870
        if (!empty($startvalue) && ($startvalue != $seqrs->fields['start_value'])) {
3871
            $sql .= " START {$startvalue}";
3872
        }
3873
3874
        // toggle cycle yes/no
3875
        if (!is_null($cycledvalue)) {
3876
            $sql .= (!$cycledvalue ? ' NO ' : '') . ' CYCLE';
3877
        }
3878
3879
        if ($sql != '') {
3880
            $f_schema = $this->_schema;
3881
            $this->fieldClean($f_schema);
3882
            $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" {$sql}";
3883
3884
            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...
3885
        }
3886
3887
        return 0;
3888
    }
3889
3890
    /**
3891
     * Rename a sequence.
3892
     *
3893
     * @param $seqrs The sequence RecordSet returned by getSequence()
3894
     * @param $name  The new name for the sequence
3895
     *
3896
     * @return int 0 if operation was successful
3897
     */
3898
    public function alterSequenceName($seqrs, $name)
3899
    {
3900
        /* vars are cleaned in _alterSequence */
3901
        if (!empty($name) && ($seqrs->fields['seqname'] != $name)) {
3902
            $f_schema = $this->_schema;
3903
            $this->fieldClean($f_schema);
3904
            $sql    = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" RENAME TO \"{$name}\"";
3905
            $status = $this->execute($sql);
3906
            if ($status == 0) {
0 ignored issues
show
introduced by
The condition $status == 0 is always false.
Loading history...
3907
                $seqrs->fields['seqname'] = $name;
3908
            } else {
3909
                return $status;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $status returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
3910
            }
3911
        }
3912
3913
        return 0;
3914
    }
3915
3916
    /**
3917
     * Alter a sequence's schema.
3918
     *
3919
     * @param $seqrs The sequence RecordSet returned by getSequence()
3920
     * @param $schema
3921
     *
3922
     * @return int 0 if operation was successful
3923
     *
3924
     * @internal param The $name new schema for the sequence
3925
     */
3926
    public function alterSequenceSchema($seqrs, $schema)
3927
    {
3928
        /* vars are cleaned in _alterSequence */
3929
        if (!empty($schema) && ($seqrs->fields['nspname'] != $schema)) {
3930
            $f_schema = $this->_schema;
3931
            $this->fieldClean($f_schema);
3932
            $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" SET SCHEMA {$schema}";
3933
3934
            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...
3935
        }
3936
3937
        return 0;
3938
    }
3939
3940
    /**
3941
     * Drops a given sequence.
3942
     *
3943
     * @param $sequence Sequence name
3944
     * @param $cascade  True to cascade drop, false to restrict
3945
     *
3946
     * @return integer 0 if operation was successful
3947
     */
3948
    public function dropSequence($sequence, $cascade)
3949
    {
3950
        $f_schema = $this->_schema;
3951
        $this->fieldClean($f_schema);
3952
        $this->fieldClean($sequence);
3953
3954
        $sql = "DROP SEQUENCE \"{$f_schema}\".\"{$sequence}\"";
3955
        if ($cascade) {
3956
            $sql .= ' CASCADE';
3957
        }
3958
3959
        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...
3960
    }
3961
3962
    /**
3963
     * Returns a list of all views in the database.
3964
     *
3965
     * @return \PHPPgAdmin\ADORecordSet All views
3966
     */
3967
    public function getViews()
3968
    {
3969
        $c_schema = $this->_schema;
3970
        $this->clean($c_schema);
3971
        $sql = "
3972
			SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
3973
				pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment
3974
			FROM pg_catalog.pg_class c
3975
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
3976
			WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'v'::\"char\")
3977
			ORDER BY relname";
3978
3979
        return $this->selectSet($sql);
3980
    }
3981
3982
    // Constraint functions
3983
3984
    /**
3985
     * Returns a list of all materialized views in the database.
3986
     *
3987
     * @return \PHPPgAdmin\ADORecordSet All materialized views
3988
     */
3989
    public function getMaterializedViews()
3990
    {
3991
        $c_schema = $this->_schema;
3992
        $this->clean($c_schema);
3993
        $sql = "
3994
			SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
3995
				pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment
3996
			FROM pg_catalog.pg_class c
3997
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
3998
			WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'm'::\"char\")
3999
			ORDER BY relname";
4000
4001
        return $this->selectSet($sql);
4002
    }
4003
4004
    /**
4005
     * Updates a view.
4006
     *
4007
     * @param string $viewname   The name fo the view to update
4008
     * @param string $definition The new definition for the view
4009
     * @param string $comment
4010
     *
4011
     * @return bool|int 0 success
4012
     */
4013
    public function setView($viewname, $definition, $comment)
4014
    {
4015
        return $this->createView($viewname, $definition, true, $comment);
4016
    }
4017
4018
    /**
4019
     * Creates a new view.
4020
     *
4021
     * @param string $viewname   The name of the view to create
4022
     * @param string $definition The definition for the new view
4023
     * @param bool $replace    True to replace the view, false otherwise
4024
     * @param string $comment
4025
     *
4026
     * @return bool|int 0 success
4027
     */
4028
    public function createView($viewname, $definition, $replace, $comment)
4029
    {
4030
        $status = $this->beginTransaction();
4031
        if ($status != 0) {
4032
            return -1;
4033
        }
4034
4035
        $f_schema = $this->_schema;
4036
        $this->fieldClean($f_schema);
4037
        $this->fieldClean($viewname);
4038
4039
        // Note: $definition not cleaned
4040
4041
        $sql = 'CREATE ';
4042
        if ($replace) {
4043
            $sql .= 'OR REPLACE ';
4044
        }
4045
4046
        $sql .= "VIEW \"{$f_schema}\".\"{$viewname}\" AS {$definition}";
4047
4048
        $status = $this->execute($sql);
4049
        if ($status) {
0 ignored issues
show
introduced by
$status is of type ADORecordSet, thus it always evaluated to true.
Loading history...
4050
            $this->rollbackTransaction();
4051
4052
            return -1;
4053
        }
4054
4055
        if ($comment != '') {
4056
            $status = $this->setComment('VIEW', $viewname, '', $comment);
4057
            if ($status) {
4058
                $this->rollbackTransaction();
4059
4060
                return -1;
4061
            }
4062
        }
4063
4064
        return $this->endTransaction();
4065
    }
4066
4067
    /**
4068
     * Alter view properties.
4069
     *
4070
     * @param string $view    The name of the view
4071
     * @param string $name    The new name for the view
4072
     * @param string $owner   The new owner for the view
4073
     * @param string $schema  The new schema for the view
4074
     * @param string $comment The comment on the view
4075
     *
4076
     * @return bool|int 0 success
4077
     */
4078
    public function alterView($view, $name, $owner, $schema, $comment)
4079
    {
4080
        $data = $this->getView($view);
4081
        if ($data->recordCount() != 1) {
0 ignored issues
show
introduced by
The condition $data->recordCount() != 1 is always true.
Loading history...
4082
            return -2;
4083
        }
4084
4085
        $status = $this->beginTransaction();
4086
        if ($status != 0) {
4087
            $this->rollbackTransaction();
4088
4089
            return -1;
4090
        }
4091
4092
        $status = $this->_alterView($data, $name, $owner, $schema, $comment);
4093
4094
        if ($status != 0) {
4095
            $this->rollbackTransaction();
4096
4097
            return $status;
4098
        }
4099
4100
        return $this->endTransaction();
4101
    }
4102
4103
    /**
4104
     * Returns all details for a particular view.
4105
     *
4106
     * @param string $view The name of the view to retrieve
4107
     *
4108
     * @return \PHPPgAdmin\ADORecordSet View info
4109
     */
4110
    public function getView($view)
4111
    {
4112
        $c_schema = $this->_schema;
4113
        $this->clean($c_schema);
4114
        $this->clean($view);
4115
4116
        $sql = "
4117
			SELECT c.relname, n.nspname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
4118
				pg_catalog.pg_get_viewdef(c.oid, true) AS vwdefinition,
4119
				pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment
4120
			FROM pg_catalog.pg_class c
4121
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
4122
			WHERE (c.relname = '{$view}') AND n.nspname='{$c_schema}'";
4123
4124
        return $this->selectSet($sql);
4125
    }
4126
4127
    /**
4128
     * Protected method which alter a view
4129
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION.
4130
     *
4131
     * @param $vwrs    The view recordSet returned by getView()
4132
     * @param $name    The new name for the view
4133
     * @param $owner   The new owner for the view
4134
     * @param $schema
4135
     * @param $comment The comment on the view
4136
     *
4137
     * @return int 0 success
4138
     */
4139
    protected function _alterView($vwrs, $name, $owner, $schema, $comment)
1 ignored issue
show
Coding Style introduced by
Protected method name "Postgres::_alterView" must not be prefixed with an underscore
Loading history...
4140
    {
4141
        $this->fieldArrayClean($vwrs->fields);
4142
4143
        // Comment
4144
        if ($this->setComment('VIEW', $vwrs->fields['relname'], '', $comment) != 0) {
4145
            return -4;
4146
        }
4147
4148
        // Owner
4149
        $this->fieldClean($owner);
4150
        $status = $this->alterViewOwner($vwrs, $owner);
4151
        if ($status != 0) {
4152
            return -5;
4153
        }
4154
4155
        // Rename
4156
        $this->fieldClean($name);
4157
        $status = $this->alterViewName($vwrs, $name);
4158
        if ($status != 0) {
4159
            return -3;
4160
        }
4161
4162
        // Schema
4163
        $this->fieldClean($schema);
4164
        $status = $this->alterViewSchema($vwrs, $schema);
4165
        if ($status != 0) {
4166
            return -6;
4167
        }
4168
4169
        return 0;
4170
    }
4171
4172
    /**
4173
     * Alter a view's owner.
4174
     *
4175
     * @param \PHPPgAdmin\ADORecordSet     $vwrs  The view recordSet returned by getView()
4176
     * @param null|string $owner
4177
     *
4178
     * @return int 0 if operation was successful
4179
     *
4180
     * @internal param  $name new view's owner
4181
     */
4182
    public function alterViewOwner($vwrs, $owner = null)
4183
    {
4184
        /* $vwrs and $owner are cleaned in _alterView */
4185
        if ((!empty($owner)) && ($vwrs->fields['relowner'] != $owner)) {
4186
            $f_schema = $this->_schema;
4187
            $this->fieldClean($f_schema);
4188
            // If owner has been changed, then do the alteration.  We are
4189
            // careful to avoid this generally as changing owner is a
4190
            // superuser only function.
4191
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$vwrs->fields['relname']}\" OWNER TO \"{$owner}\"";
4192
4193
            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...
4194
        }
4195
4196
        return 0;
4197
    }
4198
4199
    /**
4200
     * Rename a view.
4201
     *
4202
     * @param $vwrs The view recordSet returned by getView()
4203
     * @param $name The new view's name
4204
     *
4205
     * @return int 0 if operation was successful
4206
     */
4207
    public function alterViewName($vwrs, $name)
4208
    {
4209
        // Rename (only if name has changed)
4210
        /* $vwrs and $name are cleaned in _alterView */
4211
        if (!empty($name) && ($name != $vwrs->fields['relname'])) {
4212
            $f_schema = $this->_schema;
4213
            $this->fieldClean($f_schema);
4214
            $sql    = "ALTER VIEW \"{$f_schema}\".\"{$vwrs->fields['relname']}\" RENAME TO \"{$name}\"";
4215
            $status = $this->execute($sql);
4216
            if ($status == 0) {
0 ignored issues
show
introduced by
The condition $status == 0 is always false.
Loading history...
4217
                $vwrs->fields['relname'] = $name;
4218
            } else {
4219
                return $status;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $status returns the type ADORecordSet which is incompatible with the documented return type integer.
Loading history...
4220
            }
4221
        }
4222
4223
        return 0;
4224
    }
4225
4226
    /**
4227
     * Alter a view's schema.
4228
     *
4229
     * @param \PHPPgAdmin\ADORecordSet $vwrs The view recordSet returned by getView()
4230
     * @param string $schema
4231
     *
4232
     * @return int 0 if operation was successful
4233
     *
4234
     * @internal param The $name new view's schema
4235
     */
4236
    public function alterViewSchema($vwrs, $schema)
4237
    {
4238
        /* $vwrs and $schema are cleaned in _alterView */
4239
        if (!empty($schema) && ($vwrs->fields['nspname'] != $schema)) {
4240
            $f_schema = $this->_schema;
4241
            $this->fieldClean($f_schema);
4242
            // If tablespace has been changed, then do the alteration.  We
4243
            // don't want to do this unnecessarily.
4244
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$vwrs->fields['relname']}\" SET SCHEMA \"{$schema}\"";
4245
4246
            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...
4247
        }
4248
4249
        return 0;
4250
    }
4251
4252
    /**
4253
     * Drops a view.
4254
     *
4255
     * @param string $viewname The name of the view to drop
4256
     * @param string $cascade  True to cascade drop, false to restrict
4257
     *
4258
     * @return integer 0 if operation was successful
4259
     */
4260
    public function dropView($viewname, $cascade)
4261
    {
4262
        $f_schema = $this->_schema;
4263
        $this->fieldClean($f_schema);
4264
        $this->fieldClean($viewname);
4265
4266
        $sql = "DROP VIEW \"{$f_schema}\".\"{$viewname}\"";
4267
        if ($cascade) {
4268
            $sql .= ' CASCADE';
4269
        }
4270
4271
        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...
4272
    }
4273
4274
    // Domain functions
4275
4276
    /**
4277
     * test if a table has been clustered on an index.
1 ignored issue
show
Coding Style introduced by
Doc comment short description must start with a capital letter
Loading history...
4278
     *
4279
     * @param string $table The table to test
4280
     *
4281
     * @return bool true if the table has been already clustered
4282
     */
4283
    public function alreadyClustered($table)
4284
    {
4285
        $c_schema = $this->_schema;
4286
        $this->clean($c_schema);
4287
        $this->clean($table);
4288
4289
        $sql = "SELECT i.indisclustered
4290
			FROM pg_catalog.pg_class c, pg_catalog.pg_index i
4291
			WHERE c.relname = '{$table}'
4292
				AND c.oid = i.indrelid AND i.indisclustered
4293
				AND c.relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
4294
					WHERE nspname='{$c_schema}')
4295
				";
4296
4297
        $v = $this->selectSet($sql);
4298
4299
        return !($v->recordCount() == 0);
4300
    }
4301
4302
    /**
4303
     * Creates an index.
4304
     *
4305
     * @param string $name       The index name
4306
     * @param string $table      The table on which to add the index
4307
     * @param array $columns    An array of columns that form the index  or a string expression for a functional index
4308
     * @param string $type       The index type
4309
     * @param bool $unique     True if unique, false otherwise
4310
     * @param string $where      Index predicate ('' for none)
4311
     * @param string $tablespace The tablespaces ('' means none/default)
4312
     * @param bool $concurrently true to create index concurrently
4313
     *
4314
     * @return integer 0 if operation was successful
4315
     */
4316
    public function createIndex($name, $table, $columns, $type, $unique, $where, $tablespace, $concurrently)
4317
    {
4318
        $f_schema = $this->_schema;
4319
        $this->fieldClean($f_schema);
4320
        $this->fieldClean($name);
4321
        $this->fieldClean($table);
4322
4323
        $sql = 'CREATE';
4324
        if ($unique) {
4325
            $sql .= ' UNIQUE';
4326
        }
4327
4328
        $sql .= ' INDEX';
4329
        if ($concurrently) {
4330
            $sql .= ' CONCURRENTLY';
4331
        }
4332
4333
        $sql .= " \"{$name}\" ON \"{$f_schema}\".\"{$table}\" USING {$type} ";
4334
4335
        if (is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
4336
            $this->arrayClean($columns);
4337
            $sql .= '("' . implode('","', $columns) . '")';
4338
        } else {
4339
            $sql .= '(' . $columns . ')';
4340
        }
4341
4342
        // Tablespace
4343
        if ($this->hasTablespaces() && $tablespace != '') {
4344
            $this->fieldClean($tablespace);
4345
            $sql .= " TABLESPACE \"{$tablespace}\"";
4346
        }
4347
4348
        // Predicate
4349
        if (trim($where) != '') {
4350
            $sql .= " WHERE ({$where})";
4351
        }
4352
4353
        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...
4354
    }
4355
4356
    /**
4357
     * Removes an index from the database.
4358
     *
4359
     * @param string $index   The index to drop
4360
     * @param bool $cascade True to cascade drop, false to restrict
4361
     *
4362
     * @return integer 0 if operation was successful
4363
     */
4364
    public function dropIndex($index, $cascade)
4365
    {
4366
        $f_schema = $this->_schema;
4367
        $this->fieldClean($f_schema);
4368
        $this->fieldClean($index);
4369
4370
        $sql = "DROP INDEX \"{$f_schema}\".\"{$index}\"";
4371
        if ($cascade) {
4372
            $sql .= ' CASCADE';
4373
        }
4374
4375
        $status = $this->execute($sql);
4376
        return [$status, $sql];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($status, $sql) returns the type array<integer,string|ADORecordSet> which is incompatible with the documented return type integer.
Loading history...
4377
    }
4378
4379
    /**
4380
     * Rebuild indexes.
4381
     *
4382
     * @param  string  $type  'DATABASE' or 'TABLE' or 'INDEX'
4383
     * @param  string  $name  The name of the specific database, table, or index to be reindexed
4384
     * @param bool $force If true, recreates indexes forcedly in PostgreSQL 7.0-7.1, forces rebuild of system indexes in
4385
     *                                            7.2-7.3, ignored in >=7.4
4386
     *
4387
     * @return int 0 if operation was successful
4388
     */
4389
    public function reindex($type, $name, $force = false)
4390
    {
4391
        $f_schema = $this->_schema;
4392
        $this->fieldClean($f_schema);
4393
        $this->fieldClean($name);
4394
        switch ($type) {
4395
            case 'DATABASE':
4396
                $sql = "REINDEX {$type} \"{$name}\"";
4397
                if ($force) {
4398
                    $sql .= ' FORCE';
4399
                }
4400
4401
                break;
4402
            case 'TABLE':
4403
            case 'INDEX':
4404
                $sql = "REINDEX {$type} \"{$f_schema}\".\"{$name}\"";
4405
                if ($force) {
4406
                    $sql .= ' FORCE';
4407
                }
4408
4409
                break;
4410
            default:
4411
                return -1;
4412
        }
4413
4414
        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...
4415
    }
4416
4417
    /**
4418
     * Clusters an index.
4419
     *
4420
     * @param string $table The table the index is on
4421
     * @param string $index The name of the index
4422
     *
4423
     * @return integer 0 if operation was successful
4424
     */
4425
    public function clusterIndex($table = '', $index = '')
4426
    {
4427
        $sql = 'CLUSTER';
4428
4429
        // We don't bother with a transaction here, as there's no point rolling
4430
        // back an expensive cluster if a cheap analyze fails for whatever reason
4431
4432
        if (!empty($table)) {
4433
            $f_schema = $this->_schema;
4434
            $this->fieldClean($f_schema);
4435
            $this->fieldClean($table);
4436
            $sql .= " \"{$f_schema}\".\"{$table}\"";
4437
4438
            if (!empty($index)) {
4439
                $this->fieldClean($index);
4440
                $sql .= " USING \"{$index}\"";
4441
            }
4442
        }
4443
4444
        $status = $this->execute($sql);
4445
4446
        return [$status, $sql];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($status, $sql) returns the type array<integer,string|ADORecordSet> which is incompatible with the documented return type integer.
Loading history...
4447
    }
4448
4449
    /**
4450
     * Returns a list of all constraints on a table,
4451
     * including constraint name, definition, related col and referenced namespace,
4452
     * table and col if needed.
4453
     *
4454
     * @param string $table the table where we are looking for fk
4455
     *
4456
     * @return \PHPPgAdmin\ADORecordSet A recordset
4457
     */
4458
    public function getConstraintsWithFields($table)
4459
    {
4460
        $c_schema = $this->_schema;
4461
        $this->clean($c_schema);
4462
        $this->clean($table);
4463
4464
        // get the max number of col used in a constraint for the table
4465
        $sql = "SELECT DISTINCT
4466
			max(SUBSTRING(array_dims(c.conkey) FROM  \$patern\$^\\[.*:(.*)\\]$\$patern\$)) as nb
4467
		FROM pg_catalog.pg_constraint AS c
4468
			JOIN pg_catalog.pg_class AS r ON (c.conrelid=r.oid)
4469
			JOIN pg_catalog.pg_namespace AS ns ON (r.relnamespace=ns.oid)
4470
		WHERE
4471
			r.relname = '{$table}' AND ns.nspname='{$c_schema}'";
4472
4473
        $rs = $this->selectSet($sql);
4474
4475
        if ($rs->EOF) {
4476
            $max_col = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $max_col is dead and can be removed.
Loading history...
4477
        } else {
4478
            $max_col = $rs->fields['nb'];
4479
        }
4480
4481
        $sql = '
4482
			SELECT
4483
				c.oid AS conid, c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid, true) AS consrc,
4484
				ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema,
4485
				r2.relname as f_table, f1.attname as p_field, f1.attnum AS p_attnum, f2.attname as f_field,
4486
				f2.attnum AS f_attnum, pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment,
4487
				c.conrelid, c.confrelid
4488
			FROM
4489
				pg_catalog.pg_constraint AS c
4490
				JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid)
4491
				JOIN pg_catalog.pg_attribute AS f1 ON (f1.attrelid=r1.oid AND (f1.attnum=c.conkey[1]';
4492
        for ($i = 2; $i <= $rs->fields['nb']; ++$i) {
4493
            $sql .= " OR f1.attnum=c.conkey[${i}]";
4494
        }
4495
        $sql .= '))
4496
				JOIN pg_catalog.pg_namespace AS ns1 ON r1.relnamespace=ns1.oid
4497
				LEFT JOIN (
4498
					pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid)
4499
				) ON (c.confrelid=r2.oid)
4500
				LEFT JOIN pg_catalog.pg_attribute AS f2 ON
4501
					(f2.attrelid=r2.oid AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)';
4502
        for ($i = 2; $i <= $rs->fields['nb']; ++$i) {
4503
            $sql .= " OR (c.confkey[${i}]=f2.attnum AND c.conkey[${i}]=f1.attnum)";
4504
        }
4505
4506
        $sql .= sprintf("))
1 ignored issue
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
4507
			WHERE
4508
				r1.relname = '%s' AND ns1.nspname='%s'
4509
			ORDER BY 1", $table, $c_schema);
1 ignored issue
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
4510
4511
        return $this->selectSet($sql);
4512
    }
4513
4514
    /**
4515
     * Adds a primary key constraint to a table.
4516
     *
4517
     * @param        $table      The table to which to add the primery key
4518
     * @param        $fields     (array) An array of fields over which to add the primary key
4519
     * @param string $name       (optional) The name to give the key, otherwise default name is assigned
4520
     * @param string $tablespace (optional) The tablespace for the schema, '' indicates default
4521
     *
4522
     * @return int 0 if operation was successful
4523
     */
4524
    public function addPrimaryKey($table, $fields, $name = '', $tablespace = '')
4525
    {
4526
        if (!is_array($fields) || sizeof($fields) == 0) {
4527
            return -1;
4528
        }
4529
4530
        $f_schema = $this->_schema;
4531
        $this->fieldClean($f_schema);
4532
        $this->fieldClean($table);
4533
        $this->fieldArrayClean($fields);
4534
        $this->fieldClean($name);
4535
        $this->fieldClean($tablespace);
4536
4537
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD ";
4538
        if ($name != '') {
4539
            $sql .= "CONSTRAINT \"{$name}\" ";
4540
        }
4541
4542
        $sql .= 'PRIMARY KEY ("' . join('","', $fields) . '")';
4543
4544
        if ($tablespace != '' && $this->hasTablespaces()) {
4545
            $sql .= " USING INDEX TABLESPACE \"{$tablespace}\"";
4546
        }
4547
4548
        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...
4549
    }
4550
4551
    /**
4552
     * Adds a unique constraint to a table.
4553
     *
4554
     * @param        $table      The table to which to add the unique key
4555
     * @param        $fields     (array) An array of fields over which to add the unique key
4556
     * @param string $name       (optional) The name to give the key, otherwise default name is assigned
4557
     * @param string $tablespace (optional) The tablespace for the schema, '' indicates default
4558
     *
4559
     * @return int 0 if operation was successful
4560
     */
4561
    public function addUniqueKey($table, $fields, $name = '', $tablespace = '')
4562
    {
4563
        if (!is_array($fields) || sizeof($fields) == 0) {
4564
            return -1;
4565
        }
4566
4567
        $f_schema = $this->_schema;
4568
        $this->fieldClean($f_schema);
4569
        $this->fieldClean($table);
4570
        $this->fieldArrayClean($fields);
4571
        $this->fieldClean($name);
4572
        $this->fieldClean($tablespace);
4573
4574
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD ";
4575
        if ($name != '') {
4576
            $sql .= "CONSTRAINT \"{$name}\" ";
4577
        }
4578
4579
        $sql .= 'UNIQUE ("' . join('","', $fields) . '")';
4580
4581
        if ($tablespace != '' && $this->hasTablespaces()) {
4582
            $sql .= " USING INDEX TABLESPACE \"{$tablespace}\"";
4583
        }
4584
4585
        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...
4586
    }
4587
4588
    // Function functions
4589
4590
    /**
4591
     * Adds a check constraint to a table.
4592
     *
4593
     * @param        $table      The table to which to add the check
4594
     * @param        $definition The definition of the check
4595
     * @param string $name       (optional) The name to give the check, otherwise default name is assigned
4596
     *
4597
     * @return integer 0 if operation was successful
4598
     */
4599
    public function addCheckConstraint($table, $definition, $name = '')
4600
    {
4601
        $f_schema = $this->_schema;
4602
        $this->fieldClean($f_schema);
4603
        $this->fieldClean($table);
4604
        $this->fieldClean($name);
4605
        // @@ How the heck do you clean a definition???
4606
4607
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD ";
4608
        if ($name != '') {
4609
            $sql .= "CONSTRAINT \"{$name}\" ";
4610
        }
4611
4612
        $sql .= "CHECK ({$definition})";
4613
4614
        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...
4615
    }
4616
4617
    /**
4618
     * Drops a check constraint from a table.
4619
     *
4620
     * @param $table The table from which to drop the check
4621
     * @param $name  The name of the check to be dropped
4622
     *
4623
     * @return bool|int 0 success
4624
     */
4625
    public function dropCheckConstraint($table, $name)
4626
    {
4627
        $f_schema = $this->_schema;
4628
        $this->fieldClean($f_schema);
4629
        $c_schema = $this->_schema;
4630
        $this->clean($c_schema);
4631
        $c_table = $table;
4632
        $this->fieldClean($table);
4633
        $this->clean($c_table);
4634
        $this->clean($name);
4635
4636
        // Begin transaction
4637
        $status = $this->beginTransaction();
4638
        if ($status != 0) {
4639
            return -2;
4640
        }
4641
4642
        // Properly lock the table
4643
        $sql    = "LOCK TABLE \"{$f_schema}\".\"{$table}\" IN ACCESS EXCLUSIVE MODE";
4644
        $status = $this->execute($sql);
4645
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
4646
            $this->rollbackTransaction();
4647
4648
            return -3;
4649
        }
4650
4651
        // Delete the check constraint
4652
        $sql = "DELETE FROM pg_relcheck WHERE rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$c_table}'
4653
			AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
4654
			nspname = '{$c_schema}')) AND rcname='{$name}'";
4655
        $status = $this->execute($sql);
4656
        if ($status != 0) {
4657
            $this->rollbackTransaction();
4658
4659
            return -4;
4660
        }
4661
4662
        // Update the pg_class catalog to reflect the new number of checks
4663
        $sql = "UPDATE pg_class SET relchecks=(SELECT COUNT(*) FROM pg_relcheck WHERE
4664
					rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$c_table}'
4665
						AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
4666
						nspname = '{$c_schema}')))
4667
					WHERE relname='{$c_table}'";
4668
        $status = $this->execute($sql);
4669
        if ($status != 0) {
4670
            $this->rollbackTransaction();
4671
4672
            return -4;
4673
        }
4674
4675
        // Otherwise, close the transaction
4676
        return $this->endTransaction();
4677
    }
4678
4679
    /**
4680
     * Adds a foreign key constraint to a table.
4681
     *
4682
     * @param string       $table The table on which to add an FK
4683
     * @param string       $targschema The schema that houses the target table to which to add the foreign key
4684
     * @param string       $targtable  The table to which to add the foreign key
4685
     * @param array       $sfields    (array) An array of source fields over which to add the foreign key
4686
     * @param array       $tfields    (array) An array of target fields over which to add the foreign key
4687
     * @param string       $upd_action The action for updates (eg. RESTRICT)
4688
     * @param string       $del_action The action for deletes (eg. RESTRICT)
4689
     * @param string       $match      The match type (eg. MATCH FULL)
4690
     * @param string       $deferrable The deferrability (eg. NOT DEFERRABLE)
4691
     * @param string       $initially The initially parameter for the FK (eg. INITIALLY IMMEDIATE)
4692
     * @param string $name [optional] The name to give the key, otherwise default name is assigned
4693
     *
4694
     * @return integer 0 if operation was successful
4695
     *
4696
     * @internal param \PHPPgAdmin\Database\The $target table that contains the target columns
4697
     * @internal param \PHPPgAdmin\Database\The $intially initial deferrability (eg. INITIALLY IMMEDIATE)
4698
     */
4699
    public function addForeignKey(
4700
        $table,
4701
        $targschema,
4702
        $targtable,
4703
        $sfields,
4704
        $tfields,
4705
        $upd_action,
4706
        $del_action,
4707
        $match,
4708
        $deferrable,
4709
        $initially,
4710
        $name = ''
4711
    ) {
4712
        if (!is_array($sfields) || sizeof($sfields) == 0 ||
1 ignored issue
show
introduced by
The condition is_array($sfields) is always true.
Loading history...
4713
            !is_array($tfields) || sizeof($tfields) == 0) {
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...
4714
            return -1;
4715
        }
4716
4717
        $f_schema = $this->_schema;
4718
        $this->fieldClean($f_schema);
4719
        $this->fieldClean($table);
4720
        $this->fieldClean($targschema);
4721
        $this->fieldClean($targtable);
4722
        $this->fieldArrayClean($sfields);
4723
        $this->fieldArrayClean($tfields);
4724
        $this->fieldClean($name);
4725
4726
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD ";
4727
        if ($name != '') {
4728
            $sql .= "CONSTRAINT \"{$name}\" ";
4729
        }
4730
4731
        $sql .= 'FOREIGN KEY ("' . join('","', $sfields) . '") ';
4732
        // Target table needs to be fully qualified
4733
        $sql .= "REFERENCES \"{$targschema}\".\"{$targtable}\"(\"" . join('","', $tfields) . '") ';
4734
        if ($match != $this->fkmatches[0]) {
4735
            $sql .= " {$match}";
4736
        }
4737
4738
        if ($upd_action != $this->fkactions[0]) {
4739
            $sql .= " ON UPDATE {$upd_action}";
4740
        }
4741
4742
        if ($del_action != $this->fkactions[0]) {
4743
            $sql .= " ON DELETE {$del_action}";
4744
        }
4745
4746
        if ($deferrable != $this->fkdeferrable[0]) {
4747
            $sql .= " {$deferrable}";
4748
        }
4749
4750
        if ($initially != $this->fkinitial[0]) {
4751
            $sql .= " {$initially}";
4752
        }
4753
4754
        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...
4755
    }
4756
4757
    /**
4758
     * Removes a constraint from a relation.
4759
     *
4760
     * @param string $constraint The constraint to drop
4761
     * @param string $relation   The relation from which to drop
4762
     * @param string $type       The type of constraint (c, f, u or p)
4763
     * @param bool $cascade    True to cascade drop, false to restrict
4764
     *
4765
     * @return integer 0 if operation was successful
4766
     */
4767
    public function dropConstraint($constraint, $relation, $type, $cascade)
1 ignored issue
show
Unused Code introduced by
The parameter $type 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

4767
    public function dropConstraint($constraint, $relation, /** @scrutinizer ignore-unused */ $type, $cascade)

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...
4768
    {
4769
        $f_schema = $this->_schema;
4770
        $this->fieldClean($f_schema);
4771
        $this->fieldClean($constraint);
4772
        $this->fieldClean($relation);
4773
4774
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$relation}\" DROP CONSTRAINT \"{$constraint}\"";
4775
        if ($cascade) {
4776
            $sql .= ' CASCADE';
4777
        }
4778
4779
        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...
4780
    }
4781
4782
    /**
4783
     * A function for getting all columns linked by foreign keys given a group of tables.
4784
     *
4785
     * @param array $tables multi dimensional assoc array that holds schema and table name
4786
     *
4787
     * @return \PHPPgAdmin\ADORecordSet|integer  recordset of linked tables and columns or -1 if $tables isn't an array
4788
     */
4789
    public function getLinkingKeys($tables)
4790
    {
4791
        if (!is_array($tables)) {
1 ignored issue
show
introduced by
The condition is_array($tables) is always true.
Loading history...
4792
            return -1;
4793
        }
4794
4795
        $this->clean($tables[0]['tablename']);
4796
        $this->clean($tables[0]['schemaname']);
4797
        $tables_list        = "'{$tables[0]['tablename']}'";
4798
        $schema_list        = "'{$tables[0]['schemaname']}'";
4799
        $schema_tables_list = "'{$tables[0]['schemaname']}.{$tables[0]['tablename']}'";
4800
4801
        for ($i = 1; $i < sizeof($tables); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4802
            $this->clean($tables[$i]['tablename']);
4803
            $this->clean($tables[$i]['schemaname']);
4804
            $tables_list .= ", '{$tables[$i]['tablename']}'";
4805
            $schema_list .= ", '{$tables[$i]['schemaname']}'";
4806
            $schema_tables_list .= ", '{$tables[$i]['schemaname']}.{$tables[$i]['tablename']}'";
4807
        }
4808
4809
        $maxDimension = 1;
4810
4811
        $sql = "
4812
			SELECT DISTINCT
4813
				array_dims(pc.conkey) AS arr_dim,
4814
				pgc1.relname AS p_table
4815
			FROM
4816
				pg_catalog.pg_constraint AS pc,
4817
				pg_catalog.pg_class AS pgc1
4818
			WHERE
4819
				pc.contype = 'f'
4820
				AND (pc.conrelid = pgc1.relfilenode OR pc.confrelid = pgc1.relfilenode)
4821
				AND pgc1.relname IN (${tables_list})
4822
			";
4823
4824
        //parse our output to find the highest dimension of foreign keys since pc.conkey is stored in an array
4825
        $rs = $this->selectSet($sql);
4826
        while (!$rs->EOF) {
4827
            $arrData      = explode(':', $rs->fields['arr_dim']);
4828
            $tmpDimension = (int) (substr($arrData[1], 0, strlen($arrData[1] - 1)));
4829
            $maxDimension = $tmpDimension > $maxDimension ? $tmpDimension : $maxDimension;
4830
            $rs->MoveNext();
4831
        }
4832
4833
        //we know the highest index for foreign keys that conkey goes up to, expand for us in an IN query
4834
        $cons_str = '( (pfield.attnum = conkey[1] AND cfield.attnum = confkey[1]) ';
4835
        for ($i = 2; $i <= $maxDimension; ++$i) {
4836
            $cons_str .= "OR (pfield.attnum = conkey[{$i}] AND cfield.attnum = confkey[{$i}]) ";
4837
        }
4838
        $cons_str .= ') ';
4839
4840
        $sql = "
4841
			SELECT
4842
				pgc1.relname AS p_table,
4843
				pgc2.relname AS f_table,
4844
				pfield.attname AS p_field,
4845
				cfield.attname AS f_field,
4846
				pgns1.nspname AS p_schema,
4847
				pgns2.nspname AS f_schema
4848
			FROM
4849
				pg_catalog.pg_constraint AS pc,
4850
				pg_catalog.pg_class AS pgc1,
4851
				pg_catalog.pg_class AS pgc2,
4852
				pg_catalog.pg_attribute AS pfield,
4853
				pg_catalog.pg_attribute AS cfield,
4854
				(SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN (${schema_list}) ) AS pgns1,
4855
 				(SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN (${schema_list}) ) AS pgns2
4856
			WHERE
4857
				pc.contype = 'f'
4858
				AND pgc1.relnamespace = pgns1.ns_id
4859
 				AND pgc2.relnamespace = pgns2.ns_id
4860
				AND pc.conrelid = pgc1.relfilenode
4861
				AND pc.confrelid = pgc2.relfilenode
4862
				AND pfield.attrelid = pc.conrelid
4863
				AND cfield.attrelid = pc.confrelid
4864
				AND ${cons_str}
4865
				AND pgns1.nspname || '.' || pgc1.relname IN (${schema_tables_list})
4866
				AND pgns2.nspname || '.' || pgc2.relname IN (${schema_tables_list})
4867
		";
4868
4869
        return $this->selectSet($sql);
4870
    }
4871
4872
    /**
4873
     * Finds the foreign keys that refer to the specified table.
4874
     *
4875
     * @param string $table The table to find referrers for
4876
     *
4877
     * @return \PHPPgAdmin\ADORecordSet A recordset
4878
     */
4879
    public function getReferrers($table)
4880
    {
4881
        $this->clean($table);
4882
4883
        $status = $this->beginTransaction();
4884
        if ($status != 0) {
4885
            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\ADORecordSet.
Loading history...
4886
        }
4887
4888
        $c_schema = $this->_schema;
4889
        $this->clean($c_schema);
4890
4891
        $sql = "
4892
			SELECT
4893
				pn.nspname,
4894
				pl.relname,
4895
				pc.conname,
4896
				pg_catalog.pg_get_constraintdef(pc.oid) AS consrc
4897
			FROM
4898
				pg_catalog.pg_constraint pc,
4899
				pg_catalog.pg_namespace pn,
4900
				pg_catalog.pg_class pl
4901
			WHERE
4902
				pc.connamespace = pn.oid
4903
				AND pc.conrelid = pl.oid
4904
				AND pc.contype = 'f'
4905
				AND confrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
4906
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
4907
					WHERE nspname='{$c_schema}'))
4908
			ORDER BY 1,2,3
4909
		";
4910
4911
        return $this->selectSet($sql);
4912
    }
4913
4914
    // Type functions
4915
4916
    /**
4917
     * Gets all information for a single domain.
4918
     *
4919
     * @param string $domain The name of the domain to fetch
4920
     *
4921
     * @return \PHPPgAdmin\ADORecordSet A recordset
4922
     */
4923
    public function getDomain($domain)
4924
    {
4925
        $c_schema = $this->_schema;
4926
        $this->clean($c_schema);
4927
        $this->clean($domain);
4928
4929
        $sql = "
4930
			SELECT
4931
				t.typname AS domname,
4932
				pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype,
4933
				t.typnotnull AS domnotnull,
4934
				t.typdefault AS domdef,
4935
				pg_catalog.pg_get_userbyid(t.typowner) AS domowner,
4936
				pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment
4937
			FROM
4938
				pg_catalog.pg_type t
4939
			WHERE
4940
				t.typtype = 'd'
4941
				AND t.typname = '{$domain}'
4942
				AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace
4943
					WHERE nspname = '{$c_schema}')";
4944
4945
        return $this->selectSet($sql);
4946
    }
4947
4948
    /**
4949
     * Return all domains in current schema.  Excludes domain constraints.
4950
     *
4951
     * @return \PHPPgAdmin\ADORecordSet All tables, sorted alphabetically
4952
     */
4953
    public function getDomains()
4954
    {
4955
        $c_schema = $this->_schema;
4956
        $this->clean($c_schema);
4957
4958
        $sql = "
4959
			SELECT
4960
				t.typname AS domname,
4961
				pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype,
4962
				t.typnotnull AS domnotnull,
4963
				t.typdefault AS domdef,
4964
				pg_catalog.pg_get_userbyid(t.typowner) AS domowner,
4965
				pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment
4966
			FROM
4967
				pg_catalog.pg_type t
4968
			WHERE
4969
				t.typtype = 'd'
4970
				AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace
4971
					WHERE nspname='{$c_schema}')
4972
			ORDER BY t.typname";
4973
4974
        return $this->selectSet($sql);
4975
    }
4976
4977
    /**
4978
     * Get domain constraints.
4979
     *
4980
     * @param $domain The name of the domain whose constraints to fetch
4981
     *
4982
     * @return \PHPPgAdmin\ADORecordSet A recordset
4983
     */
4984
    public function getDomainConstraints($domain)
4985
    {
4986
        $c_schema = $this->_schema;
4987
        $this->clean($c_schema);
4988
        $this->clean($domain);
4989
4990
        $sql = "
4991
			SELECT
4992
				conname,
4993
				contype,
4994
				pg_catalog.pg_get_constraintdef(oid, true) AS consrc
4995
			FROM
4996
				pg_catalog.pg_constraint
4997
			WHERE
4998
				contypid = (
4999
					SELECT oid FROM pg_catalog.pg_type
5000
					WHERE typname='{$domain}'
5001
						AND typnamespace = (
5002
							SELECT oid FROM pg_catalog.pg_namespace
5003
							WHERE nspname = '{$c_schema}')
5004
				)
5005
			ORDER BY conname";
5006
5007
        return $this->selectSet($sql);
5008
    }
5009
5010
    /**
5011
     * Creates a domain.
5012
     *
5013
     * @param string $domain  The name of the domain to create
5014
     * @param string $type    The base type for the domain
5015
     * @param string $length  Optional type length
5016
     * @param bool $array   True for array type, false otherwise
5017
     * @param bool $notnull True for NOT NULL, false otherwise
5018
     * @param string $default Default value for domain
5019
     * @param string $check   A CHECK constraint if there is one
5020
     *
5021
     * @return integer 0 if operation was successful
5022
     */
5023
    public function createDomain($domain, $type, $length, $array, $notnull, $default, $check)
5024
    {
5025
        $f_schema = $this->_schema;
5026
        $this->fieldClean($f_schema);
5027
        $this->fieldClean($domain);
5028
5029
        $sql = "CREATE DOMAIN \"{$f_schema}\".\"{$domain}\" AS ";
5030
5031
        if ($length == '') {
5032
            $sql .= $type;
5033
        } else {
5034
            switch ($type) {
5035
                // Have to account for weird placing of length for with/without
5036
                // time zone types
5037
                case 'timestamp with time zone':
5038
                case 'timestamp without time zone':
5039
                    $qual = substr($type, 9);
5040
                    $sql .= "timestamp({$length}){$qual}";
5041
5042
                    break;
5043
                case 'time with time zone':
5044
                case 'time without time zone':
5045
                    $qual = substr($type, 4);
5046
                    $sql .= "time({$length}){$qual}";
5047
5048
                    break;
5049
                default:
5050
                    $sql .= "{$type}({$length})";
5051
            }
5052
        }
5053
5054
        // Add array qualifier, if requested
5055
        if ($array) {
5056
            $sql .= '[]';
5057
        }
5058
5059
        if ($notnull) {
5060
            $sql .= ' NOT NULL';
5061
        }
5062
5063
        if ($default != '') {
5064
            $sql .= " DEFAULT {$default}";
5065
        }
5066
5067
        if ($this->hasDomainConstraints() && $check != '') {
5068
            $sql .= " CHECK ({$check})";
5069
        }
5070
5071
        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...
5072
    }
5073
5074
    /**
5075
     * Alters a domain.
5076
     *
5077
     * @param string $domain     The domain to alter
5078
     * @param string  $domdefault The domain default
5079
     * @param bool $domnotnull True for NOT NULL, false otherwise
5080
     * @param string  $domowner   The domain owner
5081
     *
5082
     * @return bool|int 0 success
5083
     */
5084
    public function alterDomain($domain, $domdefault, $domnotnull, $domowner)
5085
    {
5086
        $f_schema = $this->_schema;
5087
        $this->fieldClean($f_schema);
5088
        $this->fieldClean($domain);
5089
        $this->fieldClean($domowner);
5090
5091
        $status = $this->beginTransaction();
5092
        if ($status != 0) {
5093
            $this->rollbackTransaction();
5094
5095
            return -1;
5096
        }
5097
5098
        // Default
5099
        if ($domdefault == '') {
5100
            $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP DEFAULT";
5101
        } else {
5102
            $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET DEFAULT {$domdefault}";
5103
        }
5104
5105
        $status = $this->execute($sql);
5106
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
5107
            $this->rollbackTransaction();
5108
5109
            return -2;
5110
        }
5111
5112
        // NOT NULL
5113
        if ($domnotnull) {
5114
            $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET NOT NULL";
5115
        } else {
5116
            $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP NOT NULL";
5117
        }
5118
5119
        $status = $this->execute($sql);
5120
        if ($status != 0) {
5121
            $this->rollbackTransaction();
5122
5123
            return -3;
5124
        }
5125
5126
        // Owner
5127
        $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" OWNER TO \"{$domowner}\"";
5128
5129
        $status = $this->execute($sql);
5130
        if ($status != 0) {
5131
            $this->rollbackTransaction();
5132
5133
            return -4;
5134
        }
5135
5136
        return $this->endTransaction();
5137
    }
5138
5139
    /**
5140
     * Drops a domain.
5141
     *
5142
     * @param string $domain  The name of the domain to drop
5143
     * @param string $cascade True to cascade drop, false to restrict
5144
     *
5145
     * @return integer 0 if operation was successful
5146
     */
5147
    public function dropDomain($domain, $cascade)
5148
    {
5149
        $f_schema = $this->_schema;
5150
        $this->fieldClean($f_schema);
5151
        $this->fieldClean($domain);
5152
5153
        $sql = "DROP DOMAIN \"{$f_schema}\".\"{$domain}\"";
5154
        if ($cascade) {
5155
            $sql .= ' CASCADE';
5156
        }
5157
5158
        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...
5159
    }
5160
5161
    /**
5162
     * Adds a check constraint to a domain.
5163
     *
5164
     * @param string $domain     The domain to which to add the check
5165
     * @param string $definition The definition of the check
5166
     * @param string $name       (optional) The name to give the check, otherwise default name is assigned
5167
     *
5168
     * @return integer 0 if operation was successful
5169
     */
5170
    public function addDomainCheckConstraint($domain, $definition, $name = '')
5171
    {
5172
        $f_schema = $this->_schema;
5173
        $this->fieldClean($f_schema);
5174
        $this->fieldClean($domain);
5175
        $this->fieldClean($name);
5176
5177
        $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" ADD ";
5178
        if ($name != '') {
5179
            $sql .= "CONSTRAINT \"{$name}\" ";
5180
        }
5181
5182
        $sql .= "CHECK ({$definition})";
5183
5184
        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...
5185
    }
5186
5187
    /**
5188
     * Drops a domain constraint.
5189
     *
5190
     * @param string $domain     The domain from which to remove the constraint
5191
     * @param string $constraint The constraint to remove
5192
     * @param bool $cascade    True to cascade, false otherwise
5193
     *
5194
     * @return integer 0 if operation was successful
5195
     */
5196
    public function dropDomainConstraint($domain, $constraint, $cascade)
5197
    {
5198
        $f_schema = $this->_schema;
5199
        $this->fieldClean($f_schema);
5200
        $this->fieldClean($domain);
5201
        $this->fieldClean($constraint);
5202
5203
        $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP CONSTRAINT \"{$constraint}\"";
5204
        if ($cascade) {
5205
            $sql .= ' CASCADE';
5206
        }
5207
5208
        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...
5209
    }
5210
5211
    // Rule functions
5212
5213
    /**
5214
     * Returns an array containing a function's properties.
5215
     *
5216
     * @param array $f The array of data for the function
5217
     *
5218
     * @return array An array containing the properties
5219
     */
5220
    public function getFunctionProperties($f)
5221
    {
5222
        $temp = [];
5223
5224
        // Volatility
5225
        if ($f['provolatile'] == 'v') {
5226
            $temp[] = 'VOLATILE';
5227
        } elseif ($f['provolatile'] == 'i') {
5228
            $temp[] = 'IMMUTABLE';
5229
        } elseif ($f['provolatile'] == 's') {
5230
            $temp[] = 'STABLE';
5231
        } else {
5232
            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...
5233
        }
5234
5235
        // Null handling
5236
        $f['proisstrict'] = $this->phpBool($f['proisstrict']);
5237
        if ($f['proisstrict']) {
5238
            $temp[] = 'RETURNS NULL ON NULL INPUT';
5239
        } else {
5240
            $temp[] = 'CALLED ON NULL INPUT';
5241
        }
5242
5243
        // Security
5244
        $f['prosecdef'] = $this->phpBool($f['prosecdef']);
5245
        if ($f['prosecdef']) {
5246
            $temp[] = 'SECURITY DEFINER';
5247
        } else {
5248
            $temp[] = 'SECURITY INVOKER';
5249
        }
5250
5251
        return $temp;
5252
    }
5253
5254
    /**
5255
     * Updates (replaces) a function.
5256
     *
5257
     * @param int $function_oid The OID of the function
5258
     * @param string $funcname     The name of the function to create
5259
     * @param string $newname      The new name for the function
5260
     * @param array $args         The array of argument types
5261
     * @param string $returns      The return type
5262
     * @param string $definition   The definition for the new function
5263
     * @param string $language     The language the function is written for
5264
     * @param array $flags        An array of optional flags
5265
     * @param bool $setof        True if returns a set, false otherwise
5266
     * @param string $funcown
5267
     * @param string $newown
5268
     * @param string $funcschema
5269
     * @param string $newschema
5270
     * @param float $cost
5271
     * @param integer $rows
5272
     * @param string $comment      The comment on the function
5273
     *
5274
     * @return bool|int 0 success
5275
     */
5276
    public function setFunction(
5277
        $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

5277
        /** @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...
5278
        $funcname,
5279
        $newname,
5280
        $args,
5281
        $returns,
5282
        $definition,
5283
        $language,
5284
        $flags,
5285
        $setof,
5286
        $funcown,
5287
        $newown,
5288
        $funcschema,
5289
        $newschema,
5290
        $cost,
5291
        $rows,
5292
        $comment
5293
    ) {
5294
        // Begin a transaction
5295
        $status = $this->beginTransaction();
5296
        if ($status != 0) {
5297
            $this->rollbackTransaction();
5298
5299
            return -1;
5300
        }
5301
5302
        // Replace the existing function
5303
        $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

5303
        $status = $this->createFunction($funcname, /** @scrutinizer ignore-type */ $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, true);
Loading history...
5304
        if ($status != 0) {
5305
            $this->rollbackTransaction();
5306
5307
            return $status;
5308
        }
5309
5310
        $f_schema = $this->_schema;
5311
        $this->fieldClean($f_schema);
5312
5313
        // Rename the function, if necessary
5314
        $this->fieldClean($newname);
5315
        /* $funcname is escaped in createFunction */
5316
        if ($funcname != $newname) {
5317
            $sql    = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) RENAME TO \"{$newname}\"";
5318
            $status = $this->execute($sql);
5319
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
5320
                $this->rollbackTransaction();
5321
5322
                return -5;
5323
            }
5324
5325
            $funcname = $newname;
5326
        }
5327
5328
        // Alter the owner, if necessary
5329
        if ($this->hasFunctionAlterOwner()) {
5330
            $this->fieldClean($newown);
5331
            if ($funcown != $newown) {
5332
                $sql    = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) OWNER TO \"{$newown}\"";
5333
                $status = $this->execute($sql);
5334
                if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
5335
                    $this->rollbackTransaction();
5336
5337
                    return -6;
5338
                }
5339
            }
5340
        }
5341
5342
        // Alter the schema, if necessary
5343
        if ($this->hasFunctionAlterSchema()) {
5344
            $this->fieldClean($newschema);
5345
            /* $funcschema is escaped in createFunction */
5346
            if ($funcschema != $newschema) {
5347
                $sql    = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) SET SCHEMA \"{$newschema}\"";
5348
                $status = $this->execute($sql);
5349
                if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
5350
                    $this->rollbackTransaction();
5351
5352
                    return -7;
5353
                }
5354
            }
5355
        }
5356
5357
        return $this->endTransaction();
5358
    }
5359
5360
    /**
5361
     * Creates a new function.
5362
     *
5363
     * @param string   $funcname    The name of the function to create
5364
     * @param string   $args        A comma separated string of types
5365
     * @param string   $returns     The return type
5366
     * @param string   $definition  The definition for the new function
5367
     * @param string   $language    The language the function is written for
5368
     * @param array    $flags       An array of optional flags
5369
     * @param bool     $setof       True if it returns a set, false otherwise
5370
     * @param string   $cost        cost the planner should use in the function  execution step
5371
     * @param integer  $rows        number of rows planner should estimate will be returned
5372
     * @param string   $comment     Comment for the function
5373
     * @param bool     $replace     (optional) True if OR REPLACE, false for
5374
     *                              normal
5375
     *
5376
     * @return bool|int  0 success
5377
     */
5378
    public function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, $replace = false)
5379
    {
5380
        // Begin a transaction
5381
        $status = $this->beginTransaction();
5382
        if ($status != 0) {
5383
            $this->rollbackTransaction();
5384
5385
            return -1;
5386
        }
5387
5388
        $this->fieldClean($funcname);
5389
        $this->clean($args);
5390
        $this->fieldClean($language);
5391
        $this->arrayClean($flags);
5392
        $this->clean($cost);
5393
        $this->clean($rows);
5394
        $f_schema = $this->_schema;
5395
        $this->fieldClean($f_schema);
5396
5397
        $sql = 'CREATE';
5398
        if ($replace) {
5399
            $sql .= ' OR REPLACE';
5400
        }
5401
5402
        $sql .= " FUNCTION \"{$f_schema}\".\"{$funcname}\" (";
5403
5404
        if ($args != '') {
5405
            $sql .= $args;
5406
        }
5407
5408
        // For some reason, the returns field cannot have quotes...
5409
        $sql .= ') RETURNS ';
5410
        if ($setof) {
5411
            $sql .= 'SETOF ';
5412
        }
5413
5414
        $sql .= "{$returns} AS ";
5415
5416
        if (is_array($definition)) {
1 ignored issue
show
introduced by
The condition is_array($definition) is always false.
Loading history...
5417
            $this->arrayClean($definition);
5418
            $sql .= "'" . $definition[0] . "'";
5419
            if ($definition[1]) {
5420
                $sql .= ",'" . $definition[1] . "'";
5421
            }
5422
        } else {
5423
            $this->clean($definition);
5424
            $sql .= "'" . $definition . "'";
5425
        }
5426
5427
        $sql .= " LANGUAGE \"{$language}\"";
5428
5429
        // Add costs
5430
        if (!empty($cost)) {
5431
            $sql .= " COST {$cost}";
5432
        }
5433
5434
        if ($rows != 0) {
5435
            $sql .= " ROWS {$rows}";
5436
        }
5437
5438
        // Add flags
5439
        foreach ($flags as $v) {
5440
            // Skip default flags
5441
            if ($v == '') {
5442
                continue;
5443
            }
5444
5445
            $sql .= "\n{$v}";
5446
        }
5447
5448
        $status = $this->execute($sql);
5449
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
5450
            $this->rollbackTransaction();
5451
5452
            return -3;
5453
        }
5454
5455
        /* set the comment */
5456
        $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment);
5457
        if ($status != 0) {
5458
            $this->rollbackTransaction();
5459
5460
            return -4;
5461
        }
5462
5463
        return $this->endTransaction();
5464
    }
5465
5466
    /**
5467
     * Drops a function.
5468
     *
5469
     * @param int $function_oid The OID of the function to drop
5470
     * @param bool $cascade      True to cascade drop, false to restrict
5471
     *
5472
     * @return integer 0 if operation was successful
5473
     */
5474
    public function dropFunction($function_oid, $cascade)
5475
    {
5476
        // Function comes in with $object as function OID
5477
        $fn       = $this->getFunction($function_oid);
5478
        $f_schema = $this->_schema;
5479
        $this->fieldClean($f_schema);
5480
        $this->fieldClean($fn->fields['proname']);
5481
5482
        $sql = "DROP FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})";
5483
        if ($cascade) {
5484
            $sql .= ' CASCADE';
5485
        }
5486
5487
        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...
5488
    }
5489
5490
    /**
5491
     * Returns all details for a particular function.
5492
     *
5493
     * @param int $function_oid
5494
     *
5495
     * @return \PHPPgAdmin\ADORecordSet Function info
5496
     *
5497
     * @internal param string The $func name of the function to retrieve
5498
     */
5499
    public function getFunction($function_oid)
5500
    {
5501
        $this->clean($function_oid);
5502
5503
        $sql = "
5504
			SELECT
5505
				pc.oid AS prooid, proname,
5506
				pg_catalog.pg_get_userbyid(proowner) AS proowner,
5507
				nspname as proschema, lanname as prolanguage, procost, prorows,
5508
				pg_catalog.format_type(prorettype, NULL) as proresult, prosrc,
5509
				probin, proretset, proisstrict, provolatile, prosecdef,
5510
				pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments,
5511
				proargnames AS proargnames,
5512
				pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment,
5513
				proconfig,
5514
				(select array_agg( (select typname from pg_type pt
5515
					where pt.oid = p.oid) ) from unnest(proallargtypes) p)
5516
				AS proallarguments,
5517
				proargmodes
5518
			FROM
5519
				pg_catalog.pg_proc pc, pg_catalog.pg_language pl,
5520
				pg_catalog.pg_namespace pn
5521
			WHERE
5522
				pc.oid = '{$function_oid}'::oid AND pc.prolang = pl.oid
5523
				AND pc.pronamespace = pn.oid
5524
			";
5525
5526
        return $this->selectSet($sql);
5527
    }
5528
5529
    /**
5530
     * Returns all details for a particular type.
5531
     *
5532
     * @param string $typname The name of the view to retrieve
5533
     *
5534
     * @return \PHPPgAdmin\ADORecordSet info
5535
     */
5536
    public function getType($typname)
5537
    {
5538
        $this->clean($typname);
5539
5540
        $sql = "SELECT typtype, typbyval, typname, typinput AS typin, typoutput AS typout, typlen, typalign
5541
			FROM pg_type WHERE typname='{$typname}'";
5542
5543
        return $this->selectSet($sql);
5544
    }
5545
5546
    /**
5547
     * Returns a list of all types in the database.
5548
     *
5549
     * @param bool $all        If true, will find all available types, if false just those in search path
5550
     * @param bool $tabletypes If true, will include table types
5551
     * @param bool $domains    If true, will include domains
5552
     *
5553
     * @return \PHPPgAdmin\ADORecordSet A recordset
5554
     */
5555
    public function getTypes($all = false, $tabletypes = false, $domains = false)
5556
    {
5557
        if ($all) {
5558
            $where = '1 = 1';
5559
        } else {
5560
            $c_schema = $this->_schema;
5561
            $this->clean($c_schema);
5562
            $where = "n.nspname = '{$c_schema}'";
5563
        }
5564
        // Never show system table types
5565
        $where2 = "AND c.relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname LIKE 'pg@_%' ESCAPE '@')";
5566
5567
        // Create type filter
5568
        $tqry = "'c'";
5569
        if ($tabletypes) {
5570
            $tqry .= ", 'r', 'v'";
5571
        }
5572
5573
        // Create domain filter
5574
        if (!$domains) {
5575
            $where .= " AND t.typtype != 'd'";
5576
        }
5577
5578
        $sql = "SELECT
5579
				t.typname AS basename,
5580
				pg_catalog.format_type(t.oid, NULL) AS typname,
5581
				pu.usename AS typowner,
5582
				t.typtype,
5583
				pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment
5584
			FROM (pg_catalog.pg_type t
5585
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace)
5586
				LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid
5587
			WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2}))
5588
			AND t.typname !~ '^_'
5589
			AND {$where}
5590
			ORDER BY typname
5591
		";
5592
5593
        return $this->selectSet($sql);
5594
    }
5595
5596
    /**
5597
     * Creates a new type.
5598
     *
5599
     * @param string $typname
5600
     * @param string $typin
5601
     * @param string $typout
5602
     * @param string $typlen
5603
     * @param string $typdef
5604
     * @param string $typelem
5605
     * @param string $typdelim
5606
     * @param string $typbyval
5607
     * @param string $typalign
5608
     * @param string $typstorage
5609
     *
5610
     * @return integer 0 if operation was successful
5611
     *
5612
     * @internal param $ ...
5613
     */
5614
    public function createType(
5615
        $typname,
5616
        $typin,
5617
        $typout,
5618
        $typlen,
5619
        $typdef,
5620
        $typelem,
5621
        $typdelim,
5622
        $typbyval,
5623
        $typalign,
5624
        $typstorage
5625
    ) {
5626
        $f_schema = $this->_schema;
5627
        $this->fieldClean($f_schema);
5628
        $this->fieldClean($typname);
5629
        $this->fieldClean($typin);
5630
        $this->fieldClean($typout);
5631
5632
        $sql = "
5633
			CREATE TYPE \"{$f_schema}\".\"{$typname}\" (
5634
				INPUT = \"{$typin}\",
5635
				OUTPUT = \"{$typout}\",
5636
				INTERNALLENGTH = {$typlen}";
5637
        if ($typdef != '') {
5638
            $sql .= ", DEFAULT = {$typdef}";
5639
        }
5640
5641
        if ($typelem != '') {
5642
            $sql .= ", ELEMENT = {$typelem}";
5643
        }
5644
5645
        if ($typdelim != '') {
5646
            $sql .= ", DELIMITER = {$typdelim}";
5647
        }
5648
5649
        if ($typbyval) {
5650
            $sql .= ', PASSEDBYVALUE, ';
5651
        }
5652
5653
        if ($typalign != '') {
5654
            $sql .= ", ALIGNMENT = {$typalign}";
5655
        }
5656
5657
        if ($typstorage != '') {
5658
            $sql .= ", STORAGE = {$typstorage}";
5659
        }
5660
5661
        $sql .= ')';
5662
5663
        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...
5664
    }
5665
5666
    /**
5667
     * Drops a type.
5668
     *
5669
     * @param $typname The name of the type to drop
5670
     * @param $cascade True to cascade drop, false to restrict
5671
     *
5672
     * @return integer 0 if operation was successful
5673
     */
5674
    public function dropType($typname, $cascade)
5675
    {
5676
        $f_schema = $this->_schema;
5677
        $this->fieldClean($f_schema);
5678
        $this->fieldClean($typname);
5679
5680
        $sql = "DROP TYPE \"{$f_schema}\".\"{$typname}\"";
5681
        if ($cascade) {
5682
            $sql .= ' CASCADE';
5683
        }
5684
5685
        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...
5686
    }
5687
5688
    /**
5689
     * Creates a new enum type in the database.
5690
     *
5691
     * @param $name       The name of the type
5692
     * @param $values     An array of values
5693
     * @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...
5694
     *
5695
     * @return bool|int 0 success
5696
     */
5697
    public function createEnumType($name, $values, $typcomment)
5698
    {
5699
        $f_schema = $this->_schema;
5700
        $this->fieldClean($f_schema);
5701
        $this->fieldClean($name);
5702
5703
        if (empty($values)) {
5704
            return -2;
5705
        }
5706
5707
        $status = $this->beginTransaction();
5708
        if ($status != 0) {
5709
            return -1;
5710
        }
5711
5712
        $values = array_unique($values);
5713
5714
        $nbval = count($values);
5715
5716
        for ($i = 0; $i < $nbval; ++$i) {
5717
            $this->clean($values[$i]);
5718
        }
5719
5720
        $sql = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS ENUM ('";
5721
        $sql .= implode("','", $values);
5722
        $sql .= "')";
5723
5724
        $status = $this->execute($sql);
5725
        if ($status) {
0 ignored issues
show
introduced by
$status is of type ADORecordSet, thus it always evaluated to true.
Loading history...
5726
            $this->rollbackTransaction();
5727
5728
            return -1;
5729
        }
5730
5731
        if ($typcomment != '') {
5732
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
5733
            if ($status) {
5734
                $this->rollbackTransaction();
5735
5736
                return -1;
5737
            }
5738
        }
5739
5740
        return $this->endTransaction();
5741
    }
5742
5743
    /**
5744
     * Get defined values for a given enum.
5745
     *
5746
     * @param $name
5747
     *
5748
     * @return \PHPPgAdmin\ADORecordSet A recordset
5749
     */
5750
    public function getEnumValues($name)
5751
    {
5752
        $this->clean($name);
5753
5754
        $sql = "SELECT enumlabel AS enumval
5755
		FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON (t.oid=e.enumtypid)
5756
		WHERE t.typname = '{$name}' ORDER BY e.oid";
5757
5758
        return $this->selectSet($sql);
5759
    }
5760
5761
    // Operator functions
5762
5763
    /**
5764
     * Creates a new composite type in the database.
5765
     *
5766
     * @param $name       The name of the type
5767
     * @param $fields     The number of fields
5768
     * @param $field      An array of field names
5769
     * @param $type       An array of field types
5770
     * @param $array      An array of '' or '[]' for each type if it's an array or not
5771
     * @param $length     An array of field lengths
5772
     * @param $colcomment An array of comments
5773
     * @param $typcomment Type comment
5774
     *
5775
     * @return bool|int 0 success
5776
     */
5777
    public function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment)
5778
    {
5779
        $f_schema = $this->_schema;
5780
        $this->fieldClean($f_schema);
5781
        $this->fieldClean($name);
5782
5783
        $status = $this->beginTransaction();
5784
        if ($status != 0) {
5785
            return -1;
5786
        }
5787
5788
        $found       = false;
5789
        $first       = true;
5790
        $comment_sql = ''; // Accumulate comments for the columns
5791
        $sql         = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS (";
5792
        for ($i = 0; $i < $fields; ++$i) {
5793
            $this->fieldClean($field[$i]);
5794
            $this->clean($type[$i]);
5795
            $this->clean($length[$i]);
5796
            $this->clean($colcomment[$i]);
5797
5798
            // Skip blank columns - for user convenience
5799
            if ($field[$i] == '' || $type[$i] == '') {
5800
                continue;
5801
            }
5802
5803
            // If not the first column, add a comma
5804
            if (!$first) {
5805
                $sql .= ', ';
5806
            } else {
5807
                $first = false;
5808
            }
5809
5810
            switch ($type[$i]) {
5811
                // Have to account for weird placing of length for with/without
5812
                // time zone types
5813
                case 'timestamp with time zone':
5814
                case 'timestamp without time zone':
5815
                    $qual = substr($type[$i], 9);
5816
                    $sql .= "\"{$field[$i]}\" timestamp";
5817
                    if ($length[$i] != '') {
5818
                        $sql .= "({$length[$i]})";
5819
                    }
5820
5821
                    $sql .= $qual;
5822
5823
                    break;
5824
                case 'time with time zone':
5825
                case 'time without time zone':
5826
                    $qual = substr($type[$i], 4);
5827
                    $sql .= "\"{$field[$i]}\" time";
5828
                    if ($length[$i] != '') {
5829
                        $sql .= "({$length[$i]})";
5830
                    }
5831
5832
                    $sql .= $qual;
5833
5834
                    break;
5835
                default:
5836
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
5837
                    if ($length[$i] != '') {
5838
                        $sql .= "({$length[$i]})";
5839
                    }
5840
            }
5841
            // Add array qualifier if necessary
5842
            if ($array[$i] == '[]') {
5843
                $sql .= '[]';
5844
            }
5845
5846
            if ($colcomment[$i] != '') {
5847
                $comment_sql .= "COMMENT ON COLUMN \"{$f_schema}\".\"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
5848
            }
5849
5850
            $found = true;
5851
        }
5852
5853
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
5854
            return -1;
5855
        }
5856
5857
        $sql .= ')';
5858
5859
        $status = $this->execute($sql);
5860
        if ($status) {
5861
            $this->rollbackTransaction();
5862
5863
            return -1;
5864
        }
5865
5866
        if ($typcomment != '') {
5867
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
5868
            if ($status) {
5869
                $this->rollbackTransaction();
5870
5871
                return -1;
5872
            }
5873
        }
5874
5875
        if ($comment_sql != '') {
5876
            $status = $this->execute($comment_sql);
5877
            if ($status) {
5878
                $this->rollbackTransaction();
5879
5880
                return -1;
5881
            }
5882
        }
5883
5884
        return $this->endTransaction();
5885
    }
5886
5887
    /**
5888
     * Returns a list of all casts in the database.
5889
     *
5890
     * @return \PHPPgAdmin\ADORecordSet All casts
5891
     */
5892
    public function getCasts()
5893
    {
5894
        $conf = $this->conf;
5895
5896
        if ($conf['show_system']) {
5897
            $where = '';
5898
        } else {
5899
            $where = '
5900
				AND n1.nspname NOT LIKE $$pg\_%$$
5901
				AND n2.nspname NOT LIKE $$pg\_%$$
5902
				AND n3.nspname NOT LIKE $$pg\_%$$
5903
			';
5904
        }
5905
5906
        $sql = "
5907
			SELECT
5908
				c.castsource::pg_catalog.regtype AS castsource,
5909
				c.casttarget::pg_catalog.regtype AS casttarget,
5910
				CASE WHEN c.castfunc=0 THEN NULL
5911
				ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc,
5912
				c.castcontext,
5913
				obj_description(c.oid, 'pg_cast') as castcomment
5914
			FROM
5915
				(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),
5916
				pg_catalog.pg_type t1,
5917
				pg_catalog.pg_type t2,
5918
				pg_catalog.pg_namespace n1,
5919
				pg_catalog.pg_namespace n2
5920
			WHERE
5921
				c.castsource=t1.oid
5922
				AND c.casttarget=t2.oid
5923
				AND t1.typnamespace=n1.oid
5924
				AND t2.typnamespace=n2.oid
5925
				{$where}
5926
			ORDER BY 1, 2
5927
		";
5928
5929
        return $this->selectSet($sql);
5930
    }
5931
5932
    /**
5933
     * Returns a list of all conversions in the database.
5934
     *
5935
     * @return \PHPPgAdmin\ADORecordSet  All conversions
5936
     */
5937
    public function getConversions()
5938
    {
5939
        $c_schema = $this->_schema;
5940
        $this->clean($c_schema);
5941
        $sql = "
5942
			SELECT
5943
			       c.conname,
5944
			       pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding,
5945
			       pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding,
5946
			       c.condefault,
5947
			       pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment
5948
			FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n
5949
			WHERE n.oid = c.connamespace
5950
			      AND n.nspname='{$c_schema}'
5951
			ORDER BY 1;
5952
		";
5953
5954
        return $this->selectSet($sql);
5955
    }
5956
5957
    // Operator Class functions
5958
5959
    /**
5960
     * Edits a rule on a table OR view.
5961
     *
5962
     * @param $name    The name of the new rule
5963
     * @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...
5964
     * @param $table   Table on which to create the rule
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Table 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...
5965
     * @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...
5966
     * @param $instead True if an INSTEAD rule, false otherwise
5967
     * @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...
5968
     * @param $action  The action to take
5969
     *
5970
     * @return integer 0 if operation was successful
5971
     */
5972
    public function setRule($name, $event, $table, $where, $instead, $type, $action)
5973
    {
5974
        return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true);
5975
    }
5976
5977
    // FTS functions
5978
5979
    /**
5980
     * Creates a rule.
5981
     *
5982
     * @param string   $name     The name of the new rule
5983
     * @param string   $event    SELECT, INSERT, UPDATE or DELETE
5984
     * @param string   $table    Table on which to create the rule
5985
     * @param string   $where    When to execute the rule, '' indicates always
5986
     * @param boolean  $instead  True if an INSTEAD rule, false otherwise
5987
     * @param string   $type     NOTHING for a do nothing rule, SOMETHING to use given action
5988
     * @param string   $action   The action to take
5989
     * @param bool     $replace  (optional) True to replace existing rule, false
5990
     *                           otherwise
5991
     *
5992
     * @return integer  0 if operation was successful
5993
     */
5994
    public function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false)
5995
    {
5996
        $f_schema = $this->_schema;
5997
        $this->fieldClean($f_schema);
5998
        $this->fieldClean($name);
5999
        $this->fieldClean($table);
6000
        if (!in_array($event, $this->rule_events, true)) {
6001
            return -1;
6002
        }
6003
6004
        $sql = 'CREATE';
6005
        if ($replace) {
6006
            $sql .= ' OR REPLACE';
6007
        }
6008
6009
        $sql .= " RULE \"{$name}\" AS ON {$event} TO \"{$f_schema}\".\"{$table}\"";
6010
        // Can't escape WHERE clause
6011
        if ($where != '') {
6012
            $sql .= " WHERE {$where}";
6013
        }
6014
6015
        $sql .= ' DO';
6016
        if ($instead) {
6017
            $sql .= ' INSTEAD';
6018
        }
6019
6020
        if ($type == 'NOTHING') {
6021
            $sql .= ' NOTHING';
6022
        } else {
6023
            $sql .= " ({$action})";
6024
        }
6025
6026
        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...
6027
    }
6028
6029
    /**
6030
     * Removes a rule from a table OR view.
6031
     *
6032
     * @param string $rule     The rule to drop
6033
     * @param string $relation The relation from which to drop
6034
     * @param string $cascade  True to cascade drop, false to restrict
6035
     *
6036
     * @return integer 0 if operation was successful
6037
     */
6038
    public function dropRule($rule, $relation, $cascade)
6039
    {
6040
        $f_schema = $this->_schema;
6041
        $this->fieldClean($f_schema);
6042
        $this->fieldClean($rule);
6043
        $this->fieldClean($relation);
6044
6045
        $sql = "DROP RULE \"{$rule}\" ON \"{$f_schema}\".\"{$relation}\"";
6046
        if ($cascade) {
6047
            $sql .= ' CASCADE';
6048
        }
6049
6050
        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...
6051
    }
6052
6053
    /**
6054
     * Grabs a single trigger.
6055
     *
6056
     * @param string $table   The name of a table whose triggers to retrieve
6057
     * @param string $trigger The name of the trigger to retrieve
6058
     *
6059
     * @return \PHPPgAdmin\ADORecordSet A recordset
6060
     */
6061
    public function getTrigger($table, $trigger)
6062
    {
6063
        $c_schema = $this->_schema;
6064
        $this->clean($c_schema);
6065
        $this->clean($table);
6066
        $this->clean($trigger);
6067
6068
        $sql = "
6069
			SELECT * FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c
6070
			WHERE t.tgrelid=c.oid AND c.relname='{$table}' AND t.tgname='{$trigger}'
6071
				AND c.relnamespace=(
6072
					SELECT oid FROM pg_catalog.pg_namespace
6073
					WHERE nspname='{$c_schema}')";
6074
6075
        return $this->selectSet($sql);
6076
    }
6077
6078
    /**
6079
     * A helper function for getTriggers that translates
6080
     * an array of attribute numbers to an array of field names.
6081
     * Note: Only needed for pre-7.4 servers, this function is deprecated.
6082
     *
6083
     * @param string $trigger An array containing fields from the trigger table
6084
     *
6085
     * @return string The trigger definition string
6086
     */
6087
    public function getTriggerDef($trigger)
6088
    {
6089
        $this->fieldArrayClean($trigger);
6090
        // Constants to figure out tgtype
6091
        if (!defined('TRIGGER_TYPE_ROW')) {
6092
            define('TRIGGER_TYPE_ROW', 1 << 0);
6093
        }
6094
6095
        if (!defined('TRIGGER_TYPE_BEFORE')) {
6096
            define('TRIGGER_TYPE_BEFORE', 1 << 1);
6097
        }
6098
6099
        if (!defined('TRIGGER_TYPE_INSERT')) {
6100
            define('TRIGGER_TYPE_INSERT', 1 << 2);
6101
        }
6102
6103
        if (!defined('TRIGGER_TYPE_DELETE')) {
6104
            define('TRIGGER_TYPE_DELETE', 1 << 3);
6105
        }
6106
6107
        if (!defined('TRIGGER_TYPE_UPDATE')) {
6108
            define('TRIGGER_TYPE_UPDATE', 1 << 4);
6109
        }
6110
6111
        $trigger['tgisconstraint'] = $this->phpBool($trigger['tgisconstraint']);
6112
        $trigger['tgdeferrable']   = $this->phpBool($trigger['tgdeferrable']);
6113
        $trigger['tginitdeferred'] = $this->phpBool($trigger['tginitdeferred']);
6114
6115
        // Constraint trigger or normal trigger
6116
        if ($trigger['tgisconstraint']) {
6117
            $tgdef = 'CREATE CONSTRAINT TRIGGER ';
6118
        } else {
6119
            $tgdef = 'CREATE TRIGGER ';
6120
        }
6121
6122
        $tgdef .= "\"{$trigger['tgname']}\" ";
6123
6124
        // Trigger type
6125
        $findx = 0;
6126
        if (($trigger['tgtype'] & TRIGGER_TYPE_BEFORE) == TRIGGER_TYPE_BEFORE) {
6127
            $tgdef .= 'BEFORE';
6128
        } else {
6129
            $tgdef .= 'AFTER';
6130
        }
6131
6132
        if (($trigger['tgtype'] & TRIGGER_TYPE_INSERT) == TRIGGER_TYPE_INSERT) {
6133
            $tgdef .= ' INSERT';
6134
            ++$findx;
6135
        }
6136
        if (($trigger['tgtype'] & TRIGGER_TYPE_DELETE) == TRIGGER_TYPE_DELETE) {
6137
            if ($findx > 0) {
6138
                $tgdef .= ' OR DELETE';
6139
            } else {
6140
                $tgdef .= ' DELETE';
6141
                ++$findx;
6142
            }
6143
        }
6144
        if (($trigger['tgtype'] & TRIGGER_TYPE_UPDATE) == TRIGGER_TYPE_UPDATE) {
6145
            if ($findx > 0) {
6146
                $tgdef .= ' OR UPDATE';
6147
            } else {
6148
                $tgdef .= ' UPDATE';
6149
            }
6150
        }
6151
6152
        $f_schema = $this->_schema;
6153
        $this->fieldClean($f_schema);
6154
        // Table name
6155
        $tgdef .= " ON \"{$f_schema}\".\"{$trigger['relname']}\" ";
6156
6157
        // Deferrability
6158
        if ($trigger['tgisconstraint']) {
6159
            if ($trigger['tgconstrrelid'] != 0) {
6160
                // Assume constrelname is not null
6161
                $tgdef .= " FROM \"{$trigger['tgconstrrelname']}\" ";
6162
            }
6163
            if (!$trigger['tgdeferrable']) {
6164
                $tgdef .= 'NOT ';
6165
            }
6166
6167
            $tgdef .= 'DEFERRABLE INITIALLY ';
6168
            if ($trigger['tginitdeferred']) {
6169
                $tgdef .= 'DEFERRED ';
6170
            } else {
6171
                $tgdef .= 'IMMEDIATE ';
6172
            }
6173
        }
6174
6175
        // Row or statement
6176
        if ($trigger['tgtype'] & TRIGGER_TYPE_ROW == TRIGGER_TYPE_ROW) {
6177
            $tgdef .= 'FOR EACH ROW ';
6178
        } else {
6179
            $tgdef .= 'FOR EACH STATEMENT ';
6180
        }
6181
6182
        // Execute procedure
6183
        $tgdef .= "EXECUTE PROCEDURE \"{$trigger['tgfname']}\"(";
6184
6185
        // Parameters
6186
        // Escape null characters
6187
        $v = addcslashes($trigger['tgargs'], "\0");
6188
        // Split on escaped null characters
6189
        $params = explode('\\000', $v);
6190
        for ($findx = 0; $findx < $trigger['tgnargs']; ++$findx) {
6191
            $param = "'" . str_replace('\'', '\\\'', $params[$findx]) . "'";
6192
            $tgdef .= $param;
6193
            if ($findx < ($trigger['tgnargs'] - 1)) {
6194
                $tgdef .= ', ';
6195
            }
6196
        }
6197
6198
        // Finish it off
6199
        $tgdef .= ')';
6200
6201
        return $tgdef;
6202
    }
6203
6204
    /**
6205
     * Returns a list of all functions that can be used in triggers.
6206
     */
6207
    public function getTriggerFunctions()
6208
    {
6209
        return $this->getFunctions(true, 'trigger');
6210
    }
6211
6212
    /**
6213
     * Returns a list of all functions in the database.
6214
     *
6215
     * @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...
6216
     * @param                              $type If not null, will find all functions with return value = type
6217
     *
6218
     * @return \PHPPgAdmin\ADORecordSet All functions
6219
     */
6220
    public function getFunctions($all = false, $type = null)
6221
    {
6222
        if ($all) {
6223
            $where    = 'pg_catalog.pg_function_is_visible(p.oid)';
6224
            $distinct = 'DISTINCT ON (p.proname)';
6225
6226
            if ($type) {
6227
                $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') ";
6228
            }
6229
        } else {
6230
            $c_schema = $this->_schema;
6231
            $this->clean($c_schema);
6232
            $where    = "n.nspname = '{$c_schema}'";
6233
            $distinct = '';
6234
        }
6235
6236
        $sql = "
6237
			SELECT
6238
				{$distinct}
6239
				p.oid AS prooid,
6240
				p.proname,
6241
				p.proretset,
6242
				pg_catalog.format_type(p.prorettype, NULL) AS proresult,
6243
				pg_catalog.oidvectortypes(p.proargtypes) AS proarguments,
6244
				pl.lanname AS prolanguage,
6245
				pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment,
6246
				p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
6247
				CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns,
6248
				coalesce(u.usename::text,p.proowner::text) AS proowner
6249
6250
			FROM pg_catalog.pg_proc p
6251
				INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
6252
				INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang
6253
				LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner
6254
			WHERE NOT p.proisagg
6255
				AND {$where}
6256
			ORDER BY p.proname, proresult
6257
			";
6258
6259
        return $this->selectSet($sql);
6260
    }
6261
6262
    /**
6263
     * Creates a trigger.
6264
     *
6265
     * @param $tgname  The name of the trigger to create
6266
     * @param $table   The name of the table
6267
     * @param $tgproc  The function to execute
6268
     * @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...
6269
     * @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...
6270
     * @param $tgfrequency
6271
     * @param $tgargs  The function arguments
6272
     *
6273
     * @return integer 0 if operation was successful
6274
     */
6275
    public function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs)
6276
    {
6277
        $f_schema = $this->_schema;
6278
        $this->fieldClean($f_schema);
6279
        $this->fieldClean($tgname);
6280
        $this->fieldClean($table);
6281
        $this->fieldClean($tgproc);
6282
6283
        /* No Statement Level Triggers in PostgreSQL (by now) */
6284
        $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime}
6285
				{$tgevent} ON \"{$f_schema}\".\"{$table}\"
6286
				FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})";
6287
6288
        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...
6289
    }
6290
6291
    /**
6292
     * Alters a trigger.
6293
     *
6294
     * @param $table   The name of the table containing the trigger
6295
     * @param $trigger The name of the trigger to alter
6296
     * @param $name    The new name for the trigger
6297
     *
6298
     * @return integer 0 if operation was successful
6299
     */
6300
    public function alterTrigger($table, $trigger, $name)
6301
    {
6302
        $f_schema = $this->_schema;
6303
        $this->fieldClean($f_schema);
6304
        $this->fieldClean($table);
6305
        $this->fieldClean($trigger);
6306
        $this->fieldClean($name);
6307
6308
        $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_schema}\".\"{$table}\" RENAME TO \"{$name}\"";
6309
6310
        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...
6311
    }
6312
6313
    /**
6314
     * Drops a trigger.
6315
     *
6316
     * @param $tgname  The name of the trigger to drop
6317
     * @param $table   The table from which to drop the trigger
6318
     * @param $cascade True to cascade drop, false to restrict
6319
     *
6320
     * @return integer 0 if operation was successful
6321
     */
6322
    public function dropTrigger($tgname, $table, $cascade)
6323
    {
6324
        $f_schema = $this->_schema;
6325
        $this->fieldClean($f_schema);
6326
        $this->fieldClean($tgname);
6327
        $this->fieldClean($table);
6328
6329
        $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\"";
6330
        if ($cascade) {
6331
            $sql .= ' CASCADE';
6332
        }
6333
6334
        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...
6335
    }
6336
6337
    /**
6338
     * Enables a trigger.
6339
     *
6340
     * @param $tgname The name of the trigger to enable
6341
     * @param $table  The table in which to enable the trigger
6342
     *
6343
     * @return integer 0 if operation was successful
6344
     */
6345
    public function enableTrigger($tgname, $table)
6346
    {
6347
        $f_schema = $this->_schema;
6348
        $this->fieldClean($f_schema);
6349
        $this->fieldClean($tgname);
6350
        $this->fieldClean($table);
6351
6352
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\"";
6353
6354
        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...
6355
    }
6356
6357
    /**
6358
     * Disables a trigger.
6359
     *
6360
     * @param $tgname The name of the trigger to disable
6361
     * @param $table  The table in which to disable the trigger
6362
     *
6363
     * @return integer 0 if operation was successful
6364
     */
6365
    public function disableTrigger($tgname, $table)
6366
    {
6367
        $f_schema = $this->_schema;
6368
        $this->fieldClean($f_schema);
6369
        $this->fieldClean($tgname);
6370
        $this->fieldClean($table);
6371
6372
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\"";
6373
6374
        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...
6375
    }
6376
6377
    /**
6378
     * Returns a list of all operators in the database.
6379
     *
6380
     * @return \PHPPgAdmin\ADORecordSet All operators
6381
     */
6382
    public function getOperators()
6383
    {
6384
        $c_schema = $this->_schema;
6385
        $this->clean($c_schema);
6386
        // We stick with the subselects here, as you cannot ORDER BY a regtype
6387
        $sql = "
6388
			SELECT
6389
            	po.oid,	po.oprname,
6390
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname,
6391
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname,
6392
				po.oprresult::pg_catalog.regtype AS resultname,
6393
		        pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment
6394
			FROM
6395
				pg_catalog.pg_operator po
6396
			WHERE
6397
				po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')
6398
			ORDER BY
6399
				po.oprname, oprleftname, oprrightname
6400
		";
6401
6402
        return $this->selectSet($sql);
6403
    }
6404
6405
    /**
6406
     * Drops an operator.
6407
     *
6408
     * @param $operator_oid The OID of the operator to drop
6409
     * @param $cascade      True to cascade drop, false to restrict
6410
     *
6411
     * @return integer 0 if operation was successful
6412
     */
6413
    public function dropOperator($operator_oid, $cascade)
6414
    {
6415
        // Function comes in with $object as operator OID
6416
        $opr      = $this->getOperator($operator_oid);
6417
        $f_schema = $this->_schema;
6418
        $this->fieldClean($f_schema);
6419
        $this->fieldClean($opr->fields['oprname']);
6420
6421
        $sql = "DROP OPERATOR \"{$f_schema}\".{$opr->fields['oprname']} (";
6422
        // Quoting or formatting here???
6423
        if ($opr->fields['oprleftname'] !== null) {
6424
            $sql .= $opr->fields['oprleftname'] . ', ';
6425
        } else {
6426
            $sql .= 'NONE, ';
6427
        }
6428
6429
        if ($opr->fields['oprrightname'] !== null) {
6430
            $sql .= $opr->fields['oprrightname'] . ')';
6431
        } else {
6432
            $sql .= 'NONE)';
6433
        }
6434
6435
        if ($cascade) {
6436
            $sql .= ' CASCADE';
6437
        }
6438
6439
        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...
6440
    }
6441
6442
    /**
6443
     * Returns all details for a particular operator.
6444
     *
6445
     * @param $operator_oid The oid of the operator
6446
     *
6447
     * @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...
6448
     */
6449
    public function getOperator($operator_oid)
6450
    {
6451
        $this->clean($operator_oid);
6452
6453
        $sql = "
6454
			SELECT
6455
            	po.oid, po.oprname,
6456
				oprleft::pg_catalog.regtype AS oprleftname,
6457
				oprright::pg_catalog.regtype AS oprrightname,
6458
				oprresult::pg_catalog.regtype AS resultname,
6459
				po.oprcanhash,
6460
				oprcanmerge,
6461
				oprcom::pg_catalog.regoperator AS oprcom,
6462
				oprnegate::pg_catalog.regoperator AS oprnegate,
6463
				po.oprcode::pg_catalog.regproc AS oprcode,
6464
				po.oprrest::pg_catalog.regproc AS oprrest,
6465
				po.oprjoin::pg_catalog.regproc AS oprjoin
6466
			FROM
6467
				pg_catalog.pg_operator po
6468
			WHERE
6469
				po.oid='{$operator_oid}'
6470
		";
6471
6472
        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...
6473
    }
6474
6475
    /**
6476
     *  Gets all opclasses.
6477
     *
6478
     * @return \PHPPgAdmin\ADORecordSet A recordset
6479
     */
6480
    public function getOpClasses()
6481
    {
6482
        $c_schema = $this->_schema;
6483
        $this->clean($c_schema);
6484
        $sql = "
6485
			SELECT
6486
				pa.amname, po.opcname,
6487
				po.opcintype::pg_catalog.regtype AS opcintype,
6488
				po.opcdefault,
6489
				pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment
6490
			FROM
6491
				pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn
6492
			WHERE
6493
				po.opcmethod=pa.oid
6494
				AND po.opcnamespace=pn.oid
6495
				AND pn.nspname='{$c_schema}'
6496
			ORDER BY 1,2
6497
			";
6498
6499
        return $this->selectSet($sql);
6500
    }
6501
6502
    /**
6503
     * Creates a new FTS configuration.
6504
     *
6505
     * @param string $cfgname  The name of the FTS configuration to create
6506
     * @param string $parser   The parser to be used in new FTS configuration
6507
     * @param string $template The existing FTS configuration to be used as template for the new one
6508
     * @param string $comment  If omitted, defaults to nothing
6509
     *
6510
     * @return bool|int 0 success
6511
     *
6512
     * @internal param string $locale Locale of the FTS configuration
6513
     * @internal param string $withmap Should we copy whole map of existing FTS configuration to the new one
6514
     * @internal param string $makeDefault Should this configuration be the default for locale given
6515
     */
6516
    public function createFtsConfiguration($cfgname, $parser = '', $template = '', $comment = '')
6517
    {
6518
        $f_schema = $this->_schema;
6519
        $this->fieldClean($f_schema);
6520
        $this->fieldClean($cfgname);
6521
6522
        $sql = "CREATE TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$cfgname}\" (";
6523
        if ($parser != '') {
6524
            $this->fieldClean($parser['schema']);
6525
            $this->fieldClean($parser['parser']);
6526
            $parser = "\"{$parser['schema']}\".\"{$parser['parser']}\"";
6527
            $sql .= " PARSER = {$parser}";
6528
        }
6529
        if ($template != '') {
6530
            $this->fieldClean($template['schema']);
6531
            $this->fieldClean($template['name']);
6532
            $sql .= " COPY = \"{$template['schema']}\".\"{$template['name']}\"";
6533
        }
6534
        $sql .= ')';
6535
6536
        if ($comment != '') {
6537
            $status = $this->beginTransaction();
6538
            if ($status != 0) {
6539
                return -1;
6540
            }
6541
        }
6542
6543
        // Create the FTS configuration
6544
        $status = $this->execute($sql);
6545
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
6546
            $this->rollbackTransaction();
6547
6548
            return -1;
6549
        }
6550
6551
        // Set the comment
6552
        if ($comment != '') {
6553
            $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment);
6554
            if ($status != 0) {
6555
                $this->rollbackTransaction();
6556
6557
                return -1;
6558
            }
6559
6560
            return $this->endTransaction();
6561
        }
6562
6563
        return 0;
6564
    }
6565
6566
    // Language functions
6567
6568
    /**
6569
     * Returns available FTS configurations.
6570
     *
6571
     * @param bool $all if false, returns schema qualified FTS confs
6572
     *
6573
     * @return \PHPPgAdmin\ADORecordSet A recordset
6574
     */
6575
    public function getFtsConfigurations($all = true)
6576
    {
6577
        $c_schema = $this->_schema;
6578
        $this->clean($c_schema);
6579
        $sql = "
6580
			SELECT
6581
				n.nspname as schema,
6582
				c.cfgname as name,
6583
				pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment
6584
			FROM
6585
				pg_catalog.pg_ts_config c
6586
				JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace
6587
			WHERE
6588
				pg_catalog.pg_ts_config_is_visible(c.oid)";
6589
6590
        if (!$all) {
6591
            $sql .= " AND  n.nspname='{$c_schema}'\n";
6592
        }
6593
6594
        $sql .= 'ORDER BY name';
6595
6596
        return $this->selectSet($sql);
6597
    }
6598
6599
    // Aggregate functions
6600
6601
    /**
6602
     * Returns the map of FTS configuration given
6603
     * (list of mappings (tokens) and their processing dictionaries).
6604
     *
6605
     * @param string $ftscfg Name of the FTS configuration
6606
     *
6607
     * @return \PHPPgAdmin\ADORecordSet recordset
6608
     */
6609
    public function getFtsConfigurationMap($ftscfg)
6610
    {
6611
        $c_schema = $this->_schema;
6612
        $this->clean($c_schema);
6613
        $this->fieldClean($ftscfg);
6614
6615
        $oidSet = $this->selectSet("SELECT c.oid
1 ignored issue
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
6616
			FROM pg_catalog.pg_ts_config AS c
6617
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.cfgnamespace)
6618
			WHERE c.cfgname = '{$ftscfg}'
6619
				AND n.nspname='{$c_schema}'");
1 ignored issue
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
6620
6621
        $oid = $oidSet->fields['oid'];
6622
6623
        $sql = "
6624
 			SELECT
6625
    			(SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name,
6626
        		(SELECT t.description FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS description,
6627
				c.cfgname AS cfgname, n.nspname ||'.'|| d.dictname as dictionaries
6628
			FROM
6629
				pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d,
6630
				pg_catalog.pg_namespace n
6631
			WHERE
6632
				c.oid = {$oid}
6633
				AND m.mapcfg = c.oid
6634
				AND m.mapdict = d.oid
6635
				AND d.dictnamespace = n.oid
6636
			ORDER BY name
6637
			";
6638
6639
        return $this->selectSet($sql);
6640
    }
6641
6642
    /**
6643
     * Returns FTS parsers available.
6644
     *
6645
     * @param bool $all if false, return only Parsers from the current schema
6646
     *
6647
     * @return \PHPPgAdmin\ADORecordSet A recordset
6648
     */
6649
    public function getFtsParsers($all = true)
6650
    {
6651
        $c_schema = $this->_schema;
6652
        $this->clean($c_schema);
6653
        $sql = "
6654
			SELECT
6655
			   n.nspname as schema,
6656
			   p.prsname as name,
6657
			   pg_catalog.obj_description(p.oid, 'pg_ts_parser') as comment
6658
			FROM pg_catalog.pg_ts_parser p
6659
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = p.prsnamespace)
6660
			WHERE pg_catalog.pg_ts_parser_is_visible(p.oid)";
6661
6662
        if (!$all) {
6663
            $sql .= " AND n.nspname='{$c_schema}'\n";
6664
        }
6665
6666
        $sql .= 'ORDER BY name';
6667
6668
        return $this->selectSet($sql);
6669
    }
6670
6671
    /**
6672
     * Returns FTS dictionaries available.
6673
     *
6674
     * @param bool $all if false, return only Dics from the current schema
6675
     *
6676
     * @return \PHPPgAdmin\ADORecordSet A recordset
6677
     */
6678
    public function getFtsDictionaries($all = true)
6679
    {
6680
        $c_schema = $this->_schema;
6681
        $this->clean($c_schema);
6682
        $sql = "
6683
 			SELECT
6684
				n.nspname as schema, d.dictname as name,
6685
				pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment
6686
			FROM pg_catalog.pg_ts_dict d
6687
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace
6688
			WHERE pg_catalog.pg_ts_dict_is_visible(d.oid)";
6689
6690
        if (!$all) {
6691
            $sql .= " AND n.nspname='{$c_schema}'\n";
6692
        }
6693
6694
        $sql .= 'ORDER BY name;';
6695
6696
        return $this->selectSet($sql);
6697
    }
6698
6699
    /**
6700
     * Returns all FTS dictionary templates available.
6701
     */
6702
    public function getFtsDictionaryTemplates()
6703
    {
6704
        $sql = "
6705
 			SELECT
6706
				n.nspname as schema,
6707
				t.tmplname as name,
6708
				( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname
6709
					FROM pg_catalog.pg_proc p
6710
					LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace
6711
					WHERE t.tmplinit = p.oid ) AS  init,
6712
				( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname
6713
					FROM pg_catalog.pg_proc p
6714
					LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace
6715
					WHERE t.tmpllexize = p.oid ) AS  lexize,
6716
				pg_catalog.obj_description(t.oid, 'pg_ts_template') as comment
6717
			FROM pg_catalog.pg_ts_template t
6718
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace
6719
			WHERE pg_catalog.pg_ts_template_is_visible(t.oid)
6720
			ORDER BY name;";
6721
6722
        return $this->selectSet($sql);
6723
    }
6724
6725
    /**
6726
     * Drops FTS coniguration.
6727
     *
6728
     * @param $ftscfg  The configuration's name
6729
     * @param $cascade Cascade to dependenced objects
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Cascade 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...
6730
     *
6731
     * @return integer 0 if operation was successful
6732
     */
6733
    public function dropFtsConfiguration($ftscfg, $cascade)
6734
    {
6735
        $f_schema = $this->_schema;
6736
        $this->fieldClean($f_schema);
6737
        $this->fieldClean($ftscfg);
6738
6739
        $sql = "DROP TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\"";
6740
        if ($cascade) {
6741
            $sql .= ' CASCADE';
6742
        }
6743
6744
        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...
6745
    }
6746
6747
    /**
6748
     * Drops FTS dictionary.
6749
     *
6750
     * @param $ftsdict The dico's name
6751
     * @param $cascade Cascade to dependenced objects
6752
     *
6753
     * @return integer 0 if operation was successful
6754
     *
6755
     * @todo Support of dictionary templates dropping
6756
     */
6757
    public function dropFtsDictionary($ftsdict, $cascade)
6758
    {
6759
        $f_schema = $this->_schema;
6760
        $this->fieldClean($f_schema);
6761
        $this->fieldClean($ftsdict);
6762
6763
        $sql = 'DROP TEXT SEARCH DICTIONARY';
6764
        $sql .= " \"{$f_schema}\".\"{$ftsdict}\"";
6765
        if ($cascade) {
6766
            $sql .= ' CASCADE';
6767
        }
6768
6769
        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...
6770
    }
6771
6772
    /**
6773
     * Alters FTS configuration.
6774
     *
6775
     * @param $cfgname The conf's name
6776
     * @param $comment A comment on for the conf
6777
     * @param $name    The new conf name
6778
     *
6779
     * @return bool|int 0 on success
6780
     */
6781
    public function updateFtsConfiguration($cfgname, $comment, $name)
6782
    {
6783
        $status = $this->beginTransaction();
6784
        if ($status != 0) {
6785
            $this->rollbackTransaction();
6786
6787
            return -1;
6788
        }
6789
6790
        $this->fieldClean($cfgname);
6791
6792
        $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment);
6793
        if ($status != 0) {
6794
            $this->rollbackTransaction();
6795
6796
            return -1;
6797
        }
6798
6799
        // Only if the name has changed
6800
        if ($name != $cfgname) {
6801
            $f_schema = $this->_schema;
6802
            $this->fieldClean($f_schema);
6803
            $this->fieldClean($name);
6804
6805
            $sql    = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$cfgname}\" RENAME TO \"{$name}\"";
6806
            $status = $this->execute($sql);
6807
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
6808
                $this->rollbackTransaction();
6809
6810
                return -1;
6811
            }
6812
        }
6813
6814
        return $this->endTransaction();
6815
    }
6816
6817
    /**
6818
     * Creates a new FTS dictionary or FTS dictionary template.
6819
     *
6820
     * @param string $dictname   The name of the FTS dictionary to create
6821
     * @param bool   $isTemplate Flag whether we create usual dictionary or dictionary template
6822
     * @param string $template   The existing FTS dictionary to be used as template for the new one
6823
     * @param string $lexize     The name of the function, which does transformation of input word
6824
     * @param string $init       The name of the function, which initializes dictionary
6825
     * @param string $option     Usually, it stores various options required for the dictionary
6826
     * @param string $comment    If omitted, defaults to nothing
6827
     *
6828
     * @return bool|int 0 success
6829
     */
6830
    public function createFtsDictionary(
6831
        $dictname,
6832
        $isTemplate = false,
6833
        $template = '',
6834
        $lexize = '',
6835
        $init = '',
6836
        $option = '',
6837
        $comment = ''
6838
    ) {
6839
        $f_schema = $this->_schema;
6840
        $this->fieldClean($f_schema);
6841
        $this->fieldClean($dictname);
6842
        $this->fieldClean($template);
6843
        $this->fieldClean($lexize);
6844
        $this->fieldClean($init);
6845
        $this->fieldClean($option);
6846
6847
        $sql = 'CREATE TEXT SEARCH';
6848
        if ($isTemplate) {
6849
            $sql .= " TEMPLATE \"{$f_schema}\".\"{$dictname}\" (";
6850
            if ($lexize != '') {
6851
                $sql .= " LEXIZE = {$lexize}";
6852
            }
6853
6854
            if ($init != '') {
6855
                $sql .= ", INIT = {$init}";
6856
            }
6857
6858
            $sql .= ')';
6859
            $whatToComment = 'TEXT SEARCH TEMPLATE';
6860
        } else {
6861
            $sql .= " DICTIONARY \"{$f_schema}\".\"{$dictname}\" (";
6862
            if ($template != '') {
6863
                $this->fieldClean($template['schema']);
6864
                $this->fieldClean($template['name']);
6865
                $template = "\"{$template['schema']}\".\"{$template['name']}\"";
6866
6867
                $sql .= " TEMPLATE = {$template}";
6868
            }
6869
            if ($option != '') {
6870
                $sql .= ", {$option}";
6871
            }
6872
6873
            $sql .= ')';
6874
            $whatToComment = 'TEXT SEARCH DICTIONARY';
6875
        }
6876
6877
        /* if comment, begin a transaction to
6878
         * run both commands */
6879
        if ($comment != '') {
6880
            $status = $this->beginTransaction();
6881
            if ($status != 0) {
6882
                return -1;
6883
            }
6884
        }
6885
6886
        // Create the FTS dictionary
6887
        $status = $this->execute($sql);
6888
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
6889
            $this->rollbackTransaction();
6890
6891
            return -1;
6892
        }
6893
6894
        // Set the comment
6895
        if ($comment != '') {
6896
            $status = $this->setComment($whatToComment, $dictname, '', $comment);
6897
            if ($status != 0) {
6898
                $this->rollbackTransaction();
6899
6900
                return -1;
6901
            }
6902
        }
6903
6904
        return $this->endTransaction();
6905
    }
6906
6907
    // Role, User/Group functions
6908
6909
    /**
6910
     * Alters FTS dictionary or dictionary template.
6911
     *
6912
     * @param $dictname The dico's name
6913
     * @param $comment  The comment
6914
     * @param $name     The new dico's name
6915
     *
6916
     * @return bool|int 0 on success
6917
     */
6918
    public function updateFtsDictionary($dictname, $comment, $name)
6919
    {
6920
        $status = $this->beginTransaction();
6921
        if ($status != 0) {
6922
            $this->rollbackTransaction();
6923
6924
            return -1;
6925
        }
6926
6927
        $this->fieldClean($dictname);
6928
        $status = $this->setComment('TEXT SEARCH DICTIONARY', $dictname, '', $comment);
6929
        if ($status != 0) {
6930
            $this->rollbackTransaction();
6931
6932
            return -1;
6933
        }
6934
6935
        // Only if the name has changed
6936
        if ($name != $dictname) {
6937
            $f_schema = $this->_schema;
6938
            $this->fieldClean($f_schema);
6939
            $this->fieldClean($name);
6940
6941
            $sql    = "ALTER TEXT SEARCH DICTIONARY \"{$f_schema}\".\"{$dictname}\" RENAME TO \"{$name}\"";
6942
            $status = $this->execute($sql);
6943
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
6944
                $this->rollbackTransaction();
6945
6946
                return -1;
6947
            }
6948
        }
6949
6950
        return $this->endTransaction();
6951
    }
6952
6953
    /**
6954
     * Return all information relating to a FTS dictionary.
6955
     *
6956
     * @param $ftsdict The name of the FTS dictionary
6957
     *
6958
     * @return \PHPPgAdmin\ADORecordSet recordset of FTS dictionary information
6959
     */
6960
    public function getFtsDictionaryByName($ftsdict)
6961
    {
6962
        $c_schema = $this->_schema;
6963
        $this->clean($c_schema);
6964
        $this->clean($ftsdict);
6965
6966
        $sql = "SELECT
6967
			   n.nspname as schema,
6968
			   d.dictname as name,
6969
			   ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM
6970
				 pg_catalog.pg_ts_template t
6971
									  LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace
6972
									  WHERE d.dicttemplate = t.oid ) AS  template,
6973
			   d.dictinitoption as init,
6974
			   pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment
6975
			FROM pg_catalog.pg_ts_dict d
6976
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace
6977
			WHERE d.dictname = '{$ftsdict}'
6978
			   AND pg_catalog.pg_ts_dict_is_visible(d.oid)
6979
			   AND n.nspname='{$c_schema}'
6980
			ORDER BY name";
6981
6982
        return $this->selectSet($sql);
6983
    }
6984
6985
    /**
6986
     * Creates/updates/deletes FTS mapping.
6987
     *
6988
     * @param        $ftscfg
6989
     * @param array  $mapping  Array of tokens' names
6990
     * @param string $action   What to do with the mapping: add, alter or drop
6991
     * @param string $dictname Dictionary that will process tokens given or null in case of drop action
6992
     *
6993
     * @return int 0 if operation was successful
6994
     *
6995
     * @internal param string $cfgname The name of the FTS configuration to alter
6996
     */
6997
    public function changeFtsMapping($ftscfg, $mapping, $action, $dictname = null)
6998
    {
6999
        if (count($mapping) > 0) {
7000
            $f_schema = $this->_schema;
7001
            $this->fieldClean($f_schema);
7002
            $this->fieldClean($ftscfg);
7003
            $this->fieldClean($dictname);
7004
            $this->arrayClean($mapping);
7005
7006
            switch ($action) {
7007
                case 'alter':
7008
                    $whatToDo = 'ALTER';
7009
7010
                    break;
7011
                case 'drop':
7012
                    $whatToDo = 'DROP';
7013
7014
                    break;
7015
                default:
7016
                    $whatToDo = 'ADD';
7017
7018
                    break;
7019
            }
7020
7021
            $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\" {$whatToDo} MAPPING FOR ";
7022
            $sql .= implode(',', $mapping);
7023
            if ($action != 'drop' && !empty($dictname)) {
7024
                $sql .= " WITH {$dictname}";
7025
            }
7026
7027
            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...
7028
        }
7029
7030
        return -1;
7031
    }
7032
7033
    /**
7034
     * Return all information related to a given FTS configuration's mapping.
7035
     *
7036
     * @param $ftscfg  The name of the FTS configuration
7037
     * @param $mapping The name of the mapping
7038
     *
7039
     * @return FTS configuration information
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\FTS 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...
7040
     */
7041
    public function getFtsMappingByName($ftscfg, $mapping)
7042
    {
7043
        $c_schema = $this->_schema;
7044
        $this->clean($c_schema);
7045
        $this->clean($ftscfg);
7046
        $this->clean($mapping);
7047
7048
        $oidSet = $this->selectSet("SELECT c.oid, cfgparser
1 ignored issue
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
7049
			FROM pg_catalog.pg_ts_config AS c
7050
				LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = c.cfgnamespace
7051
			WHERE c.cfgname = '{$ftscfg}'
7052
				AND n.nspname='{$c_schema}'");
1 ignored issue
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
7053
7054
        $oid       = $oidSet->fields['oid'];
7055
        $cfgparser = $oidSet->fields['cfgparser'];
7056
7057
        $tokenIdSet = $this->selectSet("SELECT tokid
1 ignored issue
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
7058
			FROM pg_catalog.ts_token_type({$cfgparser})
7059
			WHERE alias = '{$mapping}'");
1 ignored issue
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
7060
7061
        $tokid = $tokenIdSet->fields['tokid'];
7062
7063
        $sql = "SELECT
7064
			    (SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name,
7065
    	            d.dictname as dictionaries
7066
			FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d
7067
			WHERE c.oid = {$oid} AND m.mapcfg = c.oid AND m.maptokentype = {$tokid} AND m.mapdict = d.oid
7068
			LIMIT 1;";
7069
7070
        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\FTS.
Loading history...
7071
    }
7072
7073
    /**
7074
     * Return list of FTS mappings possible for given parser
7075
     * (specified by given configuration since configuration can only have 1 parser).
7076
     *
7077
     * @param $ftscfg The config's name that use the parser
7078
     *
7079
     * @return integer 0 if operation was successful
7080
     */
7081
    public function getFtsMappings($ftscfg)
7082
    {
7083
        $cfg = $this->getFtsConfigurationByName($ftscfg);
7084
7085
        $sql = "SELECT alias AS name, description
7086
			FROM pg_catalog.ts_token_type({$cfg->fields['parser_id']})
7087
			ORDER BY name";
7088
7089
        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 integer.
Loading history...
7090
    }
7091
7092
    /**
7093
     * Return all information related to a FTS configuration.
7094
     *
7095
     * @param $ftscfg The name of the FTS configuration
7096
     *
7097
     * @return FTS configuration information
7098
     */
7099
    public function getFtsConfigurationByName($ftscfg)
7100
    {
7101
        $c_schema = $this->_schema;
7102
        $this->clean($c_schema);
7103
        $this->clean($ftscfg);
7104
        $sql = "
7105
			SELECT
7106
				n.nspname as schema,
7107
				c.cfgname as name,
7108
				p.prsname as parser,
7109
				c.cfgparser as parser_id,
7110
				pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment
7111
			FROM pg_catalog.pg_ts_config c
7112
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace
7113
				LEFT JOIN pg_catalog.pg_ts_parser p ON p.oid = c.cfgparser
7114
			WHERE pg_catalog.pg_ts_config_is_visible(c.oid)
7115
				AND c.cfgname = '{$ftscfg}'
7116
				AND n.nspname='{$c_schema}'";
7117
7118
        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\FTS.
Loading history...
7119
    }
7120
7121
    /**
7122
     * Gets all languages.
7123
     *
7124
     * @param bool|true $all True to get all languages, regardless of show_system
7125
     *
7126
     * @return \PHPPgAdmin\ADORecordSet A recordset
7127
     */
7128
    public function getLanguages($all = false)
7129
    {
7130
        $conf = $this->conf;
7131
7132
        if ($conf['show_system'] || $all) {
7133
            $where = '';
7134
        } else {
7135
            $where = 'WHERE lanispl';
7136
        }
7137
7138
        $sql = "
7139
			SELECT
7140
				lanname, lanpltrusted,
7141
				lanplcallfoid::pg_catalog.regproc AS lanplcallf
7142
			FROM
7143
				pg_catalog.pg_language
7144
			{$where}
7145
			ORDER BY lanname
7146
		";
7147
7148
        return $this->selectSet($sql);
7149
    }
7150
7151
    /**
7152
     * Creates a new aggregate in the database.
7153
     *
7154
     * @param $name     The name of the aggregate
7155
     * @param $basetype The input data type of the aggregate
7156
     * @param $sfunc    The name of the state transition function for the aggregate
7157
     * @param $stype    The data type for the aggregate's state value
7158
     * @param $ffunc    The name of the final function for the aggregate
7159
     * @param $initcond The initial setting for the state value
7160
     * @param $sortop   The sort operator for the aggregate
7161
     * @param $comment  Aggregate comment
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Aggregate 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...
7162
     *
7163
     * @return bool|int 0 success
7164
     */
7165
    public function createAggregate($name, $basetype, $sfunc, $stype, $ffunc, $initcond, $sortop, $comment)
7166
    {
7167
        $f_schema = $this->_schema;
7168
        $this->fieldClean($f_schema);
7169
        $this->fieldClean($name);
7170
        $this->fieldClean($basetype);
7171
        $this->fieldClean($sfunc);
7172
        $this->fieldClean($stype);
7173
        $this->fieldClean($ffunc);
7174
        $this->fieldClean($initcond);
7175
        $this->fieldClean($sortop);
7176
7177
        $this->beginTransaction();
7178
7179
        $sql = "CREATE AGGREGATE \"{$f_schema}\".\"{$name}\" (BASETYPE = \"{$basetype}\", SFUNC = \"{$sfunc}\", STYPE = \"{$stype}\"";
7180
        if (trim($ffunc) != '') {
7181
            $sql .= ", FINALFUNC = \"{$ffunc}\"";
7182
        }
7183
7184
        if (trim($initcond) != '') {
7185
            $sql .= ", INITCOND = \"{$initcond}\"";
7186
        }
7187
7188
        if (trim($sortop) != '') {
7189
            $sql .= ", SORTOP = \"{$sortop}\"";
7190
        }
7191
7192
        $sql .= ')';
7193
7194
        $status = $this->execute($sql);
7195
        if ($status) {
0 ignored issues
show
introduced by
$status is of type ADORecordSet, thus it always evaluated to true.
Loading history...
7196
            $this->rollbackTransaction();
7197
7198
            return -1;
7199
        }
7200
7201
        if (trim($comment) != '') {
7202
            $status = $this->setComment('AGGREGATE', $name, '', $comment, $basetype);
7203
            if ($status) {
7204
                $this->rollbackTransaction();
7205
7206
                return -1;
7207
            }
7208
        }
7209
7210
        return $this->endTransaction();
7211
    }
7212
7213
    /**
7214
     * Removes an aggregate function from the database.
7215
     *
7216
     * @param $aggrname The name of the aggregate
7217
     * @param $aggrtype The input data type of the aggregate
7218
     * @param $cascade  True to cascade drop, false to restrict
7219
     *
7220
     * @return integer 0 if operation was successful
7221
     */
7222
    public function dropAggregate($aggrname, $aggrtype, $cascade)
7223
    {
7224
        $f_schema = $this->_schema;
7225
        $this->fieldClean($f_schema);
7226
        $this->fieldClean($aggrname);
7227
        $this->fieldClean($aggrtype);
7228
7229
        $sql = "DROP AGGREGATE \"{$f_schema}\".\"{$aggrname}\" (\"{$aggrtype}\")";
7230
        if ($cascade) {
7231
            $sql .= ' CASCADE';
7232
        }
7233
7234
        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...
7235
    }
7236
7237
    /**
7238
     * Gets all information for an aggregate.
7239
     *
7240
     * @param $name     The name of the aggregate
7241
     * @param $basetype The input data type of the aggregate
7242
     *
7243
     * @return \PHPPgAdmin\ADORecordSet A recordset
7244
     */
7245
    public function getAggregate($name, $basetype)
7246
    {
7247
        $c_schema = $this->_schema;
7248
        $this->clean($c_schema);
7249
        $this->fieldClean($name);
7250
        $this->fieldClean($basetype);
7251
7252
        $sql = "
7253
			SELECT p.proname, CASE p.proargtypes[0]
7254
				WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL
7255
				ELSE pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes,
7256
				a.aggtransfn, format_type(a.aggtranstype, NULL) AS aggstype, a.aggfinalfn,
7257
				a.agginitval, a.aggsortop, u.usename, pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment
7258
			FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a
7259
			WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid
7260
				AND p.proisagg AND n.nspname='{$c_schema}'
7261
				AND p.proname='" . $name . "'
7262
				AND CASE p.proargtypes[0]
7263
					WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN ''
7264
					ELSE pg_catalog.format_type(p.proargtypes[0], NULL)
7265
				END ='" . $basetype . "'";
7266
7267
        return $this->selectSet($sql);
7268
    }
7269
7270
    /**
7271
     * Gets all aggregates.
7272
     *
7273
     * @return \PHPPgAdmin\ADORecordSet A recordset
7274
     */
7275
    public function getAggregates()
7276
    {
7277
        $c_schema = $this->_schema;
7278
        $this->clean($c_schema);
7279
        $sql = "SELECT p.proname, CASE p.proargtypes[0] WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL ELSE
7280
			   pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes, a.aggtransfn, u.usename,
7281
			   pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment
7282
			   FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a
7283
			   WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid
7284
			   AND p.proisagg AND n.nspname='{$c_schema}' ORDER BY 1, 2";
7285
7286
        return $this->selectSet($sql);
7287
    }
7288
7289
    /**
7290
     * Alters an aggregate.
7291
     *
7292
     * @param $aggrname       The actual name of the aggregate
7293
     * @param $aggrtype       The actual input data type of the aggregate
7294
     * @param $aggrowner      The actual owner of the aggregate
7295
     * @param $aggrschema     The actual schema the aggregate belongs to
7296
     * @param $aggrcomment    The actual comment for the aggregate
7297
     * @param $newaggrname    The new name of the aggregate
7298
     * @param $newaggrowner   The new owner of the aggregate
7299
     * @param $newaggrschema  The new schema where the aggregate will belong to
7300
     * @param $newaggrcomment The new comment for the aggregate
7301
     *
7302
     * @return bool|int 0 success
7303
     */
7304
    public function alterAggregate(
7305
        $aggrname,
7306
        $aggrtype,
7307
        $aggrowner,
7308
        $aggrschema,
7309
        $aggrcomment,
7310
        $newaggrname,
7311
        $newaggrowner,
7312
        $newaggrschema,
7313
        $newaggrcomment
7314
    ) {
7315
        // Clean fields
7316
        $this->fieldClean($aggrname);
7317
        $this->fieldClean($aggrtype);
7318
        $this->fieldClean($aggrowner);
7319
        $this->fieldClean($aggrschema);
7320
        $this->fieldClean($newaggrname);
7321
        $this->fieldClean($newaggrowner);
7322
        $this->fieldClean($newaggrschema);
7323
7324
        $this->beginTransaction();
7325
7326
        // Change the owner, if it has changed
7327
        if ($aggrowner != $newaggrowner) {
7328
            $status = $this->changeAggregateOwner($aggrname, $aggrtype, $newaggrowner);
7329
            if ($status != 0) {
7330
                $this->rollbackTransaction();
7331
7332
                return -1;
7333
            }
7334
        }
7335
7336
        // Set the comment, if it has changed
7337
        if ($aggrcomment != $newaggrcomment) {
7338
            $status = $this->setComment('AGGREGATE', $aggrname, '', $newaggrcomment, $aggrtype);
7339
            if ($status) {
7340
                $this->rollbackTransaction();
7341
7342
                return -2;
7343
            }
7344
        }
7345
7346
        // Change the schema, if it has changed
7347
        if ($aggrschema != $newaggrschema) {
7348
            $status = $this->changeAggregateSchema($aggrname, $aggrtype, $newaggrschema);
7349
            if ($status != 0) {
7350
                $this->rollbackTransaction();
7351
7352
                return -3;
7353
            }
7354
        }
7355
7356
        // Rename the aggregate, if it has changed
7357
        if ($aggrname != $newaggrname) {
7358
            $status = $this->renameAggregate($newaggrschema, $aggrname, $aggrtype, $newaggrname);
7359
            if ($status != 0) {
7360
                $this->rollbackTransaction();
7361
7362
                return -4;
7363
            }
7364
        }
7365
7366
        return $this->endTransaction();
7367
    }
7368
7369
    /**
7370
     * Changes the owner of an aggregate function.
7371
     *
7372
     * @param $aggrname     The name of the aggregate
7373
     * @param $aggrtype     The input data type of the aggregate
7374
     * @param $newaggrowner The new owner of the aggregate
7375
     *
7376
     * @return integer 0 if operation was successful
7377
     */
7378
    public function changeAggregateOwner($aggrname, $aggrtype, $newaggrowner)
7379
    {
7380
        $f_schema = $this->_schema;
7381
        $this->fieldClean($f_schema);
7382
        $this->fieldClean($aggrname);
7383
        $this->fieldClean($newaggrowner);
7384
        $sql = "ALTER AGGREGATE \"{$f_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") OWNER TO \"{$newaggrowner}\"";
7385
7386
        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...
7387
    }
7388
7389
    /**
7390
     * Changes the schema of an aggregate function.
7391
     *
7392
     * @param $aggrname      The name of the aggregate
7393
     * @param $aggrtype      The input data type of the aggregate
7394
     * @param $newaggrschema The new schema for the aggregate
7395
     *
7396
     * @return integer 0 if operation was successful
7397
     */
7398
    public function changeAggregateSchema($aggrname, $aggrtype, $newaggrschema)
7399
    {
7400
        $f_schema = $this->_schema;
7401
        $this->fieldClean($f_schema);
7402
        $this->fieldClean($aggrname);
7403
        $this->fieldClean($newaggrschema);
7404
        $sql = "ALTER AGGREGATE \"{$f_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") SET SCHEMA  \"{$newaggrschema}\"";
7405
7406
        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...
7407
    }
7408
7409
    /**
7410
     * Renames an aggregate function.
7411
     *
7412
     * @param $aggrschema
7413
     * @param $aggrname    The actual name of the aggregate
7414
     * @param $aggrtype    The actual input data type of the aggregate
7415
     * @param $newaggrname The new name of the aggregate
7416
     *
7417
     * @return integer 0 if operation was successful
7418
     */
7419
    public function renameAggregate($aggrschema, $aggrname, $aggrtype, $newaggrname)
7420
    {
7421
        /* this function is called from alterAggregate where params are cleaned */
7422
        $sql = "ALTER AGGREGATE \"{$aggrschema}\"" . '.' . "\"{$aggrname}\" (\"{$aggrtype}\") RENAME TO \"{$newaggrname}\"";
7423
7424
        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...
7425
    }
7426
7427
    /**
7428
     * Returns all roles in the database cluster.
7429
     *
7430
     * @param $rolename (optional) The role name to exclude from the select
7431
     *
7432
     * @return All roles
7433
     */
7434
    public function getRoles($rolename = '')
7435
    {
7436
        $sql = '
7437
			SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit,
7438
				rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig
7439
			FROM pg_catalog.pg_roles';
7440
        if ($rolename) {
7441
            $sql .= " WHERE rolname!='{$rolename}'";
7442
        }
7443
7444
        $sql .= ' ORDER BY rolname';
7445
7446
        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\All.
Loading history...
7447
    }
7448
7449
    /**
7450
     * Returns information about a single role.
7451
     *
7452
     * @param $rolename The name of the role to retrieve
7453
     *
7454
     * @return The role's data
7455
     */
7456
    public function getRole($rolename)
7457
    {
7458
        $this->clean($rolename);
7459
7460
        $sql = "
7461
			SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit,
7462
				rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig
7463
			FROM pg_catalog.pg_roles WHERE rolname='{$rolename}'";
7464
7465
        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\The.
Loading history...
7466
    }
7467
7468
    /**
7469
     * Returns all users in the database cluster.
7470
     *
7471
     * @return All users
7472
     */
7473
    public function getUsers()
7474
    {
7475
        $sql = 'SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig
7476
			FROM pg_user
7477
			ORDER BY usename';
7478
7479
        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\All.
Loading history...
7480
    }
7481
7482
    /**
7483
     * Returns information about a single user.
7484
     *
7485
     * @param $username The username of the user to retrieve
7486
     *
7487
     * @return The user's data
7488
     */
7489
    public function getUser($username)
7490
    {
7491
        $this->clean($username);
7492
7493
        $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig
7494
			FROM pg_user
7495
			WHERE usename='{$username}'";
7496
7497
        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\The.
Loading history...
7498
    }
7499
7500
    /**
7501
     * Creates a new role.
7502
     *
7503
     * @param $rolename     The name of the role to create
7504
     * @param $password     A password for the role
7505
     * @param $superuser    Boolean whether or not the role is a superuser
7506
     * @param $createdb     Boolean whether or not the role can create databases
7507
     * @param $createrole   Boolean whether or not the role can create other roles
7508
     * @param $inherits     Boolean whether or not the role inherits the privileges from parent roles
7509
     * @param $login        Boolean whether or not the role will be allowed to login
7510
     * @param $connlimit    Number of concurrent connections the role can make
7511
     * @param $expiry       String Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire
7512
     * @param $memberof     (array) Roles to which the new role will be immediately added as a new member
7513
     * @param $members      (array) Roles which are automatically added as members of the new role
7514
     * @param $adminmembers (array) Roles which are automatically added as admin members of the new role
7515
     *
7516
     * @return integer 0 if operation was successful
7517
     */
7518
    public function createRole(
7519
        $rolename,
7520
        $password,
7521
        $superuser,
7522
        $createdb,
7523
        $createrole,
7524
        $inherits,
7525
        $login,
7526
        $connlimit,
7527
        $expiry,
7528
        $memberof,
7529
        $members,
7530
        $adminmembers
7531
    ) {
7532
        $enc = $this->_encryptPassword($rolename, $password);
7533
        $this->fieldClean($rolename);
7534
        $this->clean($enc);
7535
        $this->clean($connlimit);
7536
        $this->clean($expiry);
7537
        $this->fieldArrayClean($memberof);
7538
        $this->fieldArrayClean($members);
7539
        $this->fieldArrayClean($adminmembers);
7540
7541
        $sql = "CREATE ROLE \"{$rolename}\"";
7542
        if ($password != '') {
7543
            $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'";
7544
        }
7545
7546
        $sql .= $superuser ? ' SUPERUSER' : ' NOSUPERUSER';
7547
        $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB';
7548
        $sql .= $createrole ? ' CREATEROLE' : ' NOCREATEROLE';
7549
        $sql .= $inherits ? ' INHERIT' : ' NOINHERIT';
7550
        $sql .= $login ? ' LOGIN' : ' NOLOGIN';
7551
        if ($connlimit != '') {
7552
            $sql .= " CONNECTION LIMIT {$connlimit}";
7553
        } else {
7554
            $sql .= ' CONNECTION LIMIT -1';
7555
        }
7556
7557
        if ($expiry != '') {
7558
            $sql .= " VALID UNTIL '{$expiry}'";
7559
        } else {
7560
            $sql .= " VALID UNTIL 'infinity'";
7561
        }
7562
7563
        if (is_array($memberof) && sizeof($memberof) > 0) {
7564
            $sql .= ' IN ROLE "' . join('", "', $memberof) . '"';
7565
        }
7566
7567
        if (is_array($members) && sizeof($members) > 0) {
7568
            $sql .= ' ROLE "' . join('", "', $members) . '"';
7569
        }
7570
7571
        if (is_array($adminmembers) && sizeof($adminmembers) > 0) {
7572
            $sql .= ' ADMIN "' . join('", "', $adminmembers) . '"';
7573
        }
7574
7575
        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...
7576
    }
7577
7578
    /**
7579
     * Helper function that computes encypted PostgreSQL passwords.
7580
     *
7581
     * @param $username The username
7582
     * @param $password The password
7583
     *
7584
     * @return string
7585
     */
7586
    public function _encryptPassword($username, $password)
1 ignored issue
show
Coding Style introduced by
Public method name "Postgres::_encryptPassword" must not be prefixed with an underscore
Loading history...
7587
    {
7588
        return 'md5' . md5($password . $username);
7589
    }
7590
7591
    /**
7592
     * Adjusts a role's info and renames it.
7593
     *
7594
     * @param $rolename        The name of the role to adjust
7595
     * @param $password        A password for the role
7596
     * @param $superuser       Boolean whether or not the role is a superuser
7597
     * @param $createdb        Boolean whether or not the role can create databases
7598
     * @param $createrole      Boolean whether or not the role can create other roles
7599
     * @param $inherits        Boolean whether or not the role inherits the privileges from parent roles
7600
     * @param $login           Boolean whether or not the role will be allowed to login
7601
     * @param $connlimit       Number of concurrent connections the role can make
7602
     * @param $expiry          string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire
7603
     * @param $memberof        (array) Roles to which the role will be immediately added as a new member
7604
     * @param $members         (array) Roles which are automatically added as members of the role
7605
     * @param $adminmembers    (array) Roles which are automatically added as admin members of the role
7606
     * @param $memberofold     (array) Original roles whose the role belongs to
7607
     * @param $membersold      (array) Original roles that are members of the role
7608
     * @param $adminmembersold (array) Original roles that are admin members of the role
7609
     * @param $newrolename     The new name of the role
7610
     *
7611
     * @return bool|int 0 success
7612
     */
7613
    public function setRenameRole(
7614
        $rolename,
7615
        $password,
7616
        $superuser,
7617
        $createdb,
7618
        $createrole,
7619
        $inherits,
7620
        $login,
7621
        $connlimit,
7622
        $expiry,
7623
        $memberof,
7624
        $members,
7625
        $adminmembers,
7626
        $memberofold,
7627
        $membersold,
7628
        $adminmembersold,
7629
        $newrolename
7630
    ) {
7631
        $status = $this->beginTransaction();
7632
        if ($status != 0) {
7633
            return -1;
7634
        }
7635
7636
        if ($rolename != $newrolename) {
7637
            $status = $this->renameRole($rolename, $newrolename);
7638
            if ($status != 0) {
7639
                $this->rollbackTransaction();
7640
7641
                return -3;
7642
            }
7643
            $rolename = $newrolename;
7644
        }
7645
7646
        $status =
0 ignored issues
show
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
7647
        $this->setRole(
7648
            $rolename,
7649
            $password,
7650
            $superuser,
7651
            $createdb,
7652
            $createrole,
7653
            $inherits,
7654
            $login,
7655
            $connlimit,
7656
            $expiry,
7657
            $memberof,
7658
            $members,
7659
            $adminmembers,
7660
            $memberofold,
7661
            $membersold,
7662
            $adminmembersold
7663
        );
7664
        if ($status != 0) {
7665
            $this->rollbackTransaction();
7666
7667
            return -2;
7668
        }
7669
7670
        return $this->endTransaction();
7671
    }
7672
7673
    /**
7674
     * Renames a role.
7675
     *
7676
     * @param $rolename    The name of the role to rename
7677
     * @param $newrolename The new name of the role
7678
     *
7679
     * @return integer 0 if operation was successful
7680
     */
7681
    public function renameRole($rolename, $newrolename)
7682
    {
7683
        $this->fieldClean($rolename);
7684
        $this->fieldClean($newrolename);
7685
7686
        $sql = "ALTER ROLE \"{$rolename}\" RENAME TO \"{$newrolename}\"";
7687
7688
        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...
7689
    }
7690
7691
    /**
7692
     * Adjusts a role's info.
7693
     *
7694
     * @param $rolename        The name of the role to adjust
7695
     * @param $password        A password for the role
7696
     * @param $superuser       Boolean whether or not the role is a superuser
7697
     * @param $createdb        Boolean whether or not the role can create databases
7698
     * @param $createrole      Boolean whether or not the role can create other roles
7699
     * @param $inherits        Boolean whether or not the role inherits the privileges from parent roles
7700
     * @param $login           Boolean whether or not the role will be allowed to login
7701
     * @param $connlimit       Number of concurrent connections the role can make
7702
     * @param $expiry          string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire
7703
     * @param $memberof        (array) Roles to which the role will be immediately added as a new member
7704
     * @param $members         (array) Roles which are automatically added as members of the role
7705
     * @param $adminmembers    (array) Roles which are automatically added as admin members of the role
7706
     * @param $memberofold     (array) Original roles whose the role belongs to
7707
     * @param $membersold      (array) Original roles that are members of the role
7708
     * @param $adminmembersold (array) Original roles that are admin members of the role
7709
     *
7710
     * @return int 0 if operation was successful
7711
     */
7712
    public function setRole(
7713
        $rolename,
7714
        $password,
7715
        $superuser,
7716
        $createdb,
7717
        $createrole,
7718
        $inherits,
7719
        $login,
7720
        $connlimit,
7721
        $expiry,
7722
        $memberof,
7723
        $members,
7724
        $adminmembers,
7725
        $memberofold,
7726
        $membersold,
7727
        $adminmembersold
7728
    ) {
7729
        $enc = $this->_encryptPassword($rolename, $password);
7730
        $this->fieldClean($rolename);
7731
        $this->clean($enc);
7732
        $this->clean($connlimit);
7733
        $this->clean($expiry);
7734
        $this->fieldArrayClean($memberof);
7735
        $this->fieldArrayClean($members);
7736
        $this->fieldArrayClean($adminmembers);
7737
7738
        $sql = "ALTER ROLE \"{$rolename}\"";
7739
        if ($password != '') {
7740
            $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'";
7741
        }
7742
7743
        $sql .= $superuser ? ' SUPERUSER' : ' NOSUPERUSER';
7744
        $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB';
7745
        $sql .= $createrole ? ' CREATEROLE' : ' NOCREATEROLE';
7746
        $sql .= $inherits ? ' INHERIT' : ' NOINHERIT';
7747
        $sql .= $login ? ' LOGIN' : ' NOLOGIN';
7748
        if ($connlimit != '') {
7749
            $sql .= " CONNECTION LIMIT {$connlimit}";
7750
        } else {
7751
            $sql .= ' CONNECTION LIMIT -1';
7752
        }
7753
7754
        if ($expiry != '') {
7755
            $sql .= " VALID UNTIL '{$expiry}'";
7756
        } else {
7757
            $sql .= " VALID UNTIL 'infinity'";
7758
        }
7759
7760
        $status = $this->execute($sql);
7761
7762
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
7763
            return -1;
7764
        }
7765
7766
        //memberof
7767
        $old = explode(',', $memberofold);
7768
        foreach ($memberof as $m) {
7769
            if (!in_array($m, $old, true)) {
7770
                $status = $this->grantRole($m, $rolename);
7771
                if ($status != 0) {
7772
                    return -1;
7773
                }
7774
            }
7775
        }
7776
        if ($memberofold) {
7777
            foreach ($old as $o) {
7778
                if (!in_array($o, $memberof, true)) {
7779
                    $status = $this->revokeRole($o, $rolename, 0, 'CASCADE');
7780
                    if ($status != 0) {
7781
                        return -1;
7782
                    }
7783
                }
7784
            }
7785
        }
7786
7787
        //members
7788
        $old = explode(',', $membersold);
7789
        foreach ($members as $m) {
7790
            if (!in_array($m, $old, true)) {
7791
                $status = $this->grantRole($rolename, $m);
7792
                if ($status != 0) {
7793
                    return -1;
7794
                }
7795
            }
7796
        }
7797
        if ($membersold) {
7798
            foreach ($old as $o) {
7799
                if (!in_array($o, $members, true)) {
7800
                    $status = $this->revokeRole($rolename, $o, 0, 'CASCADE');
7801
                    if ($status != 0) {
7802
                        return -1;
7803
                    }
7804
                }
7805
            }
7806
        }
7807
7808
        //adminmembers
7809
        $old = explode(',', $adminmembersold);
7810
        foreach ($adminmembers as $m) {
7811
            if (!in_array($m, $old, true)) {
7812
                $status = $this->grantRole($rolename, $m, 1);
7813
                if ($status != 0) {
7814
                    return -1;
7815
                }
7816
            }
7817
        }
7818
        if ($adminmembersold) {
7819
            foreach ($old as $o) {
7820
                if (!in_array($o, $adminmembers, true)) {
7821
                    $status = $this->revokeRole($rolename, $o, 1, 'CASCADE');
7822
                    if ($status != 0) {
7823
                        return -1;
7824
                    }
7825
                }
7826
            }
7827
        }
7828
7829
        return $status;
7830
    }
7831
7832
    /**
7833
     * Grants membership in a role.
7834
     *
7835
     * @param     $role     The name of the target role
7836
     * @param     $rolename The name of the role that will belong to the target role
7837
     * @param int $admin    (optional) Flag to grant the admin option
7838
     *
7839
     * @return integer 0 if operation was successful
7840
     */
7841
    public function grantRole($role, $rolename, $admin = 0)
7842
    {
7843
        $this->fieldClean($role);
7844
        $this->fieldClean($rolename);
7845
7846
        $sql = "GRANT \"{$role}\" TO \"{$rolename}\"";
7847
        if ($admin == 1) {
7848
            $sql .= ' WITH ADMIN OPTION';
7849
        }
7850
7851
        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...
7852
    }
7853
7854
    /**
7855
     * Revokes membership in a role.
7856
     *
7857
     * @param        $role     The name of the target role
7858
     * @param        $rolename The name of the role that will not belong to the target role
7859
     * @param int    $admin    (optional) Flag to revoke only the admin option
7860
     * @param string $type     (optional) Type of revoke: RESTRICT | CASCADE
7861
     *
7862
     * @return integer 0 if operation was successful
7863
     */
7864
    public function revokeRole($role, $rolename, $admin = 0, $type = 'RESTRICT')
7865
    {
7866
        $this->fieldClean($role);
7867
        $this->fieldClean($rolename);
7868
7869
        $sql = 'REVOKE ';
7870
        if ($admin == 1) {
7871
            $sql .= 'ADMIN OPTION FOR ';
7872
        }
7873
7874
        $sql .= "\"{$role}\" FROM \"{$rolename}\" {$type}";
7875
7876
        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...
7877
    }
7878
7879
    /**
7880
     * Removes a role.
7881
     *
7882
     * @param $rolename The name of the role to drop
7883
     *
7884
     * @return integer 0 if operation was successful
7885
     */
7886
    public function dropRole($rolename)
7887
    {
7888
        $this->fieldClean($rolename);
7889
7890
        $sql = "DROP ROLE \"{$rolename}\"";
7891
7892
        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...
7893
    }
7894
7895
    /**
7896
     * Creates a new user.
7897
     *
7898
     * @param $username   The username of the user to create
7899
     * @param $password   A password for the user
7900
     * @param $createdb   boolean Whether or not the user can create databases
7901
     * @param $createuser boolean Whether or not the user can create other users
7902
     * @param $expiry     string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire
7903
     * @param $groups
7904
     *
7905
     * @return integer 0 if operation was successful
7906
     *
7907
     * @internal param $group (array) The groups to create the user in
7908
     */
7909
    public function createUser($username, $password, $createdb, $createuser, $expiry, $groups)
7910
    {
7911
        $enc = $this->_encryptPassword($username, $password);
7912
        $this->fieldClean($username);
7913
        $this->clean($enc);
7914
        $this->clean($expiry);
7915
        $this->fieldArrayClean($groups);
7916
7917
        $sql = "CREATE USER \"{$username}\"";
7918
        if ($password != '') {
7919
            $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'";
7920
        }
7921
7922
        $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB';
7923
        $sql .= $createuser ? ' CREATEUSER' : ' NOCREATEUSER';
7924
        if (is_array($groups) && sizeof($groups) > 0) {
7925
            $sql .= ' IN GROUP "' . join('", "', $groups) . '"';
7926
        }
7927
7928
        if ($expiry != '') {
7929
            $sql .= " VALID UNTIL '{$expiry}'";
7930
        } else {
7931
            $sql .= " VALID UNTIL 'infinity'";
7932
        }
7933
7934
        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...
7935
    }
7936
7937
    /**
7938
     * Adjusts a user's info and renames the user.
7939
     *
7940
     * @param $username   The username of the user to modify
7941
     * @param $password   A new password for the user
7942
     * @param $createdb   boolean Whether or not the user can create databases
7943
     * @param $createuser boolean Whether or not the user can create other users
7944
     * @param $expiry     string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire.
7945
     * @param $newname    The new name of the user
7946
     *
7947
     * @return bool|int 0 success
7948
     */
7949
    public function setRenameUser($username, $password, $createdb, $createuser, $expiry, $newname)
7950
    {
7951
        $status = $this->beginTransaction();
7952
        if ($status != 0) {
7953
            return -1;
7954
        }
7955
7956
        if ($username != $newname) {
7957
            $status = $this->renameUser($username, $newname);
7958
            if ($status != 0) {
7959
                $this->rollbackTransaction();
7960
7961
                return -3;
7962
            }
7963
            $username = $newname;
7964
        }
7965
7966
        $status = $this->setUser($username, $password, $createdb, $createuser, $expiry);
7967
        if ($status != 0) {
7968
            $this->rollbackTransaction();
7969
7970
            return -2;
7971
        }
7972
7973
        return $this->endTransaction();
7974
    }
7975
7976
    /**
7977
     * Renames a user.
7978
     *
7979
     * @param $username The username of the user to rename
7980
     * @param $newname  The new name of the user
7981
     *
7982
     * @return integer 0 if operation was successful
7983
     */
7984
    public function renameUser($username, $newname)
7985
    {
7986
        $this->fieldClean($username);
7987
        $this->fieldClean($newname);
7988
7989
        $sql = "ALTER USER \"{$username}\" RENAME TO \"{$newname}\"";
7990
7991
        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...
7992
    }
7993
7994
    // Tablespace functions
7995
7996
    /**
7997
     * Adjusts a user's info.
7998
     *
7999
     * @param $username   The username of the user to modify
8000
     * @param $password   A new password for the user
8001
     * @param $createdb   boolean Whether or not the user can create databases
8002
     * @param $createuser boolean Whether or not the user can create other users
8003
     * @param $expiry     string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire.
8004
     *
8005
     * @return integer 0 if operation was successful
8006
     */
8007
    public function setUser($username, $password, $createdb, $createuser, $expiry)
8008
    {
8009
        $enc = $this->_encryptPassword($username, $password);
8010
        $this->fieldClean($username);
8011
        $this->clean($enc);
8012
        $this->clean($expiry);
8013
8014
        $sql = "ALTER USER \"{$username}\"";
8015
        if ($password != '') {
8016
            $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'";
8017
        }
8018
8019
        $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB';
8020
        $sql .= $createuser ? ' CREATEUSER' : ' NOCREATEUSER';
8021
        if ($expiry != '') {
8022
            $sql .= " VALID UNTIL '{$expiry}'";
8023
        } else {
8024
            $sql .= " VALID UNTIL 'infinity'";
8025
        }
8026
8027
        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...
8028
    }
8029
8030
    /**
8031
     * Removes a user.
8032
     *
8033
     * @param $username The username of the user to drop
8034
     *
8035
     * @return integer 0 if operation was successful
8036
     */
8037
    public function dropUser($username)
8038
    {
8039
        $this->fieldClean($username);
8040
8041
        $sql = "DROP USER \"{$username}\"";
8042
8043
        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...
8044
    }
8045
8046
    /**
8047
     * Changes a role's password.
8048
     *
8049
     * @param $rolename The role name
8050
     * @param $password The new password
8051
     *
8052
     * @return integer 0 if operation was successful
8053
     */
8054
    public function changePassword($rolename, $password)
8055
    {
8056
        $enc = $this->_encryptPassword($rolename, $password);
8057
        $this->fieldClean($rolename);
8058
        $this->clean($enc);
8059
8060
        $sql = "ALTER ROLE \"{$rolename}\" WITH ENCRYPTED PASSWORD '{$enc}'";
8061
8062
        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...
8063
    }
8064
8065
    /**
8066
     * Adds a group member.
8067
     *
8068
     * @param $groname The name of the group
8069
     * @param $user    The name of the user to add to the group
8070
     *
8071
     * @return integer 0 if operation was successful
8072
     */
8073
    public function addGroupMember($groname, $user)
8074
    {
8075
        $this->fieldClean($groname);
8076
        $this->fieldClean($user);
8077
8078
        $sql = "ALTER GROUP \"{$groname}\" ADD USER \"{$user}\"";
8079
8080
        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...
8081
    }
8082
8083
    /**
8084
     * Returns all role names which the role belongs to.
8085
     *
8086
     * @param $rolename The role name
8087
     *
8088
     * @return All role names
8089
     */
8090
    public function getMemberOf($rolename)
8091
    {
8092
        $this->clean($rolename);
8093
8094
        $sql = "
8095
			SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M
8096
			WHERE R.oid=M.roleid
8097
				AND member IN (
8098
					SELECT oid FROM pg_catalog.pg_roles
8099
					WHERE rolname='{$rolename}')
8100
			ORDER BY rolname";
8101
8102
        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\All.
Loading history...
8103
    }
8104
8105
    // Administration functions
8106
8107
    /**
8108
     * Returns all role names that are members of a role.
8109
     *
8110
     * @param $rolename The role name
8111
     * @param $admin    (optional) Find only admin members
8112
     *
8113
     * @return All role names
8114
     */
8115
    public function getMembers($rolename, $admin = 'f')
8116
    {
8117
        $this->clean($rolename);
8118
8119
        $sql = "
8120
			SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M
8121
			WHERE R.oid=M.member AND admin_option='{$admin}'
8122
				AND roleid IN (SELECT oid FROM pg_catalog.pg_roles
8123
					WHERE rolname='{$rolename}')
8124
			ORDER BY rolname";
8125
8126
        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\All.
Loading history...
8127
    }
8128
8129
    /**
8130
     * Removes a group member.
8131
     *
8132
     * @param $groname The name of the group
8133
     * @param $user    The name of the user to remove from the group
8134
     *
8135
     * @return integer 0 if operation was successful
8136
     */
8137
    public function dropGroupMember($groname, $user)
8138
    {
8139
        $this->fieldClean($groname);
8140
        $this->fieldClean($user);
8141
8142
        $sql = "ALTER GROUP \"{$groname}\" DROP USER \"{$user}\"";
8143
8144
        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...
8145
    }
8146
8147
    /**
8148
     * Return users in a specific group.
8149
     *
8150
     * @param $groname The name of the group
8151
     *
8152
     * @return All users in the group
8153
     */
8154
    public function getGroup($groname)
8155
    {
8156
        $this->clean($groname);
8157
8158
        $sql = "
8159
			SELECT s.usename FROM pg_catalog.pg_user s, pg_catalog.pg_group g
8160
			WHERE g.groname='{$groname}' AND s.usesysid = ANY (g.grolist)
8161
			ORDER BY s.usename";
8162
8163
        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\All.
Loading history...
8164
    }
8165
8166
    /**
8167
     * Returns all groups in the database cluser.
8168
     *
8169
     * @return All groups
8170
     */
8171
    public function getGroups()
8172
    {
8173
        $sql = 'SELECT groname FROM pg_group ORDER BY groname';
8174
8175
        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\All.
Loading history...
8176
    }
8177
8178
    /**
8179
     * Creates a new group.
8180
     *
8181
     * @param $groname The name of the group
8182
     * @param $users   An array of users to add to the group
8183
     *
8184
     * @return integer 0 if operation was successful
8185
     */
8186
    public function createGroup($groname, $users)
8187
    {
8188
        $this->fieldClean($groname);
8189
8190
        $sql = "CREATE GROUP \"{$groname}\"";
8191
8192
        if (is_array($users) && sizeof($users) > 0) {
8193
            $this->fieldArrayClean($users);
8194
            $sql .= ' WITH USER "' . join('", "', $users) . '"';
8195
        }
8196
8197
        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...
8198
    }
8199
8200
    /**
8201
     * Removes a group.
8202
     *
8203
     * @param $groname The name of the group to drop
8204
     *
8205
     * @return integer 0 if operation was successful
8206
     */
8207
    public function dropGroup($groname)
8208
    {
8209
        $this->fieldClean($groname);
8210
8211
        $sql = "DROP GROUP \"{$groname}\"";
8212
8213
        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...
8214
    }
8215
8216
    /**
8217
     * Grants a privilege to a user, group or public.
8218
     *
8219
     * @param $mode        'GRANT' or 'REVOKE';
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'GRANT' at position 0 could not be parsed: Unknown type name ''GRANT'' at position 0 in 'GRANT'.
Loading history...
8220
     * @param $type        The type of object
8221
     * @param $object      The name of the object
8222
     * @param $public      True to grant to public, false otherwise
8223
     * @param $usernames   the array of usernames to grant privs to
8224
     * @param $groupnames  the array of group names to grant privs to
8225
     * @param $privileges  The array of privileges to grant (eg. ('SELECT', 'ALL PRIVILEGES', etc.) )
8226
     * @param $grantoption True if has grant option, false otherwise
8227
     * @param $cascade     True for cascade revoke, false otherwise
8228
     * @param $table       the column's table if type=column
8229
     *
8230
     * @return integer 0 if operation was successful
8231
     */
8232
    public function setPrivileges(
8233
        $mode,
8234
        $type,
8235
        $object,
8236
        $public,
8237
        $usernames,
8238
        $groupnames,
8239
        $privileges,
8240
        $grantoption,
8241
        $cascade,
8242
        $table
8243
    ) {
8244
        $f_schema = $this->_schema;
8245
        $this->fieldClean($f_schema);
8246
        $this->fieldArrayClean($usernames);
8247
        $this->fieldArrayClean($groupnames);
8248
8249
        // Input checking
8250
        if (!is_array($privileges) || sizeof($privileges) == 0) {
8251
            return -3;
8252
        }
8253
8254
        if (!is_array($usernames) || !is_array($groupnames) ||
8255
            (!$public && sizeof($usernames) == 0 && sizeof($groupnames) == 0)) {
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...
8256
            return -4;
8257
        }
8258
8259
        if ($mode != 'GRANT' && $mode != 'REVOKE') {
8260
            return -5;
8261
        }
8262
8263
        $sql = $mode;
8264
8265
        // Grant option
8266
        if ($this->hasGrantOption() && $mode == 'REVOKE' && $grantoption) {
8267
            $sql .= ' GRANT OPTION FOR';
8268
        }
8269
8270
        if (in_array('ALL PRIVILEGES', $privileges, true)) {
8271
            $sql .= ' ALL PRIVILEGES';
8272
        } else {
8273
            if ($type == 'column') {
8274
                $this->fieldClean($object);
8275
                $sql .= ' ' . join(" (\"{$object}\"), ", $privileges);
8276
            } else {
8277
                $sql .= ' ' . join(', ', $privileges);
8278
            }
8279
        }
8280
8281
        switch ($type) {
8282
            case 'column':
8283
                $sql .= " (\"{$object}\")";
8284
                $object = $table;
8285
            // no break
8286
            case 'table':
8287
            case 'view':
8288
            case 'sequence':
8289
                $this->fieldClean($object);
8290
                $sql .= " ON \"{$f_schema}\".\"{$object}\"";
8291
8292
                break;
8293
            case 'database':
8294
                $this->fieldClean($object);
8295
                $sql .= " ON DATABASE \"{$object}\"";
8296
8297
                break;
8298
            case 'function':
8299
                // Function comes in with $object as function OID
8300
                $fn = $this->getFunction($object);
8301
                $this->fieldClean($fn->fields['proname']);
8302
                $sql .= " ON FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})";
8303
8304
                break;
8305
            case 'language':
8306
                $this->fieldClean($object);
8307
                $sql .= " ON LANGUAGE \"{$object}\"";
8308
8309
                break;
8310
            case 'schema':
8311
                $this->fieldClean($object);
8312
                $sql .= " ON SCHEMA \"{$object}\"";
8313
8314
                break;
8315
            case 'tablespace':
8316
                $this->fieldClean($object);
8317
                $sql .= " ON TABLESPACE \"{$object}\"";
8318
8319
                break;
8320
            default:
8321
                return -1;
8322
        }
8323
8324
        // Dump PUBLIC
8325
        $first = true;
8326
        $sql .= ($mode == 'GRANT') ? ' TO ' : ' FROM ';
8327
        if ($public) {
8328
            $sql .= 'PUBLIC';
8329
            $first = false;
8330
        }
8331
        // Dump users
8332
        foreach ($usernames as $v) {
8333
            if ($first) {
8334
                $sql .= "\"{$v}\"";
8335
                $first = false;
8336
            } else {
8337
                $sql .= ", \"{$v}\"";
8338
            }
8339
        }
8340
        // Dump groups
8341
        foreach ($groupnames as $v) {
8342
            if ($first) {
8343
                $sql .= "GROUP \"{$v}\"";
8344
                $first = false;
8345
            } else {
8346
                $sql .= ", GROUP \"{$v}\"";
8347
            }
8348
        }
8349
8350
        // Grant option
8351
        if ($this->hasGrantOption() && $mode == 'GRANT' && $grantoption) {
8352
            $sql .= ' WITH GRANT OPTION';
8353
        }
8354
8355
        // Cascade revoke
8356
        if ($this->hasGrantOption() && $mode == 'REVOKE' && $cascade) {
8357
            $sql .= ' CASCADE';
8358
        }
8359
8360
        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...
8361
    }
8362
8363
    /**
8364
     * Retrieves information for all tablespaces.
8365
     *
8366
     * @param bool $all Include all tablespaces (necessary when moving objects back to the default space)
8367
     *
8368
     * @return \PHPPgAdmin\ADORecordSet A recordset
8369
     */
8370
    public function getTablespaces($all = false)
8371
    {
8372
        $conf = $this->conf;
8373
8374
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
8375
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
8376
					FROM pg_catalog.pg_tablespace";
8377
8378
        if (!$conf['show_system'] && !$all) {
8379
            $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$';
8380
        }
8381
8382
        $sql .= ' ORDER BY spcname';
8383
8384
        return $this->selectSet($sql);
8385
    }
8386
8387
    // Misc functions
8388
8389
    /**
8390
     * Retrieves a tablespace's information.
8391
     *
8392
     * @param $spcname
8393
     *
8394
     * @return \PHPPgAdmin\ADORecordSet A recordset
8395
     */
8396
    public function getTablespace($spcname)
8397
    {
8398
        $this->clean($spcname);
8399
8400
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
8401
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
8402
					FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'";
8403
8404
        return $this->selectSet($sql);
8405
    }
8406
8407
    /**
8408
     * Creates a tablespace.
8409
     *
8410
     * @param        $spcname  The name of the tablespace to create
8411
     * @param        $spcowner The owner of the tablespace. '' for current
8412
     * @param        $spcloc   The directory in which to create the tablespace
8413
     * @param string $comment
8414
     *
8415
     * @return int 0 success
8416
     */
8417
    public function createTablespace($spcname, $spcowner, $spcloc, $comment = '')
8418
    {
8419
        $this->fieldClean($spcname);
8420
        $this->clean($spcloc);
8421
8422
        $sql = "CREATE TABLESPACE \"{$spcname}\"";
8423
8424
        if ($spcowner != '') {
8425
            $this->fieldClean($spcowner);
8426
            $sql .= " OWNER \"{$spcowner}\"";
8427
        }
8428
8429
        $sql .= " LOCATION '{$spcloc}'";
8430
8431
        $status = $this->execute($sql);
8432
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
8433
            return -1;
8434
        }
8435
8436
        if ($comment != '' && $this->hasSharedComments()) {
8437
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
8438
            if ($status != 0) {
8439
                return -2;
8440
            }
8441
        }
8442
8443
        return 0;
8444
    }
8445
8446
    /**
8447
     * Alters a tablespace.
8448
     *
8449
     * @param        $spcname The name of the tablespace
8450
     * @param        $name    The new name for the tablespace
8451
     * @param        $owner   The new owner for the tablespace
8452
     * @param string $comment
8453
     *
8454
     * @return bool|int 0 success
8455
     */
8456
    public function alterTablespace($spcname, $name, $owner, $comment = '')
8457
    {
8458
        $this->fieldClean($spcname);
8459
        $this->fieldClean($name);
8460
        $this->fieldClean($owner);
8461
8462
        // Begin transaction
8463
        $status = $this->beginTransaction();
8464
        if ($status != 0) {
8465
            return -1;
8466
        }
8467
8468
        // Owner
8469
        $sql    = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\"";
8470
        $status = $this->execute($sql);
8471
        if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
8472
            $this->rollbackTransaction();
8473
8474
            return -2;
8475
        }
8476
8477
        // Rename (only if name has changed)
8478
        if ($name != $spcname) {
8479
            $sql    = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\"";
8480
            $status = $this->execute($sql);
8481
            if ($status != 0) {
8482
                $this->rollbackTransaction();
8483
8484
                return -3;
8485
            }
8486
8487
            $spcname = $name;
8488
        }
8489
8490
        // Set comment if it has changed
8491
        if (trim($comment) != '' && $this->hasSharedComments()) {
8492
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
8493
            if ($status != 0) {
8494
                return -4;
8495
            }
8496
        }
8497
8498
        return $this->endTransaction();
8499
    }
8500
8501
    /**
8502
     * Drops a tablespace.
8503
     *
8504
     * @param $spcname The name of the domain to drop
8505
     *
8506
     * @return integer 0 if operation was successful
8507
     */
8508
    public function dropTablespace($spcname)
8509
    {
8510
        $this->fieldClean($spcname);
8511
8512
        $sql = "DROP TABLESPACE \"{$spcname}\"";
8513
8514
        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...
8515
    }
8516
8517
    /**
8518
     * Analyze a database.
8519
     *
8520
     * @param string $table (optional) The table to analyze
8521
     *
8522
     * @return bool  0 if successful
8523
     */
8524
    public function analyzeDB($table = '')
8525
    {
8526
        if ($table != '') {
8527
            $f_schema = $this->_schema;
8528
            $this->fieldClean($f_schema);
8529
            $this->fieldClean($table);
8530
8531
            $sql = "ANALYZE \"{$f_schema}\".\"{$table}\"";
8532
        } else {
8533
            $sql = 'ANALYZE';
8534
        }
8535
8536
        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...
8537
    }
8538
8539
    /**
8540
     * Vacuums a database.
8541
     *
8542
     * @param string $table   The table to vacuum
8543
     * @param bool    $analyze If true, also does analyze
8544
     * @param bool    $full    If true, selects "full" vacuum
8545
     * @param bool    $freeze  If true, selects aggressive "freezing" of tuples
8546
     *
8547
     * @return bool  0 if successful
8548
     */
8549
    public function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false)
8550
    {
8551
        $sql = 'VACUUM';
8552
        if ($full) {
8553
            $sql .= ' FULL';
8554
        }
8555
8556
        if ($freeze) {
8557
            $sql .= ' FREEZE';
8558
        }
8559
8560
        if ($analyze) {
8561
            $sql .= ' ANALYZE';
8562
        }
8563
8564
        if ($table != '') {
8565
            $f_schema = $this->_schema;
8566
            $this->fieldClean($f_schema);
8567
            $this->fieldClean($table);
8568
            $sql .= " \"{$f_schema}\".\"{$table}\"";
8569
        }
8570
8571
        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...
8572
    }
8573
8574
    /**
8575
     * Returns all autovacuum global configuration.
8576
     *
8577
     * @return array associative array array( param => value, ...)
8578
     */
8579
    public function getAutovacuum()
8580
    {
8581
        $_defaults = $this->selectSet(
8582
            "SELECT name, setting
8583
			FROM pg_catalog.pg_settings
8584
			WHERE
8585
				name = 'autovacuum'
8586
				OR name = 'autovacuum_vacuum_threshold'
8587
				OR name = 'autovacuum_vacuum_scale_factor'
8588
				OR name = 'autovacuum_analyze_threshold'
8589
				OR name = 'autovacuum_analyze_scale_factor'
8590
				OR name = 'autovacuum_vacuum_cost_delay'
8591
				OR name = 'autovacuum_vacuum_cost_limit'
8592
				OR name = 'vacuum_freeze_min_age'
8593
				OR name = 'autovacuum_freeze_max_age'
8594
			"
8595
        );
8596
8597
        $ret = [];
8598
        while (!$_defaults->EOF) {
8599
            $ret[$_defaults->fields['name']] = $_defaults->fields['setting'];
8600
            $_defaults->moveNext();
8601
        }
8602
8603
        return $ret;
8604
    }
8605
8606
    /**
8607
     * Returns all available autovacuum per table information.
8608
     *
8609
     * @param string $table
8610
     * @param bool $vacenabled
8611
     * @param int $vacthreshold
8612
     * @param int $vacscalefactor
8613
     * @param int $anathresold
8614
     * @param int $anascalefactor
8615
     * @param int $vaccostdelay
8616
     * @param int $vaccostlimit
8617
     *
8618
     * @return bool 0 if successful
8619
     */
8620
    public function saveAutovacuum(
8621
        $table,
8622
        $vacenabled,
8623
        $vacthreshold,
8624
        $vacscalefactor,
8625
        $anathresold,
8626
        $anascalefactor,
8627
        $vaccostdelay,
8628
        $vaccostlimit
8629
    ) {
8630
        $f_schema = $this->_schema;
8631
        $this->fieldClean($f_schema);
8632
        $this->fieldClean($table);
8633
8634
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
8635
8636
        if (!empty($vacenabled)) {
8637
            $this->clean($vacenabled);
8638
            $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...
8639
        }
8640
        if (!empty($vacthreshold)) {
8641
            $this->clean($vacthreshold);
8642
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
8643
        }
8644
        if (!empty($vacscalefactor)) {
8645
            $this->clean($vacscalefactor);
8646
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
8647
        }
8648
        if (!empty($anathresold)) {
8649
            $this->clean($anathresold);
8650
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
8651
        }
8652
        if (!empty($anascalefactor)) {
8653
            $this->clean($anascalefactor);
8654
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
8655
        }
8656
        if (!empty($vaccostdelay)) {
8657
            $this->clean($vaccostdelay);
8658
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
8659
        }
8660
        if (!empty($vaccostlimit)) {
8661
            $this->clean($vaccostlimit);
8662
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
8663
        }
8664
8665
        $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...
8666
8667
        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...
8668
    }
8669
8670
    // Type conversion routines
8671
8672
    /**
8673
     * Drops autovacuum config for a table
8674
     *
8675
     * @param string  $table  The table
8676
     *
8677
     * @return bool 0 if successful
8678
     */
8679
    public function dropAutovacuum($table)
8680
    {
8681
        $f_schema = $this->_schema;
8682
        $this->fieldClean($f_schema);
8683
        $this->fieldClean($table);
8684
8685
        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...
8686
            "
8687
			ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
8688
				autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
8689
				autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
8690
			);"
8691
        );
8692
    }
8693
8694
    /**
8695
     * Returns all available process information.
8696
     *
8697
     * @param $database (optional) Find only connections to specified database
8698
     *
8699
     * @return \PHPPgAdmin\ADORecordSet A recordset
8700
     */
8701
    public function getProcesses($database = null)
8702
    {
8703
        if ($database === null) {
8704
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
8705
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
8706
				FROM pg_catalog.pg_stat_activity
8707
				ORDER BY datname, usename, pid";
8708
        } else {
8709
            $this->clean($database);
8710
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
8711
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
8712
				FROM pg_catalog.pg_stat_activity
8713
				WHERE datname='{$database}'
8714
				ORDER BY usename, pid";
8715
        }
8716
8717
        return $this->selectSet($sql);
8718
    }
8719
8720
    // interfaces Statistics collector functions
8721
8722
    /**
8723
     * Returns table locks information in the current database.
8724
     *
8725
     * @return \PHPPgAdmin\ADORecordSet A recordset
8726
     */
8727
    public function getLocks()
8728
    {
8729
        $conf = $this->conf;
8730
8731
        if (!$conf['show_system']) {
8732
            $where = 'AND pn.nspname NOT LIKE $$pg\_%$$';
8733
        } else {
8734
            $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
8735
        }
8736
8737
        $sql = "
8738
			SELECT
8739
				pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction,
8740
				(select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid'
8741
					and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction
8742
			FROM
8743
				pg_catalog.pg_locks pl,
8744
				pg_catalog.pg_class pc,
8745
				pg_catalog.pg_namespace pn
8746
			WHERE
8747
				pl.relation = pc.oid AND pc.relnamespace=pn.oid
8748
			{$where}
8749
			ORDER BY pid,nspname,tablename";
8750
8751
        return $this->selectSet($sql);
8752
    }
8753
8754
    /**
8755
     * Sends a cancel or kill command to a process.
8756
     *
8757
     * @param $pid    The ID of the backend process
8758
     * @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...
8759
     *
8760
     * @return int 0 success
8761
     */
8762
    public function sendSignal($pid, $signal)
8763
    {
8764
        // Clean
8765
        $pid = (int) $pid;
8766
8767
        if ($signal == 'CANCEL') {
8768
            $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val";
8769
        } elseif ($signal == 'KILL') {
8770
            $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val";
8771
        } else {
8772
            return -1;
8773
        }
8774
8775
        // Execute the query
8776
        $val = $this->selectField($sql, 'val');
8777
8778
        if ($val === 'f') {
0 ignored issues
show
introduced by
The condition $val === 'f' is always false.
Loading history...
8779
            return -1;
8780
        }
8781
8782
        if ($val === 't') {
0 ignored issues
show
introduced by
The condition $val === 't' is always false.
Loading history...
8783
            return 0;
8784
        }
8785
8786
        return -1;
8787
    }
8788
8789
    /**
8790
     * Executes an SQL script as a series of SQL statements.  Returns
8791
     * the result of the final step.  This is a very complicated lexer
8792
     * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in
8793
     * the PostgreSQL source code.
8794
     * XXX: It does not handle multibyte languages properly.
8795
     *
8796
     * @param string $name     Entry in $_FILES to use
8797
     * @param function|null $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...
8798
     *
8799
     * @return bool true for general success, false on any failure
8800
     */
8801
    public function executeScript($name, $callback = null)
8802
    {
8803
        // This whole function isn't very encapsulated, but hey...
8804
        $conn = $this->conn->_connectionID;
8805
        if (!is_uploaded_file($_FILES[$name]['tmp_name'])) {
8806
            return false;
8807
        }
8808
8809
        $fd = fopen($_FILES[$name]['tmp_name'], 'rb');
8810
        if (!$fd) {
0 ignored issues
show
introduced by
$fd is of type resource|false, thus it always evaluated to false.
Loading history...
8811
            return false;
8812
        }
8813
8814
        // Build up each SQL statement, they can be multiline
8815
        $query_buf    = null;
8816
        $query_start  = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $query_start is dead and can be removed.
Loading history...
8817
        $in_quote     = 0;
8818
        $in_xcomment  = 0;
8819
        $bslash_count = 0;
8820
        $dol_quote    = null;
8821
        $paren_level  = 0;
8822
        $len          = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $len is dead and can be removed.
Loading history...
8823
        $i            = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $i is dead and can be removed.
Loading history...
8824
        $prevlen      = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $prevlen is dead and can be removed.
Loading history...
8825
        $thislen      = 0;
1 ignored issue
show
Unused Code introduced by
The assignment to $thislen is dead and can be removed.
Loading history...
8826
        $lineno       = 0;
8827
8828
        // Loop over each line in the file
8829
        while (!feof($fd)) {
8830
            $line = fgets($fd);
8831
            ++$lineno;
8832
8833
            // Nothing left on line? Then ignore...
8834
            if (trim($line) == '') {
8835
                continue;
8836
            }
8837
8838
            $len         = strlen($line);
8839
            $query_start = 0;
8840
8841
            /*
8842
             * Parse line, looking for command separators.
8843
             *
8844
             * The current character is at line[i], the prior character at line[i
8845
             * - prevlen], the next character at line[i + thislen].
8846
             */
8847
            $prevlen = 0;
8848
            $thislen = ($len > 0) ? 1 : 0;
8849
8850
            for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) {
8851
                /* was the previous character a backslash? */
8852
                if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') {
8853
                    ++$bslash_count;
8854
                } else {
8855
                    $bslash_count = 0;
8856
                }
8857
8858
                /*
8859
                 * It is important to place the in_* test routines before the
8860
                 * in_* detection routines. i.e. we have to test if we are in
8861
                 * a quote before testing for comments.
8862
                 */
8863
8864
                /* in quote? */
8865
                if ($in_quote !== 0) {
8866
                    /*
8867
                     * end of quote if matching non-backslashed character.
8868
                     * backslashes don't count for double quotes, though.
8869
                     */
8870
                    if (substr($line, $i, 1) == $in_quote &&
8871
                        ($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...
8872
                        $in_quote = 0;
8873
                    }
8874
                } /* in or end of $foo$ type quote? */
8875
                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...
8876
                    if ($dol_quote) {
8877
                        if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) {
8878
                            $this->advance_1($i, $prevlen, $thislen);
8879
                            while (substr($line, $i, 1) != '$') {
8880
                                $this->advance_1($i, $prevlen, $thislen);
8881
                            }
8882
8883
                            $dol_quote = null;
8884
                        }
8885
                    } /* start of extended comment? */
8886
                    else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* start of extended comment? */\n else {\n"
Loading history...
8887
                        if (substr($line, $i, 2) == '/*') {
8888
                            ++$in_xcomment;
8889
                            if ($in_xcomment == 1) {
8890
                                $this->advance_1($i, $prevlen, $thislen);
8891
                            }
8892
                        } /* in or end of extended comment? */
8893
                        else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* in or end of extended comment? */\n else {\n"
Loading history...
8894
                            if ($in_xcomment) {
8895
                                if (substr($line, $i, 2) == '*/' && !--$in_xcomment) {
8896
                                    $this->advance_1($i, $prevlen, $thislen);
8897
                                }
8898
                            } /* start of quote? */
8899
                            else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* start of quote? */\n else {\n"
Loading history...
8900
                                if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
8901
                                    $in_quote = substr($line, $i, 1);
8902
                                } /*
8903
                                 * start of $foo$ type quote?
8904
                                 */
8905
                                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...
8906
                                    if (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
8907
                                        $dol_end   = strpos(substr($line, $i + 1), '$');
8908
                                        $dol_quote = substr($line, $i, $dol_end + 1);
8909
                                        $this->advance_1($i, $prevlen, $thislen);
8910
                                        while (substr($line, $i, 1) != '$') {
8911
                                            $this->advance_1($i, $prevlen, $thislen);
8912
                                        }
8913
                                    } /* single-line comment? truncate line */
8914
                                    else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* single-line comment? truncate line */\n else {\n"
Loading history...
8915
                                        if (substr($line, $i, 2) == '--') {
8916
                                            $line = substr($line, 0, $i); /* remove comment */
8917
                                            break;
8918
                                        } /* count nested parentheses */
8919
8920
                                        if (substr($line, $i, 1) == '(') {
8921
                                            ++$paren_level;
8922
                                        } else {
8923
                                            if (substr($line, $i, 1) == ')' && $paren_level > 0) {
8924
                                                --$paren_level;
8925
                                            } /* semicolon? then send query */
8926
                                            else {
0 ignored issues
show
Coding Style introduced by
Expected "} else \n"; found " /* semicolon? then send query */\n else {\n"
Loading history...
8927
                                                if (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) {
8928
                                                    $subline = substr(substr($line, 0, $i), $query_start);
8929
                                                    /*
8930
                                                     * insert a cosmetic newline, if this is not the first
8931
                                                     * line in the buffer
8932
                                                     */
8933
                                                    if (strlen($query_buf) > 0) {
8934
                                                        $query_buf .= "\n";
8935
                                                    }
8936
8937
                                                    /* append the line to the query buffer */
8938
                                                    $query_buf .= $subline;
8939
                                                    /* is there anything in the query_buf? */
8940
                                                    if (trim($query_buf)) {
8941
                                                        $query_buf .= ';';
8942
8943
                                                        // Execute the query. PHP cannot execute
8944
                                                        // empty queries, unlike libpq
8945
                                                        $res = @pg_query($conn, $query_buf);
8946
8947
                                                        // Call the callback function for display
8948
                                                        if ($callback !== null) {
8949
                                                            $callback($query_buf, $res, $lineno);
8950
                                                        }
8951
8952
                                                        // Check for COPY request
8953
                                                        if (pg_result_status($res) == 4) {
8954
                                                            // 4 == PGSQL_COPY_FROM
8955
                                                            while (!feof($fd)) {
8956
                                                                $copy = fgets($fd, 32768);
8957
                                                                ++$lineno;
8958
                                                                pg_put_line($conn, $copy);
8959
                                                                if ($copy == "\\.\n" || $copy == "\\.\r\n") {
8960
                                                                    pg_end_copy($conn);
8961
8962
                                                                    break;
8963
                                                                }
8964
                                                            }
8965
                                                        }
8966
                                                    }
8967
                                                    $query_buf   = null;
8968
                                                    $query_start = $i + $thislen;
8969
                                                }
8970
8971
                                                /*
8972
                                                 * keyword or identifier?
8973
                                                 * We grab the whole string so that we don't
8974
                                                 * mistakenly see $foo$ inside an identifier as the start
8975
                                                 * of a dollar quote.
8976
                                                 */
8977
                                                // XXX: multibyte here
8978
                                                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...
8979
                                                    if (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) {
8980
                                                        $sub = substr($line, $i, $thislen);
8981
                                                        while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) {
8982
                                                            /* keep going while we still have identifier chars */
8983
                                                            $this->advance_1($i, $prevlen, $thislen);
8984
                                                            $sub = substr($line, $i, $thislen);
8985
                                                        }
8986
                                                        // Since we're now over the next character to be examined, it is necessary
8987
                                                        // to move back one space.
8988
                                                        $i -= $prevlen;
8989
                                                    }
8990
                                                }
8991
                                            }
8992
                                        }
8993
                                    }
8994
                                }
8995
                            }
8996
                        }
8997
                    }
8998
                }
8999
            } // end for
9000
9001
            /* Put the rest of the line in the query buffer. */
9002
            $subline = substr($line, $query_start);
9003
            if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) {
9004
                if (strlen($query_buf) > 0) {
9005
                    $query_buf .= "\n";
9006
                }
9007
9008
                $query_buf .= $subline;
9009
            }
9010
9011
            $line = null;
1 ignored issue
show
Unused Code introduced by
The assignment to $line is dead and can be removed.
Loading history...
9012
        } // end while
9013
9014
        /*
9015
         * Process query at the end of file without a semicolon, so long as
9016
         * it's non-empty.
9017
         */
9018
        if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) {
9019
            // Execute the query
9020
            $res = @pg_query($conn, $query_buf);
9021
9022
            // Call the callback function for display
9023
            if ($callback !== null) {
9024
                $callback($query_buf, $res, $lineno);
9025
            }
9026
9027
            // Check for COPY request
9028
            if (pg_result_status($res) == 4) {
9029
                // 4 == PGSQL_COPY_FROM
9030
                while (!feof($fd)) {
9031
                    $copy = fgets($fd, 32768);
9032
                    ++$lineno;
9033
                    pg_put_line($conn, $copy);
9034
                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
9035
                        pg_end_copy($conn);
9036
9037
                        break;
9038
                    }
9039
                }
9040
            }
9041
        }
9042
9043
        fclose($fd);
9044
9045
        return true;
9046
    }
9047
9048
    /**
9049
     * A private helper method for executeScript that advances the
9050
     * character by 1.  In psql this is careful to take into account
9051
     * multibyte languages, but we don't at the moment, so this function
9052
     * is someone redundant, since it will always advance by 1.
9053
     *
9054
     * @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...
9055
     * @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...
9056
     * @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...
9057
     */
9058
    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...
9059
    {
9060
        $prevlen = $thislen;
9061
        $i += $thislen;
9062
        $thislen = 1;
9063
    }
9064
9065
    /**
9066
     * Private helper method to detect a valid $foo$ quote delimiter at
9067
     * the start of the parameter dquote.
9068
     *
9069
     * @param string $dquote
9070
     *
9071
     * @return true if valid, false otherwise
9072
     */
9073
    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...
9074
    {
9075
        // XXX: support multibyte
9076
        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...
9077
    }
9078
9079
    // Capabilities
9080
9081
    /**
9082
     * Returns a recordset of all columns in a query.  Supports paging.
9083
     *
9084
     * @param string $type       Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier,
9085
     *                    or 'SELECT" if it's a select query
9086
     * @param string $table      The base table of the query.  NULL for no table.
9087
     * @param string $query      The query that is being executed.  NULL for no query.
9088
     * @param string $sortkey    The column number to sort by, or '' or null for no sorting
9089
     * @param string $sortdir    The direction in which to sort the specified column ('asc' or 'desc')
9090
     * @param int $page       The page of the relation to retrieve
9091
     * @param int $page_size  The number of rows per page
9092
     * @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...
9093
     *
9094
     * @return A  recordset on success
9095
     * @return -1 transaction error
0 ignored issues
show
Coding Style introduced by
Only 1 @return tag is allowed in a function comment
Loading history...
9096
     * @return -2 counting error
9097
     * @return -3 page or page_size invalid
9098
     * @return -4 unknown type
9099
     * @return -5 failed setting transaction read only
9100
     */
9101
    public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages)
9102
    {
9103
        // Check that we're not going to divide by zero
9104
        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...
9105
            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...
9106
        }
9107
9108
        // If $type is TABLE, then generate the query
9109
        switch ($type) {
9110
            case 'TABLE':
9111
                if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
9112
                    $orderby = [$sortkey => $sortdir];
9113
                } else {
9114
                    $orderby = [];
9115
                }
9116
9117
                $query = $this->getSelectSQL($table, [], [], [], $orderby);
9118
9119
                break;
9120
            case 'QUERY':
9121
            case 'SELECT':
9122
                // Trim query
9123
                $query = trim($query);
9124
                // Trim off trailing semi-colon if there is one
9125
                if (substr($query, strlen($query) - 1, 1) == ';') {
9126
                    $query = substr($query, 0, strlen($query) - 1);
9127
                }
9128
9129
                break;
9130
            default:
9131
                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...
9132
        }
9133
9134
        // Generate count query
9135
        $count = "SELECT COUNT(*) AS total FROM (${query}) AS sub";
9136
9137
        // Open a transaction
9138
        $status = $this->beginTransaction();
9139
        if ($status != 0) {
9140
            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...
9141
        }
9142
9143
        // If backend supports read only queries, then specify read only mode
9144
        // to avoid side effects from repeating queries that do writes.
9145
        if ($this->hasReadOnlyQueries()) {
9146
            $status = $this->execute('SET TRANSACTION READ ONLY');
9147
            if ($status != 0) {
0 ignored issues
show
introduced by
The condition $status != 0 is always true.
Loading history...
9148
                $this->rollbackTransaction();
9149
9150
                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...
9151
            }
9152
        }
9153
9154
        // Count the number of rows
9155
        $total = $this->browseQueryCount($query, $count);
9156
        if ($total < 0) {
9157
            $this->rollbackTransaction();
9158
9159
            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...
9160
        }
9161
9162
        // Calculate max pages
9163
        $max_pages = ceil($total / $page_size);
9164
9165
        // Check that page is less than or equal to max pages
9166
        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...
9167
            $this->rollbackTransaction();
9168
9169
            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...
9170
        }
9171
9172
        // Set fetch mode to NUM so that duplicate field names are properly returned
9173
        // for non-table queries.  Since the SELECT feature only allows selecting one
9174
        // table, duplicate fields shouldn't appear.
9175
        if ($type == 'QUERY') {
9176
            $this->conn->setFetchMode(ADODB_FETCH_NUM);
9177
        }
9178
9179
        // Figure out ORDER BY.  Sort key is always the column number (based from one)
9180
        // of the column to order by.  Only need to do this for non-TABLE queries
9181
        if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
9182
            $orderby = " ORDER BY {$sortkey}";
9183
            // Add sort order
9184
            if ($sortdir == 'desc') {
9185
                $orderby .= ' DESC';
9186
            } else {
9187
                $orderby .= ' ASC';
9188
            }
9189
        } else {
9190
            $orderby = '';
9191
        }
9192
9193
        // Actually retrieve the rows, with offset and limit
9194
        $rs     = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size);
9195
        $status = $this->endTransaction();
9196
        if ($status != 0) {
9197
            $this->rollbackTransaction();
9198
9199
            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...
9200
        }
9201
9202
        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...
9203
    }
9204
9205
    /**
9206
     * Generates the SQL for the 'select' function.
9207
     *
9208
     * @param $table   The table from which to select
9209
     * @param $show    An array of columns to show.  Empty array means all columns.
9210
     * @param $values  An array mapping columns to values
9211
     * @param $ops     An array of the operators to use
9212
     * @param $orderby (optional) An array of column numbers or names (one based)
9213
     *                 mapped to sort direction (asc or desc or '' or null) to order by
9214
     *
9215
     * @return The SQL query
9216
     */
9217
    public function getSelectSQL($table, $show, $values, $ops, $orderby = [])
9218
    {
9219
        $this->fieldArrayClean($show);
9220
9221
        // If an empty array is passed in, then show all columns
9222
        if (sizeof($show) == 0) {
9223
            if ($this->hasObjectID($table)) {
9224
                $sql = "SELECT \"{$this->id}\", * FROM ";
9225
            } else {
9226
                $sql = 'SELECT * FROM ';
9227
            }
9228
        } else {
9229
            // Add oid column automatically to results for editing purposes
9230
            if (!in_array($this->id, $show, true) && $this->hasObjectID($table)) {
9231
                $sql = "SELECT \"{$this->id}\", \"";
9232
            } else {
9233
                $sql = 'SELECT "';
9234
            }
9235
9236
            $sql .= join('","', $show) . '" FROM ';
9237
        }
9238
9239
        $this->fieldClean($table);
9240
9241
        if (isset($_REQUEST['schema'])) {
9242
            $f_schema = $_REQUEST['schema'];
9243
            $this->fieldClean($f_schema);
9244
            $sql .= "\"{$f_schema}\".";
9245
        }
9246
        $sql .= "\"{$table}\"";
9247
9248
        // If we have values specified, add them to the WHERE clause
9249
        $first = true;
9250
        if (is_array($values) && sizeof($values) > 0) {
9251
            foreach ($values as $k => $v) {
9252
                if ($v != '' || $this->selectOps[$ops[$k]] == 'p') {
9253
                    $this->fieldClean($k);
9254
                    if ($first) {
9255
                        $sql .= ' WHERE ';
9256
                        $first = false;
9257
                    } else {
9258
                        $sql .= ' AND ';
9259
                    }
9260
                    // Different query format depending on operator type
9261
                    switch ($this->selectOps[$ops[$k]]) {
9262
                        case 'i':
9263
                            // Only clean the field for the inline case
9264
                            // this is because (x), subqueries need to
9265
                            // to allow 'a','b' as input.
9266
                            $this->clean($v);
9267
                            $sql .= "\"{$k}\" {$ops[$k]} '{$v}'";
9268
9269
                            break;
9270
                        case 'p':
9271
                            $sql .= "\"{$k}\" {$ops[$k]}";
9272
9273
                            break;
9274
                        case 'x':
9275
                            $sql .= "\"{$k}\" {$ops[$k]} ({$v})";
9276
9277
                            break;
9278
                        case 't':
9279
                            $sql .= "\"{$k}\" {$ops[$k]}('{$v}')";
9280
9281
                            break;
9282
                        default:
9283
                            // Shouldn't happen
9284
                    }
9285
                }
9286
            }
9287
        }
9288
9289
        // ORDER BY
9290
        if (is_array($orderby) && sizeof($orderby) > 0) {
9291
            $sql .= ' ORDER BY ';
9292
            $first = true;
9293
            foreach ($orderby as $k => $v) {
9294
                if ($first) {
9295
                    $first = false;
9296
                } else {
9297
                    $sql .= ', ';
9298
                }
9299
9300
                if (preg_match('/^[0-9]+$/', $k)) {
9301
                    $sql .= $k;
9302
                } else {
9303
                    $this->fieldClean($k);
9304
                    $sql .= '"' . $k . '"';
9305
                }
9306
                if (strtoupper($v) == 'DESC') {
9307
                    $sql .= ' DESC';
9308
                }
9309
            }
9310
        }
9311
9312
        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...
9313
    }
9314
9315
    /**
9316
     * Finds the number of rows that would be returned by a
9317
     * query.
9318
     *
9319
     * @param $query The SQL query
9320
     * @param $count The count query
9321
     *
9322
     * @return The count of rows
9323
     * @return -1  error
0 ignored issues
show
Coding Style introduced by
Only 1 @return tag is allowed in a function comment
Loading history...
9324
     */
9325
    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

9325
    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...
9326
    {
9327
        return $this->selectField($count, 'total');
9328
    }
9329
9330
    /**
9331
     * Returns a recordset of all columns in a table.
9332
     *
9333
     * @param $table The name of a table
9334
     * @param $key   The associative array holding the key to retrieve
9335
     *
9336
     * @return \PHPPgAdmin\ADORecordSet A recordset
9337
     */
9338
    public function browseRow($table, $key)
9339
    {
9340
        $f_schema = $this->_schema;
9341
        $this->fieldClean($f_schema);
9342
        $this->fieldClean($table);
9343
9344
        $sql = "SELECT * FROM \"{$f_schema}\".\"{$table}\"";
9345
        if (is_array($key) && sizeof($key) > 0) {
9346
            $sql .= ' WHERE true';
9347
            foreach ($key as $k => $v) {
9348
                $this->fieldClean($k);
9349
                $this->clean($v);
9350
                $sql .= " AND \"{$k}\"='{$v}'";
9351
            }
9352
        }
9353
9354
        return $this->selectSet($sql);
9355
    }
9356
9357
    /**
9358
     * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false.
9359
     *
9360
     * @param $parameter the parameter
9361
     *
9362
     * @return string
9363
     */
9364
    public function dbBool(&$parameter)
9365
    {
9366
        if ($parameter) {
9367
            $parameter = 't';
9368
        } else {
9369
            $parameter = 'f';
9370
        }
9371
9372
        return $parameter;
9373
    }
9374
9375
    /**
9376
     * Fetches statistics for a database.
9377
     *
9378
     * @param $database The database to fetch stats for
9379
     *
9380
     * @return \PHPPgAdmin\ADORecordSet A recordset
9381
     */
9382
    public function getStatsDatabase($database)
9383
    {
9384
        $this->clean($database);
9385
9386
        $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'";
9387
9388
        return $this->selectSet($sql);
9389
    }
9390
9391
    /**
9392
     * Fetches tuple statistics for a table.
9393
     *
9394
     * @param $table The table to fetch stats for
9395
     *
9396
     * @return \PHPPgAdmin\ADORecordSet A recordset
9397
     */
9398
    public function getStatsTableTuples($table)
9399
    {
9400
        $c_schema = $this->_schema;
9401
        $this->clean($c_schema);
9402
        $this->clean($table);
9403
9404
        $sql = "SELECT * FROM pg_stat_all_tables
9405
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
9406
9407
        return $this->selectSet($sql);
9408
    }
9409
9410
    /**
9411
     * Fetches I/0 statistics for a table.
9412
     *
9413
     * @param $table The table to fetch stats for
9414
     *
9415
     * @return \PHPPgAdmin\ADORecordSet A recordset
9416
     */
9417
    public function getStatsTableIO($table)
9418
    {
9419
        $c_schema = $this->_schema;
9420
        $this->clean($c_schema);
9421
        $this->clean($table);
9422
9423
        $sql = "SELECT * FROM pg_statio_all_tables
9424
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
9425
9426
        return $this->selectSet($sql);
9427
    }
9428
9429
    /**
9430
     * Fetches tuple statistics for all indexes on a table.
9431
     *
9432
     * @param $table The table to fetch index stats for
9433
     *
9434
     * @return \PHPPgAdmin\ADORecordSet A recordset
9435
     */
9436
    public function getStatsIndexTuples($table)
9437
    {
9438
        $c_schema = $this->_schema;
9439
        $this->clean($c_schema);
9440
        $this->clean($table);
9441
9442
        $sql = "SELECT * FROM pg_stat_all_indexes
9443
			WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname";
9444
9445
        return $this->selectSet($sql);
9446
    }
9447
9448
    /**
9449
     * Fetches I/0 statistics for all indexes on a table.
9450
     *
9451
     * @param $table The table to fetch index stats for
9452
     *
9453
     * @return \PHPPgAdmin\ADORecordSet A recordset
9454
     */
9455
    public function getStatsIndexIO($table)
9456
    {
9457
        $c_schema = $this->_schema;
9458
        $this->clean($c_schema);
9459
        $this->clean($table);
9460
9461
        $sql = "SELECT * FROM pg_statio_all_indexes
9462
			WHERE schemaname='{$c_schema}' AND relname='{$table}'
9463
			ORDER BY indexrelname";
9464
9465
        return $this->selectSet($sql);
9466
    }
9467
9468
}
9469