Passed
Push — master ( 5397c5...a7725b )
by Felipe
05:40
created

Postgres::alterDatabase()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 21
nc 12
nop 4
dl 0
loc 37
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.46
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
        \Kint::dump($sql);
650
651
        // Add advanced objects if show_advanced is set
652
        if ($conf['show_advanced']) {
653
            $sql .= "
654
				UNION ALL
655
				SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL,
656
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
657
					WHERE pt.typnamespace=pn.oid AND typname ILIKE {$term}
658
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
659
					{$where}
660
			 	UNION ALL
661
				SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn
662
					WHERE po.oprnamespace=pn.oid AND oprname ILIKE {$term} {$where}
663
				UNION ALL
664
				SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc,
665
					pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE {$term} {$where}
666
				UNION ALL
667
				SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl
668
					WHERE lanname ILIKE {$term} {$lan_where}
669
				UNION ALL
670
				SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p
671
					LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid
672
					WHERE p.proisagg AND p.proname ILIKE {$term} {$where}
673
				UNION ALL
674
				SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po,
675
					pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid
676
					AND po.opcname ILIKE {$term} {$where}
677
			";
678
        } else {
679
            // Otherwise just add domains
680
            $sql .= "
681
				UNION ALL
682
				SELECT 'DOMAIN', pt.oid, pn.nspname, NULL,
683
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
684
					WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE {$term}
685
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
686
					{$where}
687
			";
688
        }
689
690
        if ($filter != '') {
691
            // We use like to make RULE, CONSTRAINT and COLUMN searches work
692
            $sql .= ") AS sub WHERE type LIKE '{$filter}%' ";
693
        }
694
695
        $sql .= 'ORDER BY type, schemaname, relname, name';
696
697
        return $this->selectSet($sql);
698
    }
699
700
    /**
701
     * Returns all available variable information.
702
     *
703
     * @return \PHPPgAdmin\ADORecordSet A recordset
704
     */
705
    public function getVariables()
706
    {
707
        $sql = 'SHOW ALL';
708
709
        return $this->selectSet($sql);
710
    }
711
712
    // Schema functons
713
714
    /**
715
     * Return all schemas in the current database.
716
     *
717
     * @return \PHPPgAdmin\ADORecordSet All schemas, sorted alphabetically
718
     */
719
    public function getSchemas()
720
    {
721
        $conf = $this->conf;
722
723
        if (!$conf['show_system']) {
724
            $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'";
725
        } else {
726
            $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
727
        }
728
729
        $sql = "
730
			SELECT pn.nspname,
731
                   pu.rolname AS nspowner,
732
				   pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment,
733
                   pg_size_pretty(SUM(pg_total_relation_size(pg_class.oid))) as schema_size
734
			FROM pg_catalog.pg_namespace pn
735
            LEFT JOIN pg_catalog.pg_class  ON relnamespace = pn.oid
736
			LEFT JOIN pg_catalog.pg_roles pu ON (pn.nspowner = pu.oid)
737
			{$where}
738
            GROUP BY pn.nspname, pu.rolname, pg_catalog.obj_description(pn.oid, 'pg_namespace')
739
			ORDER BY nspname";
740
741
        return $this->selectSet($sql);
742
    }
743
744
    /**
745
     * Sets the current working schema.  Will also set Class variable.
746
     *
747
     * @param string $schema The the name of the schema to work in
748
     *
749
     * @return int 0 if operation was successful
750
     */
751
    public function setSchema($schema)
752
    {
753
        // Get the current schema search path, including 'pg_catalog'.
754
        $search_path = $this->getSearchPath();
755
        // Prepend $schema to search path
756
        array_unshift($search_path, $schema);
757
        $status = $this->setSearchPath($search_path);
758
        if ($status == 0) {
759
            $this->_schema = $schema;
760
761
            return 0;
762
        }
763
764
        return $status;
765
    }
766
767
    /**
768
     * Return the current schema search path.
769
     *
770
     * @return array array of schema names
771
     */
772
    public function getSearchPath()
773
    {
774
        $sql = 'SELECT current_schemas(false) AS search_path';
775
776
        return $this->phpArray($this->selectField($sql, 'search_path'));
777
    }
778
779
    /**
780
     * Sets the current schema search path.
781
     *
782
     * @param mixed $paths An array of schemas in required search order
783
     *
784
     * @return int 0 if operation was successful
785
     */
786
    public function setSearchPath($paths)
787
    {
788
        if (!is_array($paths)) {
789
            return -1;
790
        }
791
792
        if (sizeof($paths) == 0) {
793
            return -2;
794
        }
795
        if (sizeof($paths) == 1 && $paths[0] == '') {
796
            // Need to handle empty paths in some cases
797
            $paths[0] = 'pg_catalog';
798
        }
799
800
        // Loop over all the paths to check that none are empty
801
        $temp = [];
802
        foreach ($paths as $schema) {
803
            if ($schema != '') {
804
                $temp[] = $schema;
805
            }
806
        }
807
        $this->fieldArrayClean($temp);
808
809
        $sql = 'SET SEARCH_PATH TO "'.implode('","', $temp).'"';
810
811
        return $this->execute($sql);
812
    }
813
814
    /**
815
     * Creates a new schema.
816
     *
817
     * @param string $schemaname    The name of the schema to create
818
     * @param string $authorization (optional) The username to create the schema for
819
     * @param string $comment       (optional) If omitted, defaults to nothing
820
     *
821
     * @return bool|int 0 success
822
     */
823
    public function createSchema($schemaname, $authorization = '', $comment = '')
824
    {
825
        $this->fieldClean($schemaname);
826
        $this->fieldClean($authorization);
827
828
        $sql = "CREATE SCHEMA \"{$schemaname}\"";
829
        if ($authorization != '') {
830
            $sql .= " AUTHORIZATION \"{$authorization}\"";
831
        }
832
833
        if ($comment != '') {
834
            $status = $this->beginTransaction();
835
            if ($status != 0) {
836
                return -1;
837
            }
838
        }
839
840
        // Create the new schema
841
        $status = $this->execute($sql);
842
        if ($status != 0) {
843
            $this->rollbackTransaction();
844
845
            return -1;
846
        }
847
848
        // Set the comment
849
        if ($comment != '') {
850
            $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
851
            if ($status != 0) {
852
                $this->rollbackTransaction();
853
854
                return -1;
855
            }
856
857
            return $this->endTransaction();
858
        }
859
860
        return 0;
861
    }
862
863
    /**
864
     * Updates a schema.
865
     *
866
     * @param string $schemaname The name of the schema to drop
867
     * @param string $comment    The new comment for this schema
868
     * @param string $name       new name for this schema
869
     * @param string $owner      The new owner for this schema
870
     *
871
     * @return bool|int 0 success
872
     */
873
    public function updateSchema($schemaname, $comment, $name, $owner)
874
    {
875
        $this->fieldClean($schemaname);
876
        $this->fieldClean($name);
877
        $this->fieldClean($owner);
878
879
        $status = $this->beginTransaction();
880
        if ($status != 0) {
881
            $this->rollbackTransaction();
882
883
            return -1;
884
        }
885
886
        $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
887
        if ($status != 0) {
888
            $this->rollbackTransaction();
889
890
            return -1;
891
        }
892
893
        $schema_rs = $this->getSchemaByName($schemaname);
894
        /* Only if the owner change */
895
        if ($schema_rs->fields['ownername'] != $owner) {
896
            $sql    = "ALTER SCHEMA \"{$schemaname}\" OWNER TO \"{$owner}\"";
897
            $status = $this->execute($sql);
898
            if ($status != 0) {
899
                $this->rollbackTransaction();
900
901
                return -1;
902
            }
903
        }
904
905
        // Only if the name has changed
906
        if ($name != $schemaname) {
907
            $sql    = "ALTER SCHEMA \"{$schemaname}\" RENAME TO \"{$name}\"";
908
            $status = $this->execute($sql);
909
            if ($status != 0) {
910
                $this->rollbackTransaction();
911
912
                return -1;
913
            }
914
        }
915
916
        return $this->endTransaction();
917
    }
918
919
    /**
920
     * Return all information relating to a schema.
921
     *
922
     * @param string $schema The name of the schema
923
     *
924
     * @return \PHPPgAdmin\ADORecordSet Schema information
925
     */
926
    public function getSchemaByName($schema)
927
    {
928
        $this->clean($schema);
929
        $sql = "
930
			SELECT nspname, nspowner, r.rolname AS ownername, nspacl,
931
				pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment
932
			FROM pg_catalog.pg_namespace pn
933
				LEFT JOIN pg_roles as r ON pn.nspowner = r.oid
934
			WHERE nspname='{$schema}'";
935
936
        return $this->selectSet($sql);
937
    }
938
939
    // Table functions
940
941
    /**
942
     * Drops a schema.
943
     *
944
     * @param string $schemaname The name of the schema to drop
945
     * @param bool   $cascade    True to cascade drop, false to restrict
946
     *
947
     * @return int 0 if operation was successful
948
     */
949
    public function dropSchema($schemaname, $cascade)
950
    {
951
        $this->fieldClean($schemaname);
952
953
        $sql = "DROP SCHEMA \"{$schemaname}\"";
954
        if ($cascade) {
955
            $sql .= ' CASCADE';
956
        }
957
958
        return $this->execute($sql);
959
    }
960
961
    /**
962
     * Formats a type correctly for display.  Postgres 7.0 had no 'format_type'
963
     * built-in function, and hence we need to do it manually.
964
     *
965
     * @param string $typname The name of the type
966
     * @param string $typmod  The contents of the typmod field
967
     *
968
     * @return bool|string
969
     */
970
    public function formatType($typname, $typmod)
971
    {
972
        // This is a specific constant in the 7.0 source
973
        $varhdrsz = 4;
974
975
        // If the first character is an underscore, it's an array type
976
        $is_array = false;
977
        if (substr($typname, 0, 1) == '_') {
978
            $is_array = true;
979
            $typname  = substr($typname, 1);
980
        }
981
982
        // Show lengths on bpchar and varchar
983
        if ($typname == 'bpchar') {
984
            $len  = $typmod - $varhdrsz;
985
            $temp = 'character';
986
            if ($len > 1) {
987
                $temp .= "({$len})";
988
            }
989
        } elseif ($typname == 'varchar') {
990
            $temp = 'character varying';
991
            if ($typmod != -1) {
992
                $temp .= '('.($typmod - $varhdrsz).')';
993
            }
994
        } elseif ($typname == 'numeric') {
995
            $temp = 'numeric';
996
            if ($typmod != -1) {
997
                $tmp_typmod = $typmod - $varhdrsz;
998
                $precision  = ($tmp_typmod >> 16) & 0xffff;
999
                $scale      = $tmp_typmod & 0xffff;
1000
                $temp .= "({$precision}, {$scale})";
1001
            }
1002
        } else {
1003
            $temp = $typname;
1004
        }
1005
1006
        // Add array qualifier if it's an array
1007
        if ($is_array) {
1008
            $temp .= '[]';
1009
        }
1010
1011
        return $temp;
1012
    }
