Passed
Push — develop ( c6c0b1...90e9a5 )
by Felipe
10:30 queued 02:43
created

Postgres::createCompositeType()   F

Complexity

Conditions 21
Paths 739

Size

Total Lines 108
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.45
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 null|string $str The string to clean, by reference
406
     *
407
     * @return null|string The cleaned string
408
     */
409
    public function fieldClean(&$str)
410
    {
411
        if (!$str) {
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);
1 ignored issue
show
Bug introduced by
The call to pg_escape_string() has too few arguments starting with data. ( Ignorable by Annotation )

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

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

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

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

Loading history...
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;
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
     * Returns a list of all functions in the database.
1845
     *
1846
     * @param bool        $all  If true, will find all available functions, if false just those in search path
1847
     * @param null|string $type If not null, will find all functions with return value = type
1848
     *
1849
     * @return \PHPPgAdmin\ADORecordSet All functions
1850
     */
1851
    public function getFunctions($all = false, $type = null)
1852
    {
1853
        if ($all) {
1854
            $where    = 'pg_catalog.pg_function_is_visible(p.oid)';
1855
            $distinct = 'DISTINCT ON (p.proname)';
1856
1857
            if ($type) {
1858
                $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') ";
1859
            }
1860
        } else {
1861
            $c_schema = $this->_schema;
1862
            $this->clean($c_schema);
1863
            $where    = "n.nspname = '{$c_schema}'";
1864
            $distinct = '';
1865
        }
1866
1867
        $sql = "
1868
			SELECT
1869
				{$distinct}
1870
				p.oid AS prooid,
1871
				p.proname,
1872
				p.proretset,
1873
				pg_catalog.format_type(p.prorettype, NULL) AS proresult,
1874
				pg_catalog.oidvectortypes(p.proargtypes) AS proarguments,
1875
				pl.lanname AS prolanguage,
1876
				pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment,
1877
				p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
1878
				CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns,
1879
				coalesce(u.usename::text,p.proowner::text) AS proowner
1880
1881
			FROM pg_catalog.pg_proc p
1882
				INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
1883
				INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang
1884
				LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner
1885
			WHERE NOT p.proisagg
1886
				AND {$where}
1887
			ORDER BY p.proname, proresult
1888
			";
1889
1890
        return $this->selectSet($sql);
1891
    }
1892
1893
    /**
1894
     * Creates a trigger.
1895
     *
1896
     * @param string $tgname      The name of the trigger to create
1897
     * @param string $table       The name of the table
1898
     * @param string $tgproc      The function to execute
1899
     * @param string $tgtime      BEFORE or AFTER
1900
     * @param string $tgevent     Event
1901
     * @param string $tgfrequency
1902
     * @param string $tgargs      The function arguments
1903
     *
1904
     * @return int 0 if operation was successful
1905
     */
1906
    public function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs)
1907
    {
1908
        $f_schema = $this->_schema;
1909
        $this->fieldClean($f_schema);
1910
        $this->fieldClean($tgname);
1911
        $this->fieldClean($table);
1912
        $this->fieldClean($tgproc);
1913
1914
        /* No Statement Level Triggers in PostgreSQL (by now) */
1915
        $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime}
1916
				{$tgevent} ON \"{$f_schema}\".\"{$table}\"
1917
				FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})";
1918
1919
        return $this->execute($sql);
1920
    }
1921
1922
    /**
1923
     * Alters a trigger.
1924
     *
1925
     * @param string $table   The name of the table containing the trigger
1926
     * @param string $trigger The name of the trigger to alter
1927
     * @param string $name    The new name for the trigger
1928
     *
1929
     * @return int 0 if operation was successful
1930
     */
1931
    public function alterTrigger($table, $trigger, $name)
1932
    {
1933
        $f_schema = $this->_schema;
1934
        $this->fieldClean($f_schema);
1935
        $this->fieldClean($table);
1936
        $this->fieldClean($trigger);
1937
        $this->fieldClean($name);
1938
1939
        $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_schema}\".\"{$table}\" RENAME TO \"{$name}\"";
1940
1941
        return $this->execute($sql);
1942
    }
1943
1944
    /**
1945
     * Drops a trigger.
1946
     *
1947
     * @param string $tgname  The name of the trigger to drop
1948
     * @param string $table   The table from which to drop the trigger
1949
     * @param bool   $cascade True to cascade drop, false to restrict
1950
     *
1951
     * @return int 0 if operation was successful
1952
     */
1953
    public function dropTrigger($tgname, $table, $cascade)
1954
    {
1955
        $f_schema = $this->_schema;
1956
        $this->fieldClean($f_schema);
1957
        $this->fieldClean($tgname);
1958
        $this->fieldClean($table);
1959
1960
        $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\"";
1961
        if ($cascade) {
1962
            $sql .= ' CASCADE';
1963
        }
1964
1965
        return $this->execute($sql);
1966
    }
1967
1968
    /**
1969
     * Enables a trigger.
1970
     *
1971
     * @param string $tgname The name of the trigger to enable
1972
     * @param string $table  The table in which to enable the trigger
1973
     *
1974
     * @return int 0 if operation was successful
1975
     */
1976
    public function enableTrigger($tgname, $table)
1977
    {
1978
        $f_schema = $this->_schema;
1979
        $this->fieldClean($f_schema);
1980
        $this->fieldClean($tgname);
1981
        $this->fieldClean($table);
1982
1983
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\"";
1984
1985
        return $this->execute($sql);
1986
    }
1987
1988
    /**
1989
     * Disables a trigger.
1990
     *
1991
     * @param string $tgname The name of the trigger to disable
1992
     * @param string $table  The table in which to disable the trigger
1993
     *
1994
     * @return int 0 if operation was successful
1995
     */
1996
    public function disableTrigger($tgname, $table)
1997
    {
1998
        $f_schema = $this->_schema;
1999
        $this->fieldClean($f_schema);
2000
        $this->fieldClean($tgname);
2001
        $this->fieldClean($table);
2002
2003
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\"";
2004
2005
        return $this->execute($sql);
2006
    }
