Test Failed
Push — master ( 34dd7c...325a03 )
by Felipe
11:17 queued 06:14
created

Postgres::createCompositeType()   F

Complexity

Conditions 21
Paths 739

Size

Total Lines 108
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 108
rs 2.3015
c 0
b 0
f 0
cc 21
eloc 65
nc 739
nop 8

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

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

2817
                    if (strncmp(substr($line, $i), $dol_quote, strlen(/** @scrutinizer ignore-type */ $dol_quote)) == 0) {
Loading history...
Bug introduced by
$dol_quote of type void is incompatible with the type string expected by parameter $str2 of strncmp(). ( Ignorable by Annotation )

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

2817
                    if (strncmp(substr($line, $i), /** @scrutinizer ignore-type */ $dol_quote, strlen($dol_quote)) == 0) {
Loading history...
2818
                        $this->advance_1($i, $prevlen, $thislen);
2819
                        while (substr($line, $i, 1) != '$') {
2820
                            $this->advance_1($i, $prevlen, $thislen);
2821
                        }
2822
2823
                        $dol_quote = null;
2824
                    }
2825
                } elseif (substr($line, $i, 2) == '/*') {
2826
                    $this->prtrace('open_xcomment', $in_xcomment, $line, $i, $prevlen, $thislen);
2827
                    if ($in_xcomment == 0) {
2828
                        ++$in_xcomment;
2829
                        $finishpos = strpos(substr($line, $i, $len), '*/');
2830
                        if ($finishpos === false) {
2831
                            $line = substr($line, 0, $i); /* remove comment */
2832
                            break;
2833
                        }
2834
                        $pre         = substr($line, 0, $i);
2835
                        $post        = substr($line, $i + 2 + $finishpos, $len);
2836
                        $line        = $pre.' '.$post;
2837
                        $in_xcomment = 0;
2838
                        $i           = 0;
2839
                    }
2840
                } elseif ($in_xcomment) {
2841
                    $position = strpos(substr($line, $i, $len), '*/');
2842
                    if ($position === false) {
2843
                        $line = '';
2844
2845
                        break;
2846
                    }
2847
2848
                    $substr = substr($line, $i, 2);
2849
2850
                    if ($substr == '*/' && !--$in_xcomment) {
2851
                        $line = substr($line, $i + 2, $len);
2852
                        $i += 2;
2853
                        $this->advance_1($i, $prevlen, $thislen);
2854
                    }
2855
                    // old logic
2856
                    //  } else if (substr($line, $i, 2) == '/*') {
2857
                    //      if ($in_xcomment == 0) {
2858
                    //          ++$in_xcomment;
2859
                    //          $this->advance_1($i, $prevlen, $thislen);
2860
                    //      }
2861
                    //  } else if ($in_xcomment) {
2862
                    //      $substr = substr($line, $i, 2);
2863
                    //      if ($substr == '*/' && !--$in_xcomment) {
2864
                    //          $this->advance_1($i, $prevlen, $thislen);
2865
                    //      }
2866
                } elseif (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
2867
                    $in_quote = substr($line, $i, 1);
2868
                } elseif (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
2869
                    $dol_end   = strpos(substr($line, $i + 1), '$');
2870
                    $dol_quote = substr($line, $i, $dol_end + 1);
2871
                    $this->advance_1($i, $prevlen, $thislen);
2872
                    while (substr($line, $i, 1) != '$') {
2873
                        $this->advance_1($i, $prevlen, $thislen);
2874
                    }
2875
                } else {
2876
                    if (substr($line, $i, 2) == '--') {
2877
                        $line = substr($line, 0, $i); /* remove comment */
2878
                        break;
2879
                    } /* count nested parentheses */
2880
2881
                    if (substr($line, $i, 1) == '(') {
2882
                        ++$paren_level;
2883
                    } elseif (substr($line, $i, 1) == ')' && $paren_level > 0) {
2884
                        --$paren_level;
2885
                    } elseif (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) {
2886
                        $subline = substr(substr($line, 0, $i), $query_start);
2887
                        /*
2888
                         * insert a cosmetic newline, if this is not the first
2889
                         * line in the buffer
2890
                         */
2891
                        if (strlen($query_buf) > 0) {
2892
                            $query_buf .= "\n";
2893
                        }
2894
2895
                        /* append the line to the query buffer */
2896
                        $query_buf .= $subline;
2897
                        /* is there anything in the query_buf? */
2898
                        if (trim($query_buf)) {
2899
                            $query_buf .= ';';
2900
2901
                            // Execute the query. PHP cannot execute
2902
                            // empty queries, unlike libpq
2903
                            $res = @pg_query($conn, $query_buf);
2904
2905
                            // Call the callback function for display
2906
                            if ($callback !== null) {
2907
                                $callback($query_buf, $res, $lineno);
2908
                            }
2909
2910
                            // Check for COPY request
2911
                            if (pg_result_status($res) == 4) {
2912
                                // 4 == PGSQL_COPY_FROM
2913
                                while (!feof($fd)) {
2914
                                    $copy = fgets($fd, 32768);
2915
                                    ++$lineno;
2916
                                    pg_put_line($conn, $copy);
2917
                                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2918
                                        pg_end_copy($conn);
2919
2920
                                        break;
2921
                                    }
2922
                                }
2923
                            }
2924
                        }
2925
                        $query_buf   = null;
2926
                        $query_start = $i + $thislen;
2927
                    } elseif (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) {
2928
                        $sub = substr($line, $i, $thislen);
2929
                        while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) {
2930
                            /* keep going while we still have identifier chars */
2931
                            $this->advance_1($i, $prevlen, $thislen);
2932
                            $sub = substr($line, $i, $thislen);
2933
                        }