1013
1014
    /**
1015
     * Given an array of attnums and a relation, returns an array mapping
1016
     * attribute number to attribute name.
1017
     *
1018
     * @param string $table The table to get attributes for
1019
     * @param array  $atts  An array of attribute numbers
1020
     *
1021
     * @return array|int An array mapping attnum to attname or error code
1022
     *                   - -1 $atts must be an array
1023
     *                   - -2 wrong number of attributes found
1024
     */
1025
    public function getAttributeNames($table, $atts)
1026
    {
1027
        $c_schema = $this->_schema;
1028
        $this->clean($c_schema);
1029
        $this->clean($table);
1030
        $this->arrayClean($atts);
1031
1032
        if (!is_array($atts)) {
1033
            return -1;
1034
        }
1035
1036
        if (sizeof($atts) == 0) {
1037
            return [];
1038
        }
1039
1040
        $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
1041
			attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
1042
			relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
1043
			AND attnum IN ('".join("','", $atts)."')";
1044
1045
        $rs = $this->selectSet($sql);
1046
        if ($rs->RecordCount() != sizeof($atts)) {
1047
            return -2;
1048
        }
1049
1050
        $temp = [];
1051
        while (!$rs->EOF) {
1052
            $temp[$rs->fields['attnum']] = $rs->fields['attname'];
1053
            $rs->moveNext();
1054
        }
1055
1056
        return $temp;
1057
    }
1058
1059
    /**
1060
     * Cleans (escapes) an array.
1061
     *
1062
     * @param array $arr The array to clean, by reference
1063
     *
1064
     * @return array The cleaned array
1065
     */
1066
    public function arrayClean(&$arr)
1067
    {
1068
        foreach ($arr as $k => $v) {
1069
            if ($v === null) {
1070
                continue;
1071
            }
1072
1073
            $arr[$k] = pg_escape_string($v);
1074
        }
1075
1076
        return $arr;
1077
    }
1078
1079
    /**
1080
     * Grabs an array of users and their privileges for an object,
1081
     * given its type.
1082
     *
1083
     * @param string      $object The name of the object whose privileges are to be retrieved
1084
     * @param string      $type   The type of the object (eg. database, schema, relation, function or language)
1085
     * @param null|string $table  Optional, column's table if type = column
1086
     *
1087
     * @return array|int Privileges array or error code
1088
     *                   - -1         invalid type
1089
     *                   - -2         object not found
1090
     *                   - -3         unknown privilege type
1091
     */
1092
    public function getPrivileges($object, $type, $table = null)
1093
    {
1094
        $c_schema = $this->_schema;
1095
        $this->clean($c_schema);
1096
        $this->clean($object);
1097
1098
        switch ($type) {
1099
            case 'column':
1100
                $this->clean($table);
1101
                $sql = "
1102
					SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl
1103
					FROM pg_catalog.pg_attribute a
1104
						LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid)
1105
						LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid)
1106
					WHERE n.nspname='{$c_schema}'
1107
						AND c.relname='{$table}'
1108
						AND a.attname='{$object}'";
1109
1110
                break;
1111
            case 'table':
1112
            case 'view':
1113
            case 'sequence':
1114
                $sql = "
1115
					SELECT relacl AS acl FROM pg_catalog.pg_class
1116
					WHERE relname='{$object}'
1117
						AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace
1118
							WHERE nspname='{$c_schema}')";
1119
1120
                break;
1121
            case 'database':
1122
                $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'";
1123
1124
                break;
1125
            case 'function':
1126
                // Since we fetch functions by oid, they are already constrained to
1127
                // the current schema.
1128
                $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'";
1129
1130
                break;
1131
            case 'language':
1132
                $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'";
1133
1134
                break;
1135
            case 'schema':
1136
                $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'";
1137
1138
                break;
1139
            case 'tablespace':
1140
                $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'";
1141
1142
                break;
1143
            default:
1144
                return -1;
1145
        }
1146
1147
        // Fetch the ACL for object
1148
        $acl = $this->selectField($sql, 'acl');
1149
        if ($acl == -1) {
1150
            return -2;
1151
        }
1152
1153
        if ($acl == '' || $acl === null || !(bool) $acl) {
1154
            return [];
1155
        }
1156
1157
        return $this->parseACL($acl);
1158
    }
1159
1160
    /**
1161
     * Internal function used for parsing ACLs.
1162
     *
1163
     * @param string $acl The ACL to parse (of type aclitem[])
1164
     *
1165
     * @return array|int Privileges array or integer with error code
1166
     *
1167
     * @internal bool $in_quotes toggles acl in_quotes attribute
1168
     */
1169
    protected function parseACL($acl)
1170
    {
1171
        // Take off the first and last characters (the braces)
1172
        $acl = substr($acl, 1, strlen($acl) - 2);
1173
1174
        // Pick out individual ACE's by carefully parsing.  This is necessary in order
1175
        // to cope with usernames and stuff that contain commas
1176
        $aces      = [];
1177
        $i         = $j         = 0;
1178
        $in_quotes = false;
1179
        while ($i < strlen($acl)) {
1180
            // If current char is a double quote and it's not escaped, then
1181
            // enter quoted bit
1182
            $char = substr($acl, $i, 1);
1183
            if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) {
1184
                $in_quotes = !$in_quotes;
1185
            } elseif ($char == ',' && !$in_quotes) {
1186
                // Add text so far to the array
1187
                $aces[] = substr($acl, $j, $i - $j);
1188
                $j      = $i + 1;
1189
            }
1190
            ++$i;
1191
        }
1192
        // Add final text to the array
1193
        $aces[] = substr($acl, $j);
1194
1195
        // Create the array to be returned
1196
        $temp = [];
1197
1198
        // For each ACE, generate an entry in $temp
1199
        foreach ($aces as $v) {
1200
            // If the ACE begins with a double quote, strip them off both ends
1201
            // and unescape backslashes and double quotes
1202
            // $unquote = false;
1203
            if (strpos($v, '"') === 0) {
1204
                $v = substr($v, 1, strlen($v) - 2);
1205
                $v = str_replace('\\"', '"', $v);
1206
                $v = str_replace('\\\\', '\\', $v);
1207
            }
1208
1209
            // Figure out type of ACE (public, user or group)
1210
            if (strpos($v, '=') === 0) {
1211
                $atype = 'public';
1212
            } else {
1213
                if ($this->hasRoles()) {
1214
                    $atype = 'role';
1215
                } else {
1216
                    if (strpos($v, 'group ') === 0) {
1217
                        $atype = 'group';
1218
                        // Tear off 'group' prefix
1219
                        $v = substr($v, 6);
1220
                    } else {
1221
                        $atype = 'user';
1222
                    }
1223
                }
1224
            }
1225
1226
            // Break on unquoted equals sign...
1227
            $i         = 0;
1228
            $in_quotes = false;
1229
            $entity    = null;
1230
            $chars     = null;
1231
            while ($i < strlen($v)) {
1232
                // If current char is a double quote and it's not escaped, then
1233
                // enter quoted bit
1234
                $char      = substr($v, $i, 1);
1235
                $next_char = substr($v, $i + 1, 1);
1236
                if ($char == '"' && ($i == 0 || $next_char != '"')) {
1237
                    $in_quotes = !$in_quotes;
1238
                } elseif ($char == '"' && $next_char == '"') {
1239
                    // Skip over escaped double quotes
1240
                    ++$i;
1241
                } elseif ($char == '=' && !$in_quotes) {
1242
                    // Split on current equals sign
1243
                    $entity = substr($v, 0, $i);
1244
                    $chars  = substr($v, $i + 1);
1245
1246
                    break;
1247
                }
1248
                ++$i;
1249
            }
1250
1251
            // Check for quoting on entity name, and unescape if necessary
1252
            if (strpos($entity, '"') === 0) {
1253
                $entity = substr($entity, 1, strlen($entity) - 2);
1254
                $entity = str_replace('""', '"', $entity);
1255
            }
1256
1257
            // New row to be added to $temp
1258
            // (type, grantee, privileges, grantor, grant option?
1259
            $row = [$atype, $entity, [], '', []];
1260
1261
            // Loop over chars and add privs to $row
1262
            for ($i = 0; $i < strlen($chars); ++$i) {
1263
                // Append to row's privs list the string representing
1264
                // the privilege
1265
                $char = substr($chars, $i, 1);
1266
                if ($char == '*') {
1267
                    $row[4][] = $this->privmap[substr($chars, $i - 1, 1)];
1268
                } elseif ($char == '/') {
1269
                    $grantor = substr($chars, $i + 1);
1270
                    // Check for quoting
1271
                    if (strpos($grantor, '"') === 0) {
1272
                        $grantor = substr($grantor, 1, strlen($grantor) - 2);
1273
                        $grantor = str_replace('""', '"', $grantor);
1274
                    }
1275
                    $row[3] = $grantor;
1276
1277
                    break;
1278
                } else {
1279
                    if (!isset($this->privmap[$char])) {
1280
                        return -3;
1281
                    }
1282
1283
                    $row[2][] = $this->privmap[$char];
1284
                }
1285
            }
1286
1287
            // Append row to temp
1288
            $temp[] = $row;
1289
        }
1290
1291
        return $temp;
1292
    }
1293
1294
    // Rule functions
1295
1296
    /**
1297
     * Returns all details for a particular type.
1298
     *
1299
     * @param string $typname The name of the view to retrieve
1300
     *
1301
     * @return \PHPPgAdmin\ADORecordSet type info
1302
     */
1303
    public function getType($typname)
1304
    {
1305
        $this->clean($typname);
1306
1307
        $sql = "SELECT typtype, typbyval, typname, typinput AS typin, typoutput AS typout, typlen, typalign
1308
			FROM pg_type WHERE typname='{$typname}'";
1309
1310
        return $this->selectSet($sql);
1311
    }