2007
2008
    /**
2009
     * Returns a list of all operators in the database.
2010
     *
2011
     * @return \PHPPgAdmin\ADORecordSet All operators
2012
     */
2013
    public function getOperators()
2014
    {
2015
        $c_schema = $this->_schema;
2016
        $this->clean($c_schema);
2017
        // We stick with the subselects here, as you cannot ORDER BY a regtype
2018
        $sql = "
2019
			SELECT
2020
            	po.oid,	po.oprname,
2021
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname,
2022
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname,
2023
				po.oprresult::pg_catalog.regtype AS resultname,
2024
		        pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment
2025
			FROM
2026
				pg_catalog.pg_operator po
2027
			WHERE
2028
				po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')
2029
			ORDER BY
2030
				po.oprname, oprleftname, oprrightname
2031
		";
2032
2033
        return $this->selectSet($sql);
2034
    }
2035
2036
    /**
2037
     * Drops an operator.
2038
     *
2039
     * @param mixed $operator_oid The OID of the operator to drop
2040
     * @param bool  $cascade      True to cascade drop, false to restrict
2041
     *
2042
     * @return int 0 if operation was successful
2043
     */
2044
    public function dropOperator($operator_oid, $cascade)
2045
    {
2046
        // Function comes in with $object as operator OID
2047
        $opr      = $this->getOperator($operator_oid);
2048
        $f_schema = $this->_schema;
2049
        $this->fieldClean($f_schema);
2050
        $this->fieldClean($opr->fields['oprname']);
2051
2052
        $sql = "DROP OPERATOR \"{$f_schema}\".{$opr->fields['oprname']} (";
2053
        // Quoting or formatting here???
2054
        if ($opr->fields['oprleftname'] !== null) {
2055
            $sql .= $opr->fields['oprleftname'] . ', ';
2056
        } else {
2057
            $sql .= 'NONE, ';
2058
        }
2059
2060
        if ($opr->fields['oprrightname'] !== null) {
2061
            $sql .= $opr->fields['oprrightname'] . ')';
2062
        } else {
2063
            $sql .= 'NONE)';
2064
        }
2065
2066
        if ($cascade) {
2067
            $sql .= ' CASCADE';
2068
        }
2069
2070
        return $this->execute($sql);
2071
    }
2072
2073
    /**
2074
     * Returns all details for a particular operator.
2075
     *
2076
     * @param mixed $operator_oid The oid of the operator
2077
     *
2078
     * @return \PHPPgAdmin\ADORecordSet Function info
2079
     */
2080
    public function getOperator($operator_oid)
2081
    {
2082
        $this->clean($operator_oid);
2083
2084
        $sql = "
2085
			SELECT
2086
            	po.oid, po.oprname,
2087
				oprleft::pg_catalog.regtype AS oprleftname,
2088
				oprright::pg_catalog.regtype AS oprrightname,
2089
				oprresult::pg_catalog.regtype AS resultname,
2090
				po.oprcanhash,
2091
				oprcanmerge,
2092
				oprcom::pg_catalog.regoperator AS oprcom,
2093
				oprnegate::pg_catalog.regoperator AS oprnegate,
2094
				po.oprcode::pg_catalog.regproc AS oprcode,
2095
				po.oprrest::pg_catalog.regproc AS oprrest,
2096
				po.oprjoin::pg_catalog.regproc AS oprjoin
2097
			FROM
2098
				pg_catalog.pg_operator po
2099
			WHERE
2100
				po.oid='{$operator_oid}'
2101
		";
2102
2103
        return $this->selectSet($sql);
2104
    }
2105
2106
    /**
2107
     *  Gets all opclasses.
2108
     *
2109
     * @return \PHPPgAdmin\ADORecordSet A recordset
2110
     */
2111
    public function getOpClasses()
2112
    {
2113
        $c_schema = $this->_schema;
2114
        $this->clean($c_schema);
2115
        $sql = "
2116
			SELECT
2117
				pa.amname, po.opcname,
2118
				po.opcintype::pg_catalog.regtype AS opcintype,
2119
				po.opcdefault,
2120
				pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment
2121
			FROM
2122
				pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn
2123
			WHERE
2124
				po.opcmethod=pa.oid
2125
				AND po.opcnamespace=pn.oid
2126
				AND pn.nspname='{$c_schema}'
2127
			ORDER BY 1,2
2128
			";
2129
2130
        return $this->selectSet($sql);
2131
    }
2132
2133
    /**
2134
     * Gets all languages.
2135
     *
2136
     * @param bool $all True to get all languages, regardless of show_system
2137
     *
2138
     * @return \PHPPgAdmin\ADORecordSet A recordset
2139
     */
2140
    public function getLanguages($all = false)
2141
    {
2142
        $conf = $this->conf;
2143
2144
        if ($conf['show_system'] || $all) {
2145
            $where = '';
2146
        } else {
2147
            $where = 'WHERE lanispl';
2148
        }
2149
2150
        $sql = "
2151
			SELECT
2152
				lanname, lanpltrusted,
2153
				lanplcallfoid::pg_catalog.regproc AS lanplcallf
2154
			FROM
2155
				pg_catalog.pg_language
2156
			{$where}
2157
			ORDER BY lanname
2158
		";
2159
2160
        return $this->selectSet($sql);
2161
    }
2162
2163
    /**
2164
     * Retrieves information for all tablespaces.
2165
     *
2166
     * @param bool $all Include all tablespaces (necessary when moving objects back to the default space)
2167
     *
2168
     * @return \PHPPgAdmin\ADORecordSet A recordset
2169
     */
2170
    public function getTablespaces($all = false)
2171
    {
2172
        $conf = $this->conf;
2173
2174
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2175
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2176
					FROM pg_catalog.pg_tablespace";
2177
2178
        if (!$conf['show_system'] && !$all) {
2179
            $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$';
2180
        }
2181
2182
        $sql .= ' ORDER BY spcname';
2183
2184
        return $this->selectSet($sql);
2185
    }
2186
2187
    // Misc functions
