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