1312
1313
    /**
1314
     * Returns a list of all types in the database.
1315
     *
1316
     * @param bool $all        If true, will find all available types, if false just those in search path
1317
     * @param bool $tabletypes If true, will include table types
1318
     * @param bool $domains    If true, will include domains
1319
     *
1320
     * @return \PHPPgAdmin\ADORecordSet A recordset
1321
     */
1322
    public function getTypes($all = false, $tabletypes = false, $domains = false)
1323
    {
1324
        if ($all) {
1325
            $where = '1 = 1';
1326
        } else {
1327
            $c_schema = $this->_schema;
1328
            $this->clean($c_schema);
1329
            $where = "n.nspname = '{$c_schema}'";
1330
        }
1331
        // Never show system table types
1332
        $where2 = "AND c.relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname LIKE 'pg@_%' ESCAPE '@')";
1333
1334
        // Create type filter
1335
        $tqry = "'c'";
1336
        if ($tabletypes) {
1337
            $tqry .= ", 'r', 'v'";
1338
        }
1339
1340
        // Create domain filter
1341
        if (!$domains) {
1342
            $where .= " AND t.typtype != 'd'";
1343
        }
1344
1345
        $sql = "SELECT
1346
				t.typname AS basename,
1347
				pg_catalog.format_type(t.oid, NULL) AS typname,
1348
				pu.usename AS typowner,
1349
				t.typtype,
1350
				pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment
1351
			FROM (pg_catalog.pg_type t
1352
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace)
1353
				LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid
1354
			WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2}))
1355
			AND t.typname !~ '^_'
1356
			AND {$where}
1357
			ORDER BY typname
1358
		";
1359
1360
        return $this->selectSet($sql);
1361
    }
1362
1363
    /**
1364
     * Creates a new type.
1365
     *
1366
     * @param string $typname
1367
     * @param string $typin
1368
     * @param string $typout
1369
     * @param string $typlen
1370
     * @param string $typdef
1371
     * @param string $typelem
1372
     * @param string $typdelim
1373
     * @param string $typbyval
1374
     * @param string $typalign
1375
     * @param string $typstorage
1376
     *
1377
     * @return int 0 if operation was successful
1378
     *
1379
     * @internal param $ ...
1380
     */
1381
    public function createType(
1382
        $typname,
1383
        $typin,
1384
        $typout,
1385
        $typlen,
1386
        $typdef,
1387
        $typelem,
1388
        $typdelim,
1389
        $typbyval,
1390
        $typalign,
1391
        $typstorage
1392
    ) {
1393
        $f_schema = $this->_schema;
1394
        $this->fieldClean($f_schema);
1395
        $this->fieldClean($typname);
1396
        $this->fieldClean($typin);
1397
        $this->fieldClean($typout);
1398
1399
        $sql = "
1400
			CREATE TYPE \"{$f_schema}\".\"{$typname}\" (
1401
				INPUT = \"{$typin}\",
1402
				OUTPUT = \"{$typout}\",
1403
				INTERNALLENGTH = {$typlen}";
1404
        if ($typdef != '') {
1405
            $sql .= ", DEFAULT = {$typdef}";
1406
        }
1407
1408
        if ($typelem != '') {
1409
            $sql .= ", ELEMENT = {$typelem}";
1410
        }
1411
1412
        if ($typdelim != '') {
1413
            $sql .= ", DELIMITER = {$typdelim}";
1414
        }
1415
1416
        if ($typbyval) {
1417
            $sql .= ', PASSEDBYVALUE, ';
1418
        }
1419
1420
        if ($typalign != '') {
1421
            $sql .= ", ALIGNMENT = {$typalign}";
1422
        }
1423
1424
        if ($typstorage != '') {
1425
            $sql .= ", STORAGE = {$typstorage}";
1426
        }
1427
1428
        $sql .= ')';
1429
1430
        return $this->execute($sql);
1431
    }
1432
1433
    /**
1434
     * Drops a type.
1435
     *
1436
     * @param string $typname The name of the type to drop
1437
     * @param bool   $cascade True to cascade drop, false to restrict
1438
     *
1439
     * @return int 0 if operation was successful
1440
     */
1441
    public function dropType($typname, $cascade)
1442
    {
1443
        $f_schema = $this->_schema;
1444
        $this->fieldClean($f_schema);
1445
        $this->fieldClean($typname);
1446
1447
        $sql = "DROP TYPE \"{$f_schema}\".\"{$typname}\"";
1448
        if ($cascade) {
1449
            $sql .= ' CASCADE';
1450
        }
1451
1452
        return $this->execute($sql);
1453
    }
1454
1455
    /**
1456
     * Creates a new enum type in the database.
1457
     *
1458
     * @param string $name       The name of the type
1459
     * @param array  $values     An array of values
1460
     * @param string $typcomment Type comment
1461
     *
1462
     * @return bool|int 0 success
1463
     */
1464
    public function createEnumType($name, $values, $typcomment)
1465
    {
1466
        $f_schema = $this->_schema;
1467
        $this->fieldClean($f_schema);
1468
        $this->fieldClean($name);
1469
1470
        if (empty($values)) {
1471
            return -2;
1472
        }
1473
1474
        $status = $this->beginTransaction();
1475
        if ($status != 0) {
1476
            return -1;
1477
        }
1478
1479
        $values = array_unique($values);
1480
1481
        $nbval = count($values);
1482
1483
        for ($i = 0; $i < $nbval; ++$i) {
1484
            $this->clean($values[$i]);
1485
        }
1486
1487
        $sql = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS ENUM ('";
1488
        $sql .= implode("','", $values);
1489
        $sql .= "')";
1490
1491
        $status = $this->execute($sql);
1492
        if ($status) {
1493
            $this->rollbackTransaction();
1494
1495
            return -1;
1496
        }
1497
1498
        if ($typcomment != '') {
1499
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
1500
            if ($status) {
1501
                $this->rollbackTransaction();
1502
1503
                return -1;
1504
            }
1505
        }
1506
1507
        return $this->endTransaction();
1508
    }
1509
1510
    /**
1511
     * Get defined values for a given enum.
1512
     *
1513
     * @param string $name
1514
     *
1515
     * @return \PHPPgAdmin\ADORecordSet A recordset
1516
     */
1517
    public function getEnumValues($name)
1518
    {
1519
        $this->clean($name);
1520
1521
        $sql = "SELECT enumlabel AS enumval
1522
		FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON (t.oid=e.enumtypid)
1523
		WHERE t.typname = '{$name}' ORDER BY e.oid";
1524
1525
        return $this->selectSet($sql);
1526
    }
1527
1528
    // Operator functions
1529
1530
    /**
1531
     * Creates a new composite type in the database.
1532
     *
1533
     * @param string $name       The name of the type
1534
     * @param int    $fields     The number of fields
1535
     * @param array  $field      An array of field names
1536
     * @param array  $type       An array of field types
1537
     * @param array  $array      An array of '' or '[]' for each type if it's an array or not
1538
     * @param array  $length     An array of field lengths
1539
     * @param array  $colcomment An array of comments
1540
     * @param string $typcomment Type comment
1541
     *
1542
     * @return bool|int 0 success
1543
     */
1544
    public function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment)
1545
    {
1546
        $f_schema = $this->_schema;
1547
        $this->fieldClean($f_schema);
1548
        $this->fieldClean($name);
1549
1550
        $status = $this->beginTransaction();
1551
        if ($status != 0) {
1552
            return -1;
1553
        }
1554
1555
        $found       = false;
1556
        $first       = true;
1557
        $comment_sql = ''; // Accumulate comments for the columns
1558
        $sql         = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS (";
1559
        for ($i = 0; $i < $fields; ++$i) {
1560
            $this->fieldClean($field[$i]);
1561
            $this->clean($type[$i]);
1562
            $this->clean($length[$i]);
1563
            $this->clean($colcomment[$i]);
1564
1565
            // Skip blank columns - for user convenience
1566
            if ($field[$i] == '' || $type[$i] == '') {
1567
                continue;
1568
            }
1569
1570
            // If not the first column, add a comma
1571
            if (!$first) {
1572
                $sql .= ', ';
1573
            } else {
1574
                $first = false;
1575
            }
1576
1577
            switch ($type[$i]) {
1578
                // Have to account for weird placing of length for with/without
1579
                // time zone types
1580
                case 'timestamp with time zone':
1581
                case 'timestamp without time zone':
1582
                    $qual = substr($type[$i], 9);
1583
                    $sql .= "\"{$field[$i]}\" timestamp";
1584
                    if ($length[$i] != '') {
1585
                        $sql .= "({$length[$i]})";
1586
                    }
1587
1588
                    $sql .= $qual;
1589
1590
                    break;
1591
                case 'time with time zone':
1592
                case 'time without time zone':
1593
                    $qual = substr($type[$i], 4);
1594
                    $sql .= "\"{$field[$i]}\" time";
1595
                    if ($length[$i] != '') {
1596
                        $sql .= "({$length[$i]})";
1597
                    }
1598
1599
                    $sql .= $qual;
1600
1601
                    break;
1602
                default:
1603
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
1604
                    if ($length[$i] != '') {
1605
                        $sql .= "({$length[$i]})";
1606
                    }
1607
            }
1608
            // Add array qualifier if necessary
1609
            if ($array[$i] == '[]') {
1610
                $sql .= '[]';
1611
            }
1612
1613
            if ($colcomment[$i] != '') {
1614
                $comment_sql .= "COMMENT ON COLUMN \"{$f_schema}\".\"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
1615
            }
1616
1617
            $found = true;
1618
        }
1619
1620
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
1621
            return -1;
1622
        }
1623
1624
        $sql .= ')';
1625
1626
        $status = $this->execute($sql);
1627
        if ($status) {
1628
            $this->rollbackTransaction();
1629
1630
            return -1;
1631
        }
1632
1633
        if ($typcomment != '') {
1634
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
1635
            if ($status) {
1636
                $this->rollbackTransaction();
1637
1638
                return -1;
1639
            }
1640
        }
1641
1642
        if ($comment_sql != '') {
1643
            $status = $this->execute($comment_sql);
1644
            if ($status) {
1645
                $this->rollbackTransaction();
1646
1647
                return -1;
1648
            }
1649
        }
1650
1651
        return $this->endTransaction();
1652
    }
1653
1654
    /**
1655
     * Returns a list of all casts in the database.
1656
     *
1657
     * @return \PHPPgAdmin\ADORecordSet All casts
1658
     */