2188
2189
    /**
2190
     * Retrieves a tablespace's information.
2191
     *
2192
     * @param string $spcname
2193
     *
2194
     * @return \PHPPgAdmin\ADORecordSet A recordset
2195
     */
2196
    public function getTablespace($spcname)
2197
    {
2198
        $this->clean($spcname);
2199
2200
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2201
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2202
					FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'";
2203
2204
        return $this->selectSet($sql);
2205
    }
2206
2207
    /**
2208
     * Creates a tablespace.
2209
     *
2210
     * @param string $spcname  The name of the tablespace to create
2211
     * @param string $spcowner The owner of the tablespace. '' for current
2212
     * @param string $spcloc   The directory in which to create the tablespace
2213
     * @param string $comment
2214
     *
2215
     * @return int 0 success
2216
     */
2217
    public function createTablespace($spcname, $spcowner, $spcloc, $comment = '')
2218
    {
2219
        $this->fieldClean($spcname);
2220
        $this->clean($spcloc);
2221
2222
        $sql = "CREATE TABLESPACE \"{$spcname}\"";
2223
2224
        if ($spcowner != '') {
2225
            $this->fieldClean($spcowner);
2226
            $sql .= " OWNER \"{$spcowner}\"";
2227
        }
2228
2229
        $sql .= " LOCATION '{$spcloc}'";
2230
2231
        $status = $this->execute($sql);
2232
        if ($status != 0) {
2233
            return -1;
2234
        }
2235
2236
        if ($comment != '' && $this->hasSharedComments()) {
2237
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2238
            if ($status != 0) {
2239
                return -2;
2240
            }
2241
        }
2242
2243
        return 0;
2244
    }
2245
2246
    /**
2247
     * Alters a tablespace.
2248
     *
2249
     * @param string $spcname The name of the tablespace
2250
     * @param string $name    The new name for the tablespace
2251
     * @param string $owner   The new owner for the tablespace
2252
     * @param string $comment
2253
     *
2254
     * @return bool|int 0 success
2255
     */
2256
    public function alterTablespace($spcname, $name, $owner, $comment = '')
2257
    {
2258
        $this->fieldClean($spcname);
2259
        $this->fieldClean($name);
2260
        $this->fieldClean($owner);
2261
2262
        // Begin transaction
2263
        $status = $this->beginTransaction();
2264
        if ($status != 0) {
2265
            return -1;
2266
        }
2267
2268
        // Owner
2269
        $sql    = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\"";
2270
        $status = $this->execute($sql);
2271
        if ($status != 0) {
2272
            $this->rollbackTransaction();
2273
2274
            return -2;
2275
        }
2276
2277
        // Rename (only if name has changed)
2278
        if ($name != $spcname) {
2279
            $sql    = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\"";
2280
            $status = $this->execute($sql);
2281
            if ($status != 0) {
2282
                $this->rollbackTransaction();
2283
2284
                return -3;
2285
            }
2286
2287
            $spcname = $name;
2288
        }
2289
2290
        // Set comment if it has changed
2291
        if (trim($comment) != '' && $this->hasSharedComments()) {
2292
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2293
            if ($status != 0) {
2294
                return -4;
2295
            }
2296
        }
2297
2298
        return $this->endTransaction();
2299
    }
2300
2301
    /**
2302
     * Drops a tablespace.
2303
     *
2304
     * @param string $spcname The name of the domain to drop
2305
     *
2306
     * @return int 0 if operation was successful
2307
     */
2308
    public function dropTablespace($spcname)
2309
    {
2310
        $this->fieldClean($spcname);
2311
2312
        $sql = "DROP TABLESPACE \"{$spcname}\"";
2313
2314
        return $this->execute($sql);
2315
    }
2316
2317
    /**
2318
     * Analyze a database.
2319
     *
2320
     * @param string $table (optional) The table to analyze
2321
     *
2322
     * @return bool 0 if successful
2323
     */
2324
    public function analyzeDB($table = '')
2325
    {
2326
        if ($table != '') {
2327
            $f_schema = $this->_schema;
2328
            $this->fieldClean($f_schema);
2329
            $this->fieldClean($table);
2330
2331
            $sql = "ANALYZE \"{$f_schema}\".\"{$table}\"";
2332
        } else {
2333
            $sql = 'ANALYZE';
2334
        }
2335
2336
        return $this->execute($sql);
2337
    }
2338
2339
    /**
2340
     * Vacuums a database.
2341
     *
2342
     * @param string $table   The table to vacuum
2343
     * @param bool   $analyze If true, also does analyze
2344
     * @param bool   $full    If true, selects "full" vacuum
2345
     * @param bool   $freeze  If true, selects aggressive "freezing" of tuples
2346
     *
2347
     * @return bool 0 if successful
2348
     */
2349
    public function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false)
2350
    {
2351
        $sql = 'VACUUM';
2352
        if ($full) {
2353
            $sql .= ' FULL';
2354
        }
2355
2356
        if ($freeze) {
2357
            $sql .= ' FREEZE';
2358
        }
2359
2360
        if ($analyze) {
2361
            $sql .= ' ANALYZE';
2362
        }
2363
2364
        if ($table != '') {
2365
            $f_schema = $this->_schema;
2366
            $this->fieldClean($f_schema);
2367
            $this->fieldClean($table);
2368
            $sql .= " \"{$f_schema}\".\"{$table}\"";
2369
        }
2370
2371
        return $this->execute($sql);
2372
    }
2373
2374
    /**
2375
     * Returns all autovacuum global configuration.
2376
     *
2377
     * @return array associative array array( param => value, ...)
2378
     */
2379
    public function getAutovacuum()