2934
                        // Since we're now over the next character to be examined, it is necessary
2935
                        // to move back one space.
2936
                        $i -= $prevlen;
2937
                    }
2938
                }
2939
            } // end for
2940
2941
            /* Put the rest of the line in the query buffer. */
2942
            $subline = substr($line, $query_start);
2943
2944
            if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) {
2945
                if (strlen($query_buf) > 0) {
2946
                    $query_buf .= "\n";
2947
                }
2948
2949
                $query_buf .= $subline;
2950
            }
2951
2952
            $line = null;
1 ignored issue
show
Unused Code introduced by
The assignment to $line is dead and can be removed.
Loading history...
2953
        } // end while
2954
2955
        /*
2956
         * Process query at the end of file without a semicolon, so long as
2957
         * it's non-empty.
2958
         */
2959
        if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) {
2960
            // Execute the query
2961
            $res = @pg_query($conn, $query_buf);
2962
2963
            // Call the callback function for display
2964
            if ($callback !== null) {
2965
                $callback($query_buf, $res, $lineno);
2966
            }
2967
2968
            // Check for COPY request
2969
            if (pg_result_status($res) == 4) {
2970
                // 4 == PGSQL_COPY_FROM
2971
                while (!feof($fd)) {
2972
                    $copy = fgets($fd, 32768);
2973
                    ++$lineno;
2974
                    pg_put_line($conn, $copy);
2975
                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2976
                        pg_end_copy($conn);
2977
2978
                        break;
2979
                    }
2980
                }
2981
            }
2982
        }
2983
2984
        fclose($fd);
2985
2986
        return true;
2987
    }
2988
2989
    /**
2990
     * A private helper method for executeScript that advances the
2991
     * character by 1.  In psql this is careful to take into account
2992
     * multibyte languages, but we don't at the moment, so this function
2993
     * is someone redundant, since it will always advance by 1.
2994
     *
2995
     * @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...
2996
     * @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...
2997
     * @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...
2998
     */
2999
    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...
3000
    {
3001
        $prevlen = $thislen;
3002
        $i += $thislen;
3003
        $thislen = 1;
3004
    }
3005
3006
    /**
3007
     * Private helper method to detect a valid $foo$ quote delimiter at
3008
     * the start of the parameter dquote.
3009
     *
3010
     * @param string $dquote
3011
     *
3012
     * @return true if valid, false otherwise
3013
     */
3014
    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...
3015
    {
3016
        // XXX: support multibyte
3017
        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...
3018
    }
3019
3020
    // Capabilities
3021
3022
    /**
3023
     * Returns a recordset of all columns in a query.  Supports paging.
3024
     *
3025
     * @param string $type       Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier,
3026
     *                           or 'SELECT" if it's a select query
3027
     * @param string $table      The base table of the query.  NULL for no table.
3028
     * @param string $query      The query that is being executed.  NULL for no query.
3029
     * @param string $sortkey    The column number to sort by, or '' or null for no sorting
3030
     * @param string $sortdir    The direction in which to sort the specified column ('asc' or 'desc')
3031
     * @param int    $page       The page of the relation to retrieve
3032
     * @param int    $page_size  The number of rows per page
3033
     * @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...
3034
     *
3035
     * @return int|\PHPPgAdmin\ADORecordSet A  recordset on success or an int with error code
3036
     *                                      - -1 transaction error
3037
     *                                      - -2 counting error
3038
     *                                      - -3 page or page_size invalid
3039
     *                                      - -4 unknown type
3040
     *                                      - -5 failed setting transaction read only
3041
     */