1659
    public function getCasts()
1660
    {
1661
        $conf = $this->conf;
1662
1663
        if ($conf['show_system']) {
1664
            $where = '';
1665
        } else {
1666
            $where = '
1667
				AND n1.nspname NOT LIKE $$pg\_%$$
1668
				AND n2.nspname NOT LIKE $$pg\_%$$
1669
				AND n3.nspname NOT LIKE $$pg\_%$$
1670
			';
1671
        }
1672
1673
        $sql = "
1674
			SELECT
1675
				c.castsource::pg_catalog.regtype AS castsource,
1676
				c.casttarget::pg_catalog.regtype AS casttarget,
1677
				CASE WHEN c.castfunc=0 THEN NULL
1678
				ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc,
1679
				c.castcontext,
1680
				obj_description(c.oid, 'pg_cast') as castcomment
1681
			FROM
1682
				(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),
1683
				pg_catalog.pg_type t1,
1684
				pg_catalog.pg_type t2,
1685
				pg_catalog.pg_namespace n1,
1686
				pg_catalog.pg_namespace n2
1687
			WHERE
1688
				c.castsource=t1.oid
1689
				AND c.casttarget=t2.oid
1690
				AND t1.typnamespace=n1.oid
1691
				AND t2.typnamespace=n2.oid
1692
				{$where}
1693
			ORDER BY 1, 2
1694
		";
1695
1696
        return $this->selectSet($sql);
1697
    }
1698
1699
    /**
1700
     * Returns a list of all conversions in the database.
1701
     *
1702
     * @return \PHPPgAdmin\ADORecordSet All conversions
1703
     */
1704
    public function getConversions()
1705
    {
1706
        $c_schema = $this->_schema;
1707
        $this->clean($c_schema);
1708
        $sql = "
1709
			SELECT
1710
			       c.conname,
1711
			       pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding,
1712
			       pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding,
1713
			       c.condefault,
1714
			       pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment
1715
			FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n
1716
			WHERE n.oid = c.connamespace
1717
			      AND n.nspname='{$c_schema}'
1718
			ORDER BY 1;
1719
		";
1720
1721
        return $this->selectSet($sql);
1722
    }
1723
1724
    // Operator Class functions
1725
1726
    /**
1727
     * Edits a rule on a table OR view.
1728
     *
1729
     * @param string $name    The name of the new rule
1730
     * @param string $event   SELECT, INSERT, UPDATE or DELETE
1731
     * @param string $table   Table on which to create the rule
1732
     * @param string $where   Where to execute the rule, '' indicates always
1733
     * @param bool   $instead True if an INSTEAD rule, false otherwise
1734
     * @param string $type    NOTHING for a do nothing rule, SOMETHING to use given action
1735
     * @param string $action  The action to take
1736
     *
1737
     * @return int 0 if operation was successful
1738
     */
1739
    public function setRule($name, $event, $table, $where, $instead, $type, $action)
1740
    {
1741
        return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true);
1742
    }
1743
1744
    // FTS functions
1745
1746
    /**
1747
     * Creates a rule.
1748
     *
1749
     * @param string $name    The name of the new rule
1750
     * @param string $event   SELECT, INSERT, UPDATE or DELETE
1751
     * @param string $table   Table on which to create the rule
1752
     * @param string $where   When to execute the rule, '' indicates always
1753
     * @param bool   $instead True if an INSTEAD rule, false otherwise
1754
     * @param string $type    NOTHING for a do nothing rule, SOMETHING to use given action
1755
     * @param string $action  The action to take
1756
     * @param bool   $replace (optional) True to replace existing rule, false
1757
     *                        otherwise
1758
     *
1759
     * @return int 0 if operation was successful
1760
     */
1761
    public function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false)
1762
    {
1763
        $f_schema = $this->_schema;
1764
        $this->fieldClean($f_schema);
1765
        $this->fieldClean($name);
1766
        $this->fieldClean($table);
1767
        if (!in_array($event, $this->rule_events, true)) {
1768
            return -1;
1769
        }
1770
1771
        $sql = 'CREATE';
1772
        if ($replace) {
1773
            $sql .= ' OR REPLACE';
1774
        }
1775
1776
        $sql .= " RULE \"{$name}\" AS ON {$event} TO \"{$f_schema}\".\"{$table}\"";
1777
        // Can't escape WHERE clause
1778
        if ($where != '') {
1779
            $sql .= " WHERE {$where}";
1780
        }
1781
1782
        $sql .= ' DO';
1783
        if ($instead) {
1784
            $sql .= ' INSTEAD';
1785
        }
1786
1787
        if ($type == 'NOTHING') {
1788
            $sql .= ' NOTHING';
1789
        } else {
1790
            $sql .= " ({$action})";
1791
        }
1792
1793
        return $this->execute($sql);
1794
    }
1795
1796
    /**
1797
     * Removes a rule from a table OR view.
1798
     *
1799
     * @param string $rule     The rule to drop
1800
     * @param string $relation The relation from which to drop
1801
     * @param string $cascade  True to cascade drop, false to restrict
1802
     *
1803
     * @return int 0 if operation was successful
1804
     */
1805
    public function dropRule($rule, $relation, $cascade)
1806
    {
1807
        $f_schema = $this->_schema;
1808
        $this->fieldClean($f_schema);
1809
        $this->fieldClean($rule);
1810
        $this->fieldClean($relation);
1811
1812
        $sql = "DROP RULE \"{$rule}\" ON \"{$f_schema}\".\"{$relation}\"";
1813
        if ($cascade) {
1814
            $sql .= ' CASCADE';
1815
        }
1816
1817
        return $this->execute($sql);
1818
    }
1819
1820
    /**
1821
     * Grabs a single trigger.
1822
     *
1823
     * @param string $table   The name of a table whose triggers to retrieve
1824
     * @param string $trigger The name of the trigger to retrieve
1825
     *
1826
     * @return \PHPPgAdmin\ADORecordSet A recordset
1827
     */
1828
    public function getTrigger($table, $trigger)
1829
    {
1830
        $c_schema = $this->_schema;
1831
        $this->clean($c_schema);
1832
        $this->clean($table);
1833
        $this->clean($trigger);
1834
1835
        $sql = "
1836
            SELECT * FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c
1837
            WHERE t.tgrelid=c.oid AND c.relname='{$table}' AND t.tgname='{$trigger}'
1838
                AND c.relnamespace=(
1839
                    SELECT oid FROM pg_catalog.pg_namespace
1840
                    WHERE nspname='{$c_schema}')";
1841
1842
        return $this->selectSet($sql);
1843
    }
1844
1845
    /**
1846
     * Creates a trigger.
1847
     *
1848
     * @param string $tgname      The name of the trigger to create
1849
     * @param string $table       The name of the table
1850
     * @param string $tgproc      The function to execute
1851
     * @param string $tgtime      BEFORE or AFTER
1852
     * @param string $tgevent     Event
1853
     * @param string $tgfrequency
1854
     * @param string $tgargs      The function arguments
1855
     *
1856
     * @return int 0 if operation was successful
1857
     */
1858
    public function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs)
1859
    {
1860
        $f_schema = $this->_schema;
1861
        $this->fieldClean($f_schema);
1862
        $this->fieldClean($tgname);
1863
        $this->fieldClean($table);
1864
        $this->fieldClean($tgproc);
1865
1866
        /* No Statement Level Triggers in PostgreSQL (by now) */
1867
        $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime}
1868
				{$tgevent} ON \"{$f_schema}\".\"{$table}\"
1869
				FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})";
1870
1871
        return $this->execute($sql);
1872
    }
1873
1874
    /**
1875
     * Alters a trigger.
1876
     *
1877
     * @param string $table   The name of the table containing the trigger
1878
     * @param string $trigger The name of the trigger to alter
1879
     * @param string $name    The new name for the trigger
1880
     *
1881
     * @return int 0 if operation was successful
1882
     */
1883
    public function alterTrigger($table, $trigger, $name)
1884
    {
1885
        $f_schema = $this->_schema;
1886
        $this->fieldClean($f_schema);
1887
        $this->fieldClean($table);
1888
        $this->fieldClean($trigger);
1889
        $this->fieldClean($name);
1890
1891
        $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_schema}\".\"{$table}\" RENAME TO \"{$name}\"";
1892
1893
        return $this->execute($sql);
1894
    }
1895
1896
    /**
1897
     * Drops a trigger.
1898
     *
1899
     * @param string $tgname  The name of the trigger to drop
1900
     * @param string $table   The table from which to drop the trigger
1901
     * @param bool   $cascade True to cascade drop, false to restrict
1902
     *
1903
     * @return int 0 if operation was successful
1904
     */
1905
    public function dropTrigger($tgname, $table, $cascade)
1906
    {
1907
        $f_schema = $this->_schema;
1908
        $this->fieldClean($f_schema);
1909
        $this->fieldClean($tgname);
1910
        $this->fieldClean($table);
1911
1912
        $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\"";
1913
        if ($cascade) {
1914
            $sql .= ' CASCADE';
1915
        }
1916
1917
        return $this->execute($sql);
1918
    }
1919
1920
    /**
1921
     * Enables a trigger.
1922
     *
1923
     * @param string $tgname The name of the trigger to enable
1924
     * @param string $table  The table in which to enable the trigger
1925
     *
1926
     * @return int 0 if operation was successful
1927
     */
1928
    public function enableTrigger($tgname, $table)
1929
    {
1930
        $f_schema = $this->_schema;
1931
        $this->fieldClean($f_schema);
1932
        $this->fieldClean($tgname);
1933
        $this->fieldClean($table);
1934
1935
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\"";
1936
1937
        return $this->execute($sql);
1938
    }
1939
1940
    /**
1941
     * Disables a trigger.
1942
     *
1943
     * @param string $tgname The name of the trigger to disable
1944
     * @param string $table  The table in which to disable the trigger
1945
     *
1946
     * @return int 0 if operation was successful
1947
     */
1948
    public function disableTrigger($tgname, $table)
1949
    {
1950
        $f_schema = $this->_schema;
1951
        $this->fieldClean($f_schema);
1952
        $this->fieldClean($tgname);
1953
        $this->fieldClean($table);
1954
1955
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\"";
1956
1957
        return $this->execute($sql);
1958
    }
1959
1960
    /**
1961
     * Returns a list of all operators in the database.
1962
     *
1963
     * @return \PHPPgAdmin\ADORecordSet All operators
1964
     */