2380
    {
2381
        $_defaults = $this->selectSet(
2382
            "SELECT name, setting
2383
			FROM pg_catalog.pg_settings
2384
			WHERE
2385
				name = 'autovacuum'
2386
				OR name = 'autovacuum_vacuum_threshold'
2387
				OR name = 'autovacuum_vacuum_scale_factor'
2388
				OR name = 'autovacuum_analyze_threshold'
2389
				OR name = 'autovacuum_analyze_scale_factor'
2390
				OR name = 'autovacuum_vacuum_cost_delay'
2391
				OR name = 'autovacuum_vacuum_cost_limit'
2392
				OR name = 'vacuum_freeze_min_age'
2393
				OR name = 'autovacuum_freeze_max_age'
2394
			"
2395
        );
2396
2397
        $ret = [];
2398
        while (!$_defaults->EOF) {
2399
            $ret[$_defaults->fields['name']] = $_defaults->fields['setting'];
2400
            $_defaults->moveNext();
2401
        }
2402
2403
        return $ret;
2404
    }
2405
2406
    /**
2407
     * Returns all available autovacuum per table information.
2408
     *
2409
     * @param string $table          table name
2410
     * @param bool   $vacenabled     true if vacuum is enabled
2411
     * @param int    $vacthreshold   vacuum threshold
2412
     * @param int    $vacscalefactor vacuum scalefactor
2413
     * @param int    $anathresold    analyze threshold
2414
     * @param int    $anascalefactor analyze scale factor
2415
     * @param int    $vaccostdelay   vacuum cost delay
2416
     * @param int    $vaccostlimit   vacuum cost limit
2417
     *
2418
     * @return bool 0 if successful
2419
     */
2420
    public function saveAutovacuum(
2421
        $table,
2422
        $vacenabled,
2423
        $vacthreshold,
2424
        $vacscalefactor,
2425
        $anathresold,
2426
        $anascalefactor,
2427
        $vaccostdelay,
2428
        $vaccostlimit
2429
    ) {
2430
        $f_schema = $this->_schema;
2431
        $this->fieldClean($f_schema);
2432
        $this->fieldClean($table);
2433
2434
        $params = [];
2435
2436
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
2437
2438
        if (!empty($vacenabled)) {
2439
            $this->clean($vacenabled);
2440
            $params[] = "autovacuum_enabled='{$vacenabled}'";
2441
        }
2442
        if (!empty($vacthreshold)) {
2443
            $this->clean($vacthreshold);
2444
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
2445
        }
2446
        if (!empty($vacscalefactor)) {
2447
            $this->clean($vacscalefactor);
2448
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
2449
        }
2450
        if (!empty($anathresold)) {
2451
            $this->clean($anathresold);
2452
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
2453
        }
2454
        if (!empty($anascalefactor)) {
2455
            $this->clean($anascalefactor);
2456
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
2457
        }
2458
        if (!empty($vaccostdelay)) {
2459
            $this->clean($vaccostdelay);
2460
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
2461
        }
2462
        if (!empty($vaccostlimit)) {
2463
            $this->clean($vaccostlimit);
2464
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
2465
        }
2466
2467
        $sql = $sql . implode(',', $params) . ');';
2468
2469
        return $this->execute($sql);
2470
    }
2471
2472
    // Type conversion routines
2473
2474
    /**
2475
     * Drops autovacuum config for a table.
2476
     *
2477
     * @param string $table The table
2478
     *
2479
     * @return bool 0 if successful
2480
     */
2481
    public function dropAutovacuum($table)
2482
    {
2483
        $f_schema = $this->_schema;
2484
        $this->fieldClean($f_schema);
2485
        $this->fieldClean($table);
2486
2487
        return $this->execute(
2488
            "
2489
			ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
2490
				autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
2491
				autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
2492
			);"
2493
        );
2494
    }
2495
2496
    /**
2497
     * Returns all available process information.
2498
     *
2499
     * @param null|string $database (optional) Find only connections to specified database
2500
     *
2501
     * @return \PHPPgAdmin\ADORecordSet A recordset
2502
     */
2503
    public function getProcesses($database = null)
2504
    {
2505
        if ($database === null) {
2506
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
2507
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
2508
				FROM pg_catalog.pg_stat_activity
2509
				ORDER BY datname, usename, pid";
2510
        } else {
2511
            $this->clean($database);
2512
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
2513
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
2514
				FROM pg_catalog.pg_stat_activity
2515
				WHERE datname='{$database}'
2516
				ORDER BY usename, pid";
2517
        }
2518
2519
        return $this->selectSet($sql);
2520
    }
2521
2522
    // interfaces Statistics collector functions
2523
2524
    /**
2525
     * Returns table locks information in the current database.
2526
     *
2527
     * @return \PHPPgAdmin\ADORecordSet A recordset
2528
     */
2529
    public function getLocks()