3042
    public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages)
3043
    {
3044
        // Check that we're not going to divide by zero
3045
        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...
3046
            return -3;
3047
        }
3048
3049
        // If $type is TABLE, then generate the query
3050
        switch ($type) {
3051
            case 'TABLE':
3052
                if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
3053
                    $orderby = [$sortkey => $sortdir];
3054
                } else {
3055
                    $orderby = [];
3056
                }
3057
3058
                $query = $this->getSelectSQL($table, [], [], [], $orderby);
3059
3060
                break;
3061
            case 'QUERY':
3062
            case 'SELECT':
3063
                // Trim query
3064
                $query = trim($query);
3065
                // Trim off trailing semi-colon if there is one
3066
                if (substr($query, strlen($query) - 1, 1) == ';') {
3067
                    $query = substr($query, 0, strlen($query) - 1);
3068
                }
3069
3070
                break;
3071
            default:
3072
                return -4;
3073
        }
3074
3075
        // Generate count query
3076
        $count = "SELECT COUNT(*) AS total FROM (${query}) AS sub";
3077
3078
        // Open a transaction
3079
        $status = $this->beginTransaction();
3080
        if ($status != 0) {
3081
            return -1;
3082
        }
3083
3084
        // If backend supports read only queries, then specify read only mode
3085
        // to avoid side effects from repeating queries that do writes.
3086
        if ($this->hasReadOnlyQueries()) {
3087
            $status = $this->execute('SET TRANSACTION READ ONLY');
3088
            if ($status != 0) {
3089
                $this->rollbackTransaction();
3090
3091
                return -5;
3092
            }
3093
        }
3094
3095
        // Count the number of rows
3096
        $total = $this->browseQueryCount($query, $count);
3097
        if ($total < 0) {
3098
            $this->rollbackTransaction();
3099
3100
            return -2;
3101
        }
3102
3103
        // Calculate max pages
3104
        $max_pages = ceil($total / $page_size);
3105
3106
        // Check that page is less than or equal to max pages
3107
        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...
3108
            $this->rollbackTransaction();
3109
3110
            return -3;
3111
        }
3112
3113
        // Set fetch mode to NUM so that duplicate field names are properly returned
3114
        // for non-table queries.  Since the SELECT feature only allows selecting one
3115
        // table, duplicate fields shouldn't appear.
3116
        if ($type == 'QUERY') {
3117
            $this->conn->setFetchMode(ADODB_FETCH_NUM);
3118
        }
3119
3120
        // Figure out ORDER BY.  Sort key is always the column number (based from one)
3121
        // of the column to order by.  Only need to do this for non-TABLE queries
3122
        if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
3123
            $orderby = " ORDER BY {$sortkey}";
3124
            // Add sort order
3125
            if ($sortdir == 'desc') {
3126
                $orderby .= ' DESC';
3127
            } else {
3128
                $orderby .= ' ASC';
3129
            }
3130
        } else {
3131
            $orderby = '';
3132
        }
3133
3134
        // Actually retrieve the rows, with offset and limit
3135
        $rs     = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET ".($page - 1) * $page_size);
3136
        $status = $this->endTransaction();
3137
        if ($status != 0) {
3138
            $this->rollbackTransaction();
3139
3140
            return -1;
3141
        }
3142
3143
        return $rs;
3144
    }
3145
3146
    /**
3147
     * Generates the SQL for the 'select' function.
3148
     *
3149
     * @param string $table The table from which to select
3150
     * @param $show    An array of columns to show.  Empty array means all columns.
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\An was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
3157
     */
3158
    public function getSelectSQL($table, $show, $values, $ops, $orderby = [])