1965
    public function getOperators()
1966
    {
1967
        $c_schema = $this->_schema;
1968
        $this->clean($c_schema);
1969
        // We stick with the subselects here, as you cannot ORDER BY a regtype
1970
        $sql = "
1971
			SELECT
1972
            	po.oid,	po.oprname,
1973
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname,
1974
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname,
1975
				po.oprresult::pg_catalog.regtype AS resultname,
1976
		        pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment
1977
			FROM
1978
				pg_catalog.pg_operator po
1979
			WHERE
1980
				po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')
1981
			ORDER BY
1982
				po.oprname, oprleftname, oprrightname
1983
		";
1984
1985
        return $this->selectSet($sql);
1986
    }
1987
1988
    /**
1989
     * Drops an operator.
1990
     *
1991
     * @param mixed $operator_oid The OID of the operator to drop
1992
     * @param bool  $cascade      True to cascade drop, false to restrict
1993
     *
1994
     * @return int 0 if operation was successful
1995
     */
1996
    public function dropOperator($operator_oid, $cascade)
1997
    {
1998
        // Function comes in with $object as operator OID
1999
        $opr      = $this->getOperator($operator_oid);
2000
        $f_schema = $this->_schema;
2001
        $this->fieldClean($f_schema);
2002
        $this->fieldClean($opr->fields['oprname']);
2003
2004
        $sql = "DROP OPERATOR \"{$f_schema}\".{$opr->fields['oprname']} (";
2005
        // Quoting or formatting here???
2006
        if ($opr->fields['oprleftname'] !== null) {
2007
            $sql .= $opr->fields['oprleftname'].', ';
2008
        } else {
2009
            $sql .= 'NONE, ';
2010
        }
2011
2012
        if ($opr->fields['oprrightname'] !== null) {
2013
            $sql .= $opr->fields['oprrightname'].')';
2014
        } else {
2015
            $sql .= 'NONE)';
2016
        }
2017
2018
        if ($cascade) {
2019
            $sql .= ' CASCADE';
2020
        }
2021
2022
        return $this->execute($sql);
2023
    }
2024
2025
    /**
2026
     * Returns all details for a particular operator.
2027
     *
2028
     * @param mixed $operator_oid The oid of the operator
2029
     *
2030
     * @return \PHPPgAdmin\ADORecordSet Function info
2031
     */
2032
    public function getOperator($operator_oid)
2033
    {
2034
        $this->clean($operator_oid);
2035
2036
        $sql = "
2037
			SELECT
2038
            	po.oid, po.oprname,
2039
				oprleft::pg_catalog.regtype AS oprleftname,
2040
				oprright::pg_catalog.regtype AS oprrightname,
2041
				oprresult::pg_catalog.regtype AS resultname,
2042
				po.oprcanhash,
2043
				oprcanmerge,
2044
				oprcom::pg_catalog.regoperator AS oprcom,
2045
				oprnegate::pg_catalog.regoperator AS oprnegate,
2046
				po.oprcode::pg_catalog.regproc AS oprcode,
2047
				po.oprrest::pg_catalog.regproc AS oprrest,
2048
				po.oprjoin::pg_catalog.regproc AS oprjoin
2049
			FROM
2050
				pg_catalog.pg_operator po
2051
			WHERE
2052
				po.oid='{$operator_oid}'
2053
		";
2054
2055
        return $this->selectSet($sql);
2056
    }
2057
2058
    /**
2059
     *  Gets all opclasses.
2060
     *
2061
     * @return \PHPPgAdmin\ADORecordSet A recordset
2062
     */
2063
    public function getOpClasses()
2064
    {
2065
        $c_schema = $this->_schema;
2066
        $this->clean($c_schema);
2067
        $sql = "
2068
			SELECT
2069
				pa.amname, po.opcname,
2070
				po.opcintype::pg_catalog.regtype AS opcintype,
2071
				po.opcdefault,
2072
				pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment
2073
			FROM
2074
				pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn
2075
			WHERE
2076
				po.opcmethod=pa.oid
2077
				AND po.opcnamespace=pn.oid
2078
				AND pn.nspname='{$c_schema}'
2079
			ORDER BY 1,2
2080
			";
2081
2082
        return $this->selectSet($sql);
2083
    }
2084
2085
    /**
2086
     * Gets all languages.
2087
     *
2088
     * @param bool $all True to get all languages, regardless of show_system
2089
     *
2090
     * @return \PHPPgAdmin\ADORecordSet A recordset
2091
     */
2092
    public function getLanguages($all = false)
2093
    {
2094
        $conf = $this->conf;
2095
2096
        if ($conf['show_system'] || $all) {
2097
            $where = '';
2098
        } else {
2099
            $where = 'WHERE lanispl';
2100
        }
2101
2102
        $sql = "
2103
			SELECT
2104
				lanname, lanpltrusted,
2105
				lanplcallfoid::pg_catalog.regproc AS lanplcallf
2106
			FROM
2107
				pg_catalog.pg_language
2108
			{$where}
2109
			ORDER BY lanname
2110
		";
2111
2112
        return $this->selectSet($sql);
2113
    }
2114
2115
    /**
2116
     * Retrieves information for all tablespaces.
2117
     *
2118
     * @param bool $all Include all tablespaces (necessary when moving objects back to the default space)
2119
     *
2120
     * @return \PHPPgAdmin\ADORecordSet A recordset
2121
     */
2122
    public function getTablespaces($all = false)
2123
    {
2124
        $conf = $this->conf;
2125
2126
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2127
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2128
					FROM pg_catalog.pg_tablespace";
2129
2130
        if (!$conf['show_system'] && !$all) {
2131
            $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$';
2132
        }
2133
2134
        $sql .= ' ORDER BY spcname';
2135
2136
        return $this->selectSet($sql);
2137
    }
2138
2139
    // Misc functions
2140
2141
    /**
2142
     * Retrieves a tablespace's information.
2143
     *
2144
     * @param string $spcname
2145
     *
2146
     * @return \PHPPgAdmin\ADORecordSet A recordset
2147
     */
2148
    public function getTablespace($spcname)
2149
    {
2150
        $this->clean($spcname);
2151
2152
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
2153
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
2154
					FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'";
2155
2156
        return $this->selectSet($sql);
2157
    }
2158
2159
    /**
2160
     * Creates a tablespace.
2161
     *
2162
     * @param string $spcname  The name of the tablespace to create
2163
     * @param string $spcowner The owner of the tablespace. '' for current
2164
     * @param string $spcloc   The directory in which to create the tablespace
2165
     * @param string $comment
2166
     *
2167
     * @return int 0 success
2168
     */
2169
    public function createTablespace($spcname, $spcowner, $spcloc, $comment = '')
2170
    {
2171
        $this->fieldClean($spcname);
2172
        $this->clean($spcloc);
2173
2174
        $sql = "CREATE TABLESPACE \"{$spcname}\"";
2175
2176
        if ($spcowner != '') {
2177
            $this->fieldClean($spcowner);
2178
            $sql .= " OWNER \"{$spcowner}\"";
2179
        }
2180
2181
        $sql .= " LOCATION '{$spcloc}'";
2182
2183
        $status = $this->execute($sql);
2184
        if ($status != 0) {
2185
            return -1;
2186
        }
2187
2188
        if ($comment != '' && $this->hasSharedComments()) {
2189
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2190
            if ($status != 0) {
2191
                return -2;
2192
            }
2193
        }
2194
2195
        return 0;
2196
    }
2197
2198
    /**
2199
     * Alters a tablespace.
2200
     *
2201
     * @param string $spcname The name of the tablespace
2202
     * @param string $name    The new name for the tablespace
2203
     * @param string $owner   The new owner for the tablespace
2204
     * @param string $comment
2205
     *
2206
     * @return bool|int 0 success
2207
     */
2208
    public function alterTablespace($spcname, $name, $owner, $comment = '')
2209
    {
2210
        $this->fieldClean($spcname);
2211
        $this->fieldClean($name);
2212
        $this->fieldClean($owner);
2213
2214
        // Begin transaction
2215
        $status = $this->beginTransaction();
2216
        if ($status != 0) {
2217
            return -1;
2218
        }
2219
2220
        // Owner
2221
        $sql    = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\"";
2222
        $status = $this->execute($sql);
2223
        if ($status != 0) {
2224
            $this->rollbackTransaction();
2225
2226
            return -2;
2227
        }
2228
2229
        // Rename (only if name has changed)
2230
        if ($name != $spcname) {
2231
            $sql    = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\"";
2232
            $status = $this->execute($sql);
2233
            if ($status != 0) {
2234
                $this->rollbackTransaction();
2235
2236
                return -3;
2237
            }
2238
2239
            $spcname = $name;
2240
        }
2241
2242
        // Set comment if it has changed
2243
        if (trim($comment) != '' && $this->hasSharedComments()) {
2244
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
2245
            if ($status != 0) {
2246
                return -4;
2247
            }
2248
        }
2249
2250
        return $this->endTransaction();
2251
    }
2252
2253
    /**
2254
     * Drops a tablespace.
2255
     *
2256
     * @param string $spcname The name of the domain to drop
2257
     *
2258
     * @return int 0 if operation was successful
2259
     */
2260
    public function dropTablespace($spcname)
2261
    {
2262
        $this->fieldClean($spcname);
2263
2264
        $sql = "DROP TABLESPACE \"{$spcname}\"";
2265
2266
        return $this->execute($sql);
2267
    }
2268
2269
    /**
2270
     * Analyze a database.
2271
     *
2272
     * @param string $table (optional) The table to analyze
2273
     *
2274
     * @return bool 0 if successful
2275
     */
2276
    public function analyzeDB($table = '')
2277
    {
2278
        if ($table != '') {
2279
            $f_schema = $this->_schema;
2280
            $this->fieldClean($f_schema);
2281
            $this->fieldClean($table);
2282
2283
            $sql = "ANALYZE \"{$f_schema}\".\"{$table}\"";
2284
        } else {
2285
            $sql = 'ANALYZE';
2286
        }
2287
2288
        return $this->execute($sql);
2289
    }
2290
2291
    /**
2292
     * Vacuums a database.
2293
     *
2294
     * @param string $table   The table to vacuum
2295
     * @param bool   $analyze If true, also does analyze
2296
     * @param bool   $full    If true, selects "full" vacuum
2297
     * @param bool   $freeze  If true, selects aggressive "freezing" of tuples
2298
     *
2299
     * @return bool 0 if successful
2300
     */