2530
    {
2531
        $conf = $this->conf;
2532
2533
        if (!$conf['show_system']) {
2534
            $where = 'AND pn.nspname NOT LIKE $$pg\_%$$';
2535
        } else {
2536
            $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
2537
        }
2538
2539
        $sql = "
2540
			SELECT
2541
				pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction,
2542
				(select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid'
2543
					and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction
2544
			FROM
2545
				pg_catalog.pg_locks pl,
2546
				pg_catalog.pg_class pc,
2547
				pg_catalog.pg_namespace pn
2548
			WHERE
2549
				pl.relation = pc.oid AND pc.relnamespace=pn.oid
2550
			{$where}
2551
			ORDER BY pid,nspname,tablename";
2552
2553
        return $this->selectSet($sql);
2554
    }
2555
2556
    /**
2557
     * Sends a cancel or kill command to a process.
2558
     *
2559
     * @param int    $pid    The ID of the backend process
2560
     * @param string $signal 'CANCEL' or 'KILL'
2561
     *
2562
     * @return int 0 success
2563
     */
2564
    public function sendSignal($pid, $signal)
2565
    {
2566
        // Clean
2567
        $pid = (int) $pid;
2568
2569
        if ($signal == 'CANCEL') {
2570
            $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val";
2571
        } elseif ($signal == 'KILL') {
2572
            $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val";
2573
        } else {
2574
            return -1;
2575
        }
2576
2577
        // Execute the query
2578
        $val = $this->selectField($sql, 'val');
2579
2580
        if ($val === 'f') {
2581
            return -1;
2582
        }
2583
2584
        if ($val === 't') {
2585
            return 0;
2586
        }
2587
2588
        return -1;
2589
    }
2590
2591
    /**
2592
     * Executes an SQL script as a series of SQL statements.  Returns
2593
     * the result of the final step.  This is a very complicated lexer
2594
     * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in
2595
     * the PostgreSQL source code.
2596
     * XXX: It does not handle multibyte languages properly.
2597
     *
2598
     * @param string        $name     Entry in $_FILES to use
2599
     * @param null|callable $callback (optional) Callback function to call with each query, its result and line number
2600
     *
2601
     * @return bool true for general success, false on any failure
2602
     */
2603
    public function executeScript($name, $callback = null)
2604
    {
2605
        // This whole function isn't very encapsulated, but hey...
2606
        $conn = $this->conn->_connectionID;
2607
        if (!is_uploaded_file($_FILES[$name]['tmp_name'])) {
2608
            return false;
2609
        }
2610
2611
        $fd = fopen($_FILES[$name]['tmp_name'], 'rb');
2612
        if ($fd === false) {
2613
            return false;
2614
        }
2615
2616
        // Build up each SQL statement, they can be multiline
2617
        $query_buf    = null;
2618
        $query_start  = 0;
2619
        $in_quote     = 0;
2620
        $in_xcomment  = 0;
2621
        $bslash_count = 0;
2622
        $dol_quote    = '';
2623
        $paren_level  = 0;
2624
        $len          = 0;
2625
        $i            = 0;
2626
        $prevlen      = 0;
2627
        $thislen      = 0;
2628
        $lineno       = 0;
2629
2630
        // Loop over each line in the file
2631
        while (!feof($fd)) {
2632
            $line = fgets($fd);
2633
            ++$lineno;
2634
2635
            // Nothing left on line? Then ignore...
2636
            if (trim($line) == '') {
2637
                continue;
2638
            }
2639
2640
            $len         = strlen($line);
2641
            $query_start = 0;
2642
2643
            /**
2644
             * Parse line, looking for command separators.
2645
             *
2646
             * The current character is at line[i], the prior character at line[i
2647
             * - prevlen], the next character at line[i + thislen].
2648
             */
2649
            $prevlen = 0;
2650
            $thislen = ($len > 0) ? 1 : 0;
2651
2652
            for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) {
2653
                /* was the previous character a backslash? */
2654
                if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') {
2655
                    $this->prtrace('bslash_count', $bslash_count, $line);
2656
                    ++$bslash_count;
2657
                } else {
2658
                    $bslash_count = 0;
2659
                }
2660
2661
                /*
2662
                 * It is important to place the in_* test routines before the
2663
                 * in_* detection routines. i.e. we have to test if we are in
2664
                 * a quote before testing for comments.
2665
                 */
2666
2667
                /* in quote? */
2668
                if ($in_quote !== 0) {
2669
                    //$this->prtrace('in_quote', $in_quote, $line);
2670
                    /*
2671
                     * end of quote if matching non-backslashed character.
2672
                     * backslashes don't count for double quotes, though.
2673
                     */
2674
                    if (substr($line, $i, 1) == $in_quote &&
2675
                        ($bslash_count % 2 == 0 || $in_quote == '"')
2676
                    ) {
2677
                        $in_quote = 0;
2678
                    }
2679
                } elseif ($dol_quote) {
2680
                    $this->prtrace('dol_quote', $dol_quote, $line);
2681
                    if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) {
2682
                        $this->advance_1($i, $prevlen, $thislen);
2683
                        while (substr($line, $i, 1) != '$') {
2684
                            $this->advance_1($i, $prevlen, $thislen);
2685
                        }
2686
2687
                        $dol_quote = '';
2688
                    }
2689
                } elseif (substr($line, $i, 2) == '/*') {
2690
                    $this->prtrace('open_xcomment', $in_xcomment, $line, $i, $prevlen, $thislen);
2691
                    if ($in_xcomment == 0) {
2692
                        ++$in_xcomment;
2693
                        $finishpos = strpos(substr($line, $i, $len), '*/');
2694
                        if ($finishpos === false) {
2695
                            $line = substr($line, 0, $i); /* remove comment */
2696
                            break;
2697
                        }
2698
                        $pre         = substr($line, 0, $i);
2699
                        $post        = substr($line, $i + 2 + $finishpos, $len);
2700
                        $line        = $pre . ' ' . $post;
2701
                        $in_xcomment = 0;
2702
                        $i           = 0;
2703
                    }
2704
                } elseif ($in_xcomment) {
2705
                    $position = strpos(substr($line, $i, $len), '*/');
2706
                    if ($position === false) {
2707
                        $line = '';
2708
2709
                        break;
2710
                    }
2711
2712
                    $substr = substr($line, $i, 2);
2713
2714
                    if ($substr == '*/' && !--$in_xcomment) {
2715
                        $line = substr($line, $i + 2, $len);
2716
                        $i += 2;
2717
                        $this->advance_1($i, $prevlen, $thislen);
2718
                    }
2719
                    // old logic
2720
                    //  } else if (substr($line, $i, 2) == '/*') {
2721
                    //      if ($in_xcomment == 0) {
2722
                    //          ++$in_xcomment;
2723
                    //          $this->advance_1($i, $prevlen, $thislen);
2724
                    //      }
2725
                    //  } else if ($in_xcomment) {
2726
                    //      $substr = substr($line, $i, 2);
2727
                    //      if ($substr == '*/' && !--$in_xcomment) {
2728
                    //          $this->advance_1($i, $prevlen, $thislen);
2729
                    //      }
2730
                } elseif (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
2731
                    $in_quote = substr($line, $i, 1);
2732
                } elseif (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
2733
                    $dol_end   = strpos(substr($line, $i + 1), '$');
2734
                    $dol_quote = substr($line, $i, $dol_end + 1);
2735
                    $this->advance_1($i, $prevlen, $thislen);
2736
                    while (substr($line, $i, 1) != '$') {
2737
                        $this->advance_1($i, $prevlen, $thislen);
2738
                    }
2739
                } else {
2740
                    if (substr($line, $i, 2) == '--') {
2741
                        $line = substr($line, 0, $i); /* remove comment */
2742
                        break;
2743
                    } /* count nested parentheses */
2744
2745
                    if (substr($line, $i, 1) == '(') {
2746
                        ++$paren_level;
2747
                    } elseif (substr($line, $i, 1) == ')' && $paren_level > 0) {
2748
                        --$paren_level;
2749
                    } elseif (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) {
2750
                        $subline = substr(substr($line, 0, $i), $query_start);
2751
                        /*
2752
                         * insert a cosmetic newline, if this is not the first
2753
                         * line in the buffer
2754
                         */
2755
                        if (strlen($query_buf) > 0) {
2756
                            $query_buf .= "\n";
2757
                        }
2758
2759
                        /* append the line to the query buffer */
2760
                        $query_buf .= $subline;
2761
                        /* is there anything in the query_buf? */
2762
                        if (trim($query_buf)) {
2763
                            $query_buf .= ';';
2764
2765
                            // Execute the query. PHP cannot execute
2766
                            // empty queries, unlike libpq
2767
                            $res = @pg_query($conn, $query_buf);
2768
2769
                            // Call the callback function for display
2770
                            if ($callback !== null) {
2771
                                $callback($query_buf, $res, $lineno);
2772
                            }
2773
2774
                            // Check for COPY request
2775
                            if (pg_result_status($res) == 4) {
2776
                                // 4 == PGSQL_COPY_FROM
2777
                                while (!feof($fd)) {
2778
                                    $copy = fgets($fd, 32768);
2779
                                    ++$lineno;
2780
                                    pg_put_line($conn, $copy);
2781
                                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2782
                                        pg_end_copy($conn);
2783
2784
                                        break;
2785
                                    }
2786
                                }
2787
                            }
2788
                        }
2789
                        $query_buf   = null;
2790
                        $query_start = $i + $thislen;
2791
                    } elseif (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) {
2792
                        $sub = substr($line, $i, $thislen);
2793
                        while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) {
2794
                            /* keep going while we still have identifier chars */
2795
                            $this->advance_1($i, $prevlen, $thislen);
2796
                            $sub = substr($line, $i, $thislen);
2797
                        }
2798
                        // Since we're now over the next character to be examined, it is necessary
2799
                        // to move back one space.
2800
                        $i -= $prevlen;
2801
                    }
