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

Postgres::vacuumDB()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 16
nop 4
dl 0
loc 23
rs 8.5906
c 0
b 0
f 0
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