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

Postgres::createType()   C

Complexity

Conditions 7
Paths 64

Size

Total Lines 50
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 24
nc 64
nop 10
dl 0
loc 50
rs 6.7272
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.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