2802
                }
2803
            } // end for
2804
2805
            /* Put the rest of the line in the query buffer. */
2806
            $subline = substr($line, $query_start);
2807
2808
            if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) {
2809
                if (strlen($query_buf) > 0) {
2810
                    $query_buf .= "\n";
2811
                }
2812
2813
                $query_buf .= $subline;
2814
            }
2815
2816
            $line = null;
2817
        } // end while
2818
2819
        /*
2820
         * Process query at the end of file without a semicolon, so long as
2821
         * it's non-empty.
2822
         */
2823
        if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) {
2824
            // Execute the query
2825
            $res = @pg_query($conn, $query_buf);
2826
2827
            // Call the callback function for display
2828
            if ($callback !== null) {
2829
                $callback($query_buf, $res, $lineno);
2830
            }
2831
2832
            // Check for COPY request
2833
            if (pg_result_status($res) == 4) {
2834
                // 4 == PGSQL_COPY_FROM
2835
                while (!feof($fd)) {
2836
                    $copy = fgets($fd, 32768);
2837
                    ++$lineno;
2838
                    pg_put_line($conn, $copy);
2839
                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2840
                        pg_end_copy($conn);
2841
2842
                        break;
2843
                    }
2844
                }
2845
            }
2846
        }
2847
2848
        fclose($fd);
2849
2850
        return true;
2851
    }
2852
2853
    /**
2854
     * A private helper method for executeScript that advances the
2855
     * character by 1.  In psql this is careful to take into account
2856
     * multibyte languages, but we don't at the moment, so this function
2857
     * is someone redundant, since it will always advance by 1.
2858
     *
2859
     * @param int $i       The current character position in the line
2860
     * @param int $prevlen Length of previous character (ie. 1)
2861
     * @param int $thislen Length of current character (ie. 1)
2862
     */
2863
    protected function advance_1(&$i, &$prevlen, &$thislen)
2864
    {
2865
        $prevlen = $thislen;
2866
        $i += $thislen;
2867
        $thislen = 1;
2868
    }
2869
2870
    /**
2871
     * Private helper method to detect a valid $foo$ quote delimiter at
2872
     * the start of the parameter dquote.
2873
     *
2874
     * @param string $dquote
2875
     *
2876
     * @return bool true if valid, false otherwise
2877
     */
2878
    protected function valid_dolquote($dquote)
2879
    {
2880
        // XXX: support multibyte
2881
        return preg_match('/^[$][$]/', $dquote) || preg_match('/^[$][_[:alpha:]][_[:alnum:]]*[$]/', $dquote);
2882
    }
2883
2884
    // Capabilities
2885
2886
    /**
2887
     * Returns a recordset of all columns in a query.  Supports paging.
2888
     *
2889
     * @param string   $type      Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier,
2890
     *                            or 'SELECT" if it's a select query
2891
     * @param string   $table     The base table of the query.  NULL for no table.
2892
     * @param string   $query     The query that is being executed.  NULL for no query.
2893
     * @param string   $sortkey   The column number to sort by, or '' or null for no sorting
2894
     * @param string   $sortdir   The direction in which to sort the specified column ('asc' or 'desc')
2895
     * @param null|int $page      The page of the relation to retrieve
2896
     * @param null|int $page_size The number of rows per page
2897
     * @param int      $max_pages (return-by-ref) The max number of pages in the relation
2898
     *
2899
     * @return int|\PHPPgAdmin\ADORecordSet A  recordset on success or an int with error code
2900
     *                                      - -1 transaction error
2901
     *                                      - -2 counting error
2902
     *                                      - -3 page or page_size invalid
2903
     *                                      - -4 unknown type
2904
     *                                      - -5 failed setting transaction read only
2905
     */
2906
    public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages)