2301
    public function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false)
2302
    {
2303
        $sql = 'VACUUM';
2304
        if ($full) {
2305
            $sql .= ' FULL';
2306
        }
2307
2308
        if ($freeze) {
2309
            $sql .= ' FREEZE';
2310
        }
2311
2312
        if ($analyze) {
2313
            $sql .= ' ANALYZE';
2314
        }
2315
2316
        if ($table != '') {
2317
            $f_schema = $this->_schema;
2318
            $this->fieldClean($f_schema);
2319
            $this->fieldClean($table);
2320
            $sql .= " \"{$f_schema}\".\"{$table}\"";
2321
        }
2322
2323
        return $this->execute($sql);
2324
    }
2325
2326
    /**
2327
     * Returns all autovacuum global configuration.
2328
     *
2329
     * @return array associative array array( param => value, ...)
2330
     */
2331
    public function getAutovacuum()
2332
    {
2333
        $_defaults = $this->selectSet(
2334
            "SELECT name, setting
2335
			FROM pg_catalog.pg_settings
2336
			WHERE
2337
				name = 'autovacuum'
2338
				OR name = 'autovacuum_vacuum_threshold'
2339
				OR name = 'autovacuum_vacuum_scale_factor'
2340
				OR name = 'autovacuum_analyze_threshold'
2341
				OR name = 'autovacuum_analyze_scale_factor'
2342
				OR name = 'autovacuum_vacuum_cost_delay'
2343
				OR name = 'autovacuum_vacuum_cost_limit'
2344
				OR name = 'vacuum_freeze_min_age'
2345
				OR name = 'autovacuum_freeze_max_age'
2346
			"
2347
        );
2348
2349
        $ret = [];
2350
        while (!$_defaults->EOF) {
2351
            $ret[$_defaults->fields['name']] = $_defaults->fields['setting'];
2352
            $_defaults->moveNext();
2353
        }
2354
2355
        return $ret;
2356
    }
2357
2358
    /**
2359
     * Returns all available autovacuum per table information.
2360
     *
2361
     * @param string $table          table name
2362
     * @param bool   $vacenabled     true if vacuum is enabled
2363
     * @param int    $vacthreshold   vacuum threshold
2364
     * @param int    $vacscalefactor vacuum scalefactor
2365
     * @param int    $anathresold    analyze threshold
2366
     * @param int    $anascalefactor analyze scale factor
2367
     * @param int    $vaccostdelay   vacuum cost delay
2368
     * @param int    $vaccostlimit   vacuum cost limit
2369
     *
2370
     * @return bool 0 if successful
2371
     */
2372
    public function saveAutovacuum(
2373
        $table,
2374
        $vacenabled,
2375
        $vacthreshold,
2376
        $vacscalefactor,
2377
        $anathresold,
2378
        $anascalefactor,
2379
        $vaccostdelay,
2380
        $vaccostlimit
2381
    ) {
2382
        $f_schema = $this->_schema;
2383
        $this->fieldClean($f_schema);
2384
        $this->fieldClean($table);
2385
2386
        $params = [];
2387
2388
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
2389
2390
        if (!empty($vacenabled)) {
2391
            $this->clean($vacenabled);
2392
            $params[] = "autovacuum_enabled='{$vacenabled}'";
2393
        }
2394
        if (!empty($vacthreshold)) {
2395
            $this->clean($vacthreshold);
2396
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
2397
        }
2398
        if (!empty($vacscalefactor)) {
2399
            $this->clean($vacscalefactor);
2400
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
2401
        }
2402
        if (!empty($anathresold)) {
2403
            $this->clean($anathresold);
2404
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
2405
        }
2406
        if (!empty($anascalefactor)) {
2407
            $this->clean($anascalefactor);
2408
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
2409
        }
2410
        if (!empty($vaccostdelay)) {
2411
            $this->clean($vaccostdelay);
2412
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
2413
        }
2414
        if (!empty($vaccostlimit)) {
2415
            $this->clean($vaccostlimit);
2416
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
2417
        }
2418
2419
        $sql = $sql.implode(',', $params).');';
2420
2421
        return $this->execute($sql);
2422
    }
2423
2424
    // Type conversion routines
2425
2426
    /**
2427
     * Drops autovacuum config for a table.
2428
     *
2429
     * @param string $table The table
2430
     *
2431
     * @return bool 0 if successful
2432
     */
2433
    public function dropAutovacuum($table)
2434
    {
2435
        $f_schema = $this->_schema;
2436
        $this->fieldClean($f_schema);
2437
        $this->fieldClean($table);
2438
2439
        return $this->execute(
2440
            "
2441
			ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
2442
				autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
2443
				autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
2444
			);"
2445
        );
2446
    }
2447
2448
    /**
2449
     * Returns all available process information.
2450
     *
2451
     * @param null|string $database (optional) Find only connections to specified database
2452
     *
2453
     * @return \PHPPgAdmin\ADORecordSet A recordset
2454
     */
2455
    public function getProcesses($database = null)
2456
    {
2457
        if ($database === null) {
2458
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
2459
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
2460
				FROM pg_catalog.pg_stat_activity
2461
				ORDER BY datname, usename, pid";
2462
        } else {
2463
            $this->clean($database);
2464
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
2465
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
2466
				FROM pg_catalog.pg_stat_activity
2467
				WHERE datname='{$database}'
2468
				ORDER BY usename, pid";
2469
        }
2470
2471
        return $this->selectSet($sql);
2472
    }
2473
2474
    // interfaces Statistics collector functions
2475
2476
    /**
2477
     * Returns table locks information in the current database.
2478
     *
2479
     * @return \PHPPgAdmin\ADORecordSet A recordset
2480
     */
2481
    public function getLocks()