3159
    {
3160
        $this->fieldArrayClean($show);
3161
3162
        // If an empty array is passed in, then show all columns
3163
        if (sizeof($show) == 0) {
3164
            if ($this->hasObjectID($table)) {
3165
                $sql = "SELECT \"{$this->id}\", * FROM ";
3166
            } else {
3167
                $sql = 'SELECT * FROM ';
3168
            }
3169
        } else {
3170
            // Add oid column automatically to results for editing purposes
3171
            if (!in_array($this->id, $show, true) && $this->hasObjectID($table)) {
3172
                $sql = "SELECT \"{$this->id}\", \"";
3173
            } else {
3174
                $sql = 'SELECT "';
3175
            }
3176
3177
            $sql .= join('","', $show).'" FROM ';
3178
        }
3179
3180
        $this->fieldClean($table);
3181
3182
        if (isset($_REQUEST['schema'])) {
3183
            $f_schema = $_REQUEST['schema'];
3184
            $this->fieldClean($f_schema);
3185
            $sql .= "\"{$f_schema}\".";
3186
        }
3187
        $sql .= "\"{$table}\"";
3188
3189
        // If we have values specified, add them to the WHERE clause
3190
        $first = true;
3191
        if (is_array($values) && sizeof($values) > 0) {
3192
            foreach ($values as $k => $v) {
3193
                if ($v != '' || $this->selectOps[$ops[$k]] == 'p') {
3194
                    $this->fieldClean($k);
3195
                    if ($first) {
3196
                        $sql .= ' WHERE ';
3197
                        $first = false;
3198
                    } else {
3199
                        $sql .= ' AND ';
3200
                    }
3201
                    // Different query format depending on operator type
3202
                    switch ($this->selectOps[$ops[$k]]) {
3203
                        case 'i':
3204
                            // Only clean the field for the inline case
3205
                            // this is because (x), subqueries need to
3206
                            // to allow 'a','b' as input.
3207
                            $this->clean($v);
3208
                            $sql .= "\"{$k}\" {$ops[$k]} '{$v}'";
3209
3210
                            break;
3211
                        case 'p':
3212
                            $sql .= "\"{$k}\" {$ops[$k]}";
3213
3214
                            break;
3215
                        case 'x':
3216
                            $sql .= "\"{$k}\" {$ops[$k]} ({$v})";
3217
3218
                            break;
3219
                        case 't':
3220
                            $sql .= "\"{$k}\" {$ops[$k]}('{$v}')";
3221
3222
                            break;
3223
                        default:
3224
                            // Shouldn't happen
3225
                    }
3226
                }
3227
            }
3228
        }
3229
3230
        // ORDER BY
3231
        if (is_array($orderby) && sizeof($orderby) > 0) {
3232
            $sql .= ' ORDER BY ';
3233
            $first = true;
3234
            foreach ($orderby as $k => $v) {
3235
                if ($first) {
3236
                    $first = false;
3237
                } else {
3238
                    $sql .= ', ';
3239
                }
3240
3241
                if (preg_match('/^[0-9]+$/', $k)) {
3242
                    $sql .= $k;
3243
                } else {
3244
                    $this->fieldClean($k);
3245
                    $sql .= '"'.$k.'"';
3246
                }
3247
                if (strtoupper($v) == 'DESC') {
3248
                    $sql .= ' DESC';
3249
                }
3250
            }
3251
        }
3252
3253
        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...
3254
    }
3255
3256
    /**
3257
     * Finds the number of rows that would be returned by a
3258
     * query.
3259
     *
3260
     * @param $query The SQL query
3261
     * @param $count The count query
3262
     *
3263
     * @return int The count of rows or -1 of no rows are found
3264
     */
3265
    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

3265
    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...
3266
    {
3267
        return $this->selectField($count, 'total');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->selectField($count, 'total') also could return the type string which is incompatible with the documented return type integer.
Loading history...
3268
    }
3269
3270
    /**
3271
     * Returns a recordset of all columns in a table.
3272
     *
3273
     * @param string $table The name of a table
3274
     * @param $key   The associative array holding the key to retrieve
3275
     *
3276
     * @return \PHPPgAdmin\ADORecordSet A recordset
3277
     */
3278
    public function browseRow($table, $key)
3279
    {
3280
        $f_schema = $this->_schema;
3281
        $this->fieldClean($f_schema);
3282
        $this->fieldClean($table);
3283
3284
        $sql = "SELECT * FROM \"{$f_schema}\".\"{$table}\"";
3285
        if (is_array($key) && sizeof($key) > 0) {
3286
            $sql .= ' WHERE true';
3287
            foreach ($key as $k => $v) {
3288
                $this->fieldClean($k);
3289
                $this->clean($v);
3290
                $sql .= " AND \"{$k}\"='{$v}'";
3291
            }
3292
        }
3293
3294
        return $this->selectSet($sql);
3295
    }
3296
3297
    /**
3298
     * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false.
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\the was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

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