2907
    {
2908
        // Check that we're not going to divide by zero
2909
        if (!is_numeric($page_size) || $page_size != (int) $page_size || $page_size <= 0) {
2910
            return -3;
2911
        }
2912
2913
        // If $type is TABLE, then generate the query
2914
        switch ($type) {
2915
            case 'TABLE':
2916
                if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
2917
                    $orderby = [$sortkey => $sortdir];
2918
                } else {
2919
                    $orderby = [];
2920
                }
2921
2922
                $query = $this->getSelectSQL($table, [], [], [], $orderby);
2923
2924
                break;
2925
            case 'QUERY':
2926
            case 'SELECT':
2927
                // Trim query
2928
                $query = trim($query);
2929
                // Trim off trailing semi-colon if there is one
2930
                if (substr($query, strlen($query) - 1, 1) == ';') {
2931
                    $query = substr($query, 0, strlen($query) - 1);
2932
                }
2933
2934
                break;
2935
            default:
2936
                return -4;
2937
        }
2938
2939
        // Generate count query
2940
        $count = "SELECT COUNT(*) AS total FROM (${query}) AS sub";
2941
2942
        // Open a transaction
2943
        $status = $this->beginTransaction();
2944
        if ($status != 0) {
2945
            return -1;
2946
        }
2947
2948
        // If backend supports read only queries, then specify read only mode
2949
        // to avoid side effects from repeating queries that do writes.
2950
        if ($this->hasReadOnlyQueries()) {
2951
            $status = $this->execute('SET TRANSACTION READ ONLY');
2952
            if ($status != 0) {
2953
                $this->rollbackTransaction();
2954
2955
                return -5;
2956
            }
2957
        }
2958
2959
        // Count the number of rows
2960
        $total = $this->browseQueryCount($count);
2961
        if ($total < 0) {
2962
            $this->rollbackTransaction();
2963
2964
            return -2;
2965
        }
2966
2967
        // Calculate max pages
2968
        $max_pages = ceil($total / $page_size);
2969
2970
        // Check that page is less than or equal to max pages
2971
        if (!is_numeric($page) || $page != (int) $page || $page > $max_pages || $page < 1) {
2972
            $this->rollbackTransaction();
2973
2974
            return -3;
2975
        }
2976
2977
        // Set fetch mode to NUM so that duplicate field names are properly returned
2978
        // for non-table queries.  Since the SELECT feature only allows selecting one
2979
        // table, duplicate fields shouldn't appear.
2980
        if ($type == 'QUERY') {
2981
            $this->conn->setFetchMode(ADODB_FETCH_NUM);
2982
        }
2983
2984
        // Figure out ORDER BY.  Sort key is always the column number (based from one)
2985
        // of the column to order by.  Only need to do this for non-TABLE queries
2986
        if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
2987
            $orderby = " ORDER BY {$sortkey}";
2988
            // Add sort order
2989
            if ($sortdir == 'desc') {
2990
                $orderby .= ' DESC';
2991
            } else {
2992
                $orderby .= ' ASC';
2993
            }
2994
        } else {
2995
            $orderby = '';
2996
        }
2997
2998
        // Actually retrieve the rows, with offset and limit
2999
        $rs     = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size);
3000
        $status = $this->endTransaction();
3001
        if ($status != 0) {
3002
            $this->rollbackTransaction();
3003
3004
            return -1;
3005
        }
3006
3007
        return $rs;
3008
    }
3009
3010
    /**
3011
     * Generates the SQL for the 'select' function.
3012
     *
3013
     * @param string $table   The table from which to select
3014
     * @param array  $show    An array of columns to show.  Empty array means all columns.
3015
     * @param array  $values  An array mapping columns to values
3016
     * @param array  $ops     An array of the operators to use
3017
     * @param array  $orderby (optional) An array of column numbers or names (one based)
3018
     *                        mapped to sort direction (asc or desc or '' or null) to order by
3019
     *
3020
     * @return string The SQL query
3021
     */
3022
    public function getSelectSQL($table, $show, $values, $ops, $orderby = [])
3023
    {
3024
        $this->fieldArrayClean($show);
3025
3026
        // If an empty array is passed in, then show all columns
3027
        if (sizeof($show) == 0) {
3028
            if ($this->hasObjectID($table)) {
3029
                $sql = "SELECT \"{$this->id}\", * FROM ";
3030
            } else {
3031
                $sql = 'SELECT * FROM ';
3032
            }
3033
        } else {
3034
            // Add oid column automatically to results for editing purposes
3035
            if (!in_array($this->id, $show, true) && $this->hasObjectID($table)) {
3036
                $sql = "SELECT \"{$this->id}\", \"";
3037
            } else {
3038
                $sql = 'SELECT "';
3039
            }
3040
3041
            $sql .= join('","', $show) . '" FROM ';
3042
        }
3043
3044
        $this->fieldClean($table);
3045
3046
        if (isset($_REQUEST['schema'])) {
3047
            $f_schema = $_REQUEST['schema'];
3048
            $this->fieldClean($f_schema);
3049
            $sql .= "\"{$f_schema}\".";
3050
        }
3051
        $sql .= "\"{$table}\"";
3052
3053
        // If we have values specified, add them to the WHERE clause
3054
        $first = true;
3055
        if (is_array($values) && sizeof($values) > 0) {
3056
            foreach ($values as $k => $v) {
3057
                if ($v != '' || $this->selectOps[$ops[$k]] == 'p') {
3058
                    $this->fieldClean($k);
3059
                    if ($first) {
3060
                        $sql .= ' WHERE ';
3061
                        $first = false;
3062
                    } else {
3063
                        $sql .= ' AND ';
3064
                    }
3065
                    // Different query format depending on operator type
3066
                    switch ($this->selectOps[$ops[$k]]) {
3067
                        case 'i':
3068
                            // Only clean the field for the inline case
3069
                            // this is because (x), subqueries need to
3070
                            // to allow 'a','b' as input.
3071
                            $this->clean($v);
3072
                            $sql .= "\"{$k}\" {$ops[$k]} '{$v}'";
3073
3074
                            break;
3075
                        case 'p':
3076
                            $sql .= "\"{$k}\" {$ops[$k]}";
3077
3078
                            break;
3079
                        case 'x':
3080
                            $sql .= "\"{$k}\" {$ops[$k]} ({$v})";
3081
3082
                            break;
3083
                        case 't':
3084
                            $sql .= "\"{$k}\" {$ops[$k]}('{$v}')";
3085
3086
                            break;
3087
                        default:
3088
                            // Shouldn't happen
3089
                    }
3090
                }
3091
            }
3092
        }
3093
3094
        // ORDER BY
3095
        if (is_array($orderby) && sizeof($orderby) > 0) {
3096
            $sql .= ' ORDER BY ';
3097
            $first = true;
3098
            foreach ($orderby as $k => $v) {
3099
                if ($first) {
3100
                    $first = false;
3101
                } else {
3102
                    $sql .= ', ';
3103
                }
3104
3105
                if (preg_match('/^[0-9]+$/', $k)) {
3106
                    $sql .= $k;
3107
                } else {
3108
                    $this->fieldClean($k);
3109
                    $sql .= '"' . $k . '"';
3110
                }
3111
                if (strtoupper($v) == 'DESC') {
3112
                    $sql .= ' DESC';
3113
                }
3114
            }
3115
        }
3116
3117
        return $sql;
3118
    }