2482
    {
2483
        $conf = $this->conf;
2484
2485
        if (!$conf['show_system']) {
2486
            $where = 'AND pn.nspname NOT LIKE $$pg\_%$$';
2487
        } else {
2488
            $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
2489
        }
2490
2491
        $sql = "
2492
			SELECT
2493
				pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction,
2494
				(select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid'
2495
					and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction
2496
			FROM
2497
				pg_catalog.pg_locks pl,
2498
				pg_catalog.pg_class pc,
2499
				pg_catalog.pg_namespace pn
2500
			WHERE
2501
				pl.relation = pc.oid AND pc.relnamespace=pn.oid
2502
			{$where}
2503
			ORDER BY pid,nspname,tablename";
2504
2505
        return $this->selectSet($sql);
2506
    }
2507
2508
    /**
2509
     * Sends a cancel or kill command to a process.
2510
     *
2511
     * @param int    $pid    The ID of the backend process
2512
     * @param string $signal 'CANCEL' or 'KILL'
2513
     *
2514
     * @return int 0 success
2515
     */
2516
    public function sendSignal($pid, $signal)
2517
    {
2518
        // Clean
2519
        $pid = (int) $pid;
2520
2521
        if ($signal == 'CANCEL') {
2522
            $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val";
2523
        } elseif ($signal == 'KILL') {
2524
            $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val";
2525
        } else {
2526
            return -1;
2527
        }
2528
2529
        // Execute the query
2530
        $val = $this->selectField($sql, 'val');
2531
2532
        if ($val === 'f') {
2533
            return -1;
2534
        }
2535
2536
        if ($val === 't') {
2537
            return 0;
2538
        }
2539
2540
        return -1;
2541
    }
2542
2543
    /**
2544
     * Executes an SQL script as a series of SQL statements.  Returns
2545
     * the result of the final step.  This is a very complicated lexer
2546
     * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in
2547
     * the PostgreSQL source code.
2548
     * XXX: It does not handle multibyte languages properly.
2549
     *
2550
     * @param string        $name     Entry in $_FILES to use
2551
     * @param null|callable $callback (optional) Callback function to call with each query, its result and line number
2552
     *
2553
     * @return bool true for general success, false on any failure
2554
     */
2555
    public function executeScript($name, $callback = null)
2556
    {
2557
        // This whole function isn't very encapsulated, but hey...
2558
        $conn = $this->conn->_connectionID;
2559
        if (!is_uploaded_file($_FILES[$name]['tmp_name'])) {
2560
            return false;
2561
        }
2562
2563
        $fd = fopen($_FILES[$name]['tmp_name'], 'rb');
2564
        if ($fd === false) {
2565
            return false;
2566
        }
2567
2568
        // Build up each SQL statement, they can be multiline
2569
        $query_buf    = null;
2570
        $query_start  = 0;
2571
        $in_quote     = 0;
2572
        $in_xcomment  = 0;
2573
        $bslash_count = 0;
2574
        $dol_quote    = '';
2575
        $paren_level  = 0;
2576
        $len          = 0;
2577
        $i            = 0;
2578
        $prevlen      = 0;
2579
        $thislen      = 0;
2580
        $lineno       = 0;
2581
2582
        // Loop over each line in the file
2583
        while (!feof($fd)) {
2584
            $line = fgets($fd);
2585
            ++$lineno;
2586
2587
            // Nothing left on line? Then ignore...
2588
            if (trim($line) == '') {
2589
                continue;
2590
            }
2591
2592
            $len         = strlen($line);
2593
            $query_start = 0;
2594
2595
            /**
2596
             * Parse line, looking for command separators.
2597
             *
2598
             * The current character is at line[i], the prior character at line[i
2599
             * - prevlen], the next character at line[i + thislen].
2600
             */
2601
            $prevlen = 0;
2602
            $thislen = ($len > 0) ? 1 : 0;
2603
2604
            for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) {
2605
                /* was the previous character a backslash? */
2606
                if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') {
2607
                    $this->prtrace('bslash_count', $bslash_count, $line);
2608
                    ++$bslash_count;
2609
                } else {
2610
                    $bslash_count = 0;
2611
                }
2612
2613
                /*
2614
                 * It is important to place the in_* test routines before the
2615
                 * in_* detection routines. i.e. we have to test if we are in
2616
                 * a quote before testing for comments.
2617
                 */
2618
2619
                /* in quote? */
2620
                if ($in_quote !== 0) {
2621
                    //$this->prtrace('in_quote', $in_quote, $line);
2622
                    /*
2623
                     * end of quote if matching non-backslashed character.
2624
                     * backslashes don't count for double quotes, though.
2625
                     */
2626
                    if (substr($line, $i, 1) == $in_quote &&
2627
                        ($bslash_count % 2 == 0 || $in_quote == '"')
2628
                    ) {
2629
                        $in_quote = 0;
2630
                    }
2631
                } elseif ($dol_quote) {
2632
                    $this->prtrace('dol_quote', $dol_quote, $line);
2633
                    if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) {
2634
                        $this->advance_1($i, $prevlen, $thislen);
2635
                        while (substr($line, $i, 1) != '$') {
2636
                            $this->advance_1($i, $prevlen, $thislen);
2637
                        }
2638
2639
                        $dol_quote = '';
2640
                    }
2641
                } elseif (substr($line, $i, 2) == '/*') {
2642
                    $this->prtrace('open_xcomment', $in_xcomment, $line, $i, $prevlen, $thislen);
2643
                    if ($in_xcomment == 0) {
2644
                        ++$in_xcomment;
2645
                        $finishpos = strpos(substr($line, $i, $len), '*/');
2646
                        if ($finishpos === false) {
2647
                            $line = substr($line, 0, $i); /* remove comment */
2648
                            break;
2649
                        }
2650
                        $pre         = substr($line, 0, $i);
2651
                        $post        = substr($line, $i + 2 + $finishpos, $len);
2652
                        $line        = $pre.' '.$post;
2653
                        $in_xcomment = 0;
2654
                        $i           = 0;
2655
                    }
2656
                } elseif ($in_xcomment) {
2657
                    $position = strpos(substr($line, $i, $len), '*/');
2658
                    if ($position === false) {
2659
                        $line = '';
2660
2661
                        break;
2662
                    }
2663
2664
                    $substr = substr($line, $i, 2);
2665
2666
                    if ($substr == '*/' && !--$in_xcomment) {
2667
                        $line = substr($line, $i + 2, $len);
2668
                        $i += 2;
2669
                        $this->advance_1($i, $prevlen, $thislen);
2670
                    }
2671
                    // old logic
2672
                    //  } else if (substr($line, $i, 2) == '/*') {
2673
                    //      if ($in_xcomment == 0) {
2674
                    //          ++$in_xcomment;
2675
                    //          $this->advance_1($i, $prevlen, $thislen);
2676
                    //      }
2677
                    //  } else if ($in_xcomment) {
2678
                    //      $substr = substr($line, $i, 2);
2679
                    //      if ($substr == '*/' && !--$in_xcomment) {
2680
                    //          $this->advance_1($i, $prevlen, $thislen);
2681
                    //      }
2682
                } elseif (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
2683
                    $in_quote = substr($line, $i, 1);
2684
                } elseif (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
2685
                    $dol_end   = strpos(substr($line, $i + 1), '$');
2686
                    $dol_quote = substr($line, $i, $dol_end + 1);
2687
                    $this->advance_1($i, $prevlen, $thislen);
2688
                    while (substr($line, $i, 1) != '$') {
2689
                        $this->advance_1($i, $prevlen, $thislen);
2690
                    }
2691
                } else {
2692
                    if (substr($line, $i, 2) == '--') {
2693
                        $line = substr($line, 0, $i); /* remove comment */
2694
                        break;
2695
                    } /* count nested parentheses */
2696
2697
                    if (substr($line, $i, 1) == '(') {
2698
                        ++$paren_level;
2699
                    } elseif (substr($line, $i, 1) == ')' && $paren_level > 0) {
2700
                        --$paren_level;
2701
                    } elseif (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) {
2702
                        $subline = substr(substr($line, 0, $i), $query_start);
2703
                        /*
2704
                         * insert a cosmetic newline, if this is not the first
2705
                         * line in the buffer
2706
                         */
2707
                        if (strlen($query_buf) > 0) {
2708
                            $query_buf .= "\n";
2709
                        }
2710
2711
                        /* append the line to the query buffer */
2712
                        $query_buf .= $subline;
2713
                        /* is there anything in the query_buf? */
2714
                        if (trim($query_buf)) {
2715
                            $query_buf .= ';';
2716
2717
                            // Execute the query. PHP cannot execute
2718
                            // empty queries, unlike libpq
2719
                            $res = @pg_query($conn, $query_buf);
2720
2721
                            // Call the callback function for display
2722
                            if ($callback !== null) {
2723
                                $callback($query_buf, $res, $lineno);
2724
                            }
2725
2726
                            // Check for COPY request
2727
                            if (pg_result_status($res) == 4) {
2728
                                // 4 == PGSQL_COPY_FROM
2729
                                while (!feof($fd)) {
2730
                                    $copy = fgets($fd, 32768);
2731
                                    ++$lineno;
2732
                                    pg_put_line($conn, $copy);
2733
                                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2734
                                        pg_end_copy($conn);
2735
2736
                                        break;
2737
                                    }
2738
                                }
2739
                            }
2740
                        }
2741
                        $query_buf   = null;
2742
                        $query_start = $i + $thislen;
2743
                    } elseif (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) {
2744
                        $sub = substr($line, $i, $thislen);
2745
                        while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) {
2746
                            /* keep going while we still have identifier chars */
2747
                            $this->advance_1($i, $prevlen, $thislen);
2748
                            $sub = substr($line, $i, $thislen);
2749
                        }
2750
                        // Since we're now over the next character to be examined, it is necessary
2751
                        // to move back one space.
2752
                        $i -= $prevlen;
2753
                    }
2754
                }
2755
            } // end for
2756
2757
            /* Put the rest of the line in the query buffer. */
2758
            $subline = substr($line, $query_start);
2759
2760
            if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) {
2761
                if (strlen($query_buf) > 0) {
2762
                    $query_buf .= "\n";
2763
                }
2764
2765
                $query_buf .= $subline;
2766
            }
2767
2768
            $line = null;
2769
        } // end while
2770
2771
        /*
2772
         * Process query at the end of file without a semicolon, so long as
2773
         * it's non-empty.
2774
         */
2775
        if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) {
2776
            // Execute the query
2777
            $res = @pg_query($conn, $query_buf);
2778
2779
            // Call the callback function for display
2780
            if ($callback !== null) {
2781
                $callback($query_buf, $res, $lineno);
2782
            }
2783
2784
            // Check for COPY request
2785
            if (pg_result_status($res) == 4) {
2786
                // 4 == PGSQL_COPY_FROM
2787
                while (!feof($fd)) {
2788
                    $copy = fgets($fd, 32768);
2789
                    ++$lineno;
2790
                    pg_put_line($conn, $copy);
2791
                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
2792
                        pg_end_copy($conn);
2793
2794
                        break;
2795
                    }
2796
                }
2797
            }
2798
        }
2799
2800
        fclose($fd);
2801
2802
        return true;
2803
    }
2804
2805
    /**
2806
     * A private helper method for executeScript that advances the
2807
     * character by 1.  In psql this is careful to take into account
2808
     * multibyte languages, but we don't at the moment, so this function
2809
     * is someone redundant, since it will always advance by 1.
2810
     *
2811
     * @param int $i       The current character position in the line
2812
     * @param int $prevlen Length of previous character (ie. 1)
2813
     * @param int $thislen Length of current character (ie. 1)
2814
     */
2815
    protected function advance_1(&$i, &$prevlen, &$thislen)
2816
    {
2817
        $prevlen = $thislen;
2818
        $i += $thislen;
2819
        $thislen = 1;
2820
    }
2821
2822
    /**
2823
     * Private helper method to detect a valid $foo$ quote delimiter at
2824
     * the start of the parameter dquote.
2825
     *
2826
     * @param string $dquote
2827
     *
2828
     * @return bool true if valid, false otherwise
2829
     */
2830
    protected function valid_dolquote($dquote)
2831
    {
2832
        // XXX: support multibyte
2833
        return preg_match('/^[$][$]/', $dquote) || preg_match('/^[$][_[:alpha:]][_[:alnum:]]*[$]/', $dquote);
2834
    }
2835
2836
    // Capabilities
2837
2838
    /**
2839
     * Returns a recordset of all columns in a query.  Supports paging.
2840
     *
2841
     * @param string   $type      Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier,
2842
     *                            or 'SELECT" if it's a select query
2843
     * @param string   $table     The base table of the query.  NULL for no table.
2844
     * @param string   $query     The query that is being executed.  NULL for no query.
2845
     * @param string   $sortkey   The column number to sort by, or '' or null for no sorting
2846
     * @param string   $sortdir   The direction in which to sort the specified column ('asc' or 'desc')
2847
     * @param null|int $page      The page of the relation to retrieve
2848
     * @param null|int $page_size The number of rows per page
2849
     * @param int      $max_pages (return-by-ref) The max number of pages in the relation
2850
     *
2851
     * @return int|\PHPPgAdmin\ADORecordSet A  recordset on success or an int with error code
2852
     *                                      - -1 transaction error
2853
     *                                      - -2 counting error
2854
     *                                      - -3 page or page_size invalid
2855
     *                                      - -4 unknown type
2856
     *                                      - -5 failed setting transaction read only
2857
     */
2858
    public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages)
2859
    {
2860
        // Check that we're not going to divide by zero
2861
        if (!is_numeric($page_size) || $page_size != (int) $page_size || $page_size <= 0) {
2862
            return -3;
2863
        }
2864
2865
        // If $type is TABLE, then generate the query
2866
        switch ($type) {
2867
            case 'TABLE':
2868
                if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
2869
                    $orderby = [$sortkey => $sortdir];
2870
                } else {
2871
                    $orderby = [];
2872
                }
2873
2874
                $query = $this->getSelectSQL($table, [], [], [], $orderby);
2875
2876
                break;
2877
            case 'QUERY':
2878
            case 'SELECT':
2879
                // Trim query
2880
                $query = trim($query);
2881
                // Trim off trailing semi-colon if there is one
2882
                if (substr($query, strlen($query) - 1, 1) == ';') {
2883
                    $query = substr($query, 0, strlen($query) - 1);
2884
                }
2885
2886
                break;
2887
            default:
2888
                return -4;
2889
        }
2890
2891
        // Generate count query
2892
        $count = "SELECT COUNT(*) AS total FROM (${query}) AS sub";
2893
2894
        // Open a transaction
2895
        $status = $this->beginTransaction();
2896
        if ($status != 0) {
2897
            return -1;
2898
        }
2899
2900
        // If backend supports read only queries, then specify read only mode
2901
        // to avoid side effects from repeating queries that do writes.
2902
        if ($this->hasReadOnlyQueries()) {
2903
            $status = $this->execute('SET TRANSACTION READ ONLY');
2904
            if ($status != 0) {
2905
                $this->rollbackTransaction();
2906
2907
                return -5;
2908
            }
2909
        }
2910
2911
        // Count the number of rows
2912
        $total = $this->browseQueryCount($count);
2913
        if ($total < 0) {
2914
            $this->rollbackTransaction();
2915
2916
            return -2;
2917
        }
2918
2919
        // Calculate max pages
2920
        $max_pages = ceil($total / $page_size);
2921
2922
        // Check that page is less than or equal to max pages
2923
        if (!is_numeric($page) || $page != (int) $page || $page > $max_pages || $page < 1) {
2924
            $this->rollbackTransaction();
2925
2926
            return -3;
2927
        }
2928
2929
        // Set fetch mode to NUM so that duplicate field names are properly returned
2930
        // for non-table queries.  Since the SELECT feature only allows selecting one
2931
        // table, duplicate fields shouldn't appear.
2932
        if ($type == 'QUERY') {
2933
            $this->conn->setFetchMode(ADODB_FETCH_NUM);
2934
        }
2935
2936
        // Figure out ORDER BY.  Sort key is always the column number (based from one)
2937
        // of the column to order by.  Only need to do this for non-TABLE queries
2938
        if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
2939
            $orderby = " ORDER BY {$sortkey}";
2940
            // Add sort order
2941
            if ($sortdir == 'desc') {
2942
                $orderby .= ' DESC';
2943
            } else {
2944
                $orderby .= ' ASC';
2945
            }
2946
        } else {
2947
            $orderby = '';
2948
        }
2949
2950
        // Actually retrieve the rows, with offset and limit
2951
        $rs     = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET ".($page - 1) * $page_size);
2952
        $status = $this->endTransaction();
2953
        if ($status != 0) {
2954
            $this->rollbackTransaction();
2955
2956
            return -1;
2957
        }
2958
2959
        return $rs;
2960
    }
2961
2962
    /**
2963
     * Generates the SQL for the 'select' function.
2964
     *
2965
     * @param string $table   The table from which to select
2966
     * @param array  $show    An array of columns to show.  Empty array means all columns.
2967
     * @param array  $values  An array mapping columns to values
2968
     * @param array  $ops     An array of the operators to use
2969
     * @param array  $orderby (optional) An array of column numbers or names (one based)
2970
     *                        mapped to sort direction (asc or desc or '' or null) to order by
2971
     *
2972
     * @return string The SQL query
2973
     */
2974
    public function getSelectSQL($table, $show, $values, $ops, $orderby = [])
2975
    {
2976
        $this->fieldArrayClean($show);
2977
2978
        // If an empty array is passed in, then show all columns
2979
        if (sizeof($show) == 0) {
2980
            if ($this->hasObjectID($table)) {
2981
                $sql = "SELECT \"{$this->id}\", * FROM ";
2982
            } else {
2983
                $sql = 'SELECT * FROM ';
2984
            }
2985
        } else {
2986
            // Add oid column automatically to results for editing purposes
2987
            if (!in_array($this->id, $show, true) && $this->hasObjectID($table)) {
2988
                $sql = "SELECT \"{$this->id}\", \"";
2989
            } else {
2990
                $sql = 'SELECT "';
2991
            }
2992
2993
            $sql .= join('","', $show).'" FROM ';
2994
        }
2995
2996
        $this->fieldClean($table);
2997
2998
        if (isset($_REQUEST['schema'])) {
2999
            $f_schema = $_REQUEST['schema'];
3000
            $this->fieldClean($f_schema);
3001
            $sql .= "\"{$f_schema}\".";
3002
        }
3003
        $sql .= "\"{$table}\"";
3004
3005
        // If we have values specified, add them to the WHERE clause
3006
        $first = true;
3007
        if (is_array($values) && sizeof($values) > 0) {
3008
            foreach ($values as $k => $v) {
3009
                if ($v != '' || $this->selectOps[$ops[$k]] == 'p') {
3010
                    $this->fieldClean($k);
3011
                    if ($first) {
3012
                        $sql .= ' WHERE ';
3013
                        $first = false;
3014
                    } else {
3015
                        $sql .= ' AND ';
3016
                    }
3017
                    // Different query format depending on operator type
3018
                    switch ($this->selectOps[$ops[$k]]) {
3019
                        case 'i':
3020
                            // Only clean the field for the inline case
3021
                            // this is because (x), subqueries need to
3022
                            // to allow 'a','b' as input.
3023
                            $this->clean($v);
3024
                            $sql .= "\"{$k}\" {$ops[$k]} '{$v}'";
3025
3026
                            break;
3027
                        case 'p':
3028
                            $sql .= "\"{$k}\" {$ops[$k]}";
3029
3030
                            break;
3031
                        case 'x':
3032
                            $sql .= "\"{$k}\" {$ops[$k]} ({$v})";
3033
3034
                            break;
3035
                        case 't':
3036
                            $sql .= "\"{$k}\" {$ops[$k]}('{$v}')";
3037
3038
                            break;
3039
                        default:
3040
                            // Shouldn't happen
3041
                    }
3042
                }
3043
            }
3044
        }
3045
3046
        // ORDER BY
3047
        if (is_array($orderby) && sizeof($orderby) > 0) {
3048
            $sql .= ' ORDER BY ';
3049
            $first = true;
3050
            foreach ($orderby as $k => $v) {
3051
                if ($first) {
3052
                    $first = false;
3053
                } else {
3054
                    $sql .= ', ';
3055
                }
3056
3057
                if (preg_match('/^[0-9]+$/', $k)) {
3058
                    $sql .= $k;
3059
                } else {
3060
                    $this->fieldClean($k);
3061
                    $sql .= '"'.$k.'"';
3062
                }
3063
                if (strtoupper($v) == 'DESC') {
3064
                    $sql .= ' DESC';
3065
                }
3066
            }
3067
        }
3068
3069
        return $sql;
3070
    }
3071
3072
    /**
3073
     * Finds the number of rows that would be returned by a
3074
     * query.
3075
     *
3076
     * @param string $count The count query
3077
     *
3078
     * @return int|string The count of rows or -1 of no rows are found
3079
     */
3080
    public function browseQueryCount($count)
3081
    {
3082
        return $this->selectField($count, 'total');
3083
    }
3084
3085
    /**
3086
     * Returns a recordset of all columns in a table.
3087
     *
3088
     * @param string $table The name of a table
3089
     * @param array  $key   The associative array holding the key to retrieve
3090
     *
3091
     * @return \PHPPgAdmin\ADORecordSet A recordset
3092
     */
3093
    public function browseRow($table, $key)
3094
    {
3095
        $f_schema = $this->_schema;
3096
        $this->fieldClean($f_schema);
3097
        $this->fieldClean($table);
3098
3099
        $sql = "SELECT * FROM \"{$f_schema}\".\"{$table}\"";
3100
        if (is_array($key) && sizeof($key) > 0) {
3101
            $sql .= ' WHERE true';
3102
            foreach ($key as $k => $v) {
3103
                $this->fieldClean($k);
3104
                $this->clean($v);
3105
                $sql .= " AND \"{$k}\"='{$v}'";
3106
            }
3107
        }
3108
3109
        return $this->selectSet($sql);
3110
    }
3111
3112
    /**
3113
     * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false.
3114
     *
3115
     * @param null|bool|int|string $parameter the parameter
3116
     *
3117
     * @return string the parameter transformed to 't' of 'f'
3118
     */
3119
    public function dbBool(&$parameter)
3120
    {
3121
        if ($parameter) {
3122
            $parameter = 't';
3123
        } else {
3124
            $parameter = 'f';
3125
        }
3126
3127
        return $parameter;
3128
    }
3129
3130
    /**
3131
     * Fetches statistics for a database.
3132
     *
3133
     * @param string $database The database to fetch stats for
3134
     *
3135
     * @return \PHPPgAdmin\ADORecordSet A recordset
3136
     */
3137
    public function getStatsDatabase($database)
3138
    {
3139
        $this->clean($database);
3140
3141
        $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'";
3142
3143
        return $this->selectSet($sql);
3144
    }
3145
3146
    /**
3147
     * Fetches tuple statistics for a table.
3148
     *
3149
     * @param string $table The table to fetch stats for
3150
     *
3151
     * @return \PHPPgAdmin\ADORecordSet A recordset
3152
     */
3153
    public function getStatsTableTuples($table)
3154
    {
3155
        $c_schema = $this->_schema;
3156
        $this->clean($c_schema);
3157
        $this->clean($table);
3158
3159
        $sql = "SELECT * FROM pg_stat_all_tables
3160
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3161
3162
        return $this->selectSet($sql);
3163
    }
3164
3165
    /**
3166
     * Fetches I/0 statistics for a table.
3167
     *
3168
     * @param string $table The table to fetch stats for
3169
     *
3170
     * @return \PHPPgAdmin\ADORecordSet A recordset
3171
     */
3172
    public function getStatsTableIO($table)
3173
    {
3174
        $c_schema = $this->_schema;
3175
        $this->clean($c_schema);
3176
        $this->clean($table);
3177
3178
        $sql = "SELECT * FROM pg_statio_all_tables
3179
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
3180
3181
        return $this->selectSet($sql);
3182
    }
3183
3184
    /**
3185
     * Fetches tuple statistics for all indexes on a table.
3186
     *
3187
     * @param string $table The table to fetch index stats for
3188
     *
3189
     * @return \PHPPgAdmin\ADORecordSet A recordset
3190
     */
3191
    public function getStatsIndexTuples($table)
3192
    {
3193
        $c_schema = $this->_schema;
3194
        $this->clean($c_schema);
3195
        $this->clean($table);
3196
3197
        $sql = "SELECT * FROM pg_stat_all_indexes
3198
			WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname";
3199
3200
        return $this->selectSet($sql);
3201
    }
3202
3203
    /**
3204
     * Fetches I/0 statistics for all indexes on a table.
3205
     *
3206
     * @param string $table The table to fetch index stats for
3207
     *
3208
     * @return \PHPPgAdmin\ADORecordSet A recordset
3209
     */
3210
    public function getStatsIndexIO($table)
3211
    {
3212
        $c_schema = $this->_schema;
3213
        $this->clean($c_schema);
3214
        $this->clean($table);
3215
3216
        $sql = "SELECT * FROM pg_statio_all_indexes
3217
			WHERE schemaname='{$c_schema}' AND relname='{$table}'
3218
			ORDER BY indexrelname";
3219
3220
        return $this->selectSet($sql);
3221
    }
3222
}
3223