3119
3120
    /**
3121
     * Finds the number of rows that would be returned by a
3122
     * query.
3123
     *
3124
     * @param string $count The count query
3125
     *
3126
     * @return int The count of rows or -1 of no rows are found
3127
     */
3128
    public function browseQueryCount($count)
3129
    {
3130
        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...
3131
    }
3132
3133
    /**
3134
     * Returns a recordset of all columns in a table.
3135
     *
3136
     * @param string $table The name of a table
3137
     * @param array  $key   The associative array holding the key to retrieve
3138
     *
3139
     * @return \PHPPgAdmin\ADORecordSet A recordset
3140
     */
3141
    public function browseRow($table, $key)
3142
    {
3143
        $f_schema = $this->_schema;
3144
        $this->fieldClean($f_schema);
3145
        $this->fieldClean($table);
3146
3147
        $sql = "SELECT * FROM \"{$f_schema}\".\"{$table}\"";
3148
        if (is_array($key) && sizeof($key) > 0) {
3149
            $sql .= ' WHERE true';
3150
            foreach ($key as $k => $v) {
3151
                $this->fieldClean($k);
3152
                $this->clean($v);
3153
                $sql .= " AND \"{$k}\"='{$v}'";
3154
            }
3155
        }
3156
3157
        return $this->selectSet($sql);
3158
    }
3159
3160
    /**
3161
     * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false.
3162
     *
3163
     * @param null|bool|int|string $parameter the parameter
3164
     *
3165
     * @return string the parameter transformed to 't' of 'f'
3166
     */
3167
    public function dbBool(&$parameter)
3168
    {
3169
        if ($parameter) {
3170
            $parameter = 't';
3171
        } else {
3172
            $parameter = 'f';
3173
        }
3174
3175
        return $parameter;
3176
    }
3177
3178
    /**
3179
     * Fetches statistics for a database.
3180
     *
3181
     * @param string $database The database to fetch stats for
3182
     *
3183
     * @return \PHPPgAdmin\ADORecordSet A recordset
3184
     */
3185
    public function getStatsDatabase($database)
3186
    {
3187
        $this->clean($database);
3188
3189
        $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'";
3190
3191
        return $this->selectSet($sql);
3192
    }
3193
3194
    /**
3195
     * Fetches tuple statistics for a table.
3196
     *
3197
     * @param string $table The table to fetch stats for
3198
     *
3199
     * @return \PHPPgAdmin\ADORecordSet A recordset
3200
     */
3201
    public function getStatsTableTuples($table)
3202
    {
3203
        $c_schema = $this->_schema;
3204
        $this->clean($c_schema);
3205
        $this->clean($table);
3206
3207
        $sql = "SELECT * FROM pg_stat_all_tables
3208
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3209
3210
        return $this->selectSet($sql);
3211
    }
3212
3213
    /**
3214
     * Fetches I/0 statistics for a table.
3215
     *
3216
     * @param string $table The table to fetch stats for
3217
     *
3218
     * @return \PHPPgAdmin\ADORecordSet A recordset
3219
     */
3220
    public function getStatsTableIO($table)
3221
    {
3222
        $c_schema = $this->_schema;
3223
        $this->clean($c_schema);
3224
        $this->clean($table);
3225
3226
        $sql = "SELECT * FROM pg_statio_all_tables
3227
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3228
3229
        return $this->selectSet($sql);
3230
    }
3231
3232
    /**
3233
     * Fetches tuple statistics for all indexes on a table.
3234
     *
3235
     * @param string $table The table to fetch index stats for
3236
     *
3237
     * @return \PHPPgAdmin\ADORecordSet A recordset
3238
     */
3239
    public function getStatsIndexTuples($table)
3240
    {
3241
        $c_schema = $this->_schema;
3242
        $this->clean($c_schema);
3243
        $this->clean($table);
3244
3245
        $sql = "SELECT * FROM pg_stat_all_indexes
3246
			WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname";
3247
3248
        return $this->selectSet($sql);
3249
    }
3250
3251
    /**
3252
     * Fetches I/0 statistics for all indexes on a table.
3253
     *
3254
     * @param string $table The table to fetch index stats for
3255
     *
3256
     * @return \PHPPgAdmin\ADORecordSet A recordset
3257
     */
3258
    public function getStatsIndexIO($table)
3259
    {
3260
        $c_schema = $this->_schema;
3261
        $this->clean($c_schema);
3262
        $this->clean($table);
3263
3264
        $sql = "SELECT * FROM pg_statio_all_indexes
3265
			WHERE schemaname='{$c_schema}' AND relname='{$table}'
3266
			ORDER BY indexrelname";
3267
3268
        return $this->selectSet($sql);
3269
    }
3270
}
3271