Test Failed
Branch develop (db5506)
by Felipe
03:46
created

Postgres::createUser()   C

Complexity

Conditions 7
Paths 32

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 17
nc 32
nop 6
dl 0
loc 26
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace PHPPgAdmin\Database;
4
5
/**
6
 * A Class that implements the DB Interface for Postgres
7
 * Note: This Class uses ADODB and returns RecordSets.
8
 *
9
 * $Id: Postgres.php,v 1.320 2008/02/20 20:43:09 ioguix Exp $
10
 */
11
12
class Postgres extends ADOdbBase
13
{
14
    use \PHPPgAdmin\HelperTrait;
15
16
    public $major_version = 9.6;
17
    // Max object name length
18
    public $_maxNameLen = 63;
19
    // Store the current schema
20
    public $_schema;
21
    // Map of database encoding names to HTTP encoding names.  If a
22
    // database encoding does not appear in this list, then its HTTP
23
    // encoding name is the same as its database encoding name.
24
    public $codemap = [
25
        'BIG5'       => 'BIG5',
26
        'EUC_CN'     => 'GB2312',
27
        'EUC_JP'     => 'EUC-JP',
28
        'EUC_KR'     => 'EUC-KR',
29
        'EUC_TW'     => 'EUC-TW',
30
        'GB18030'    => 'GB18030',
31
        'GBK'        => 'GB2312',
32
        'ISO_8859_5' => 'ISO-8859-5',
33
        'ISO_8859_6' => 'ISO-8859-6',
34
        'ISO_8859_7' => 'ISO-8859-7',
35
        'ISO_8859_8' => 'ISO-8859-8',
36
        'JOHAB'      => 'CP1361',
37
        'KOI8'       => 'KOI8-R',
38
        'LATIN1'     => 'ISO-8859-1',
39
        'LATIN2'     => 'ISO-8859-2',
40
        'LATIN3'     => 'ISO-8859-3',
41
        'LATIN4'     => 'ISO-8859-4',
42
        'LATIN5'     => 'ISO-8859-9',
43
        'LATIN6'     => 'ISO-8859-10',
44
        'LATIN7'     => 'ISO-8859-13',
45
        'LATIN8'     => 'ISO-8859-14',
46
        'LATIN9'     => 'ISO-8859-15',
47
        'LATIN10'    => 'ISO-8859-16',
48
        'SJIS'       => 'SHIFT_JIS',
49
        'SQL_ASCII'  => 'US-ASCII',
50
        'UHC'        => 'WIN949',
51
        'UTF8'       => 'UTF-8',
52
        'WIN866'     => 'CP866',
53
        'WIN874'     => 'CP874',
54
        'WIN1250'    => 'CP1250',
55
        'WIN1251'    => 'CP1251',
56
        'WIN1252'    => 'CP1252',
57
        'WIN1256'    => 'CP1256',
58
        'WIN1258'    => 'CP1258',
59
    ];
60
    public $defaultprops = ['', '', ''];
61
    // Extra "magic" types.  BIGSERIAL was added in PostgreSQL 7.2.
62
    public $extraTypes = ['SERIAL', 'BIGSERIAL'];
63
    // Foreign key stuff.  First element MUST be the default.
64
    public $fkactions    = ['NO ACTION', 'RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT'];
65
    public $fkdeferrable = ['NOT DEFERRABLE', 'DEFERRABLE'];
66
    public $fkinitial    = ['INITIALLY IMMEDIATE', 'INITIALLY DEFERRED'];
67
    public $fkmatches    = ['MATCH SIMPLE', 'MATCH FULL'];
68
    // Function properties
69
    public $funcprops = [
70
        ['', 'VOLATILE', 'IMMUTABLE', 'STABLE'],
71
        ['', 'CALLED ON NULL INPUT', 'RETURNS NULL ON NULL INPUT'],
72
        ['', 'SECURITY INVOKER', 'SECURITY DEFINER'],
73
    ];
74
75
    // Default help URL
76
    public $help_base = null;
77
    // Help sub pages
78
    public $help_page = null;
79
    // Name of id column
80
    public $id = 'oid';
81
82
    // Supported join operations for use with view wizard
83
    public $joinOps = ['INNER JOIN' => 'INNER JOIN', 'LEFT JOIN' => 'LEFT JOIN', 'RIGHT JOIN' => 'RIGHT JOIN', 'FULL JOIN' => 'FULL JOIN'];
84
    // Map of internal language name to syntax highlighting name
85
    public $langmap = [
86
        'sql'       => 'SQL',
87
        'plpgsql'   => 'SQL',
88
        'php'       => 'PHP',
89
        'phpu'      => 'PHP',
90
        'plphp'     => 'PHP',
91
        'plphpu'    => 'PHP',
92
        'perl'      => 'Perl',
93
        'perlu'     => 'Perl',
94
        'plperl'    => 'Perl',
95
        'plperlu'   => 'Perl',
96
        'java'      => 'Java',
97
        'javau'     => 'Java',
98
        'pljava'    => 'Java',
99
        'pljavau'   => 'Java',
100
        'plj'       => 'Java',
101
        'plju'      => 'Java',
102
        'python'    => 'Python',
103
        'pythonu'   => 'Python',
104
        'plpython'  => 'Python',
105
        'plpythonu' => 'Python',
106
        'ruby'      => 'Ruby',
107
        'rubyu'     => 'Ruby',
108
        'plruby'    => 'Ruby',
109
        'plrubyu'   => 'Ruby',
110
    ];
111
    // Predefined size types
112
    public $predefined_size_types = [
113
        'abstime',
114
        'aclitem',
115
        'bigserial',
116
        'boolean',
117
        'bytea',
118
        'cid',
119
        'cidr',
120
        'circle',
121
        'date',
122
        'float4',
123
        'float8',
124
        'gtsvector',
125
        'inet',
126
        'int2',
127
        'int4',
128
        'int8',
129
        'macaddr',
130
        'money',
131
        'oid',
132
        'path',
133
        'polygon',
134
        'refcursor',
135
        'regclass',
136
        'regoper',
137
        'regoperator',
138
        'regproc',
139
        'regprocedure',
140
        'regtype',
141
        'reltime',
142
        'serial',
143
        'smgr',
144
        'text',
145
        'tid',
146
        'tinterval',
147
        'tsquery',
148
        'tsvector',
149
        'varbit',
150
        'void',
151
        'xid',
152
    ];
153
    // List of all legal privileges that can be applied to different types
154
    // of objects.
155
    public $privlist = [
156
        'table'      => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'],
157
        'view'       => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'],
158
        'sequence'   => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'],
159
        'database'   => ['CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'],
160
        'function'   => ['EXECUTE', 'ALL PRIVILEGES'],
161
        'language'   => ['USAGE', 'ALL PRIVILEGES'],
162
        'schema'     => ['CREATE', 'USAGE', 'ALL PRIVILEGES'],
163
        'tablespace' => ['CREATE', 'ALL PRIVILEGES'],
164
        'column'     => ['SELECT', 'INSERT', 'UPDATE', 'REFERENCES', 'ALL PRIVILEGES'],
165
    ];
166
    // List of characters in acl lists and the privileges they
167
    // refer to.
168
    public $privmap = [
169
        'r' => 'SELECT',
170
        'w' => 'UPDATE',
171
        'a' => 'INSERT',
172
        'd' => 'DELETE',
173
        'D' => 'TRUNCATE',
174
        'R' => 'RULE',
175
        'x' => 'REFERENCES',
176
        't' => 'TRIGGER',
177
        'X' => 'EXECUTE',
178
        'U' => 'USAGE',
179
        'C' => 'CREATE',
180
        'T' => 'TEMPORARY',
181
        'c' => 'CONNECT',
182
    ];
183
    // Rule action types
184
    public $rule_events = ['SELECT', 'INSERT', 'UPDATE', 'DELETE'];
185
    // Select operators
186
    public $selectOps = [
187
        '='                   => 'i',
188
        '!='                  => 'i',
189
        '<'                   => 'i',
190
        '>'                   => 'i',
191
        '<='                  => 'i',
192
        '>='                  => 'i',
193
        '<<'                  => 'i',
194
        '>>'                  => 'i',
195
        '<<='                 => 'i',
196
        '>>='                 => 'i',
197
        'LIKE'                => 'i',
198
        'NOT LIKE'            => 'i',
199
        'ILIKE'               => 'i',
200
        'NOT ILIKE'           => 'i',
201
        'SIMILAR TO'          => 'i',
202
        'NOT SIMILAR TO'      => 'i',
203
        '~'                   => 'i',
204
        '!~'                  => 'i',
205
        '~*'                  => 'i',
206
        '!~*'                 => 'i',
207
        'IS NULL'             => 'p',
208
        'IS NOT NULL'         => 'p',
209
        'IN'                  => 'x',
210
        'NOT IN'              => 'x',
211
        '@@'                  => 'i',
212
        '@@@'                 => 'i',
213
        '@>'                  => 'i',
214
        '<@'                  => 'i',
215
        '@@ to_tsquery'       => 't',
216
        '@@@ to_tsquery'      => 't',
217
        '@> to_tsquery'       => 't',
218
        '<@ to_tsquery'       => 't',
219
        '@@ plainto_tsquery'  => 't',
220
        '@@@ plainto_tsquery' => 't',
221
        '@> plainto_tsquery'  => 't',
222
        '<@ plainto_tsquery'  => 't',
223
    ];
224
    // Array of allowed trigger events
225
    public $triggerEvents = [
226
        'INSERT',
227
        'UPDATE',
228
        'DELETE',
229
        'INSERT OR UPDATE',
230
        'INSERT OR DELETE',
231
        'DELETE OR UPDATE',
232
        'INSERT OR DELETE OR UPDATE',
233
    ];
234
    // When to execute the trigger
235
    public $triggerExecTimes = ['BEFORE', 'AFTER'];
236
    // How often to execute the trigger
237
    public $triggerFrequency = ['ROW', 'STATEMENT'];
238
    // Array of allowed type alignments
239
    public $typAligns = ['char', 'int2', 'int4', 'double'];
240
    // The default type alignment
241
    public $typAlignDef = 'int4';
242
    // Default index type
243
    public $typIndexDef = 'BTREE';
244
    // Array of allowed index types
245
    public $typIndexes = ['BTREE', 'RTREE', 'GIST', 'GIN', 'HASH'];
246
    // Array of allowed type storage attributes
247
    public $typStorages = ['plain', 'external', 'extended', 'main'];
248
    // The default type storage
249
    public $typStorageDef = 'plain';
250
251
    public function __construct(&$conn, $conf)
252
    {
253
        //$this->prtrace('major_version :' . $this->major_version);
254
        $this->conn = $conn;
255
        $this->conf = $conf;
256
    }
257
258
    /**
259
     * Fetch a URL (or array of URLs) for a given help page.
260
     *
261
     * @param $help
262
     * @return array|null|string
263
     */
264
    public function getHelp($help)
265
    {
266
        $this->getHelpPages();
267
268
        if (isset($this->help_page[$help])) {
269
            if (is_array($this->help_page[$help])) {
270
                $urls = [];
271
                foreach ($this->help_page[$help] as $link) {
272
                    $urls[] = $this->help_base . $link;
273
                }
274
275
                return $urls;
276
            }
277
278
            return $this->help_base . $this->help_page[$help];
279
        } else {
280
            return null;
281
        }
282
    }
283
284
    /**
285
     * get help page by instancing the corresponding help class
286
     * if $this->help_page and $this->help_base are set, this function is a noop
287
     */
288
    public function getHelpPages()
289
    {
290
        if ($this->help_page === null || $this->help_base === null) {
291
            $help_classname = '\PHPPgAdmin\Help\PostgresDoc' . str_replace('.', '', $this->major_version);
292
293
            $help_class = new $help_classname($this->conf, $this->major_version);
294
295
            $this->help_base = $help_class->getHelpBase();
296
            $this->help_page = $help_class->getHelpPage();
297
        }
298
    }
299
300
    // Formatting functions
301
302
    /**
303
     * Outputs the HTML code for a particular field
304
     *
305
     * @param                               $name   The name to give the field
306
     * @param                               $value  The value of the field.  Note this could be 'numeric(7,2)' sort of thing...
307
     * @param                               $type   The database type of the field
308
     * @param array|\PHPPgAdmin\Database\An $extras An array of attributes name as key and attributes' values as value
309
     */
310
    public function printField($name, $value, $type, $extras = [])
311
    {
312
        $lang = $this->lang;
313
314
        // Determine actions string
315
        $extra_str = '';
316
        foreach ($extras as $k => $v) {
317
            $extra_str .= " {$k}=\"" . htmlspecialchars($v) . '"';
318
        }
319
320
        switch (substr($type, 0, 9)) {
321
            case 'bool':
322
            case 'boolean':
323
                if ($value !== null && $value == '') {
324
                    $value = null;
325
                } elseif ($value == 'true') {
326
                    $value = 't';
327
                } elseif ($value == 'false') {
328
                    $value = 'f';
329
                }
330
331
                // If value is null, 't' or 'f'...
332
                if ($value === null || $value == 't' || $value == 'f') {
333
                    echo '<select name="', htmlspecialchars($name), "\"{$extra_str}>\n";
334
                    echo '<option value=""', ($value === null) ? ' selected="selected"' : '', "></option>\n";
335
                    echo '<option value="t"', ($value == 't') ? ' selected="selected"' : '', ">{$lang['strtrue']}</option>\n";
336
                    echo '<option value="f"', ($value == 'f') ? ' selected="selected"' : '', ">{$lang['strfalse']}</option>\n";
337
                    echo "</select>\n";
338
                } else {
339
                    echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
340
                }
341
                break;
342
            case 'bytea':
343
            case 'bytea[]':
344
                if (!is_null($value)) {
345
                    $value = $this->escapeBytea($value);
346
                }
347
            case 'text':
348
            case 'text[]':
349
            case 'json':
350
            case 'jsonb':
351
            case 'xml':
352 View Code Duplication
            case 'xml[]':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
353
                $n = substr_count($value, "\n");
354
                $n = $n < 5 ? max(2, $n) : $n;
355
                $n = $n > 20 ? 20 : $n;
356
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"85\"{$extra_str}>\n";
357
                echo htmlspecialchars($value);
358
                echo "</textarea>\n";
359
                break;
360
            case 'character':
361 View Code Duplication
            case 'character[]':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
362
                $n = substr_count($value, "\n");
363
                $n = $n < 5 ? 5 : $n;
364
                $n = $n > 20 ? 20 : $n;
365
                echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"35\"{$extra_str}>\n";
366
                echo htmlspecialchars($value);
367
                echo "</textarea>\n";
368
                break;
369
            default:
370
                echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n";
371
                break;
372
        }
373
    }
374
375
    /**
376
     * Escapes bytea data for display on the screen
377
     *
378
     * @param $data The bytea data
379
     * @return Data formatted for on-screen display
380
     */
381
    public function escapeBytea($data)
382
    {
383
        return htmlentities($data, ENT_QUOTES, 'UTF-8');
384
    }
385
386
    /**
387
     * Return all information about a particular database
388
     *
389
     * @param $database The name of the database to retrieve
390
     * @return The database info
391
     */
392
    public function getDatabase($database)
393
    {
394
        $this->clean($database);
395
        $sql = "SELECT * FROM pg_database WHERE datname='{$database}'";
396
397
        return $this->selectSet($sql);
398
    }
399
400
    /**
401
     * Cleans (escapes) a string
402
     *
403
     * @param $str The string to clean, by reference
404
     * @return The cleaned string
405
     */
406
    public function clean(&$str)
407
    {
408
        if ($str === null) {
409
            return null;
410
        }
411
412
        $str = str_replace("\r\n", "\n", $str);
413
        $str = pg_escape_string($str);
414
415
        return $str;
416
    }
417
418
    /**
419
     * Return all database available on the server
420
     *
421
     * @param $currentdatabase database name that should be on top of the resultset
422
     *
423
     * @return A list of databases, sorted alphabetically
424
     */
425
    public function getDatabases($currentdatabase = null)
426
    {
427
        $conf        = $this->conf;
428
        $server_info = $this->server_info;
429
430
        if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) {
431
            $username = $server_info['username'];
432
            $this->clean($username);
433
            $clause = " AND pr.rolname='{$username}'";
434
        } else {
435
            $clause = '';
436
        }
437
        if (isset($server_info['useonlydefaultdb']) && $server_info['useonlydefaultdb']) {
438
            $currentdatabase = $server_info['defaultdb'];
439
            $clause .= " AND pdb.datname = '{$currentdatabase}' ";
440
        }
441
442
        if (isset($server_info['hiddendbs']) && $server_info['hiddendbs']) {
443
            $hiddendbs = $server_info['hiddendbs'];
444
            $not_in    = "('" . implode("','", $hiddendbs) . "')";
445
            $clause .= " AND pdb.datname NOT IN {$not_in} ";
446
        }
447
448
        if ($currentdatabase != null) {
449
            $this->clean($currentdatabase);
450
            $orderby = "ORDER BY pdb.datname = '{$currentdatabase}' DESC, pdb.datname";
451
        } else {
452
            $orderby = 'ORDER BY pdb.datname';
453
        }
454
455
        if (!$conf['show_system']) {
456
            $where = ' AND NOT pdb.datistemplate';
457
        } else {
458
            $where = ' AND pdb.datallowconn';
459
        }
460
461
        $sql = "
462
			SELECT pdb.datname AS datname,
463
                    pr.rolname AS datowner,
464
                    pg_encoding_to_char(encoding) AS datencoding,
465
				    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pdb.oid=pd.objoid AND pd.classoid='pg_database'::regclass) AS datcomment,
466
				    (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace,
467
				CASE WHEN pg_catalog.has_database_privilege(current_user, pdb.oid, 'CONNECT')
468
					THEN pg_catalog.pg_database_size(pdb.oid)
469
					ELSE -1 -- set this magic value, which we will convert to no access later
470
				END as dbsize,
471
                pdb.datcollate,
472
                pdb.datctype
473
			FROM pg_catalog.pg_database pdb
474
            LEFT JOIN pg_catalog.pg_roles pr ON (pdb.datdba = pr.oid)
475
			WHERE true
476
				{$where}
477
				{$clause}
478
			{$orderby}";
479
480
        return $this->selectSet($sql);
481
    }
482
483
    /**
484
     * Determines whether or not a user is a super user
485
     *
486
     * @param \PHPPgAdmin\Database\The|string $username The username of the user
487
     * @return True if is a super user, false otherwise
488
     */
489
    public function isSuperUser($username = '')
490
    {
491
        $this->clean($username);
492
493
        if (empty($usename)) {
494
            $val = pg_parameter_status($this->conn->_connectionID, 'is_superuser');
495
            if ($val !== false) {
496
                return $val == 'on';
497
            }
498
        }
499
500
        $sql = "SELECT usesuper FROM pg_user WHERE usename='{$username}'";
501
502
        $usesuper = $this->selectField($sql, 'usesuper');
503
        if ($usesuper == -1) {
504
            return false;
505
        }
506
507
        return $usesuper == 't';
508
    }
509
510
    /**
511
     * Return the database comment of a db from the shared description table
512
     *
513
     * @param string $database the name of the database to get the comment for
514
     * @return recordset of the db comment info
515
     */
516
    public function getDatabaseComment($database)
517
    {
518
        $this->clean($database);
519
        $sql =
520
            "SELECT description FROM pg_catalog.pg_database JOIN pg_catalog.pg_shdescription ON (oid=objoid AND classoid='pg_database'::regclass) WHERE pg_database.datname = '{$database}' ";
521
522
        return $this->selectSet($sql);
523
    }
524
525
    /**
526
     * Return the database owner of a db
527
     *
528
     * @param string $database the name of the database to get the owner for
529
     * @return recordset of the db owner info
530
     */
531
    public function getDatabaseOwner($database)
532
    {
533
        $this->clean($database);
534
        $sql = "SELECT usename FROM pg_user, pg_database WHERE pg_user.usesysid = pg_database.datdba AND pg_database.datname = '{$database}' ";
535
536
        return $this->selectSet($sql);
537
    }
538
539
    // Help functions
540
541
    // Database functions
542
543
    /**
544
     * Returns the current database encoding
545
     *
546
     * @return The encoding.  eg. SQL_ASCII, UTF-8, etc.
547
     */
548
    public function getDatabaseEncoding()
549
    {
550
        return pg_parameter_status($this->conn->_connectionID, 'server_encoding');
551
    }
552
553
    /**
554
     * Returns the current default_with_oids setting
555
     *
556
     * @return default_with_oids setting
557
     */
558
    public function getDefaultWithOid()
559
    {
560
        $sql = 'SHOW default_with_oids';
561
562
        return $this->selectField($sql, 'default_with_oids');
563
    }
564
565
    /**
566
     * Creates a database
567
     *
568
     * @param        $database   The name of the database to create
569
     * @param        $encoding   Encoding of the database
570
     * @param string $tablespace (optional) The tablespace name
571
     * @param string $comment
572
     * @param string $template
573
     * @param string $lc_collate
574
     * @param string $lc_ctype
575
     * @return int 0 success
576
     */
577
    public function createDatabase(
578
        $database,
579
        $encoding,
580
        $tablespace = '',
581
        $comment = '',
582
        $template = 'template1',
583
        $lc_collate = '',
584
        $lc_ctype = ''
585
    ) {
586
        $this->fieldClean($database);
587
        $this->clean($encoding);
588
        $this->fieldClean($tablespace);
589
        $this->fieldClean($template);
590
        $this->clean($lc_collate);
591
        $this->clean($lc_ctype);
592
593
        $sql = "CREATE DATABASE \"{$database}\" WITH TEMPLATE=\"{$template}\"";
594
595
        if ($encoding != '') {
596
            $sql .= " ENCODING='{$encoding}'";
597
        }
598
599
        if ($lc_collate != '') {
600
            $sql .= " LC_COLLATE='{$lc_collate}'";
601
        }
602
603
        if ($lc_ctype != '') {
604
            $sql .= " LC_CTYPE='{$lc_ctype}'";
605
        }
606
607
        if ($tablespace != '' && $this->hasTablespaces()) {
608
            $sql .= " TABLESPACE \"{$tablespace}\"";
609
        }
610
611
        $status = $this->execute($sql);
612
        if ($status != 0) {
613
            return -1;
614
        }
615
616 View Code Duplication
        if ($comment != '' && $this->hasSharedComments()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
617
            $status = $this->setComment('DATABASE', $database, '', $comment);
618
            if ($status != 0) {
619
                return -2;
620
            }
621
        }
622
623
        return 0;
624
    }
625
626
    /**
627
     * Cleans (escapes) an object name (eg. table, field)
628
     *
629
     * @param $str The string to clean, by reference
630
     * @return The cleaned string
631
     */
632
    public function fieldClean(&$str)
633
    {
634
        if ($str === null) {
635
            return null;
636
        }
637
638
        $str = str_replace('"', '""', $str);
639
640
        return $str;
641
    }
642
643
    public function hasTablespaces()
644
    {
645
        return true;
646
    }
647
648
    public function hasSharedComments()
649
    {
650
        return true;
651
    }
652
653
    /**
654
     * Sets the comment for an object in the database
655
     *
656
     * @pre All parameters must already be cleaned
657
     * @param      $obj_type One of 'TABLE' | 'COLUMN' | 'VIEW' | 'SCHEMA' | 'SEQUENCE' | 'TYPE' | 'FUNCTION' | 'AGGREGATE'
658
     * @param      $obj_name The name of the object for which to attach a comment.
659
     * @param      $table    Name of table that $obj_name belongs to.  Ignored unless $obj_type is 'TABLE' or 'COLUMN'.
660
     * @param      $comment  The comment to add.
661
     * @param null $basetype
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $basetype is correct as it would always require null to be passed?
Loading history...
662
     * @return int|\PHPPgAdmin\Database\A 0 success
663
     */
664
    public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null)
665
    {
666
        $sql      = "COMMENT ON {$obj_type} ";
667
        $f_schema = $this->_schema;
668
        $this->fieldClean($f_schema);
669
        $this->clean($comment); // Passing in an already cleaned comment will lead to double escaped data
670
        // So, while counter-intuitive, it is important to not clean comments before
671
        // calling setComment. We will clean it here instead.
672
        /*
673
        $this->fieldClean($table);
674
        $this->fieldClean($obj_name);
675
         */
676
677
        switch ($obj_type) {
678
            case 'TABLE':
679
                $sql .= "\"{$f_schema}\".\"{$table}\" IS ";
680
                break;
681
            case 'COLUMN':
682
                $sql .= "\"{$f_schema}\".\"{$table}\".\"{$obj_name}\" IS ";
683
                break;
684
            case 'SEQUENCE':
685
            case 'VIEW':
686
            case 'TEXT SEARCH CONFIGURATION':
687
            case 'TEXT SEARCH DICTIONARY':
688
            case 'TEXT SEARCH TEMPLATE':
689
            case 'TEXT SEARCH PARSER':
690
            case 'TYPE':
691
                $sql .= "\"{$f_schema}\".";
692
            case 'DATABASE':
693
            case 'ROLE':
694
            case 'SCHEMA':
695
            case 'TABLESPACE':
696
                $sql .= "\"{$obj_name}\" IS ";
697
                break;
698
            case 'FUNCTION':
699
                $sql .= "\"{$f_schema}\".{$obj_name} IS ";
700
                break;
701
            case 'AGGREGATE':
702
                $sql .= "\"{$f_schema}\".\"{$obj_name}\" (\"{$basetype}\") IS ";
703
                break;
704
            default:
705
                // Unknown object type
706
                return -1;
707
        }
708
709
        if ($comment != '') {
710
            $sql .= "'{$comment}';";
711
        } else {
712
            $sql .= 'NULL;';
713
        }
714
715
        return $this->execute($sql);
716
    }
717
718
    /**
719
     * Drops a database
720
     *
721
     * @param $database The name of the database to drop
722
     * @return \PHPPgAdmin\Database\A 0 success
723
     */
724
    public function dropDatabase($database)
725
    {
726
        $this->fieldClean($database);
727
        $sql = "DROP DATABASE \"{$database}\"";
728
729
        return $this->execute($sql);
730
    }
731
732
    /**
733
     * Alters a database
734
     * the multiple return vals are for postgres 8+ which support more functionality in alter database
735
     *
736
     * @param                                 $dbName   The name of the database
737
     * @param                                 $newName  new name for the database
738
     * @param \PHPPgAdmin\Database\The|string $newOwner The new owner for the database
739
     * @param string                          $comment
740
     * @return bool|int 0 success
741
     */
742
    public function alterDatabase($dbName, $newName, $newOwner = '', $comment = '')
743
    {
744
        $status = $this->beginTransaction();
745
        if ($status != 0) {
746
            $this->rollbackTransaction();
747
748
            return -1;
749
        }
750
751 View Code Duplication
        if ($dbName != $newName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
752
            $status = $this->alterDatabaseRename($dbName, $newName);
753
            if ($status != 0) {
754
                $this->rollbackTransaction();
755
756
                return -3;
757
            }
758
            $dbName = $newName;
759
        }
760
761
        if ($newOwner != '') {
762
            $status = $this->alterDatabaseOwner($newName, $newOwner);
763
            if ($status != 0) {
764
                $this->rollbackTransaction();
765
766
                return -2;
767
            }
768
        }
769
770
        $this->fieldClean($dbName);
771
        $status = $this->setComment('DATABASE', $dbName, '', $comment);
772
        if ($status != 0) {
773
            $this->rollbackTransaction();
774
775
            return -4;
776
        }
777
778
        return $this->endTransaction();
779
    }
780
781
    /**
782
     * Renames a database, note that this operation cannot be
783
     * performed on a database that is currently being connected to
784
     *
785
     * @param string $oldName name of database to rename
786
     * @param string $newName new name of database
787
     * @return int 0 on success
788
     */
789
    public function alterDatabaseRename($oldName, $newName)
790
    {
791
        $this->fieldClean($oldName);
792
        $this->fieldClean($newName);
793
794
        if ($oldName != $newName) {
795
            $sql = "ALTER DATABASE \"{$oldName}\" RENAME TO \"{$newName}\"";
796
797
            return $this->execute($sql);
798
        }
799
800
        return 0;
801
    }
802
803
    /**
804
     * Changes ownership of a database
805
     * This can only be done by a superuser or the owner of the database
806
     *
807
     * @param string $dbName   database to change ownership of
808
     * @param string $newOwner user that will own the database
809
     * @return int 0 on success
810
     */
811
    public function alterDatabaseOwner($dbName, $newOwner)
812
    {
813
        $this->fieldClean($dbName);
814
        $this->fieldClean($newOwner);
815
816
        $sql = "ALTER DATABASE \"{$dbName}\" OWNER TO \"{$newOwner}\"";
817
818
        return $this->execute($sql);
819
    }
820
821
    /**
822
     * Returns prepared transactions information
823
     *
824
     * @param $database (optional) Find only prepared transactions executed in a specific database
825
     * @return A recordset
826
     */
827
    public function getPreparedXacts($database = null)
828
    {
829
        if ($database === null) {
830
            $sql = 'SELECT * FROM pg_prepared_xacts';
831
        } else {
832
            $this->clean($database);
833
            $sql = "SELECT transaction, gid, prepared, owner FROM pg_prepared_xacts
834
				WHERE database='{$database}' ORDER BY owner";
835
        }
836
837
        return $this->selectSet($sql);
838
    }
839
840
    /**
841
     * Searches all system catalogs to find objects that match a certain name.
842
     *
843
     * @param $term   The search term
844
     * @param $filter The object type to restrict to ('' means no restriction)
845
     * @return A recordset
846
     */
847 View Code Duplication
    public function findObject($term, $filter)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
848
    {
849
        $conf = $this->conf;
850
851
        /*about escaping:
852
         * SET standard_conforming_string is not available before 8.2
853
         * So we must use PostgreSQL specific notation :/
854
         * E'' notation is not available before 8.1
855
         * $$ is available since 8.0
856
         * Nothing specific from 7.4
857
         */
858
859
        // Escape search term for ILIKE match
860
        $this->clean($term);
861
        $this->clean($filter);
862
        $term = str_replace('_', '\_', $term);
863
        $term = str_replace('%', '\%', $term);
864
865
        // Exclude system relations if necessary
866
        if (!$conf['show_system']) {
867
            // XXX: The mention of information_schema here is in the wrong place, but
868
            // it's the quickest fix to exclude the info schema from 7.4
869
            $where     = " AND pn.nspname NOT LIKE \$_PATERN_\$pg\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'";
870
            $lan_where = 'AND pl.lanispl';
871
        } else {
872
            $where     = '';
873
            $lan_where = '';
874
        }
875
876
        // Apply outer filter
877
        $sql = '';
878
        if ($filter != '') {
879
            $sql = 'SELECT * FROM (';
880
        }
881
882
        $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$";
883
884
        $sql .= "
885
			SELECT 'SCHEMA' AS type, oid, NULL AS schemaname, NULL AS relname, nspname AS name
886
				FROM pg_catalog.pg_namespace pn WHERE nspname ILIKE {$term} {$where}
887
			UNION ALL
888
			SELECT CASE WHEN relkind='r' THEN 'TABLE' WHEN relkind='v' THEN 'VIEW' WHEN relkind='S' THEN 'SEQUENCE' END, pc.oid,
889
				pn.nspname, NULL, pc.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn
890
				WHERE pc.relnamespace=pn.oid AND relkind IN ('r', 'v', 'S') AND relname ILIKE {$term} {$where}
891
			UNION ALL
892
			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,
893
				pg_catalog.pg_attribute pa WHERE pc.relnamespace=pn.oid AND pc.oid=pa.attrelid
894
				AND pa.attname ILIKE {$term} AND pa.attnum > 0 AND NOT pa.attisdropped AND pc.relkind IN ('r', 'v') {$where}
895
			UNION ALL
896
			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
897
				WHERE pp.pronamespace=pn.oid AND NOT pp.proisagg AND pp.proname ILIKE {$term} {$where}
898
			UNION ALL
899
			SELECT 'INDEX', NULL, pn.nspname, pc.relname, pc2.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
900
				pg_catalog.pg_index pi, pg_catalog.pg_class pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pi.indrelid
901
				AND pi.indexrelid=pc2.oid
902
				AND NOT EXISTS (
903
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
904
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
905
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
906
				)
907
				AND pc2.relname ILIKE {$term} {$where}
908
			UNION ALL
909
			SELECT 'CONSTRAINTTABLE', NULL, pn.nspname, pc.relname, pc2.conname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
910
				pg_catalog.pg_constraint pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pc2.conrelid AND pc2.conrelid != 0
911
				AND CASE WHEN pc2.contype IN ('f', 'c') THEN TRUE ELSE NOT EXISTS (
912
					SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
913
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
914
					WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
915
				) END
916
				AND pc2.conname ILIKE {$term} {$where}
917
			UNION ALL
918
			SELECT 'CONSTRAINTDOMAIN', pt.oid, pn.nspname, pt.typname, pc.conname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn,
919
				pg_catalog.pg_constraint pc WHERE pt.typnamespace=pn.oid AND pt.oid=pc.contypid AND pc.contypid != 0
920
				AND pc.conname ILIKE {$term} {$where}
921
			UNION ALL
922
			SELECT 'TRIGGER', NULL, pn.nspname, pc.relname, pt.tgname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
923
				pg_catalog.pg_trigger pt WHERE pc.relnamespace=pn.oid AND pc.oid=pt.tgrelid
924
					AND ( pt.tgconstraint = 0 OR NOT EXISTS
925
					(SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
926
					ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
927
					WHERE d.classid = pt.tableoid AND d.objid = pt.oid AND d.deptype = 'i' AND c.contype = 'f'))
928
				AND pt.tgname ILIKE {$term} {$where}
929
			UNION ALL
930
			SELECT 'RULETABLE', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
931
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
932
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
933
				WHERE c.relkind='r' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
934
			UNION ALL
935
			SELECT 'RULEVIEW', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
936
				JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
937
				LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
938
				WHERE c.relkind='v' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
939
		";
940
941
        // Add advanced objects if show_advanced is set
942
        if ($conf['show_advanced']) {
943
            $sql .= "
944
				UNION ALL
945
				SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL,
946
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
947
					WHERE pt.typnamespace=pn.oid AND typname ILIKE {$term}
948
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
949
					{$where}
950
			 	UNION ALL
951
				SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn
952
					WHERE po.oprnamespace=pn.oid AND oprname ILIKE {$term} {$where}
953
				UNION ALL
954
				SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc,
955
					pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE {$term} {$where}
956
				UNION ALL
957
				SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl
958
					WHERE lanname ILIKE {$term} {$lan_where}
959
				UNION ALL
960
				SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p
961
					LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid
962
					WHERE p.proisagg AND p.proname ILIKE {$term} {$where}
963
				UNION ALL
964
				SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po,
965
					pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid
966
					AND po.opcname ILIKE {$term} {$where}
967
			";
968
        } // Otherwise just add domains
969
        else {
970
            $sql .= "
971
				UNION ALL
972
				SELECT 'DOMAIN', pt.oid, pn.nspname, NULL,
973
					pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
974
					WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE {$term}
975
					AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
976
					{$where}
977
			";
978
        }
979
980
        if ($filter != '') {
981
            // We use like to make RULE, CONSTRAINT and COLUMN searches work
982
            $sql .= ") AS sub WHERE type LIKE '{$filter}%' ";
983
        }
984
985
        $sql .= 'ORDER BY type, schemaname, relname, name';
986
987
        return $this->selectSet($sql);
988
    }
989
990
    /**
991
     * Returns all available variable information.
992
     *
993
     * @return A recordset
994
     */
995
    public function getVariables()
996
    {
997
        $sql = 'SHOW ALL';
998
999
        return $this->selectSet($sql);
1000
    }
1001
1002
    // Schema functons
1003
1004
    /**
1005
     * Return all schemas in the current database.
1006
     *
1007
     * @return All schemas, sorted alphabetically
1008
     */
1009 View Code Duplication
    public function getSchemas()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1010
    {
1011
        $conf = $this->conf;
1012
1013
        if (!$conf['show_system']) {
1014
            $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'";
1015
        } else {
1016
            $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
1017
        }
1018
1019
        $sql = "
1020
			SELECT pn.nspname, pu.rolname AS nspowner,
1021
				pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment
1022
			FROM pg_catalog.pg_namespace pn
1023
				LEFT JOIN pg_catalog.pg_roles pu ON (pn.nspowner = pu.oid)
1024
			{$where}
1025
			ORDER BY nspname";
1026
1027
        return $this->selectSet($sql);
1028
    }
1029
1030
    /**
1031
     * Sets the current working schema.  Will also set Class variable.
1032
     *
1033
     * @param $schema The the name of the schema to work in
1034
     * @return int|\PHPPgAdmin\Database\A 0 success
1035
     */
1036
    public function setSchema($schema)
1037
    {
1038
        // Get the current schema search path, including 'pg_catalog'.
1039
        $search_path = $this->getSearchPath();
1040
        // Prepend $schema to search path
1041
        array_unshift($search_path, $schema);
1042
        $status = $this->setSearchPath($search_path);
1043
        if ($status == 0) {
1044
            $this->_schema = $schema;
1045
1046
            return 0;
1047
        }
1048
1049
        return $status;
1050
    }
1051
1052
    /**
1053
     * Return the current schema search path
1054
     *
1055
     * @return Array of schema names
1056
     */
1057
    public function getSearchPath()
1058
    {
1059
        $sql = 'SELECT current_schemas(false) AS search_path';
1060
1061
        return $this->phpArray($this->selectField($sql, 'search_path'));
1062
    }
1063
1064
    /**
1065
     * Sets the current schema search path
1066
     *
1067
     * @param $paths An array of schemas in required search order
1068
     * @return int|\PHPPgAdmin\Database\A 0 success
1069
     */
1070
    public function setSearchPath($paths)
1071
    {
1072
        if (!is_array($paths)) {
1073
            return -1;
1074
        }
1075
1076
        if (sizeof($paths) == 0) {
1077
            return -2;
1078
        } elseif (sizeof($paths) == 1 && $paths[0] == '') {
1079
            // Need to handle empty paths in some cases
1080
            $paths[0] = 'pg_catalog';
1081
        }
1082
1083
        // Loop over all the paths to check that none are empty
1084
        $temp = [];
1085
        foreach ($paths as $schema) {
1086
            if ($schema != '') {
1087
                $temp[] = $schema;
1088
            }
1089
        }
1090
        $this->fieldArrayClean($temp);
1091
1092
        $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"';
1093
1094
        return $this->execute($sql);
1095
    }
1096
1097
    /**
1098
     * Cleans (escapes) an array of field names
1099
     *
1100
     * @param $arr The array to clean, by reference
1101
     * @return The cleaned array
1102
     */
1103
    public function fieldArrayClean(&$arr)
1104
    {
1105
        foreach ($arr as $k => $v) {
1106
            if ($v === null) {
1107
                continue;
1108
            }
1109
1110
            $arr[$k] = str_replace('"', '""', $v);
1111
        }
1112
1113
        return $arr;
1114
    }
1115
1116
    /**
1117
     * Creates a new schema.
1118
     *
1119
     * @param        $schemaname    The name of the schema to create
1120
     * @param string $authorization (optional) The username to create the schema for.
1121
     * @param string $comment       (optional) If omitted, defaults to nothing
1122
     * @return bool|int 0 success
1123
     */
1124
    public function createSchema($schemaname, $authorization = '', $comment = '')
1125
    {
1126
        $this->fieldClean($schemaname);
1127
        $this->fieldClean($authorization);
1128
1129
        $sql = "CREATE SCHEMA \"{$schemaname}\"";
1130
        if ($authorization != '') {
1131
            $sql .= " AUTHORIZATION \"{$authorization}\"";
1132
        }
1133
1134
        if ($comment != '') {
1135
            $status = $this->beginTransaction();
1136
            if ($status != 0) {
1137
                return -1;
1138
            }
1139
        }
1140
1141
        // Create the new schema
1142
        $status = $this->execute($sql);
1143
        if ($status != 0) {
1144
            $this->rollbackTransaction();
1145
1146
            return -1;
1147
        }
1148
1149
        // Set the comment
1150 View Code Duplication
        if ($comment != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1151
            $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
1152
            if ($status != 0) {
1153
                $this->rollbackTransaction();
1154
1155
                return -1;
1156
            }
1157
1158
            return $this->endTransaction();
1159
        }
1160
1161
        return 0;
1162
    }
1163
1164
    /**
1165
     * Updates a schema.
1166
     *
1167
     * @param $schemaname The name of the schema to drop
1168
     * @param $comment    The new comment for this schema
1169
     * @param $name
1170
     * @param $owner      The new owner for this schema
1171
     * @return bool|int 0 success
1172
     */
1173
    public function updateSchema($schemaname, $comment, $name, $owner)
1174
    {
1175
        $this->fieldClean($schemaname);
1176
        $this->fieldClean($name);
1177
        $this->fieldClean($owner);
1178
1179
        $status = $this->beginTransaction();
1180
        if ($status != 0) {
1181
            $this->rollbackTransaction();
1182
1183
            return -1;
1184
        }
1185
1186
        $status = $this->setComment('SCHEMA', $schemaname, '', $comment);
1187
        if ($status != 0) {
1188
            $this->rollbackTransaction();
1189
1190
            return -1;
1191
        }
1192
1193
        $schema_rs = $this->getSchemaByName($schemaname);
1194
        /* Only if the owner change */
1195 View Code Duplication
        if ($schema_rs->fields['ownername'] != $owner) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1196
            $sql    = "ALTER SCHEMA \"{$schemaname}\" OWNER TO \"{$owner}\"";
1197
            $status = $this->execute($sql);
1198
            if ($status != 0) {
1199
                $this->rollbackTransaction();
1200
1201
                return -1;
1202
            }
1203
        }
1204
1205
        // Only if the name has changed
1206 View Code Duplication
        if ($name != $schemaname) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1207
            $sql    = "ALTER SCHEMA \"{$schemaname}\" RENAME TO \"{$name}\"";
1208
            $status = $this->execute($sql);
1209
            if ($status != 0) {
1210
                $this->rollbackTransaction();
1211
1212
                return -1;
1213
            }
1214
        }
1215
1216
        return $this->endTransaction();
1217
    }
1218
1219
    /**
1220
     * Return all information relating to a schema
1221
     *
1222
     * @param $schema The name of the schema
1223
     * @return Schema information
1224
     */
1225
    public function getSchemaByName($schema)
1226
    {
1227
        $this->clean($schema);
1228
        $sql = "
1229
			SELECT nspname, nspowner, r.rolname AS ownername, nspacl,
1230
				pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment
1231
			FROM pg_catalog.pg_namespace pn
1232
				LEFT JOIN pg_roles as r ON pn.nspowner = r.oid
1233
			WHERE nspname='{$schema}'";
1234
1235
        return $this->selectSet($sql);
1236
    }
1237
1238
    // Table functions
1239
1240
    /**
1241
     * Drops a schema.
1242
     *
1243
     * @param $schemaname The name of the schema to drop
1244
     * @param $cascade    True to cascade drop, false to restrict
1245
     * @return \PHPPgAdmin\Database\A 0 success
1246
     */
1247
    public function dropSchema($schemaname, $cascade)
1248
    {
1249
        $this->fieldClean($schemaname);
1250
1251
        $sql = "DROP SCHEMA \"{$schemaname}\"";
1252
        if ($cascade) {
1253
            $sql .= ' CASCADE';
1254
        }
1255
1256
        return $this->execute($sql);
1257
    }
1258
1259
    /**
1260
     * Return all tables in current database (and schema)
1261
     *
1262
     * @param bool|True $all True to fetch all tables, false for just in current schema
1263
     * @return \PHPPgAdmin\Database\All tables, sorted alphabetically
1264
     */
1265
    public function getTables($all = false)
1266
    {
1267
        $c_schema = $this->_schema;
1268
        $this->clean($c_schema);
1269
        if ($all) {
1270
            // Exclude pg_catalog and information_schema tables
1271
            $sql = "SELECT schemaname AS nspname, tablename AS relname, tableowner AS relowner
1272
					FROM pg_catalog.pg_tables
1273
					WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
1274
					ORDER BY schemaname, tablename";
1275
        } else {
1276
            $sql = "SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
1277
						pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
1278
						reltuples::bigint,
1279
						(SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
1280
					FROM pg_catalog.pg_class c
1281
					LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1282
					WHERE c.relkind = 'r'
1283
					AND nspname='{$c_schema}'
1284
					ORDER BY c.relname";
1285
        }
1286
1287
        return $this->selectSet($sql);
1288
    }
1289
1290
    /**
1291
     * Finds the names and schemas of parent tables (in order)
1292
     *
1293
     * @param $table The table to find the parents for
1294
     * @return A recordset
1295
     */
1296 View Code Duplication
    public function getTableParents($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1297
    {
1298
        $c_schema = $this->_schema;
1299
        $this->clean($c_schema);
1300
        $this->clean($table);
1301
1302
        $sql = "
1303
			SELECT
1304
				pn.nspname, relname
1305
			FROM
1306
				pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
1307
			WHERE
1308
				pc.oid=pi.inhparent
1309
				AND pc.relnamespace=pn.oid
1310
				AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
1311
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
1312
			ORDER BY
1313
				pi.inhseqno
1314
		";
1315
1316
        return $this->selectSet($sql);
1317
    }
1318
1319
    /**
1320
     * Finds the names and schemas of child tables
1321
     *
1322
     * @param $table The table to find the children for
1323
     * @return A recordset
1324
     */
1325 View Code Duplication
    public function getTableChildren($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1326
    {
1327
        $c_schema = $this->_schema;
1328
        $this->clean($c_schema);
1329
        $this->clean($table);
1330
1331
        $sql = "
1332
			SELECT
1333
				pn.nspname, relname
1334
			FROM
1335
				pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
1336
			WHERE
1337
				pc.oid=pi.inhrelid
1338
				AND pc.relnamespace=pn.oid
1339
				AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
1340
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
1341
		";
1342
1343
        return $this->selectSet($sql);
1344
    }
1345
1346
    /**
1347
     * Returns the SQL definition for the table.
1348
     *
1349
     * @pre MUST be run within a transaction
1350
     * @param           $table The table to define
1351
     * @param bool|True $clean True to issue drop command, false otherwise
1352
     * @return \PHPPgAdmin\Database\A string containing the formatted SQL code
1353
     */
1354
    public function getTableDefPrefix($table, $clean = false)
1355
    {
1356
        // Fetch table
1357
        $t = $this->getTable($table);
1358
        if (!is_object($t) || $t->recordCount() != 1) {
1359
            $this->rollbackTransaction();
1360
1361
            return null;
1362
        }
1363
        $this->fieldClean($t->fields['relname']);
1364
        $this->fieldClean($t->fields['nspname']);
1365
1366
        // Fetch attributes
1367
        $atts = $this->getTableAttributes($table);
1368
        if (!is_object($atts)) {
1369
            $this->rollbackTransaction();
1370
1371
            return null;
1372
        }
1373
1374
        // Fetch constraints
1375
        $cons = $this->getConstraints($table);
1376
        if (!is_object($cons)) {
1377
            $this->rollbackTransaction();
1378
1379
            return null;
1380
        }
1381
1382
        // Output a reconnect command to create the table as the correct user
1383
        $sql = $this->getChangeUserSQL($t->fields['relowner']) . "\n\n";
1384
1385
        // Set schema search path
1386
        $sql .= "SET search_path = \"{$t->fields['nspname']}\", pg_catalog;\n\n";
1387
1388
        // Begin CREATE TABLE definition
1389
        $sql .= "-- Definition\n\n";
1390
        // DROP TABLE must be fully qualified in case a table with the same name exists
1391
        // in pg_catalog.
1392
        if (!$clean) {
1393
            $sql .= '-- ';
1394
        }
1395
1396
        $sql .= 'DROP TABLE ';
1397
        $sql .= "\"{$t->fields['nspname']}\".\"{$t->fields['relname']}\";\n";
1398
        $sql .= "CREATE TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" (\n";
1399
1400
        // Output all table columns
1401
        $col_comments_sql = ''; // Accumulate comments on columns
1402
        $num              = $atts->recordCount() + $cons->recordCount();
1403
        $i                = 1;
1404
        while (!$atts->EOF) {
1405
            $this->fieldClean($atts->fields['attname']);
1406
            $sql .= "    \"{$atts->fields['attname']}\"";
1407
            // Dump SERIAL and BIGSERIAL columns correctly
1408
            if ($this->phpBool($atts->fields['attisserial']) &&
1409
                ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) {
1410
                if ($atts->fields['type'] == 'integer') {
1411
                    $sql .= ' SERIAL';
1412
                } else {
1413
                    $sql .= ' BIGSERIAL';
1414
                }
1415
            } else {
1416
                $sql .= ' ' . $this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
1417
1418
                // Add NOT NULL if necessary
1419
                if ($this->phpBool($atts->fields['attnotnull'])) {
1420
                    $sql .= ' NOT NULL';
1421
                }
1422
1423
                // Add default if necessary
1424
                if ($atts->fields['adsrc'] !== null) {
1425
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
1426
                }
1427
            }
1428
1429
            // Output comma or not
1430
            if ($i < $num) {
1431
                $sql .= ",\n";
1432
            } else {
1433
                $sql .= "\n";
1434
            }
1435
1436
            // Does this column have a comment?
1437 View Code Duplication
            if ($atts->fields['comment'] !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1438
                $this->clean($atts->fields['comment']);
1439
                $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
1440
            }
1441
1442
            $atts->moveNext();
1443
            $i++;
1444
        }
1445
        // Output all table constraints
1446
        while (!$cons->EOF) {
1447
            $this->fieldClean($cons->fields['conname']);
1448
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
1449
            // Nasty hack to support pre-7.4 PostgreSQL
1450
            if ($cons->fields['consrc'] !== null) {
1451
                $sql .= $cons->fields['consrc'];
1452
            } else {
1453
                switch ($cons->fields['contype']) {
1454 View Code Duplication
                    case 'p':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1455
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
1456
                        $sql .= 'PRIMARY KEY (' . join(',', $keys) . ')';
1457
                        break;
1458 View Code Duplication
                    case 'u':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1459
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
1460
                        $sql .= 'UNIQUE (' . join(',', $keys) . ')';
1461
                        break;
1462
                    default:
1463
                        // Unrecognised constraint
1464
                        $this->rollbackTransaction();
1465
1466
                        return null;
1467
                }
1468
            }
1469
1470
            // Output comma or not
1471
            if ($i < $num) {
1472
                $sql .= ",\n";
1473
            } else {
1474
                $sql .= "\n";
1475
            }
1476
1477
            $cons->moveNext();
1478
            $i++;
1479
        }
1480
1481
        $sql .= ')';
1482
1483
        // @@@@ DUMP CLUSTERING INFORMATION
1484
1485
        // Inherits
1486
        /*
1487
         * XXX: This is currently commented out as handling inheritance isn't this simple.
1488
         * You also need to make sure you don't dump inherited columns and defaults, as well
1489
         * as inherited NOT NULL and CHECK constraints.  So for the time being, we just do
1490
         * not claim to support inheritance.
1491
        $parents = $this->getTableParents($table);
1492
        if ($parents->recordCount() > 0) {
1493
        $sql .= " INHERITS (";
1494
        while (!$parents->EOF) {
1495
        $this->fieldClean($parents->fields['relname']);
1496
        // Qualify the parent table if it's in another schema
1497
        if ($parents->fields['schemaname'] != $this->_schema) {
1498
        $this->fieldClean($parents->fields['schemaname']);
1499
        $sql .= "\"{$parents->fields['schemaname']}\".";
1500
        }
1501
        $sql .= "\"{$parents->fields['relname']}\"";
1502
1503
        $parents->moveNext();
1504
        if (!$parents->EOF) $sql .= ', ';
1505
        }
1506
        $sql .= ")";
1507
        }
1508
         */
1509
1510
        // Handle WITHOUT OIDS
1511
        if ($this->hasObjectID($table)) {
1512
            $sql .= ' WITH OIDS';
1513
        } else {
1514
            $sql .= ' WITHOUT OIDS';
1515
        }
1516
1517
        $sql .= ";\n";
1518
1519
        // Column storage and statistics
1520
        $atts->moveFirst();
1521
        $first = true;
1522
        while (!$atts->EOF) {
1523
            $this->fieldClean($atts->fields['attname']);
1524
            // Statistics first
1525
            if ($atts->fields['attstattarget'] >= 0) {
1526
                if ($first) {
1527
                    $sql .= "\n";
1528
                    $first = false;
1529
                }
1530
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
1531
            }
1532
            // Then storage
1533
            if ($atts->fields['attstorage'] != $atts->fields['typstorage']) {
1534
                switch ($atts->fields['attstorage']) {
1535
                    case 'p':
1536
                        $storage = 'PLAIN';
1537
                        break;
1538
                    case 'e':
1539
                        $storage = 'EXTERNAL';
1540
                        break;
1541
                    case 'm':
1542
                        $storage = 'MAIN';
1543
                        break;
1544
                    case 'x':
1545
                        $storage = 'EXTENDED';
1546
                        break;
1547
                    default:
1548
                        // Unknown storage type
1549
                        $this->rollbackTransaction();
1550
1551
                        return null;
1552
                }
1553
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
1554
            }
1555
1556
            $atts->moveNext();
1557
        }
1558
1559
        // Comment
1560 View Code Duplication
        if ($t->fields['relcomment'] !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1561
            $this->clean($t->fields['relcomment']);
1562
            $sql .= "\n-- Comment\n\n";
1563
            $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n";
1564
        }
1565
1566
        // Add comments on columns, if any
1567
        if ($col_comments_sql != '') {
1568
            $sql .= $col_comments_sql;
1569
        }
1570
1571
        // Privileges
1572
        $privs = $this->getPrivileges($table, 'table');
1573
        if (!is_array($privs)) {
1574
            $this->rollbackTransaction();
1575
1576
            return null;
1577
        }
1578
1579
        if (sizeof($privs) > 0) {
1580
            $sql .= "\n-- Privileges\n\n";
1581
            /*
1582
             * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
1583
             * wire-in knowledge about the default public privileges for different
1584
             * kinds of objects.
1585
             */
1586
            $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n";
1587
            foreach ($privs as $v) {
1588
                // Get non-GRANT OPTION privs
1589
                $nongrant = array_diff($v[2], $v[4]);
1590
1591
                // Skip empty or owner ACEs
1592
                if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) {
1593
                    continue;
1594
                }
1595
1596
                // Change user if necessary
1597 View Code Duplication
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1598
                    $grantor = $v[3];
1599
                    $this->clean($grantor);
1600
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
1601
                }
1602
1603
                // Output privileges with no GRANT OPTION
1604
                $sql .= 'GRANT ' . join(', ', $nongrant) . " ON TABLE \"{$t->fields['relname']}\" TO ";
1605 View Code Duplication
                switch ($v[0]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1606
                    case 'public':
1607
                        $sql .= "PUBLIC;\n";
1608
                        break;
1609
                    case 'user':
1610
                        $this->fieldClean($v[1]);
1611
                        $sql .= "\"{$v[1]}\";\n";
1612
                        break;
1613
                    case 'group':
1614
                        $this->fieldClean($v[1]);
1615
                        $sql .= "GROUP \"{$v[1]}\";\n";
1616
                        break;
1617
                    default:
1618
                        // Unknown privilege type - fail
1619
                        $this->rollbackTransaction();
1620
1621
                        return null;
1622
                }
1623
1624
                // Reset user if necessary
1625
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
1626
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
1627
                }
1628
1629
                // Output privileges with GRANT OPTION
1630
1631
                // Skip empty or owner ACEs
1632
                if (!$this->hasGrantOption() || sizeof($v[4]) == 0) {
1633
                    continue;
1634
                }
1635
1636
                // Change user if necessary
1637 View Code Duplication
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1638
                    $grantor = $v[3];
1639
                    $this->clean($grantor);
1640
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
1641
                }
1642
1643
                $sql .= 'GRANT ' . join(', ', $v[4]) . " ON \"{$t->fields['relname']}\" TO ";
1644 View Code Duplication
                switch ($v[0]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1645
                    case 'public':
1646
                        $sql .= 'PUBLIC';
1647
                        break;
1648
                    case 'user':
1649
                        $this->fieldClean($v[1]);
1650
                        $sql .= "\"{$v[1]}\"";
1651
                        break;
1652
                    case 'group':
1653
                        $this->fieldClean($v[1]);
1654
                        $sql .= "GROUP \"{$v[1]}\"";
1655
                        break;
1656
                    default:
1657
                        // Unknown privilege type - fail
1658
                        return null;
1659
                }
1660
                $sql .= " WITH GRANT OPTION;\n";
1661
1662
                // Reset user if necessary
1663
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
1664
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
1665
                }
1666
            }
1667
        }
1668
1669
        // Add a newline to separate data that follows (if any)
1670
        $sql .= "\n";
1671
1672
        return $sql;
1673
    }
1674
1675
    /**
1676
     * Returns table information
1677
     *
1678
     * @param $table The name of the table
1679
     * @return A recordset
1680
     */
1681 View Code Duplication
    public function getTable($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1682
    {
1683
        $c_schema = $this->_schema;
1684
        $this->clean($c_schema);
1685
        $this->clean($table);
1686
1687
        $sql = "
1688
			SELECT
1689
			  c.relname, n.nspname, u.usename AS relowner,
1690
			  pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
1691
			  (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
1692
			FROM pg_catalog.pg_class c
1693
			     LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
1694
			     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1695
			WHERE c.relkind = 'r'
1696
			      AND n.nspname = '{$c_schema}'
1697
			      AND n.oid = c.relnamespace
1698
			      AND c.relname = '{$table}'";
1699
1700
        return $this->selectSet($sql);
1701
    }
1702
1703
    /**
1704
     * Retrieve the attribute definition of a table
1705
     *
1706
     * @param $table The name of the table
1707
     * @param $field (optional) The name of a field to return
1708
     * @return All attributes in order
1709
     */
1710
    public function getTableAttributes($table, $field = '')
1711
    {
1712
        $c_schema = $this->_schema;
1713
        $this->clean($c_schema);
1714
        $this->clean($table);
1715
        $this->clean($field);
1716
1717
        if ($field == '') {
1718
            // This query is made much more complex by the addition of the 'attisserial' field.
1719
            // The subquery to get that field checks to see if there is an internally dependent
1720
            // sequence on the field.
1721
            $sql = "
1722
				SELECT
1723
					a.attname, a.attnum,
1724
					pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
1725
					a.atttypmod,
1726
					a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
1727
					a.attstattarget, a.attstorage, t.typstorage,
1728
					(
1729
						SELECT 1 FROM pg_catalog.pg_depend pd, pg_catalog.pg_class pc
1730
						WHERE pd.objid=pc.oid
1731
						AND pd.classid=pc.tableoid
1732
						AND pd.refclassid=pc.tableoid
1733
						AND pd.refobjid=a.attrelid
1734
						AND pd.refobjsubid=a.attnum
1735
						AND pd.deptype='i'
1736
						AND pc.relkind='S'
1737
					) IS NOT NULL AS attisserial,
1738
					pg_catalog.col_description(a.attrelid, a.attnum) AS comment
1739
				FROM
1740
					pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
1741
					ON a.attrelid=adef.adrelid
1742
					AND a.attnum=adef.adnum
1743
					LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
1744
				WHERE
1745
					a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
1746
						AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
1747
						nspname = '{$c_schema}'))
1748
					AND a.attnum > 0 AND NOT a.attisdropped
1749
				ORDER BY a.attnum";
1750
        } else {
1751
            $sql = "
1752
				SELECT
1753
					a.attname, a.attnum,
1754
					pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
1755
					pg_catalog.format_type(a.atttypid, NULL) as base_type,
1756
					a.atttypmod,
1757
					a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
1758
					a.attstattarget, a.attstorage, t.typstorage,
1759
					pg_catalog.col_description(a.attrelid, a.attnum) AS comment
1760
				FROM
1761
					pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
1762
					ON a.attrelid=adef.adrelid
1763
					AND a.attnum=adef.adnum
1764
					LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
1765
				WHERE
1766
					a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
1767
						AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
1768
						nspname = '{$c_schema}'))
1769
					AND a.attname = '{$field}'";
1770
        }
1771
1772
        return $this->selectSet($sql);
1773
    }
1774
1775
    /**
1776
     * Returns a list of all constraints on a table
1777
     *
1778
     * @param $table The table to find rules for
1779
     * @return A recordset
1780
     */
1781 View Code Duplication
    public function getConstraints($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1782
    {
1783
        $c_schema = $this->_schema;
1784
        $this->clean($c_schema);
1785
        $this->clean($table);
1786
1787
        // This SQL is greatly complicated by the need to retrieve
1788
        // index clustering information for primary and unique constraints
1789
        $sql = "SELECT
1790
				pc.conname,
1791
				pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc,
1792
				pc.contype,
1793
				CASE WHEN pc.contype='u' OR pc.contype='p' THEN (
1794
					SELECT
1795
						indisclustered
1796
					FROM
1797
						pg_catalog.pg_depend pd,
1798
						pg_catalog.pg_class pl,
1799
						pg_catalog.pg_index pi
1800
					WHERE
1801
						pd.refclassid=pc.tableoid
1802
						AND pd.refobjid=pc.oid
1803
						AND pd.objid=pl.oid
1804
						AND pl.oid=pi.indexrelid
1805
				) ELSE
1806
					NULL
1807
				END AS indisclustered
1808
			FROM
1809
				pg_catalog.pg_constraint pc
1810
			WHERE
1811
				pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
1812
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
1813
					WHERE nspname='{$c_schema}'))
1814
			ORDER BY
1815
				1
1816
		";
1817
1818
        return $this->selectSet($sql);
1819
    }
1820
1821
    /**
1822
     * Returns the SQL for changing the current user
1823
     *
1824
     * @param $user The user to change to
1825
     * @return The SQL
1826
     */
1827
    public function getChangeUserSQL($user)
1828
    {
1829
        $this->clean($user);
1830
1831
        return "SET SESSION AUTHORIZATION '{$user}';";
1832
    }
1833
1834
    /**
1835
     * Change a parameter from 't' or 'f' to a boolean, (others evaluate to false)
1836
     *
1837
     * @param $parameter the parameter
1838
     * @return bool|\PHPPgAdmin\Database\the
1839
     */
1840
    public function phpBool($parameter)
1841
    {
1842
        $parameter = ($parameter == 't');
1843
1844
        return $parameter;
1845
    }
1846
1847
    /**
1848
     * Formats a type correctly for display.  Postgres 7.0 had no 'format_type'
1849
     * built-in function, and hence we need to do it manually.
1850
     *
1851
     * @param $typname The name of the type
1852
     * @param $typmod  The contents of the typmod field
1853
     * @return bool|\PHPPgAdmin\Database\The|string
1854
     */
1855
    public function formatType($typname, $typmod)
1856
    {
1857
        // This is a specific constant in the 7.0 source
1858
        $varhdrsz = 4;
1859
1860
        // If the first character is an underscore, it's an array type
1861
        $is_array = false;
1862
        if (substr($typname, 0, 1) == '_') {
1863
            $is_array = true;
1864
            $typname  = substr($typname, 1);
1865
        }
1866
1867
        // Show lengths on bpchar and varchar
1868
        if ($typname == 'bpchar') {
1869
            $len  = $typmod - $varhdrsz;
1870
            $temp = 'character';
1871
            if ($len > 1) {
1872
                $temp .= "({$len})";
1873
            }
1874
        } elseif ($typname == 'varchar') {
1875
            $temp = 'character varying';
1876
            if ($typmod != -1) {
1877
                $temp .= '(' . ($typmod - $varhdrsz) . ')';
1878
            }
1879
        } elseif ($typname == 'numeric') {
1880
            $temp = 'numeric';
1881
            if ($typmod != -1) {
1882
                $tmp_typmod = $typmod - $varhdrsz;
1883
                $precision  = ($tmp_typmod >> 16) & 0xffff;
1884
                $scale      = $tmp_typmod & 0xffff;
1885
                $temp .= "({$precision}, {$scale})";
1886
            }
1887
        } else {
1888
            $temp = $typname;
1889
        }
1890
1891
        // Add array qualifier if it's an array
1892
        if ($is_array) {
1893
            $temp .= '[]';
1894
        }
1895
1896
        return $temp;
1897
    }
1898
1899
    /**
1900
     * Given an array of attnums and a relation, returns an array mapping
1901
     * attribute number to attribute name.
1902
     *
1903
     * @param $table The table to get attributes for
1904
     * @param $atts  An array of attribute numbers
1905
     * @return An array mapping attnum to attname
1906
     * @return -1 $atts must be an array
1907
     * @return -2 wrong number of attributes found
1908
     */
1909
    public function getAttributeNames($table, $atts)
1910
    {
1911
        $c_schema = $this->_schema;
1912
        $this->clean($c_schema);
1913
        $this->clean($table);
1914
        $this->arrayClean($atts);
1915
1916
        if (!is_array($atts)) {
1917
            return -1;
1918
        }
1919
1920
        if (sizeof($atts) == 0) {
1921
            return [];
1922
        }
1923
1924
        $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
1925
			attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
1926
			relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
1927
			AND attnum IN ('" . join("','", $atts) . "')";
1928
1929
        $rs = $this->selectSet($sql);
1930
        if ($rs->recordCount() != sizeof($atts)) {
1931
            return -2;
1932
        }
1933
1934
        $temp = [];
1935
        while (!$rs->EOF) {
1936
            $temp[$rs->fields['attnum']] = $rs->fields['attname'];
1937
            $rs->moveNext();
1938
        }
1939
1940
        return $temp;
1941
    }
1942
1943
    /**
1944
     * Cleans (escapes) an array
1945
     *
1946
     * @param $arr The array to clean, by reference
1947
     * @return The cleaned array
1948
     */
1949
    public function arrayClean(&$arr)
1950
    {
1951
        foreach ($arr as $k => $v) {
1952
            if ($v === null) {
1953
                continue;
1954
            }
1955
1956
            $arr[$k] = pg_escape_string($v);
1957
        }
1958
1959
        return $arr;
1960
    }
1961
1962
    /**
1963
     * Checks to see whether or not a table has a unique id column
1964
     *
1965
     * @param $table The table name
1966
     * @return True if it has a unique id, false otherwise
1967
     * @return null error
1968
     **/
1969
    public function hasObjectID($table)
1970
    {
1971
        $c_schema = $this->_schema;
1972
        $this->clean($c_schema);
1973
        $this->clean($table);
1974
1975
        $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}'
1976
			AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')";
1977
1978
        $rs = $this->selectSet($sql);
1979
        if ($rs->recordCount() != 1) {
1980
            return null;
1981
        }
1982
1983
        $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']);
1984
1985
        return $rs->fields['relhasoids'];
1986
    }
1987
1988
    /**
1989
     * Grabs an array of users and their privileges for an object,
1990
     * given its type.
1991
     *
1992
     * @param $object The name of the object whose privileges are to be retrieved
1993
     * @param $type   The type of the object (eg. database, schema, relation, function or language)
1994
     * @param $table  Optional, column's table if type = column
1995
     * @return Privileges array
1996
     * @return -1 invalid type
1997
     * @return -2 object not found
1998
     * @return -3 unknown privilege type
1999
     */
2000
    public function getPrivileges($object, $type, $table = null)
2001
    {
2002
        $c_schema = $this->_schema;
2003
        $this->clean($c_schema);
2004
        $this->clean($object);
2005
2006
        switch ($type) {
2007
            case 'column':
2008
                $this->clean($table);
2009
                $sql = "
2010
					SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl
2011
					FROM pg_catalog.pg_attribute a
2012
						LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid)
2013
						LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid)
2014
					WHERE n.nspname='{$c_schema}'
2015
						AND c.relname='{$table}'
2016
						AND a.attname='{$object}'";
2017
                break;
2018
            case 'table':
2019
            case 'view':
2020
            case 'sequence':
2021
                $sql = "
2022
					SELECT relacl AS acl FROM pg_catalog.pg_class
2023
					WHERE relname='{$object}'
2024
						AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace
2025
							WHERE nspname='{$c_schema}')";
2026
                break;
2027
            case 'database':
2028
                $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'";
2029
                break;
2030
            case 'function':
2031
                // Since we fetch functions by oid, they are already constrained to
2032
                // the current schema.
2033
                $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'";
2034
                break;
2035
            case 'language':
2036
                $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'";
2037
                break;
2038
            case 'schema':
2039
                $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'";
2040
                break;
2041
            case 'tablespace':
2042
                $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'";
2043
                break;
2044
            default:
2045
                return -1;
2046
        }
2047
2048
        // Fetch the ACL for object
2049
        $acl = $this->selectField($sql, 'acl');
2050
        if ($acl == -1) {
2051
            return -2;
2052
        }
2053
2054
        if ($acl == '' || $acl == null) {
2055
            return [];
2056
        } else {
2057
            return $this->_parseACL($acl);
2058
        }
2059
    }
2060
2061
    /**
2062
     * Internal function used for parsing ACLs
2063
     *
2064
     * @param $acl The ACL to parse (of type aclitem[])
2065
     * @return Privileges array
2066
     */
2067
    public function _parseACL($acl)
2068
    {
2069
        // Take off the first and last characters (the braces)
2070
        $acl = substr($acl, 1, strlen($acl) - 2);
2071
2072
        // Pick out individual ACE's by carefully parsing.  This is necessary in order
2073
        // to cope with usernames and stuff that contain commas
2074
        $aces      = [];
2075
        $i         = $j         = 0;
2076
        $in_quotes = false;
2077 View Code Duplication
        while ($i < strlen($acl)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2078
            // If current char is a double quote and it's not escaped, then
2079
            // enter quoted bit
2080
            $char = substr($acl, $i, 1);
2081
            if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) {
2082
                $in_quotes = !$in_quotes;
2083
            } elseif ($char == ',' && !$in_quotes) {
2084
                // Add text so far to the array
2085
                $aces[] = substr($acl, $j, $i - $j);
2086
                $j      = $i + 1;
2087
            }
2088
            $i++;
2089
        }
2090
        // Add final text to the array
2091
        $aces[] = substr($acl, $j);
2092
2093
        // Create the array to be returned
2094
        $temp = [];
2095
2096
        // For each ACE, generate an entry in $temp
2097
        foreach ($aces as $v) {
2098
2099
            // If the ACE begins with a double quote, strip them off both ends
2100
            // and unescape backslashes and double quotes
2101
            $unquote = false;
2102
            if (strpos($v, '"') === 0) {
2103
                $v = substr($v, 1, strlen($v) - 2);
2104
                $v = str_replace('\\"', '"', $v);
2105
                $v = str_replace('\\\\', '\\', $v);
2106
            }
2107
2108
            // Figure out type of ACE (public, user or group)
2109
            if (strpos($v, '=') === 0) {
2110
                $atype = 'public';
2111
            } else {
2112
                if ($this->hasRoles()) {
2113
                    $atype = 'role';
2114
                } else {
2115
                    if (strpos($v, 'group ') === 0) {
2116
                        $atype = 'group';
2117
                        // Tear off 'group' prefix
2118
                        $v = substr($v, 6);
2119
                    } else {
2120
                        $atype = 'user';
2121
                    }
2122
                }
2123
            }
2124
2125
            // Break on unquoted equals sign...
2126
            $i         = 0;
2127
            $in_quotes = false;
2128
            $entity    = null;
2129
            $chars     = null;
2130
            while ($i < strlen($v)) {
2131
                // If current char is a double quote and it's not escaped, then
2132
                // enter quoted bit
2133
                $char      = substr($v, $i, 1);
2134
                $next_char = substr($v, $i + 1, 1);
2135
                if ($char == '"' && ($i == 0 || $next_char != '"')) {
2136
                    $in_quotes = !$in_quotes;
2137
                } // Skip over escaped double quotes
2138
                elseif ($char == '"' && $next_char == '"') {
2139
                    $i++;
2140
                } elseif ($char == '=' && !$in_quotes) {
2141
                    // Split on current equals sign
2142
                    $entity = substr($v, 0, $i);
2143
                    $chars  = substr($v, $i + 1);
2144
                    break;
2145
                }
2146
                $i++;
2147
            }
2148
2149
            // Check for quoting on entity name, and unescape if necessary
2150
            if (strpos($entity, '"') === 0) {
2151
                $entity = substr($entity, 1, strlen($entity) - 2);
2152
                $entity = str_replace('""', '"', $entity);
2153
            }
2154
2155
            // New row to be added to $temp
2156
            // (type, grantee, privileges, grantor, grant option?
2157
            $row = [$atype, $entity, [], '', []];
2158
2159
            // Loop over chars and add privs to $row
2160
            for ($i = 0; $i < strlen($chars); $i++) {
2161
                // Append to row's privs list the string representing
2162
                // the privilege
2163
                $char = substr($chars, $i, 1);
2164
                if ($char == '*') {
2165
                    $row[4][] = $this->privmap[substr($chars, $i - 1, 1)];
2166 View Code Duplication
                } elseif ($char == '/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2167
                    $grantor = substr($chars, $i + 1);
2168
                    // Check for quoting
2169
                    if (strpos($grantor, '"') === 0) {
2170
                        $grantor = substr($grantor, 1, strlen($grantor) - 2);
2171
                        $grantor = str_replace('""', '"', $grantor);
2172
                    }
2173
                    $row[3] = $grantor;
2174
                    break;
2175
                } else {
2176
                    if (!isset($this->privmap[$char])) {
2177
                        return -3;
2178
                    }
2179
2180
                    $row[2][] = $this->privmap[$char];
2181
                }
2182
            }
2183
2184
            // Append row to temp
2185
            $temp[] = $row;
2186
        }
2187
2188
        return $temp;
2189
    }
2190
2191
    public function hasRoles()
2192
    {
2193
        return true;
2194
    }
2195
2196
    public function hasGrantOption()
2197
    {
2198
        return true;
2199
    }
2200
2201
    /**
2202
     * Returns extra table definition information that is most usefully
2203
     * dumped after the table contents for speed and efficiency reasons
2204
     *
2205
     * @param $table The table to define
2206
     * @return A string containing the formatted SQL code
2207
     * @return null On error
2208
     */
2209
    public function getTableDefSuffix($table)
2210
    {
2211
        $sql = '';
2212
2213
        // Indexes
2214
        $indexes = $this->getIndexes($table);
2215
        if (!is_object($indexes)) {
2216
            $this->rollbackTransaction();
2217
2218
            return null;
2219
        }
2220
2221 View Code Duplication
        if ($indexes->recordCount() > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2222
            $sql .= "\n-- Indexes\n\n";
2223
            while (!$indexes->EOF) {
2224
                $sql .= $indexes->fields['inddef'] . ";\n";
2225
2226
                $indexes->moveNext();
2227
            }
2228
        }
2229
2230
        // Triggers
2231
        $triggers = $this->getTriggers($table);
2232
        if (!is_object($triggers)) {
2233
            $this->rollbackTransaction();
2234
2235
            return null;
2236
        }
2237
2238 View Code Duplication
        if ($triggers->recordCount() > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2239
            $sql .= "\n-- Triggers\n\n";
2240
            while (!$triggers->EOF) {
2241
                $sql .= $triggers->fields['tgdef'];
2242
                $sql .= ";\n";
2243
2244
                $triggers->moveNext();
2245
            }
2246
        }
2247
2248
        // Rules
2249
        $rules = $this->getRules($table);
2250
        if (!is_object($rules)) {
2251
            $this->rollbackTransaction();
2252
2253
            return null;
2254
        }
2255
2256 View Code Duplication
        if ($rules->recordCount() > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2257
            $sql .= "\n-- Rules\n\n";
2258
            while (!$rules->EOF) {
2259
                $sql .= $rules->fields['definition'] . "\n";
2260
2261
                $rules->moveNext();
2262
            }
2263
        }
2264
2265
        return $sql;
2266
    }
2267
2268
    /**
2269
     * Grabs a list of indexes for a table
2270
     *
2271
     * @param \PHPPgAdmin\Database\The|string $table  The name of a table whose indexes to retrieve
2272
     * @param bool|\PHPPgAdmin\Database\Only  $unique Only get unique/pk indexes
2273
     * @return \PHPPgAdmin\Database\A recordset
2274
     */
2275
    public function getIndexes($table = '', $unique = false)
2276
    {
2277
        $this->clean($table);
2278
2279
        $sql = "
2280
			SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
2281
				pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef
2282
			FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
2283
			WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid)
2284
				AND c.oid = i.indrelid AND i.indexrelid = c2.oid
2285
		";
2286
        if ($unique) {
2287
            $sql .= ' AND i.indisunique ';
2288
        }
2289
2290
        $sql .= ' ORDER BY c2.relname';
2291
2292
        return $this->selectSet($sql);
2293
    }
2294
2295
    /**
2296
     * Grabs a list of triggers on a table
2297
     *
2298
     * @param \PHPPgAdmin\Database\The|string $table The name of a table whose triggers to retrieve
2299
     * @return \PHPPgAdmin\Database\A recordset
2300
     */
2301 View Code Duplication
    public function getTriggers($table = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2302
    {
2303
        $c_schema = $this->_schema;
2304
        $this->clean($c_schema);
2305
        $this->clean($table);
2306
2307
        $sql = "SELECT
2308
				t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef,
2309
				CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid,
2310
				p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
2311
				ns.nspname AS pronamespace
2312
			FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns
2313
			WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
2314
				AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
2315
				AND ( tgconstraint = 0 OR NOT EXISTS
2316
						(SELECT 1 FROM pg_catalog.pg_depend d    JOIN pg_catalog.pg_constraint c
2317
							ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
2318
						WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))
2319
				AND p.oid=t.tgfoid
2320
				AND p.pronamespace = ns.oid";
2321
2322
        return $this->selectSet($sql);
2323
    }
2324
2325
    /**
2326
     * Returns a list of all rules on a table OR view
2327
     *
2328
     * @param $table The table to find rules for
2329
     * @return A recordset
2330
     */
2331 View Code Duplication
    public function getRules($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2332
    {
2333
        $c_schema = $this->_schema;
2334
        $this->clean($c_schema);
2335
        $this->clean($table);
2336
2337
        $sql = "
2338
			SELECT *
2339
			FROM pg_catalog.pg_rules
2340
			WHERE
2341
				schemaname='{$c_schema}' AND tablename='{$table}'
2342
			ORDER BY rulename
2343
		";
2344
2345
        return $this->selectSet($sql);
2346
    }
2347
2348
    /**
2349
     * Creates a new table in the database
2350
     *
2351
     * @param $name        The name of the table
2352
     * @param $fields      The number of fields
2353
     * @param $field       An array of field names
2354
     * @param $type        An array of field types
2355
     * @param $array       An array of '' or '[]' for each type if it's an array or not
2356
     * @param $length      An array of field lengths
2357
     * @param $notnull     An array of not null
2358
     * @param $default     An array of default values
2359
     * @param $withoutoids True if WITHOUT OIDS, false otherwise
2360
     * @param $colcomment  An array of comments
2361
     * @param $tblcomment
2362
     * @param $tablespace  The tablespace name ('' means none/default)
2363
     * @param $uniquekey   An Array indicating the fields that are unique (those indexes that are set)
2364
     * @param $primarykey  An Array indicating the field used for the primarykey (those indexes that are set)
2365
     * @return bool|int 0 success
2366
     * @internal param \PHPPgAdmin\Database\Table $comment comment
2367
     */
2368
    public function createTable(
2369
        $name,
2370
        $fields,
2371
        $field,
2372
        $type,
2373
        $array,
2374
        $length,
2375
        $notnull,
2376
        $default,
2377
        $withoutoids,
2378
        $colcomment,
2379
        $tblcomment,
2380
        $tablespace,
2381
        $uniquekey,
2382
        $primarykey
2383
    ) {
2384
        $f_schema = $this->_schema;
2385
        $this->fieldClean($f_schema);
2386
        $this->fieldClean($name);
2387
2388
        $status = $this->beginTransaction();
2389
        if ($status != 0) {
2390
            return -1;
2391
        }
2392
2393
        $found       = false;
2394
        $first       = true;
2395
        $comment_sql = ''; //Accumulate comments for the columns
2396
        $sql         = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (";
2397
        for ($i = 0; $i < $fields; $i++) {
2398
            $this->fieldClean($field[$i]);
2399
            $this->clean($type[$i]);
2400
            $this->clean($length[$i]);
2401
            $this->clean($colcomment[$i]);
2402
2403
            // Skip blank columns - for user convenience
2404
            if ($field[$i] == '' || $type[$i] == '') {
2405
                continue;
2406
            }
2407
2408
            // If not the first column, add a comma
2409
            if (!$first) {
2410
                $sql .= ', ';
2411
            } else {
2412
                $first = false;
2413
            }
2414
2415 View Code Duplication
            switch ($type[$i]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2416
                // Have to account for weird placing of length for with/without
2417
                // time zone types
2418
                case 'timestamp with time zone':
2419
                case 'timestamp without time zone':
2420
                    $qual = substr($type[$i], 9);
2421
                    $sql .= "\"{$field[$i]}\" timestamp";
2422
                    if ($length[$i] != '') {
2423
                        $sql .= "({$length[$i]})";
2424
                    }
2425
2426
                    $sql .= $qual;
2427
                    break;
2428
                case 'time with time zone':
2429
                case 'time without time zone':
2430
                    $qual = substr($type[$i], 4);
2431
                    $sql .= "\"{$field[$i]}\" time";
2432
                    if ($length[$i] != '') {
2433
                        $sql .= "({$length[$i]})";
2434
                    }
2435
2436
                    $sql .= $qual;
2437
                    break;
2438
                default:
2439
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
2440
                    if ($length[$i] != '') {
2441
                        $sql .= "({$length[$i]})";
2442
                    }
2443
            }
2444
            // Add array qualifier if necessary
2445
            if ($array[$i] == '[]') {
2446
                $sql .= '[]';
2447
            }
2448
2449
            // Add other qualifiers
2450
            if (!isset($primarykey[$i])) {
2451
                if (isset($uniquekey[$i])) {
2452
                    $sql .= ' UNIQUE';
2453
                }
2454
2455
                if (isset($notnull[$i])) {
2456
                    $sql .= ' NOT NULL';
2457
                }
2458
            }
2459
            if ($default[$i] != '') {
2460
                $sql .= " DEFAULT {$default[$i]}";
2461
            }
2462
2463 View Code Duplication
            if ($colcomment[$i] != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2464
                $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
2465
            }
2466
2467
            $found = true;
2468
        }
2469
2470
        if (!$found) {
2471
            return -1;
2472
        }
2473
2474
        // PRIMARY KEY
2475
        $primarykeycolumns = [];
2476
        for ($i = 0; $i < $fields; $i++) {
2477
            if (isset($primarykey[$i])) {
2478
                $primarykeycolumns[] = "\"{$field[$i]}\"";
2479
            }
2480
        }
2481
        if (count($primarykeycolumns) > 0) {
2482
            $sql .= ', PRIMARY KEY (' . implode(', ', $primarykeycolumns) . ')';
2483
        }
2484
2485
        $sql .= ')';
2486
2487
        // WITHOUT OIDS
2488
        if ($withoutoids) {
2489
            $sql .= ' WITHOUT OIDS';
2490
        } else {
2491
            $sql .= ' WITH OIDS';
2492
        }
2493
2494
        // Tablespace
2495
        if ($this->hasTablespaces() && $tablespace != '') {
2496
            $this->fieldClean($tablespace);
2497
            $sql .= " TABLESPACE \"{$tablespace}\"";
2498
        }
2499
2500
        $status = $this->execute($sql);
2501
        if ($status) {
2502
            $this->rollbackTransaction();
2503
2504
            return -1;
2505
        }
2506
2507 View Code Duplication
        if ($tblcomment != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2508
            $status = $this->setComment('TABLE', '', $name, $tblcomment, true);
2509
            if ($status) {
2510
                $this->rollbackTransaction();
2511
2512
                return -1;
2513
            }
2514
        }
2515
2516 View Code Duplication
        if ($comment_sql != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2517
            $status = $this->execute($comment_sql);
2518
            if ($status) {
2519
                $this->rollbackTransaction();
2520
2521
                return -1;
2522
            }
2523
        }
2524
2525
        return $this->endTransaction();
2526
    }
2527
2528
    /**
2529
     * Creates a new table in the database copying attribs and other properties from another table
2530
     *
2531
     * @param                                 $name        The name of the table
2532
     * @param                                 $like        an array giving the schema ans the name of the table from which attribs are copying
2533
     *                                                     from: array(
2534
     *                                                     'table' => table name,
2535
     *                                                     'schema' => the schema name,
2536
     *                                                     )
2537
     * @param bool|\PHPPgAdmin\Database\if    $defaults    if true, copy the defaults values as well
2538
     * @param bool|\PHPPgAdmin\Database\if    $constraints if true, copy the constraints as well (CHECK on table & attr)
2539
     * @param bool                            $idx
2540
     * @param \PHPPgAdmin\Database\The|string $tablespace  The tablespace name ('' means none/default)
2541
     * @return bool|int
2542
     */
2543
    public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '')
2544
    {
2545
        $f_schema = $this->_schema;
2546
        $this->fieldClean($f_schema);
2547
        $this->fieldClean($name);
2548
        $this->fieldClean($like['schema']);
2549
        $this->fieldClean($like['table']);
2550
        $like = "\"{$like['schema']}\".\"{$like['table']}\"";
2551
2552
        $status = $this->beginTransaction();
2553
        if ($status != 0) {
2554
            return -1;
2555
        }
2556
2557
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}";
2558
2559
        if ($defaults) {
2560
            $sql .= ' INCLUDING DEFAULTS';
2561
        }
2562
2563
        if ($this->hasCreateTableLikeWithConstraints() && $constraints) {
2564
            $sql .= ' INCLUDING CONSTRAINTS';
2565
        }
2566
2567
        if ($this->hasCreateTableLikeWithIndexes() && $idx) {
2568
            $sql .= ' INCLUDING INDEXES';
2569
        }
2570
2571
        $sql .= ')';
2572
2573
        if ($this->hasTablespaces() && $tablespace != '') {
2574
            $this->fieldClean($tablespace);
2575
            $sql .= " TABLESPACE \"{$tablespace}\"";
2576
        }
2577
2578
        $status = $this->execute($sql);
2579
        if ($status) {
2580
            $this->rollbackTransaction();
2581
2582
            return -1;
2583
        }
2584
2585
        return $this->endTransaction();
2586
    }
2587
2588
    public function hasCreateTableLikeWithConstraints()
2589
    {
2590
        return true;
2591
    }
2592
2593
    public function hasCreateTableLikeWithIndexes()
2594
    {
2595
        return true;
2596
    }
2597
2598
    /**
2599
     * Alter table properties
2600
     *
2601
     * @param $table      The name of the table
2602
     * @param $name       The new name for the table
2603
     * @param $owner      The new owner for the table
2604
     * @param $schema     The new schema for the table
2605
     * @param $comment    The comment on the table
2606
     * @param $tablespace The new tablespace for the table ('' means leave as is)
2607
     * @return bool|int 0 success
2608
     */
2609 View Code Duplication
    public function alterTable($table, $name, $owner, $schema, $comment, $tablespace)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2610
    {
2611
        $data = $this->getTable($table);
2612
2613
        if ($data->recordCount() != 1) {
2614
            return -2;
2615
        }
2616
2617
        $status = $this->beginTransaction();
2618
        if ($status != 0) {
2619
            $this->rollbackTransaction();
2620
2621
            return -1;
2622
        }
2623
2624
        $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace);
2625
2626
        if ($status != 0) {
2627
            $this->rollbackTransaction();
2628
2629
            return $status;
2630
        }
2631
2632
        return $this->endTransaction();
2633
    }
2634
2635
    /**
2636
     * Protected method which alter a table
2637
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION
2638
     *
2639
     * @param $tblrs      The table recordSet returned by getTable()
2640
     * @param $name       The new name for the table
2641
     * @param $owner      The new owner for the table
2642
     * @param $schema     The new schema for the table
2643
     * @param $comment    The comment on the table
2644
     * @param $tablespace The new tablespace for the table ('' means leave as is)
2645
     * @return int 0 success
2646
     */
2647
    protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace)
2648
    {
2649
        $this->fieldArrayClean($tblrs->fields);
2650
2651
        // Comment
2652
        $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment);
2653
        if ($status != 0) {
2654
            return -4;
2655
        }
2656
2657
        // Owner
2658
        $this->fieldClean($owner);
2659
        $status = $this->alterTableOwner($tblrs, $owner);
2660
        if ($status != 0) {
2661
            return -5;
2662
        }
2663
2664
        // Tablespace
2665
        $this->fieldClean($tablespace);
2666
        $status = $this->alterTableTablespace($tblrs, $tablespace);
2667
        if ($status != 0) {
2668
            return -6;
2669
        }
2670
2671
        // Rename
2672
        $this->fieldClean($name);
2673
        $status = $this->alterTableName($tblrs, $name);
2674
        if ($status != 0) {
2675
            return -3;
2676
        }
2677
2678
        // Schema
2679
        $this->fieldClean($schema);
2680
        $status = $this->alterTableSchema($tblrs, $schema);
2681
        if ($status != 0) {
2682
            return -7;
2683
        }
2684
2685
        return 0;
2686
    }
2687
2688
    /**
2689
     * Alter a table's owner
2690
     * /!\ this function is called from _alterTable which take care of escaping fields
2691
     *
2692
     * @param      $tblrs The table RecordSet returned by getTable()
2693
     * @param null $owner
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $owner is correct as it would always require null to be passed?
Loading history...
2694
     * @return int|\PHPPgAdmin\Database\A 0 success
2695
     * @internal param \PHPPgAdmin\Database\The $name new table's owner
2696
     */
2697 View Code Duplication
    public function alterTableOwner($tblrs, $owner = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2698
    {
2699
        /* vars cleaned in _alterTable */
2700
        if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) {
2701
            $f_schema = $this->_schema;
2702
            $this->fieldClean($f_schema);
2703
            // If owner has been changed, then do the alteration.  We are
2704
            // careful to avoid this generally as changing owner is a
2705
            // superuser only function.
2706
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\"";
2707
2708
            return $this->execute($sql);
2709
        }
2710
2711
        return 0;
2712
    }
2713
2714
    /**
2715
     * Alter a table's tablespace
2716
     * /!\ this function is called from _alterTable which take care of escaping fields
2717
     *
2718
     * @param      $tblrs The table RecordSet returned by getTable()
2719
     * @param null $tablespace
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $tablespace is correct as it would always require null to be passed?
Loading history...
2720
     * @return int|\PHPPgAdmin\Database\A 0 success
2721
     * @internal param \PHPPgAdmin\Database\The $name new table's tablespace
2722
     */
2723 View Code Duplication
    public function alterTableTablespace($tblrs, $tablespace = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2724
    {
2725
        /* vars cleaned in _alterTable */
2726
        if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) {
2727
            $f_schema = $this->_schema;
2728
            $this->fieldClean($f_schema);
2729
2730
            // If tablespace has been changed, then do the alteration.  We
2731
            // don't want to do this unnecessarily.
2732
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\"";
2733
2734
            return $this->execute($sql);
2735
        }
2736
2737
        return 0;
2738
    }
2739
2740
    /**
2741
     * Alter a table's name
2742
     * /!\ this function is called from _alterTable which take care of escaping fields
2743
     *
2744
     * @param $tblrs The table RecordSet returned by getTable()
2745
     * @param $name  The new table's name
2746
     * @return int|\PHPPgAdmin\Database\A 0 success
2747
     */
2748 View Code Duplication
    public function alterTableName($tblrs, $name = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2749
    {
2750
        /* vars cleaned in _alterTable */
2751
        // Rename (only if name has changed)
2752
        if (!empty($name) && ($name != $tblrs->fields['relname'])) {
2753
            $f_schema = $this->_schema;
2754
            $this->fieldClean($f_schema);
2755
2756
            $sql    = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" RENAME TO \"{$name}\"";
2757
            $status = $this->execute($sql);
2758
            if ($status == 0) {
2759
                $tblrs->fields['relname'] = $name;
2760
            } else {
2761
                return $status;
2762
            }
2763
        }
2764
2765
        return 0;
2766
    }
2767
2768
    // Row functions
2769
2770
    /**
2771
     * Alter a table's schema
2772
     * /!\ this function is called from _alterTable which take care of escaping fields
2773
     *
2774
     * @param      $tblrs The table RecordSet returned by getTable()
2775
     * @param null $schema
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $schema is correct as it would always require null to be passed?
Loading history...
2776
     * @return int|\PHPPgAdmin\Database\A 0 success
2777
     * @internal param \PHPPgAdmin\Database\The $name new table's schema
2778
     */
2779 View Code Duplication
    public function alterTableSchema($tblrs, $schema = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2780
    {
2781
        /* vars cleaned in _alterTable */
2782
        if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) {
2783
            $f_schema = $this->_schema;
2784
            $this->fieldClean($f_schema);
2785
            // If tablespace has been changed, then do the alteration.  We
2786
            // don't want to do this unnecessarily.
2787
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET SCHEMA \"{$schema}\"";
2788
2789
            return $this->execute($sql);
2790
        }
2791
2792
        return 0;
2793
    }
2794
2795
    /**
2796
     * Empties a table in the database
2797
     *
2798
     * @param $table The table to be emptied
2799
     * @return \PHPPgAdmin\Database\A 0 success
2800
     */
2801
    public function emptyTable($table)
2802
    {
2803
        $f_schema = $this->_schema;
2804
        $this->fieldClean($f_schema);
2805
        $this->fieldClean($table);
2806
2807
        $sql = "DELETE FROM \"{$f_schema}\".\"{$table}\"";
2808
2809
        return $this->execute($sql);
2810
    }
2811
2812
    /**
2813
     * Removes a table from the database
2814
     *
2815
     * @param $table   The table to drop
2816
     * @param $cascade True to cascade drop, false to restrict
2817
     * @return \PHPPgAdmin\Database\A 0 success
2818
     */
2819 View Code Duplication
    public function dropTable($table, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2820
    {
2821
        $f_schema = $this->_schema;
2822
        $this->fieldClean($f_schema);
2823
        $this->fieldClean($table);
2824
2825
        $sql = "DROP TABLE \"{$f_schema}\".\"{$table}\"";
2826
        if ($cascade) {
2827
            $sql .= ' CASCADE';
2828
        }
2829
2830
        return $this->execute($sql);
2831
    }
2832
2833
    /**
2834
     * Add a new column to a table
2835
     *
2836
     * @param $table   The table to add to
2837
     * @param $column  The name of the new column
2838
     * @param $type    The type of the column
2839
     * @param $array   True if array type, false otherwise
2840
     * @param $length  The optional size of the column (ie. 30 for varchar(30))
2841
     * @param $notnull True if NOT NULL, false otherwise
2842
     * @param $default The default for the column.  '' for none.
2843
     * @param $comment
2844
     * @return bool|int 0 success
2845
     */
2846
    public function addColumn($table, $column, $type, $array, $length, $notnull, $default, $comment)
2847
    {
2848
        $f_schema = $this->_schema;
2849
        $this->fieldClean($f_schema);
2850
        $this->fieldClean($table);
2851
        $this->fieldClean($column);
2852
        $this->clean($type);
2853
        $this->clean($length);
2854
2855
        if ($length == '') {
2856
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}";
2857
        } else {
2858
            switch ($type) {
2859
                // Have to account for weird placing of length for with/without
2860
                // time zone types
2861
                case 'timestamp with time zone':
2862 View Code Duplication
                case 'timestamp without time zone':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2863
                    $qual = substr($type, 9);
2864
                    $sql  = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" timestamp({$length}){$qual}";
2865
                    break;
2866
                case 'time with time zone':
2867 View Code Duplication
                case 'time without time zone':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2868
                    $qual = substr($type, 4);
2869
                    $sql  = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" time({$length}){$qual}";
2870
                    break;
2871
                default:
2872
                    $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}({$length})";
2873
            }
2874
        }
2875
2876
        // Add array qualifier, if requested
2877
        if ($array) {
2878
            $sql .= '[]';
2879
        }
2880
2881
        // If we have advanced column adding, add the extra qualifiers
2882
        if ($this->hasCreateFieldWithConstraints()) {
2883
            // NOT NULL clause
2884
            if ($notnull) {
2885
                $sql .= ' NOT NULL';
2886
            }
2887
2888
            // DEFAULT clause
2889
            if ($default != '') {
2890
                $sql .= ' DEFAULT ' . $default;
2891
            }
2892
        }
2893
2894
        $status = $this->beginTransaction();
2895
        if ($status != 0) {
2896
            return -1;
2897
        }
2898
2899
        $status = $this->execute($sql);
2900
        if ($status != 0) {
2901
            $this->rollbackTransaction();
2902
2903
            return -1;
2904
        }
2905
2906
        $status = $this->setComment('COLUMN', $column, $table, $comment);
2907
        if ($status != 0) {
2908
            $this->rollbackTransaction();
2909
2910
            return -1;
2911
        }
2912
2913
        return $this->endTransaction();
2914
    }
2915
2916
    // Sequence functions
2917
2918
    public function hasCreateFieldWithConstraints()
2919
    {
2920
        return true;
2921
    }
2922
2923
    /**
2924
     * Alters a column in a table
2925
     *
2926
     * @param $table      The table in which the column resides
2927
     * @param $column     The column to alter
2928
     * @param $name       The new name for the column
2929
     * @param $notnull    (boolean) True if not null, false otherwise
2930
     * @param $oldnotnull (boolean) True if column is already not null, false otherwise
2931
     * @param $default    The new default for the column
2932
     * @param $olddefault The old default for the column
2933
     * @param $type       The new type for the column
2934
     * @param $length     The optional size of the column (ie. 30 for varchar(30))
2935
     * @param $array      True if array type, false otherwise
2936
     * @param $oldtype    The old type for the column
2937
     * @param $comment    Comment for the column
2938
     * @return array 0 success
2939
     */
2940
    public function alterColumn(
2941
        $table,
2942
        $column,
2943
        $name,
2944
        $notnull,
2945
        $oldnotnull,
2946
        $default,
2947
        $olddefault,
2948
        $type,
2949
        $length,
2950
        $array,
2951
        $oldtype,
2952
        $comment
2953
    ) {
2954
        // Begin transaction
2955
        $status = $this->beginTransaction();
2956
        $sql    = '';
2957
        if ($status != 0) {
2958
            $this->rollbackTransaction();
2959
2960
            return [-6, $sql];
2961
        }
2962
2963
        // Rename the column, if it has been changed
2964 View Code Duplication
        if ($column != $name) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2965
            $status = $this->renameColumn($table, $column, $name);
2966
            if ($status != 0) {
2967
                $this->rollbackTransaction();
2968
2969
                return [-4, $sql];
2970
            }
2971
        }
2972
2973
        $f_schema = $this->_schema;
2974
        $this->fieldClean($f_schema);
2975
        $this->fieldClean($name);
2976
        $this->fieldClean($table);
2977
        $this->fieldClean($column);
2978
2979
        $toAlter = [];
2980
        // Create the command for changing nullability
2981
        if ($notnull != $oldnotnull) {
2982
            $toAlter[] = "ALTER COLUMN \"{$name}\" " . ($notnull ? 'SET' : 'DROP') . ' NOT NULL';
2983
        }
2984
2985
        // Add default, if it has changed
2986
        if ($default != $olddefault) {
2987
            if ($default == '') {
2988
                $toAlter[] = "ALTER COLUMN \"{$name}\" DROP DEFAULT";
2989
            } else {
2990
                $toAlter[] = "ALTER COLUMN \"{$name}\" SET DEFAULT {$default}";
2991
            }
2992
        }
2993
2994
        // Add type, if it has changed
2995
        if ($length == '') {
2996
            $ftype = $type;
2997
        } else {
2998
            switch ($type) {
2999
                // Have to account for weird placing of length for with/without
3000
                // time zone types
3001
                case 'timestamp with time zone':
3002
                case 'timestamp without time zone':
3003
                    $qual  = substr($type, 9);
3004
                    $ftype = "timestamp({$length}){$qual}";
3005
                    break;
3006
                case 'time with time zone':
3007
                case 'time without time zone':
3008
                    $qual  = substr($type, 4);
3009
                    $ftype = "time({$length}){$qual}";
3010
                    break;
3011
                default:
3012
                    $ftype = "{$type}({$length})";
3013
            }
3014
        }
3015
3016
        // Add array qualifier, if requested
3017
        if ($array) {
3018
            $ftype .= '[]';
3019
        }
3020
3021
        if ($ftype != $oldtype) {
3022
            $toAlter[] = "ALTER COLUMN \"{$name}\" TYPE {$ftype}";
3023
        }
3024
3025
        // Attempt to process the batch alteration, if anything has been changed
3026
        if (!empty($toAlter)) {
3027
            // Initialise an empty SQL string
3028
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" "
3029
            . implode(',', $toAlter);
3030
3031
            $status = $this->execute($sql);
3032
            if ($status != 0) {
3033
                $this->rollbackTransaction();
3034
3035
                return [-1, $sql];
3036
            }
3037
        }
3038
3039
        // Update the comment on the column
3040
        $status = $this->setComment('COLUMN', $name, $table, $comment);
3041
        if ($status != 0) {
3042
            $this->rollbackTransaction();
3043
3044
            return [-5, $sql];
3045
        }
3046
3047
        return [$this->endTransaction(), $sql];
3048
    }
3049
3050
    /**
3051
     * Renames a column in a table
3052
     *
3053
     * @param $table   The table containing the column to be renamed
3054
     * @param $column  The column to be renamed
3055
     * @param $newName The new name for the column
3056
     * @return \PHPPgAdmin\Database\A 0 success
3057
     */
3058
    public function renameColumn($table, $column, $newName)
3059
    {
3060
        $f_schema = $this->_schema;
3061
        $this->fieldClean($f_schema);
3062
        $this->fieldClean($table);
3063
        $this->fieldClean($column);
3064
        $this->fieldClean($newName);
3065
3066
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" RENAME COLUMN \"{$column}\" TO \"{$newName}\"";
3067
3068
        return $this->execute($sql);
3069
    }
3070
3071
    /**
3072
     * Sets default value of a column
3073
     *
3074
     * @param $table   The table from which to drop
3075
     * @param $column  The column name to set
3076
     * @param $default The new default value
3077
     * @return \PHPPgAdmin\Database\A 0 success
3078
     */
3079 View Code Duplication
    public function setColumnDefault($table, $column, $default)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3080
    {
3081
        $f_schema = $this->_schema;
3082
        $this->fieldClean($f_schema);
3083
        $this->fieldClean($table);
3084
        $this->fieldClean($column);
3085
3086
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" SET DEFAULT {$default}";
3087
3088
        return $this->execute($sql);
3089
    }
3090
3091
    /**
3092
     * Sets whether or not a column can contain NULLs
3093
     *
3094
     * @param $table  The table that contains the column
3095
     * @param $column The column to alter
3096
     * @param $state  True to set null, false to set not null
3097
     * @return \PHPPgAdmin\Database\A 0 success
3098
     */
3099
    public function setColumnNull($table, $column, $state)
3100
    {
3101
        $f_schema = $this->_schema;
3102
        $this->fieldClean($f_schema);
3103
        $this->fieldClean($table);
3104
        $this->fieldClean($column);
3105
3106
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . ($state ? 'DROP' : 'SET') . ' NOT NULL';
3107
3108
        return $this->execute($sql);
3109
    }
3110
3111
    /**
3112
     * Drops a column from a table
3113
     *
3114
     * @param $table   The table from which to drop a column
3115
     * @param $column  The column to be dropped
3116
     * @param $cascade True to cascade drop, false to restrict
3117
     * @return \PHPPgAdmin\Database\A 0 success
3118
     */
3119 View Code Duplication
    public function dropColumn($table, $column, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3120
    {
3121
        $f_schema = $this->_schema;
3122
        $this->fieldClean($f_schema);
3123
        $this->fieldClean($table);
3124
        $this->fieldClean($column);
3125
3126
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DROP COLUMN \"{$column}\"";
3127
        if ($cascade) {
3128
            $sql .= ' CASCADE';
3129
        }
3130
3131
        return $this->execute($sql);
3132
    }
3133
3134
    /**
3135
     * Drops default value of a column
3136
     *
3137
     * @param $table  The table from which to drop
3138
     * @param $column The column name to drop default
3139
     * @return \PHPPgAdmin\Database\A 0 success
3140
     */
3141 View Code Duplication
    public function dropColumnDefault($table, $column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3142
    {
3143
        $f_schema = $this->_schema;
3144
        $this->fieldClean($f_schema);
3145
        $this->fieldClean($table);
3146
        $this->fieldClean($column);
3147
3148
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT";
3149
3150
        return $this->execute($sql);
3151
    }
3152
3153
    /**
3154
     * Sets up the data object for a dump.  eg. Starts the appropriate
3155
     * transaction, sets variables, etc.
3156
     *
3157
     * @return int 0 success
3158
     */
3159
    public function beginDump()
3160
    {
3161
        // Begin serializable transaction (to dump consistent data)
3162
        $status = $this->beginTransaction();
3163
        if ($status != 0) {
3164
            return -1;
3165
        }
3166
3167
        // Set serializable
3168
        $sql    = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE';
3169
        $status = $this->execute($sql);
3170
        if ($status != 0) {
3171
            $this->rollbackTransaction();
3172
3173
            return -1;
3174
        }
3175
3176
        // Set datestyle to ISO
3177
        $sql    = 'SET DATESTYLE = ISO';
3178
        $status = $this->execute($sql);
3179
        if ($status != 0) {
3180
            $this->rollbackTransaction();
3181
3182
            return -1;
3183
        }
3184
3185
        // Set extra_float_digits to 2
3186
        $sql    = 'SET extra_float_digits TO 2';
3187
        $status = $this->execute($sql);
3188
        if ($status != 0) {
3189
            $this->rollbackTransaction();
3190
3191
            return -1;
3192
        }
3193
3194
        return 0;
3195
    }
3196
3197
    /**
3198
     * Ends the data object for a dump.
3199
     *
3200
     * @return bool 0 success
3201
     */
3202
    public function endDump()
3203
    {
3204
        return $this->endTransaction();
3205
    }
3206
3207
    /**
3208
     * Returns a recordset of all columns in a relation.  Used for data export.
3209
     * @@ Note: Really needs to use a cursor
3210
     *
3211
     * @param $relation The name of a relation
3212
     * @param $oids
3213
     * @return \PHPPgAdmin\Database\A recordset on success
3214
     */
3215
    public function dumpRelation($relation, $oids)
3216
    {
3217
        $this->fieldClean($relation);
3218
3219
        // Actually retrieve the rows
3220
        if ($oids) {
3221
            $oid_str = $this->id . ', ';
3222
        } else {
3223
            $oid_str = '';
3224
        }
3225
3226
        return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\"");
3227
    }
3228
3229
    /**
3230
     * Returns all available autovacuum per table information.
3231
     *
3232
     * @param \PHPPgAdmin\Database\if|string $table if given, return autovacuum info for the given table or return all informations for all table
3233
     * @return \PHPPgAdmin\Database\A recordset
3234
     */
3235
    public function getTableAutovacuum($table = '')
3236
    {
3237
        $sql = '';
3238
3239
        if ($table !== '') {
3240
            $this->clean($table);
3241
            $c_schema = $this->_schema;
3242
            $this->clean($c_schema);
3243
3244
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
3245
				FROM pg_class c
3246
					LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
3247
				WHERE c.relkind = 'r'::\"char\"
3248
					AND n.nspname NOT IN ('pg_catalog','information_schema')
3249
					AND c.reloptions IS NOT NULL
3250
					AND c.relname = '{$table}' AND n.nspname = '{$c_schema}'
3251
				ORDER BY nspname, relname";
3252
        } else {
3253
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
3254
				FROM pg_class c
3255
					LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
3256
				WHERE c.relkind = 'r'::\"char\"
3257
					AND n.nspname NOT IN ('pg_catalog','information_schema')
3258
					AND c.reloptions IS NOT NULL
3259
				ORDER BY nspname, relname";
3260
        }
3261
3262
        /* tmp var to parse the results */
3263
        $_autovacs = $this->selectSet($sql);
3264
3265
        /* result aray to return as RS */
3266
        $autovacs = [];
3267
        while (!$_autovacs->EOF) {
3268
            $_ = [
3269
                'nspname' => $_autovacs->fields['nspname'],
3270
                'relname' => $_autovacs->fields['relname'],
3271
            ];
3272
3273
            foreach (explode(',', $_autovacs->fields['reloptions']) as $var) {
3274
                list($o, $v) = explode('=', $var);
3275
                $_[$o]       = $v;
3276
            }
3277
3278
            $autovacs[] = $_;
3279
3280
            $_autovacs->moveNext();
3281
        }
3282
3283
        return new \PHPPgAdmin\ArrayRecordSet($autovacs);
3284
    }
3285
3286
    /**
3287
     * Get the fields for uniquely identifying a row in a table
3288
     *
3289
     * @param $table The table for which to retrieve the identifier
3290
     * @return An array mapping attribute number to attribute name, empty for no identifiers
3291
     * @return -1 error
3292
     */
3293
    public function getRowIdentifier($table)
3294
    {
3295
        $oldtable = $table;
3296
        $c_schema = $this->_schema;
3297
        $this->clean($c_schema);
3298
        $this->clean($table);
3299
3300
        $status = $this->beginTransaction();
3301
        if ($status != 0) {
3302
            return -1;
3303
        }
3304
3305
        // Get the first primary or unique index (sorting primary keys first) that
3306
        // is NOT a partial index.
3307
        $sql = "
3308
			SELECT indrelid, indkey
3309
			FROM pg_catalog.pg_index
3310
			WHERE indisunique AND indrelid=(
3311
				SELECT oid FROM pg_catalog.pg_class
3312
				WHERE relname='{$table}' AND relnamespace=(
3313
					SELECT oid FROM pg_catalog.pg_namespace
3314
					WHERE nspname='{$c_schema}'
3315
				)
3316
			) AND indpred IS NULL AND indexprs IS NULL
3317
			ORDER BY indisprimary DESC LIMIT 1";
3318
        $rs = $this->selectSet($sql);
3319
3320
        // If none, check for an OID column.  Even though OIDs can be duplicated, the edit and delete row
3321
        // functions check that they're only modiying a single row.  Otherwise, return empty array.
3322
        if ($rs->recordCount() == 0) {
3323
            // Check for OID column
3324
            $temp = [];
3325
            if ($this->hasObjectID($table)) {
3326
                $temp = ['oid'];
3327
            }
3328
            $this->endTransaction();
3329
3330
            return $temp;
3331
        } // Otherwise find the names of the keys
3332
3333
        $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey']));
3334
        if (!is_array($attnames)) {
3335
            $this->rollbackTransaction();
3336
3337
            return -1;
3338
        }
3339
3340
        $this->endTransaction();
3341
3342
        return $attnames;
3343
    }
3344
3345
    /**
3346
     * Adds a new row to a table
3347
     *
3348
     * @param $table  The table in which to insert
3349
     * @param $fields Array of given field in values
3350
     * @param $values Array of new values for the row
3351
     * @param $nulls  An array mapping column => something if it is to be null
3352
     * @param $format An array of the data type (VALUE or EXPRESSION)
3353
     * @param $types  An array of field types
3354
     * @return int|\PHPPgAdmin\Database\A 0 success
3355
     */
3356
    public function insertRow($table, $fields, $values, $nulls, $format, $types)
3357
    {
3358
        if (!is_array($fields) || !is_array($values) || !is_array($nulls)
3359
            || !is_array($format) || !is_array($types)
3360
            || (count($fields) != count($values))
3361
        ) {
3362
            return -1;
3363
        }
3364
3365
        // Build clause
3366
        if (count($values) > 0) {
3367
            // Escape all field names
3368
            $fields   = array_map(['\PHPPgAdmin\Database\Postgres', 'fieldClean'], $fields);
3369
            $f_schema = $this->_schema;
3370
            $this->fieldClean($table);
3371
            $this->fieldClean($f_schema);
3372
3373
            $sql = '';
3374
            foreach ($values as $i => $value) {
3375
3376
                // Handle NULL values
3377
                if (isset($nulls[$i])) {
3378
                    $sql .= ',NULL';
3379
                } else {
3380
                    $sql .= ',' . $this->formatValue($types[$i], $format[$i], $value);
3381
                }
3382
            }
3383
3384
            $sql = "INSERT INTO \"{$f_schema}\".\"{$table}\" (\"" . implode('","', $fields) . '")
3385
                VALUES (' . substr($sql, 1) . ')';
3386
3387
            return $this->execute($sql);
3388
        }
3389
3390
        return -1;
3391
    }
3392
3393
    /**
3394
     * Formats a value or expression for sql purposes
3395
     *
3396
     * @param $type   The type of the field
3397
     * @param $format VALUE or EXPRESSION
3398
     * @param $value  The actual value entered in the field.  Can be NULL
3399
     * @return The suitably quoted and escaped value.
3400
     */
3401
    public function formatValue($type, $format, $value)
3402
    {
3403
        switch ($type) {
3404
            case 'bool':
3405
            case 'boolean':
3406
                if ($value == 't') {
3407
                    return 'TRUE';
3408
                }
3409
3410
                if ($value == 'f') {
3411
                    return 'FALSE';
3412
                } elseif ($value == '') {
3413
                    return 'NULL';
3414
                } else {
3415
                    return $value;
3416
                }
3417
3418
                break;
3419
            default:
3420
                // Checking variable fields is difficult as there might be a size
3421
                // attribute...
3422
                if (strpos($type, 'time') === 0) {
3423
                    // Assume it's one of the time types...
3424
                    if ($value == '') {
3425
                        return "''";
3426
                    }
3427
3428
                    if (strcasecmp($value, 'CURRENT_TIMESTAMP') == 0
3429
                        || strcasecmp($value, 'CURRENT_TIME') == 0
3430
                        || strcasecmp($value, 'CURRENT_DATE') == 0
3431
                        || strcasecmp($value, 'LOCALTIME') == 0
3432
                        || strcasecmp($value, 'LOCALTIMESTAMP') == 0) {
3433
                        return $value;
3434
                    } elseif ($format == 'EXPRESSION') {
3435
                        return $value;
3436
                    } else {
3437
                        $this->clean($value);
3438
3439
                        return "'{$value}'";
3440
                    }
3441
                } else {
3442
                    if ($format == 'VALUE') {
3443
                        $this->clean($value);
3444
3445
                        return "'{$value}'";
3446
                    }
3447
3448
                    return $value;
3449
                }
3450
        }
3451
    }
3452
3453
    // View functions
3454
3455
    /**
3456
     * Updates a row in a table
3457
     *
3458
     * @param $table  The table in which to update
3459
     * @param $vars   An array mapping new values for the row
3460
     * @param $nulls  An array mapping column => something if it is to be null
3461
     * @param $format An array of the data type (VALUE or EXPRESSION)
3462
     * @param $types  An array of field types
3463
     * @param $keyarr An array mapping column => value to update
3464
     * @return bool|int 0 success
3465
     */
3466
    public function editRow($table, $vars, $nulls, $format, $types, $keyarr)
3467
    {
3468
        if (!is_array($vars) || !is_array($nulls) || !is_array($format) || !is_array($types)) {
3469
            return -1;
3470
        }
3471
3472
        $f_schema = $this->_schema;
3473
        $this->fieldClean($f_schema);
3474
        $this->fieldClean($table);
3475
3476
        // Build clause
3477
        if (sizeof($vars) > 0) {
3478
            foreach ($vars as $key => $value) {
3479
                $this->fieldClean($key);
3480
3481
                // Handle NULL values
3482
                if (isset($nulls[$key])) {
3483
                    $tmp = 'NULL';
3484
                } else {
3485
                    $tmp = $this->formatValue($types[$key], $format[$key], $value);
3486
                }
3487
3488
                if (isset($sql)) {
3489
                    $sql .= ", \"{$key}\"={$tmp}";
3490
                } else {
3491
                    $sql = "UPDATE \"{$f_schema}\".\"{$table}\" SET \"{$key}\"={$tmp}";
3492
                }
3493
            }
3494
            $first = true;
3495
            foreach ($keyarr as $k => $v) {
3496
                $this->fieldClean($k);
3497
                $this->clean($v);
3498
                if ($first) {
3499
                    $sql .= " WHERE \"{$k}\"='{$v}'";
3500
                    $first = false;
3501
                } else {
3502
                    $sql .= " AND \"{$k}\"='{$v}'";
3503
                }
3504
            }
3505
        }
3506
3507
        // Begin transaction.  We do this so that we can ensure only one row is
3508
        // edited
3509
        $status = $this->beginTransaction();
3510
        if ($status != 0) {
3511
            $this->rollbackTransaction();
3512
3513
            return -1;
3514
        }
3515
3516
        $status = $this->execute($sql);
3517
        if ($status != 0) {
3518
            // update failed
3519
            $this->rollbackTransaction();
3520
3521
            return -1;
3522
        }
3523
3524
        if ($this->conn->Affected_Rows() != 1) {
3525
            // more than one row could be updated
3526
            $this->rollbackTransaction();
3527
3528
            return -2;
3529
        }
3530
3531
        // End transaction
3532
        return $this->endTransaction();
3533
    }
3534
3535
    /**
3536
     * Delete a row from a table
3537
     *
3538
     * @param      $table The table from which to delete
3539
     * @param      $key   An array mapping column => value to delete
3540
     * @param bool $schema
3541
     * @return bool|int 0 success
3542
     */
3543
    public function deleteRow($table, $key, $schema = false)
3544
    {
3545
        if (!is_array($key)) {
3546
            return -1;
3547
        }
3548
3549
        // Begin transaction.  We do this so that we can ensure only one row is
3550
        // deleted
3551
        $status = $this->beginTransaction();
3552
        if ($status != 0) {
3553
            $this->rollbackTransaction();
3554
3555
            return -1;
3556
        }
3557
3558
        if ($schema === false) {
3559
            $schema = $this->_schema;
3560
        }
3561
3562
        $status = $this->delete($table, $key, $schema);
3563
        if ($status != 0 || $this->conn->Affected_Rows() != 1) {
3564
            $this->rollbackTransaction();
3565
3566
            return -2;
3567
        }
3568
3569
        // End transaction
3570
        return $this->endTransaction();
3571
    }
3572
3573
    /**
3574
     * Returns all sequences in the current database
3575
     *
3576
     * @param bool $all
3577
     * @return \PHPPgAdmin\Database\A recordset
3578
     */
3579
    public function getSequences($all = false)
3580
    {
3581
        if ($all) {
3582
            // Exclude pg_catalog and information_schema tables
3583
            $sql = "SELECT n.nspname, c.relname AS seqname, u.usename AS seqowner
3584
				FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
3585
				WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
3586
				AND c.relkind = 'S'
3587
				AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
3588
				ORDER BY nspname, seqname";
3589
        } else {
3590
            $c_schema = $this->_schema;
3591
            $this->clean($c_schema);
3592
            $sql = "SELECT c.relname AS seqname, u.usename AS seqowner, pg_catalog.obj_description(c.oid, 'pg_class') AS seqcomment,
3593
				(SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
3594
				FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
3595
				WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
3596
				AND c.relkind = 'S' AND n.nspname='{$c_schema}' ORDER BY seqname";
3597
        }
3598
3599
        return $this->selectSet($sql);
3600
    }
3601
3602
    /**
3603
     * Execute nextval on a given sequence
3604
     *
3605
     * @param $sequence Sequence name
3606
     * @return \PHPPgAdmin\Database\A 0 success
3607
     */
3608
    public function nextvalSequence($sequence)
3609
    {
3610
        /* This double-cleaning is deliberate */
3611
        $f_schema = $this->_schema;
3612
        $this->fieldClean($f_schema);
3613
        $this->clean($f_schema);
3614
        $this->fieldClean($sequence);
3615
        $this->clean($sequence);
3616
3617
        $sql = "SELECT pg_catalog.NEXTVAL('\"{$f_schema}\".\"{$sequence}\"')";
3618
3619
        return $this->execute($sql);
3620
    }
3621
3622
    /**
3623
     * Execute setval on a given sequence
3624
     *
3625
     * @param $sequence  Sequence name
3626
     * @param $nextvalue The next value
3627
     * @return \PHPPgAdmin\Database\A 0 success
3628
     */
3629
    public function setvalSequence($sequence, $nextvalue)
3630
    {
3631
        /* This double-cleaning is deliberate */
3632
        $f_schema = $this->_schema;
3633
        $this->fieldClean($f_schema);
3634
        $this->clean($f_schema);
3635
        $this->fieldClean($sequence);
3636
        $this->clean($sequence);
3637
        $this->clean($nextvalue);
3638
3639
        $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', '{$nextvalue}')";
3640
3641
        return $this->execute($sql);
3642
    }
3643
3644
    /**
3645
     * Restart a given sequence to its start value
3646
     *
3647
     * @param $sequence Sequence name
3648
     * @return \PHPPgAdmin\Database\A 0 success
3649
     */
3650 View Code Duplication
    public function restartSequence($sequence)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3651
    {
3652
        $f_schema = $this->_schema;
3653
        $this->fieldClean($f_schema);
3654
        $this->fieldClean($sequence);
3655
3656
        $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$sequence}\" RESTART;";
3657
3658
        return $this->execute($sql);
3659
    }
3660
3661
    /**
3662
     * Resets a given sequence to min value of sequence
3663
     *
3664
     * @param $sequence Sequence name
3665
     * @return int|\PHPPgAdmin\Database\A 0 success
3666
     */
3667
    public function resetSequence($sequence)
3668
    {
3669
        // Get the minimum value of the sequence
3670
        $seq = $this->getSequence($sequence);
3671
        if ($seq->recordCount() != 1) {
3672
            return -1;
3673
        }
3674
3675
        $minvalue = $seq->fields['min_value'];
3676
3677
        $f_schema = $this->_schema;
3678
        $this->fieldClean($f_schema);
3679
        /* This double-cleaning is deliberate */
3680
        $this->fieldClean($sequence);
3681
        $this->clean($sequence);
3682
3683
        $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', {$minvalue})";
3684
3685
        return $this->execute($sql);
3686
    }
3687
3688
    /**
3689
     * Returns properties of a single sequence
3690
     *
3691
     * @param $sequence Sequence name
3692
     * @return A recordset
3693
     */
3694
    public function getSequence($sequence)
3695
    {
3696
        $c_schema = $this->_schema;
3697
        $this->clean($c_schema);
3698
        $c_sequence = $sequence;
3699
        $this->fieldClean($sequence);
3700
        $this->clean($c_sequence);
3701
3702
        $sql = "
3703
			SELECT c.relname AS seqname, s.*,
3704
				pg_catalog.obj_description(s.tableoid, 'pg_class') AS seqcomment,
3705
				u.usename AS seqowner, n.nspname
3706
			FROM \"{$sequence}\" AS s, pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
3707
			WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
3708
				AND c.relname = '{$c_sequence}' AND c.relkind = 'S' AND n.nspname='{$c_schema}'
3709
				AND n.oid = c.relnamespace";
3710
3711
        return $this->selectSet($sql);
3712
    }
3713
3714
    /**
3715
     * Creates a new sequence
3716
     *
3717
     * @param $sequence    Sequence name
3718
     * @param $increment   The increment
3719
     * @param $minvalue    The min value
3720
     * @param $maxvalue    The max value
3721
     * @param $startvalue  The starting value
3722
     * @param $cachevalue  The cache value
3723
     * @param $cycledvalue True if cycled, false otherwise
3724
     * @return \PHPPgAdmin\Database\A 0 success
3725
     */
3726
    public function createSequence(
3727
        $sequence,
3728
        $increment,
3729
        $minvalue,
3730
        $maxvalue,
3731
        $startvalue,
3732
        $cachevalue,
3733
        $cycledvalue
3734
    ) {
3735
        $f_schema = $this->_schema;
3736
        $this->fieldClean($f_schema);
3737
        $this->fieldClean($sequence);
3738
        $this->clean($increment);
3739
        $this->clean($minvalue);
3740
        $this->clean($maxvalue);
3741
        $this->clean($startvalue);
3742
        $this->clean($cachevalue);
3743
3744
        $sql = "CREATE SEQUENCE \"{$f_schema}\".\"{$sequence}\"";
3745
        if ($increment != '') {
3746
            $sql .= " INCREMENT {$increment}";
3747
        }
3748
3749
        if ($minvalue != '') {
3750
            $sql .= " MINVALUE {$minvalue}";
3751
        }
3752
3753
        if ($maxvalue != '') {
3754
            $sql .= " MAXVALUE {$maxvalue}";
3755
        }
3756
3757
        if ($startvalue != '') {
3758
            $sql .= " START {$startvalue}";
3759
        }
3760
3761
        if ($cachevalue != '') {
3762
            $sql .= " CACHE {$cachevalue}";
3763
        }
3764
3765
        if ($cycledvalue) {
3766
            $sql .= ' CYCLE';
3767
        }
3768
3769
        return $this->execute($sql);
3770
    }
3771
3772
    /**
3773
     * Alters a sequence
3774
     *
3775
     * @param $sequence     The name of the sequence
3776
     * @param $name         The new name for the sequence
3777
     * @param $comment      The comment on the sequence
3778
     * @param $owner        The new owner for the sequence
3779
     * @param $schema       The new schema for the sequence
3780
     * @param $increment    The increment
3781
     * @param $minvalue     The min value
3782
     * @param $maxvalue     The max value
3783
     * @param $restartvalue The starting value
3784
     * @param $cachevalue   The cache value
3785
     * @param $cycledvalue  True if cycled, false otherwise
3786
     * @param $startvalue   The sequence start value when issueing a restart
3787
     * @return bool|int 0 success
3788
     */
3789
    public function alterSequence(
3790
        $sequence,
3791
        $name,
3792
        $comment,
3793
        $owner = null,
3794
        $schema = null,
3795
        $increment = null,
3796
        $minvalue = null,
3797
        $maxvalue = null,
3798
        $restartvalue = null,
3799
        $cachevalue = null,
3800
        $cycledvalue = null,
3801
        $startvalue = null
3802
    ) {
3803
        $this->fieldClean($sequence);
3804
3805
        $data = $this->getSequence($sequence);
3806
3807
        if ($data->recordCount() != 1) {
3808
            return -2;
3809
        }
3810
3811
        $status = $this->beginTransaction();
3812
        if ($status != 0) {
3813
            $this->rollbackTransaction();
3814
3815
            return -1;
3816
        }
3817
3818
        $status = $this->_alterSequence($data, $name, $comment, $owner, $schema, $increment,
3819
            $minvalue, $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue);
3820
3821
        if ($status != 0) {
3822
            $this->rollbackTransaction();
3823
3824
            return $status;
3825
        }
3826
3827
        return $this->endTransaction();
3828
    }
3829
3830
    /**
3831
     * Protected method which alter a sequence
3832
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION
3833
     *
3834
     * @param $seqrs        The sequence recordSet returned by getSequence()
3835
     * @param $name         The new name for the sequence
3836
     * @param $comment      The comment on the sequence
3837
     * @param $owner        The new owner for the sequence
3838
     * @param $schema       The new schema for the sequence
3839
     * @param $increment    The increment
3840
     * @param $minvalue     The min value
3841
     * @param $maxvalue     The max value
3842
     * @param $restartvalue The starting value
3843
     * @param $cachevalue   The cache value
3844
     * @param $cycledvalue  True if cycled, false otherwise
3845
     * @param $startvalue   The sequence start value when issueing a restart
3846
     * @return int 0 success
3847
     */
3848
    protected function _alterSequence(
3849
        $seqrs,
3850
        $name,
3851
        $comment,
3852
        $owner,
3853
        $schema,
3854
        $increment,
3855
        $minvalue,
3856
        $maxvalue,
3857
        $restartvalue,
3858
        $cachevalue,
3859
        $cycledvalue,
3860
        $startvalue
3861
    ) {
3862
        $this->fieldArrayClean($seqrs->fields);
3863
3864
        // Comment
3865
        $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment);
3866
        if ($status != 0) {
3867
            return -4;
3868
        }
3869
3870
        // Owner
3871
        $this->fieldClean($owner);
3872
        $status = $this->alterSequenceOwner($seqrs, $owner);
3873
        if ($status != 0) {
3874
            return -5;
3875
        }
3876
3877
        // Props
3878
        $this->clean($increment);
3879
        $this->clean($minvalue);
3880
        $this->clean($maxvalue);
3881
        $this->clean($restartvalue);
3882
        $this->clean($cachevalue);
3883
        $this->clean($cycledvalue);
3884
        $this->clean($startvalue);
3885
        $status = $this->alterSequenceProps($seqrs, $increment, $minvalue,
3886
            $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue);
3887
        if ($status != 0) {
3888
            return -6;
3889
        }
3890
3891
        // Rename
3892
        $this->fieldClean($name);
3893
        $status = $this->alterSequenceName($seqrs, $name);
3894
        if ($status != 0) {
3895
            return -3;
3896
        }
3897
3898
        // Schema
3899
        $this->clean($schema);
3900
        $status = $this->alterSequenceSchema($seqrs, $schema);
3901
        if ($status != 0) {
3902
            return -7;
3903
        }
3904
3905
        return 0;
3906
    }
3907
3908
    // Index functions
3909
3910
    /**
3911
     * Alter a sequence's owner
3912
     *
3913
     * @param $seqrs The sequence RecordSet returned by getSequence()
3914
     * @param $owner
3915
     * @return int|\PHPPgAdmin\Database\A 0 success
3916
     * @internal param \PHPPgAdmin\Database\The $name new owner for the sequence
3917
     */
3918 View Code Duplication
    public function alterSequenceOwner($seqrs, $owner)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3919
    {
3920
        // If owner has been changed, then do the alteration.  We are
3921
        // careful to avoid this generally as changing owner is a
3922
        // superuser only function.
3923
        /* vars are cleaned in _alterSequence */
3924
        if (!empty($owner) && ($seqrs->fields['seqowner'] != $owner)) {
3925
            $f_schema = $this->_schema;
3926
            $this->fieldClean($f_schema);
3927
            $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" OWNER TO \"{$owner}\"";
3928
3929
            return $this->execute($sql);
3930
        }
3931
3932
        return 0;
3933
    }
3934
3935
    /**
3936
     * Alter a sequence's properties
3937
     *
3938
     * @param $seqrs        The sequence RecordSet returned by getSequence()
3939
     * @param $increment    The sequence incremental value
3940
     * @param $minvalue     The sequence minimum value
3941
     * @param $maxvalue     The sequence maximum value
3942
     * @param $restartvalue The sequence current value
3943
     * @param $cachevalue   The sequence cache value
3944
     * @param $cycledvalue  Sequence can cycle ?
3945
     * @param $startvalue   The sequence start value when issueing a restart
3946
     * @return int|\PHPPgAdmin\Database\A 0 success
3947
     */
3948
    public function alterSequenceProps(
3949
        $seqrs,
3950
        $increment,
3951
        $minvalue,
3952
        $maxvalue,
3953
        $restartvalue,
3954
        $cachevalue,
3955
        $cycledvalue,
3956
        $startvalue
3957
    ) {
3958
        $sql = '';
3959
        /* vars are cleaned in _alterSequence */
3960
        if (!empty($increment) && ($increment != $seqrs->fields['increment_by'])) {
3961
            $sql .= " INCREMENT {$increment}";
3962
        }
3963
3964
        if (!empty($minvalue) && ($minvalue != $seqrs->fields['min_value'])) {
3965
            $sql .= " MINVALUE {$minvalue}";
3966
        }
3967
3968
        if (!empty($maxvalue) && ($maxvalue != $seqrs->fields['max_value'])) {
3969
            $sql .= " MAXVALUE {$maxvalue}";
3970
        }
3971
3972
        if (!empty($restartvalue) && ($restartvalue != $seqrs->fields['last_value'])) {
3973
            $sql .= " RESTART {$restartvalue}";
3974
        }
3975
3976
        if (!empty($cachevalue) && ($cachevalue != $seqrs->fields['cache_value'])) {
3977
            $sql .= " CACHE {$cachevalue}";
3978
        }
3979
3980
        if (!empty($startvalue) && ($startvalue != $seqrs->fields['start_value'])) {
3981
            $sql .= " START {$startvalue}";
3982
        }
3983
3984
        // toggle cycle yes/no
3985
        if (!is_null($cycledvalue)) {
3986
            $sql .= (!$cycledvalue ? ' NO ' : '') . ' CYCLE';
3987
        }
3988
3989
        if ($sql != '') {
3990
            $f_schema = $this->_schema;
3991
            $this->fieldClean($f_schema);
3992
            $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" {$sql}";
3993
3994
            return $this->execute($sql);
3995
        }
3996
3997
        return 0;
3998
    }
3999
4000
    /**
4001
     * Rename a sequence
4002
     *
4003
     * @param $seqrs The sequence RecordSet returned by getSequence()
4004
     * @param $name  The new name for the sequence
4005
     * @return int|\PHPPgAdmin\Database\A 0 success
4006
     */
4007 View Code Duplication
    public function alterSequenceName($seqrs, $name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4008
    {
4009
        /* vars are cleaned in _alterSequence */
4010
        if (!empty($name) && ($seqrs->fields['seqname'] != $name)) {
4011
            $f_schema = $this->_schema;
4012
            $this->fieldClean($f_schema);
4013
            $sql    = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" RENAME TO \"{$name}\"";
4014
            $status = $this->execute($sql);
4015
            if ($status == 0) {
4016
                $seqrs->fields['seqname'] = $name;
4017
            } else {
4018
                return $status;
4019
            }
4020
        }
4021
4022
        return 0;
4023
    }
4024
4025
    /**
4026
     * Alter a sequence's schema
4027
     *
4028
     * @param $seqrs The sequence RecordSet returned by getSequence()
4029
     * @param $schema
4030
     * @return int|\PHPPgAdmin\Database\A 0 success
4031
     * @internal param \PHPPgAdmin\Database\The $name new schema for the sequence
4032
     */
4033 View Code Duplication
    public function alterSequenceSchema($seqrs, $schema)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4034
    {
4035
        /* vars are cleaned in _alterSequence */
4036
        if (!empty($schema) && ($seqrs->fields['nspname'] != $schema)) {
4037
            $f_schema = $this->_schema;
4038
            $this->fieldClean($f_schema);
4039
            $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" SET SCHEMA {$schema}";
4040
4041
            return $this->execute($sql);
4042
        }
4043
4044
        return 0;
4045
    }
4046
4047
    /**
4048
     * Drops a given sequence
4049
     *
4050
     * @param $sequence Sequence name
4051
     * @param $cascade  True to cascade drop, false to restrict
4052
     * @return \PHPPgAdmin\Database\A 0 success
4053
     */
4054 View Code Duplication
    public function dropSequence($sequence, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4055
    {
4056
        $f_schema = $this->_schema;
4057
        $this->fieldClean($f_schema);
4058
        $this->fieldClean($sequence);
4059
4060
        $sql = "DROP SEQUENCE \"{$f_schema}\".\"{$sequence}\"";
4061
        if ($cascade) {
4062
            $sql .= ' CASCADE';
4063
        }
4064
4065
        return $this->execute($sql);
4066
    }
4067
4068
    /**
4069
     * Returns a list of all views in the database
4070
     *
4071
     * @return All views
4072
     */
4073
    public function getViews()
4074
    {
4075
        $c_schema = $this->_schema;
4076
        $this->clean($c_schema);
4077
        $sql = "
4078
			SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
4079
				pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment
4080
			FROM pg_catalog.pg_class c
4081
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
4082
			WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'v'::\"char\")
4083
			ORDER BY relname";
4084
4085
        return $this->selectSet($sql);
4086
    }
4087
4088
    // Constraint functions
4089
4090
    /**
4091
     * Returns a list of all views in the database
4092
     *
4093
     * @return All views
4094
     */
4095
    public function getMaterializedViews()
4096
    {
4097
        $c_schema = $this->_schema;
4098
        $this->clean($c_schema);
4099
        $sql = "
4100
			SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
4101
				pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment
4102
			FROM pg_catalog.pg_class c
4103
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
4104
			WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'm'::\"char\")
4105
			ORDER BY relname";
4106
4107
        return $this->selectSet($sql);
4108
    }
4109
4110
    /**
4111
     * Updates a view.
4112
     *
4113
     * @param $viewname   The name fo the view to update
4114
     * @param $definition The new definition for the view
4115
     * @param $comment
4116
     * @return bool|int 0 success
4117
     */
4118
    public function setView($viewname, $definition, $comment)
4119
    {
4120
        return $this->createView($viewname, $definition, true, $comment);
4121
    }
4122
4123
    /**
4124
     * Creates a new view.
4125
     *
4126
     * @param $viewname   The name of the view to create
4127
     * @param $definition The definition for the new view
4128
     * @param $replace    True to replace the view, false otherwise
4129
     * @param $comment
4130
     * @return bool|int 0 success
4131
     */
4132
    public function createView($viewname, $definition, $replace, $comment)
4133
    {
4134
        $status = $this->beginTransaction();
4135
        if ($status != 0) {
4136
            return -1;
4137
        }
4138
4139
        $f_schema = $this->_schema;
4140
        $this->fieldClean($f_schema);
4141
        $this->fieldClean($viewname);
4142
4143
        // Note: $definition not cleaned
4144
4145
        $sql = 'CREATE ';
4146
        if ($replace) {
4147
            $sql .= 'OR REPLACE ';
4148
        }
4149
4150
        $sql .= "VIEW \"{$f_schema}\".\"{$viewname}\" AS {$definition}";
4151
4152
        $status = $this->execute($sql);
4153
        if ($status) {
4154
            $this->rollbackTransaction();
4155
4156
            return -1;
4157
        }
4158
4159
        if ($comment != '') {
4160
            $status = $this->setComment('VIEW', $viewname, '', $comment);
4161
            if ($status) {
4162
                $this->rollbackTransaction();
4163
4164
                return -1;
4165
            }
4166
        }
4167
4168
        return $this->endTransaction();
4169
    }
4170
4171
    /**
4172
     * Alter view properties
4173
     *
4174
     * @param $view    The name of the view
4175
     * @param $name    The new name for the view
4176
     * @param $owner   The new owner for the view
4177
     * @param $schema  The new schema for the view
4178
     * @param $comment The comment on the view
4179
     * @return bool|int 0 success
4180
     */
4181 View Code Duplication
    public function alterView($view, $name, $owner, $schema, $comment)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4182
    {
4183
        $data = $this->getView($view);
4184
        if ($data->recordCount() != 1) {
4185
            return -2;
4186
        }
4187
4188
        $status = $this->beginTransaction();
4189
        if ($status != 0) {
4190
            $this->rollbackTransaction();
4191
4192
            return -1;
4193
        }
4194
4195
        $status = $this->_alterView($data, $name, $owner, $schema, $comment);
4196
4197
        if ($status != 0) {
4198
            $this->rollbackTransaction();
4199
4200
            return $status;
4201
        }
4202
4203
        return $this->endTransaction();
4204
    }
4205
4206
    /**
4207
     * Returns all details for a particular view
4208
     *
4209
     * @param $view The name of the view to retrieve
4210
     * @return View info
4211
     */
4212
    public function getView($view)
4213
    {
4214
        $c_schema = $this->_schema;
4215
        $this->clean($c_schema);
4216
        $this->clean($view);
4217
4218
        $sql = "
4219
			SELECT c.relname, n.nspname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
4220
				pg_catalog.pg_get_viewdef(c.oid, true) AS vwdefinition,
4221
				pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment
4222
			FROM pg_catalog.pg_class c
4223
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
4224
			WHERE (c.relname = '{$view}') AND n.nspname='{$c_schema}'";
4225
4226
        return $this->selectSet($sql);
4227
    }
4228
4229
    /**
4230
     * Protected method which alter a view
4231
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION
4232
     *
4233
     * @param $vwrs    The view recordSet returned by getView()
4234
     * @param $name    The new name for the view
4235
     * @param $owner   The new owner for the view
4236
     * @param $schema
4237
     * @param $comment The comment on the view
4238
     * @return int 0 success
4239
     */
4240 View Code Duplication
    protected function _alterView($vwrs, $name, $owner, $schema, $comment)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4241
    {
4242
        $this->fieldArrayClean($vwrs->fields);
4243
4244
        // Comment
4245
        if ($this->setComment('VIEW', $vwrs->fields['relname'], '', $comment) != 0) {
4246
            return -4;
4247
        }
4248
4249
        // Owner
4250
        $this->fieldClean($owner);
4251
        $status = $this->alterViewOwner($vwrs, $owner);
4252
        if ($status != 0) {
4253
            return -5;
4254
        }
4255
4256
        // Rename
4257
        $this->fieldClean($name);
4258
        $status = $this->alterViewName($vwrs, $name);
4259
        if ($status != 0) {
4260
            return -3;
4261
        }
4262
4263
        // Schema
4264
        $this->fieldClean($schema);
4265
        $status = $this->alterViewSchema($vwrs, $schema);
4266
        if ($status != 0) {
4267
            return -6;
4268
        }
4269
4270
        return 0;
4271
    }
4272
4273
    /**
4274
     * Alter a view's owner
4275
     *
4276
     * @param      $vwrs The view recordSet returned by getView()
4277
     * @param null $owner
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $owner is correct as it would always require null to be passed?
Loading history...
4278
     * @return int|\PHPPgAdmin\Database\A 0 success
4279
     * @internal param \PHPPgAdmin\Database\The $name new view's owner
4280
     */
4281 View Code Duplication
    public function alterViewOwner($vwrs, $owner = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4282
    {
4283
        /* $vwrs and $owner are cleaned in _alterView */
4284
        if ((!empty($owner)) && ($vwrs->fields['relowner'] != $owner)) {
4285
            $f_schema = $this->_schema;
4286
            $this->fieldClean($f_schema);
4287
            // If owner has been changed, then do the alteration.  We are
4288
            // careful to avoid this generally as changing owner is a
4289
            // superuser only function.
4290
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$vwrs->fields['relname']}\" OWNER TO \"{$owner}\"";
4291
4292
            return $this->execute($sql);
4293
        }
4294
4295
        return 0;
4296
    }
4297
4298
    /**
4299
     * Rename a view
4300
     *
4301
     * @param $vwrs The view recordSet returned by getView()
4302
     * @param $name The new view's name
4303
     * @return int|\PHPPgAdmin\Database\A 0 success
4304
     */
4305 View Code Duplication
    public function alterViewName($vwrs, $name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4306
    {
4307
        // Rename (only if name has changed)
4308
        /* $vwrs and $name are cleaned in _alterView */
4309
        if (!empty($name) && ($name != $vwrs->fields['relname'])) {
4310
            $f_schema = $this->_schema;
4311
            $this->fieldClean($f_schema);
4312
            $sql    = "ALTER VIEW \"{$f_schema}\".\"{$vwrs->fields['relname']}\" RENAME TO \"{$name}\"";
4313
            $status = $this->execute($sql);
4314
            if ($status == 0) {
4315
                $vwrs->fields['relname'] = $name;
4316
            } else {
4317
                return $status;
4318
            }
4319
        }
4320
4321
        return 0;
4322
    }
4323
4324
    /**
4325
     * Alter a view's schema
4326
     *
4327
     * @param $vwrs The view recordSet returned by getView()
4328
     * @param $schema
4329
     * @return int|\PHPPgAdmin\Database\A 0 success
4330
     * @internal param \PHPPgAdmin\Database\The $name new view's schema
4331
     */
4332 View Code Duplication
    public function alterViewSchema($vwrs, $schema)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4333
    {
4334
        /* $vwrs and $schema are cleaned in _alterView */
4335
        if (!empty($schema) && ($vwrs->fields['nspname'] != $schema)) {
4336
            $f_schema = $this->_schema;
4337
            $this->fieldClean($f_schema);
4338
            // If tablespace has been changed, then do the alteration.  We
4339
            // don't want to do this unnecessarily.
4340
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$vwrs->fields['relname']}\" SET SCHEMA \"{$schema}\"";
4341
4342
            return $this->execute($sql);
4343
        }
4344
4345
        return 0;
4346
    }
4347
4348
    /**
4349
     * Drops a view.
4350
     *
4351
     * @param $viewname The name of the view to drop
4352
     * @param $cascade  True to cascade drop, false to restrict
4353
     * @return \PHPPgAdmin\Database\A 0 success
4354
     */
4355 View Code Duplication
    public function dropView($viewname, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4356
    {
4357
        $f_schema = $this->_schema;
4358
        $this->fieldClean($f_schema);
4359
        $this->fieldClean($viewname);
4360
4361
        $sql = "DROP VIEW \"{$f_schema}\".\"{$viewname}\"";
4362
        if ($cascade) {
4363
            $sql .= ' CASCADE';
4364
        }
4365
4366
        return $this->execute($sql);
4367
    }
4368
4369
    // Domain functions
4370
4371
    /**
4372
     * test if a table has been clustered on an index
4373
     *
4374
     * @param $table The table to test
4375
     *
4376
     * @return true if the table has been already clustered
4377
     */
4378 View Code Duplication
    public function alreadyClustered($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4379
    {
4380
        $c_schema = $this->_schema;
4381
        $this->clean($c_schema);
4382
        $this->clean($table);
4383
4384
        $sql = "SELECT i.indisclustered
4385
			FROM pg_catalog.pg_class c, pg_catalog.pg_index i
4386
			WHERE c.relname = '{$table}'
4387
				AND c.oid = i.indrelid AND i.indisclustered
4388
				AND c.relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
4389
					WHERE nspname='{$c_schema}')
4390
				";
4391
4392
        $v = $this->selectSet($sql);
4393
4394
        return !($v->recordCount() == 0);
4395
    }
4396
4397
    /**
4398
     * Creates an index
4399
     *
4400
     * @param $name       The index name
4401
     * @param $table      The table on which to add the index
4402
     * @param $columns    An array of columns that form the index
4403
     *                    or a string expression for a functional index
4404
     * @param $type       The index type
4405
     * @param $unique     True if unique, false otherwise
4406
     * @param $where      Index predicate ('' for none)
4407
     * @param $tablespace The tablespaces ('' means none/default)
4408
     * @param $concurrently
4409
     * @return \PHPPgAdmin\Database\A 0 success
4410
     */
4411
    public function createIndex($name, $table, $columns, $type, $unique, $where, $tablespace, $concurrently)
4412
    {
4413
        $f_schema = $this->_schema;
4414
        $this->fieldClean($f_schema);
4415
        $this->fieldClean($name);
4416
        $this->fieldClean($table);
4417
4418
        $sql = 'CREATE';
4419
        if ($unique) {
4420
            $sql .= ' UNIQUE';
4421
        }
4422
4423
        $sql .= ' INDEX';
4424
        if ($concurrently) {
4425
            $sql .= ' CONCURRENTLY';
4426
        }
4427
4428
        $sql .= " \"{$name}\" ON \"{$f_schema}\".\"{$table}\" USING {$type} ";
4429
4430
        if (is_array($columns)) {
4431
            $this->arrayClean($columns);
4432
            $sql .= '("' . implode('","', $columns) . '")';
4433
        } else {
4434
            $sql .= '(' . $columns . ')';
4435
        }
4436
4437
        // Tablespace
4438
        if ($this->hasTablespaces() && $tablespace != '') {
4439
            $this->fieldClean($tablespace);
4440
            $sql .= " TABLESPACE \"{$tablespace}\"";
4441
        }
4442
4443
        // Predicate
4444
        if (trim($where) != '') {
4445
            $sql .= " WHERE ({$where})";
4446
        }
4447
4448
        return $this->execute($sql);
4449
    }
4450
4451
    /**
4452
     * Removes an index from the database
4453
     *
4454
     * @param $index   The index to drop
4455
     * @param $cascade True to cascade drop, false to restrict
4456
     * @return \PHPPgAdmin\Database\A 0 success
4457
     */
4458 View Code Duplication
    public function dropIndex($index, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4459
    {
4460
        $f_schema = $this->_schema;
4461
        $this->fieldClean($f_schema);
4462
        $this->fieldClean($index);
4463
4464
        $sql = "DROP INDEX \"{$f_schema}\".\"{$index}\"";
4465
        if ($cascade) {
4466
            $sql .= ' CASCADE';
4467
        }
4468
4469
        return $this->execute($sql);
4470
    }
4471
4472
    /**
4473
     * Rebuild indexes
4474
     *
4475
     * @param                              $type  'DATABASE' or 'TABLE' or 'INDEX'
4476
     * @param                              $name  The name of the specific database, table, or index to be reindexed
4477
     * @param bool|\PHPPgAdmin\Database\If $force If true, recreates indexes forcedly in PostgreSQL 7.0-7.1, forces rebuild of system indexes in
4478
     *                                            7.2-7.3, ignored in >=7.4
4479
     * @return int|\PHPPgAdmin\Database\A
4480
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'DATABASE' at position 0 could not be parsed: Unknown type name ''DATABASE'' at position 0 in 'DATABASE'.
Loading history...
4481
    public function reindex($type, $name, $force = false)
4482
    {
4483
        $f_schema = $this->_schema;
4484
        $this->fieldClean($f_schema);
4485
        $this->fieldClean($name);
4486
        switch ($type) {
4487
            case 'DATABASE':
4488
                $sql = "REINDEX {$type} \"{$name}\"";
4489
                if ($force) {
4490
                    $sql .= ' FORCE';
4491
                }
4492
4493
                break;
4494
            case 'TABLE':
4495
            case 'INDEX':
4496
                $sql = "REINDEX {$type} \"{$f_schema}\".\"{$name}\"";
4497
                if ($force) {
4498
                    $sql .= ' FORCE';
4499
                }
4500
4501
                break;
4502
            default:
4503
                return -1;
4504
        }
4505
4506
        return $this->execute($sql);
4507
    }
4508
4509
    /**
4510
     * Clusters an index
4511
     *
4512
     * @param \PHPPgAdmin\Database\The|string $table The table the index is on
4513
     * @param \PHPPgAdmin\Database\The|string $index The name of the index
4514
     * @return \PHPPgAdmin\Database\A 0 success
4515
     */
4516 View Code Duplication
    public function clusterIndex($table = '', $index = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4517
    {
4518
        $sql = 'CLUSTER';
4519
4520
        // We don't bother with a transaction here, as there's no point rolling
4521
        // back an expensive cluster if a cheap analyze fails for whatever reason
4522
4523
        if (!empty($table)) {
4524
            $f_schema = $this->_schema;
4525
            $this->fieldClean($f_schema);
4526
            $this->fieldClean($table);
4527
            $sql .= " \"{$f_schema}\".\"{$table}\"";
4528
4529
            if (!empty($index)) {
4530
                $this->fieldClean($index);
4531
                $sql .= " USING \"{$index}\"";
4532
            }
4533
        }
4534
4535
        return $this->execute($sql);
4536
    }
4537
4538
    /**
4539
     * Returns a list of all constraints on a table,
4540
     * including constraint name, definition, related col and referenced namespace,
4541
     * table and col if needed
4542
     *
4543
     * @param $table the table where we are looking for fk
4544
     * @return a recordset
4545
     */
4546 View Code Duplication
    public function getConstraintsWithFields($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4547
    {
4548
        $c_schema = $this->_schema;
4549
        $this->clean($c_schema);
4550
        $this->clean($table);
4551
4552
        // get the max number of col used in a constraint for the table
4553
        $sql = "SELECT DISTINCT
4554
			max(SUBSTRING(array_dims(c.conkey) FROM  \$patern\$^\\[.*:(.*)\\]$\$patern\$)) as nb
4555
		FROM pg_catalog.pg_constraint AS c
4556
			JOIN pg_catalog.pg_class AS r ON (c.conrelid=r.oid)
4557
			JOIN pg_catalog.pg_namespace AS ns ON (r.relnamespace=ns.oid)
4558
		WHERE
4559
			r.relname = '{$table}' AND ns.nspname='{$c_schema}'";
4560
4561
        $rs = $this->selectSet($sql);
4562
4563
        if ($rs->EOF) {
4564
            $max_col = 0;
4565
        } else {
4566
            $max_col = $rs->fields['nb'];
4567
        }
4568
4569
        $sql = '
4570
			SELECT
4571
				c.oid AS conid, c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid, true) AS consrc,
4572
				ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema,
4573
				r2.relname as f_table, f1.attname as p_field, f1.attnum AS p_attnum, f2.attname as f_field,
4574
				f2.attnum AS f_attnum, pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment,
4575
				c.conrelid, c.confrelid
4576
			FROM
4577
				pg_catalog.pg_constraint AS c
4578
				JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid)
4579
				JOIN pg_catalog.pg_attribute AS f1 ON (f1.attrelid=r1.oid AND (f1.attnum=c.conkey[1]';
4580
        for ($i = 2; $i <= $rs->fields['nb']; $i++) {
4581
            $sql .= " OR f1.attnum=c.conkey[$i]";
4582
        }
4583
        $sql .= '))
4584
				JOIN pg_catalog.pg_namespace AS ns1 ON r1.relnamespace=ns1.oid
4585
				LEFT JOIN (
4586
					pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid)
4587
				) ON (c.confrelid=r2.oid)
4588
				LEFT JOIN pg_catalog.pg_attribute AS f2 ON
4589
					(f2.attrelid=r2.oid AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)';
4590
        for ($i = 2; $i <= $rs->fields['nb']; $i++) {
4591
            $sql .= " OR (c.confkey[$i]=f2.attnum AND c.conkey[$i]=f1.attnum)";
4592
        }
4593
4594
        $sql .= sprintf("))
4595
			WHERE
4596
				r1.relname = '%s' AND ns1.nspname='%s'
4597
			ORDER BY 1", $table, $c_schema);
4598
4599
        return $this->selectSet($sql);
4600
    }
4601
4602
    /**
4603
     * Adds a primary key constraint to a table
4604
     *
4605
     * @param        $table      The table to which to add the primery key
4606
     * @param        $fields     (array) An array of fields over which to add the primary key
4607
     * @param string $name       (optional) The name to give the key, otherwise default name is assigned
4608
     * @param string $tablespace (optional) The tablespace for the schema, '' indicates default.
4609
     * @return int|\PHPPgAdmin\Database\A 0 success
4610
     */
4611 View Code Duplication
    public function addPrimaryKey($table, $fields, $name = '', $tablespace = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4612
    {
4613
        if (!is_array($fields) || sizeof($fields) == 0) {
4614
            return -1;
4615
        }
4616
4617
        $f_schema = $this->_schema;
4618
        $this->fieldClean($f_schema);
4619
        $this->fieldClean($table);
4620
        $this->fieldArrayClean($fields);
4621
        $this->fieldClean($name);
4622
        $this->fieldClean($tablespace);
4623
4624
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD ";
4625
        if ($name != '') {
4626
            $sql .= "CONSTRAINT \"{$name}\" ";
4627
        }
4628
4629
        $sql .= 'PRIMARY KEY ("' . join('","', $fields) . '")';
4630
4631
        if ($tablespace != '' && $this->hasTablespaces()) {
4632
            $sql .= " USING INDEX TABLESPACE \"{$tablespace}\"";
4633
        }
4634
4635
        return $this->execute($sql);
4636
    }
4637
4638
    /**
4639
     * Adds a unique constraint to a table
4640
     *
4641
     * @param        $table      The table to which to add the unique key
4642
     * @param        $fields     (array) An array of fields over which to add the unique key
4643
     * @param string $name       (optional) The name to give the key, otherwise default name is assigned
4644
     * @param string $tablespace (optional) The tablespace for the schema, '' indicates default.
4645
     * @return int|\PHPPgAdmin\Database\A 0 success
4646
     */
4647 View Code Duplication
    public function addUniqueKey($table, $fields, $name = '', $tablespace = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4648
    {
4649
        if (!is_array($fields) || sizeof($fields) == 0) {
4650
            return -1;
4651
        }
4652
4653
        $f_schema = $this->_schema;
4654
        $this->fieldClean($f_schema);
4655
        $this->fieldClean($table);
4656
        $this->fieldArrayClean($fields);
4657
        $this->fieldClean($name);
4658
        $this->fieldClean($tablespace);
4659
4660
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD ";
4661
        if ($name != '') {
4662
            $sql .= "CONSTRAINT \"{$name}\" ";
4663
        }
4664
4665
        $sql .= 'UNIQUE ("' . join('","', $fields) . '")';
4666
4667
        if ($tablespace != '' && $this->hasTablespaces()) {
4668
            $sql .= " USING INDEX TABLESPACE \"{$tablespace}\"";
4669
        }
4670
4671
        return $this->execute($sql);
4672
    }
4673
4674
    // Function functions
4675
4676
    /**
4677
     * Adds a check constraint to a table
4678
     *
4679
     * @param        $table      The table to which to add the check
4680
     * @param        $definition The definition of the check
4681
     * @param string $name       (optional) The name to give the check, otherwise default name is assigned
4682
     * @return \PHPPgAdmin\Database\A 0 success
4683
     */
4684 View Code Duplication
    public function addCheckConstraint($table, $definition, $name = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4685
    {
4686
        $f_schema = $this->_schema;
4687
        $this->fieldClean($f_schema);
4688
        $this->fieldClean($table);
4689
        $this->fieldClean($name);
4690
        // @@ How the heck do you clean a definition???
4691
4692
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD ";
4693
        if ($name != '') {
4694
            $sql .= "CONSTRAINT \"{$name}\" ";
4695
        }
4696
4697
        $sql .= "CHECK ({$definition})";
4698
4699
        return $this->execute($sql);
4700
    }
4701
4702
    /**
4703
     * Drops a check constraint from a table
4704
     *
4705
     * @param $table The table from which to drop the check
4706
     * @param $name  The name of the check to be dropped
4707
     * @return bool|int 0 success
4708
     */
4709
    public function dropCheckConstraint($table, $name)
4710
    {
4711
        $f_schema = $this->_schema;
4712
        $this->fieldClean($f_schema);
4713
        $c_schema = $this->_schema;
4714
        $this->clean($c_schema);
4715
        $c_table = $table;
4716
        $this->fieldClean($table);
4717
        $this->clean($c_table);
4718
        $this->clean($name);
4719
4720
        // Begin transaction
4721
        $status = $this->beginTransaction();
4722
        if ($status != 0) {
4723
            return -2;
4724
        }
4725
4726
        // Properly lock the table
4727
        $sql    = "LOCK TABLE \"{$f_schema}\".\"{$table}\" IN ACCESS EXCLUSIVE MODE";
4728
        $status = $this->execute($sql);
4729
        if ($status != 0) {
4730
            $this->rollbackTransaction();
4731
4732
            return -3;
4733
        }
4734
4735
        // Delete the check constraint
4736
        $sql = "DELETE FROM pg_relcheck WHERE rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$c_table}'
4737
			AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
4738
			nspname = '{$c_schema}')) AND rcname='{$name}'";
4739
        $status = $this->execute($sql);
4740
        if ($status != 0) {
4741
            $this->rollbackTransaction();
4742
4743
            return -4;
4744
        }
4745
4746
        // Update the pg_class catalog to reflect the new number of checks
4747
        $sql = "UPDATE pg_class SET relchecks=(SELECT COUNT(*) FROM pg_relcheck WHERE
4748
					rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$c_table}'
4749
						AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
4750
						nspname = '{$c_schema}')))
4751
					WHERE relname='{$c_table}'";
4752
        $status = $this->execute($sql);
4753
        if ($status != 0) {
4754
            $this->rollbackTransaction();
4755
4756
            return -4;
4757
        }
4758
4759
        // Otherwise, close the transaction
4760
        return $this->endTransaction();
4761
    }
4762
4763
    /**
4764
     * Adds a foreign key constraint to a table
4765
     *
4766
     * @param        $table
4767
     * @param        $targschema The schema that houses the target table to which to add the foreign key
4768
     * @param        $targtable  The table to which to add the foreign key
4769
     * @param        $sfields    (array) An array of source fields over which to add the foreign key
4770
     * @param        $tfields    (array) An array of target fields over which to add the foreign key
4771
     * @param        $upd_action The action for updates (eg. RESTRICT)
4772
     * @param        $del_action The action for deletes (eg. RESTRICT)
4773
     * @param        $match      The match type (eg. MATCH FULL)
4774
     * @param        $deferrable The deferrability (eg. NOT DEFERRABLE)
4775
     * @param        $initially
4776
     * @param string $name       (optional) The name to give the key, otherwise default name is assigned
4777
     * @return \PHPPgAdmin\Database\A 0 success
4778
     * @internal param \PHPPgAdmin\Database\The $target table that contains the target columns
4779
     * @internal param \PHPPgAdmin\Database\The $intially initial deferrability (eg. INITIALLY IMMEDIATE)
4780
     */
4781
    public function addForeignKey(
4782
        $table,
4783
        $targschema,
4784
        $targtable,
4785
        $sfields,
4786
        $tfields,
4787
        $upd_action,
4788
        $del_action,
4789
        $match,
4790
        $deferrable,
4791
        $initially,
4792
        $name = ''
4793
    ) {
4794
        if (!is_array($sfields) || sizeof($sfields) == 0 ||
4795
            !is_array($tfields) || sizeof($tfields) == 0) {
4796
            return -1;
4797
        }
4798
4799
        $f_schema = $this->_schema;
4800
        $this->fieldClean($f_schema);
4801
        $this->fieldClean($table);
4802
        $this->fieldClean($targschema);
4803
        $this->fieldClean($targtable);
4804
        $this->fieldArrayClean($sfields);
4805
        $this->fieldArrayClean($tfields);
4806
        $this->fieldClean($name);
4807
4808
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD ";
4809
        if ($name != '') {
4810
            $sql .= "CONSTRAINT \"{$name}\" ";
4811
        }
4812
4813
        $sql .= 'FOREIGN KEY ("' . join('","', $sfields) . '") ';
4814
        // Target table needs to be fully qualified
4815
        $sql .= "REFERENCES \"{$targschema}\".\"{$targtable}\"(\"" . join('","', $tfields) . '") ';
4816
        if ($match != $this->fkmatches[0]) {
4817
            $sql .= " {$match}";
4818
        }
4819
4820
        if ($upd_action != $this->fkactions[0]) {
4821
            $sql .= " ON UPDATE {$upd_action}";
4822
        }
4823
4824
        if ($del_action != $this->fkactions[0]) {
4825
            $sql .= " ON DELETE {$del_action}";
4826
        }
4827
4828
        if ($deferrable != $this->fkdeferrable[0]) {
4829
            $sql .= " {$deferrable}";
4830
        }
4831
4832
        if ($initially != $this->fkinitial[0]) {
4833
            $sql .= " {$initially}";
4834
        }
4835
4836
        return $this->execute($sql);
4837
    }
4838
4839
    /**
4840
     * Removes a constraint from a relation
4841
     *
4842
     * @param $constraint The constraint to drop
4843
     * @param $relation   The relation from which to drop
4844
     * @param $type       The type of constraint (c, f, u or p)
4845
     * @param $cascade    True to cascade drop, false to restrict
4846
     * @return \PHPPgAdmin\Database\A 0 success
4847
     */
4848 View Code Duplication
    public function dropConstraint($constraint, $relation, $type, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4849
    {
4850
        $f_schema = $this->_schema;
4851
        $this->fieldClean($f_schema);
4852
        $this->fieldClean($constraint);
4853
        $this->fieldClean($relation);
4854
4855
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$relation}\" DROP CONSTRAINT \"{$constraint}\"";
4856
        if ($cascade) {
4857
            $sql .= ' CASCADE';
4858
        }
4859
4860
        return $this->execute($sql);
4861
    }
4862
4863
    /**
4864
     * A function for getting all columns linked by foreign keys given a group of tables
4865
     *
4866
     * @param $tables multi dimensional assoc array that holds schema and table name
4867
     * @return A recordset of linked tables and columns
4868
     * @return -1 $tables isn't an array
4869
     */
4870
    public function getLinkingKeys($tables)
4871
    {
4872
        if (!is_array($tables)) {
4873
            return -1;
4874
        }
4875
4876
        $this->clean($tables[0]['tablename']);
4877
        $this->clean($tables[0]['schemaname']);
4878
        $tables_list        = "'{$tables[0]['tablename']}'";
4879
        $schema_list        = "'{$tables[0]['schemaname']}'";
4880
        $schema_tables_list = "'{$tables[0]['schemaname']}.{$tables[0]['tablename']}'";
4881
4882
        for ($i = 1; $i < sizeof($tables); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4883
            $this->clean($tables[$i]['tablename']);
4884
            $this->clean($tables[$i]['schemaname']);
4885
            $tables_list .= ", '{$tables[$i]['tablename']}'";
4886
            $schema_list .= ", '{$tables[$i]['schemaname']}'";
4887
            $schema_tables_list .= ", '{$tables[$i]['schemaname']}.{$tables[$i]['tablename']}'";
4888
        }
4889
4890
        $maxDimension = 1;
4891
4892
        $sql = "
4893
			SELECT DISTINCT
4894
				array_dims(pc.conkey) AS arr_dim,
4895
				pgc1.relname AS p_table
4896
			FROM
4897
				pg_catalog.pg_constraint AS pc,
4898
				pg_catalog.pg_class AS pgc1
4899
			WHERE
4900
				pc.contype = 'f'
4901
				AND (pc.conrelid = pgc1.relfilenode OR pc.confrelid = pgc1.relfilenode)
4902
				AND pgc1.relname IN ($tables_list)
4903
			";
4904
4905
        //parse our output to find the highest dimension of foreign keys since pc.conkey is stored in an array
4906
        $rs = $this->selectSet($sql);
4907
        while (!$rs->EOF) {
4908
            $arrData      = explode(':', $rs->fields['arr_dim']);
4909
            $tmpDimension = intval(substr($arrData[1], 0, strlen($arrData[1] - 1)));
4910
            $maxDimension = $tmpDimension > $maxDimension ? $tmpDimension : $maxDimension;
4911
            $rs->MoveNext();
4912
        }
4913
4914
        //we know the highest index for foreign keys that conkey goes up to, expand for us in an IN query
4915
        $cons_str = '( (pfield.attnum = conkey[1] AND cfield.attnum = confkey[1]) ';
4916
        for ($i = 2; $i <= $maxDimension; $i++) {
4917
            $cons_str .= "OR (pfield.attnum = conkey[{$i}] AND cfield.attnum = confkey[{$i}]) ";
4918
        }
4919
        $cons_str .= ') ';
4920
4921
        $sql = "
4922
			SELECT
4923
				pgc1.relname AS p_table,
4924
				pgc2.relname AS f_table,
4925
				pfield.attname AS p_field,
4926
				cfield.attname AS f_field,
4927
				pgns1.nspname AS p_schema,
4928
				pgns2.nspname AS f_schema
4929
			FROM
4930
				pg_catalog.pg_constraint AS pc,
4931
				pg_catalog.pg_class AS pgc1,
4932
				pg_catalog.pg_class AS pgc2,
4933
				pg_catalog.pg_attribute AS pfield,
4934
				pg_catalog.pg_attribute AS cfield,
4935
				(SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN ($schema_list) ) AS pgns1,
4936
 				(SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN ($schema_list) ) AS pgns2
4937
			WHERE
4938
				pc.contype = 'f'
4939
				AND pgc1.relnamespace = pgns1.ns_id
4940
 				AND pgc2.relnamespace = pgns2.ns_id
4941
				AND pc.conrelid = pgc1.relfilenode
4942
				AND pc.confrelid = pgc2.relfilenode
4943
				AND pfield.attrelid = pc.conrelid
4944
				AND cfield.attrelid = pc.confrelid
4945
				AND $cons_str
4946
				AND pgns1.nspname || '.' || pgc1.relname IN ($schema_tables_list)
4947
				AND pgns2.nspname || '.' || pgc2.relname IN ($schema_tables_list)
4948
		";
4949
4950
        return $this->selectSet($sql);
4951
    }
4952
4953
    /**
4954
     * Finds the foreign keys that refer to the specified table
4955
     *
4956
     * @param $table The table to find referrers for
4957
     * @return A recordset
4958
     */
4959 View Code Duplication
    public function getReferrers($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4960
    {
4961
        $this->clean($table);
4962
4963
        $status = $this->beginTransaction();
4964
        if ($status != 0) {
4965
            return -1;
4966
        }
4967
4968
        $c_schema = $this->_schema;
4969
        $this->clean($c_schema);
4970
4971
        $sql = "
4972
			SELECT
4973
				pn.nspname,
4974
				pl.relname,
4975
				pc.conname,
4976
				pg_catalog.pg_get_constraintdef(pc.oid) AS consrc
4977
			FROM
4978
				pg_catalog.pg_constraint pc,
4979
				pg_catalog.pg_namespace pn,
4980
				pg_catalog.pg_class pl
4981
			WHERE
4982
				pc.connamespace = pn.oid
4983
				AND pc.conrelid = pl.oid
4984
				AND pc.contype = 'f'
4985
				AND confrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
4986
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
4987
					WHERE nspname='{$c_schema}'))
4988
			ORDER BY 1,2,3
4989
		";
4990
4991
        return $this->selectSet($sql);
4992
    }
4993
4994
    // Type functions
4995
4996
    /**
4997
     * Gets all information for a single domain
4998
     *
4999
     * @param $domain The name of the domain to fetch
5000
     * @return A recordset
5001
     */
5002
    public function getDomain($domain)
5003
    {
5004
        $c_schema = $this->_schema;
5005
        $this->clean($c_schema);
5006
        $this->clean($domain);
5007
5008
        $sql = "
5009
			SELECT
5010
				t.typname AS domname,
5011
				pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype,
5012
				t.typnotnull AS domnotnull,
5013
				t.typdefault AS domdef,
5014
				pg_catalog.pg_get_userbyid(t.typowner) AS domowner,
5015
				pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment
5016
			FROM
5017
				pg_catalog.pg_type t
5018
			WHERE
5019
				t.typtype = 'd'
5020
				AND t.typname = '{$domain}'
5021
				AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace
5022
					WHERE nspname = '{$c_schema}')";
5023
5024
        return $this->selectSet($sql);
5025
    }
5026
5027
    /**
5028
     * Return all domains in current schema.  Excludes domain constraints.
5029
     *
5030
     * @return All tables, sorted alphabetically
5031
     */
5032
    public function getDomains()
5033
    {
5034
        $c_schema = $this->_schema;
5035
        $this->clean($c_schema);
5036
5037
        $sql = "
5038
			SELECT
5039
				t.typname AS domname,
5040
				pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype,
5041
				t.typnotnull AS domnotnull,
5042
				t.typdefault AS domdef,
5043
				pg_catalog.pg_get_userbyid(t.typowner) AS domowner,
5044
				pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment
5045
			FROM
5046
				pg_catalog.pg_type t
5047
			WHERE
5048
				t.typtype = 'd'
5049
				AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace
5050
					WHERE nspname='{$c_schema}')
5051
			ORDER BY t.typname";
5052
5053
        return $this->selectSet($sql);
5054
    }
5055
5056
    /**
5057
     * Get domain constraints
5058
     *
5059
     * @param $domain The name of the domain whose constraints to fetch
5060
     * @return A recordset
5061
     */
5062
    public function getDomainConstraints($domain)
5063
    {
5064
        $c_schema = $this->_schema;
5065
        $this->clean($c_schema);
5066
        $this->clean($domain);
5067
5068
        $sql = "
5069
			SELECT
5070
				conname,
5071
				contype,
5072
				pg_catalog.pg_get_constraintdef(oid, true) AS consrc
5073
			FROM
5074
				pg_catalog.pg_constraint
5075
			WHERE
5076
				contypid = (
5077
					SELECT oid FROM pg_catalog.pg_type
5078
					WHERE typname='{$domain}'
5079
						AND typnamespace = (
5080
							SELECT oid FROM pg_catalog.pg_namespace
5081
							WHERE nspname = '{$c_schema}')
5082
				)
5083
			ORDER BY conname";
5084
5085
        return $this->selectSet($sql);
5086
    }
5087
5088
    /**
5089
     * Creates a domain
5090
     *
5091
     * @param $domain  The name of the domain to create
5092
     * @param $type    The base type for the domain
5093
     * @param $length  Optional type length
5094
     * @param $array   True for array type, false otherwise
5095
     * @param $notnull True for NOT NULL, false otherwise
5096
     * @param $default Default value for domain
5097
     * @param $check   A CHECK constraint if there is one
5098
     * @return \PHPPgAdmin\Database\A 0 success
5099
     */
5100
    public function createDomain($domain, $type, $length, $array, $notnull, $default, $check)
5101
    {
5102
        $f_schema = $this->_schema;
5103
        $this->fieldClean($f_schema);
5104
        $this->fieldClean($domain);
5105
5106
        $sql = "CREATE DOMAIN \"{$f_schema}\".\"{$domain}\" AS ";
5107
5108
        if ($length == '') {
5109
            $sql .= $type;
5110
        } else {
5111
            switch ($type) {
5112
                // Have to account for weird placing of length for with/without
5113
                // time zone types
5114
                case 'timestamp with time zone':
5115
                case 'timestamp without time zone':
5116
                    $qual = substr($type, 9);
5117
                    $sql .= "timestamp({$length}){$qual}";
5118
                    break;
5119
                case 'time with time zone':
5120
                case 'time without time zone':
5121
                    $qual = substr($type, 4);
5122
                    $sql .= "time({$length}){$qual}";
5123
                    break;
5124
                default:
5125
                    $sql .= "{$type}({$length})";
5126
            }
5127
        }
5128
5129
        // Add array qualifier, if requested
5130
        if ($array) {
5131
            $sql .= '[]';
5132
        }
5133
5134
        if ($notnull) {
5135
            $sql .= ' NOT NULL';
5136
        }
5137
5138
        if ($default != '') {
5139
            $sql .= " DEFAULT {$default}";
5140
        }
5141
5142
        if ($this->hasDomainConstraints() && $check != '') {
5143
            $sql .= " CHECK ({$check})";
5144
        }
5145
5146
        return $this->execute($sql);
5147
    }
5148
5149
    public function hasDomainConstraints()
5150
    {
5151
        return true;
5152
    }
5153
5154
    /**
5155
     * Alters a domain
5156
     *
5157
     * @param $domain     The domain to alter
5158
     * @param $domdefault The domain default
5159
     * @param $domnotnull True for NOT NULL, false otherwise
5160
     * @param $domowner   The domain owner
5161
     * @return bool|int 0 success
5162
     */
5163
    public function alterDomain($domain, $domdefault, $domnotnull, $domowner)
5164
    {
5165
        $f_schema = $this->_schema;
5166
        $this->fieldClean($f_schema);
5167
        $this->fieldClean($domain);
5168
        $this->fieldClean($domowner);
5169
5170
        $status = $this->beginTransaction();
5171
        if ($status != 0) {
5172
            $this->rollbackTransaction();
5173
5174
            return -1;
5175
        }
5176
5177
        // Default
5178
        if ($domdefault == '') {
5179
            $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP DEFAULT";
5180
        } else {
5181
            $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET DEFAULT {$domdefault}";
5182
        }
5183
5184
        $status = $this->execute($sql);
5185
        if ($status != 0) {
5186
            $this->rollbackTransaction();
5187
5188
            return -2;
5189
        }
5190
5191
        // NOT NULL
5192
        if ($domnotnull) {
5193
            $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET NOT NULL";
5194
        } else {
5195
            $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP NOT NULL";
5196
        }
5197
5198
        $status = $this->execute($sql);
5199
        if ($status != 0) {
5200
            $this->rollbackTransaction();
5201
5202
            return -3;
5203
        }
5204
5205
        // Owner
5206
        $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" OWNER TO \"{$domowner}\"";
5207
5208
        $status = $this->execute($sql);
5209
        if ($status != 0) {
5210
            $this->rollbackTransaction();
5211
5212
            return -4;
5213
        }
5214
5215
        return $this->endTransaction();
5216
    }
5217
5218
    /**
5219
     * Drops a domain.
5220
     *
5221
     * @param $domain  The name of the domain to drop
5222
     * @param $cascade True to cascade drop, false to restrict
5223
     * @return \PHPPgAdmin\Database\A 0 success
5224
     */
5225 View Code Duplication
    public function dropDomain($domain, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5226
    {
5227
        $f_schema = $this->_schema;
5228
        $this->fieldClean($f_schema);
5229
        $this->fieldClean($domain);
5230
5231
        $sql = "DROP DOMAIN \"{$f_schema}\".\"{$domain}\"";
5232
        if ($cascade) {
5233
            $sql .= ' CASCADE';
5234
        }
5235
5236
        return $this->execute($sql);
5237
    }
5238
5239
    /**
5240
     * Adds a check constraint to a domain
5241
     *
5242
     * @param        $domain     The domain to which to add the check
5243
     * @param        $definition The definition of the check
5244
     * @param string $name       (optional) The name to give the check, otherwise default name is assigned
5245
     * @return \PHPPgAdmin\Database\A 0 success
5246
     */
5247 View Code Duplication
    public function addDomainCheckConstraint($domain, $definition, $name = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5248
    {
5249
        $f_schema = $this->_schema;
5250
        $this->fieldClean($f_schema);
5251
        $this->fieldClean($domain);
5252
        $this->fieldClean($name);
5253
5254
        $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" ADD ";
5255
        if ($name != '') {
5256
            $sql .= "CONSTRAINT \"{$name}\" ";
5257
        }
5258
5259
        $sql .= "CHECK ({$definition})";
5260
5261
        return $this->execute($sql);
5262
    }
5263
5264
    /**
5265
     * Drops a domain constraint
5266
     *
5267
     * @param $domain     The domain from which to remove the constraint
5268
     * @param $constraint The constraint to remove
5269
     * @param $cascade    True to cascade, false otherwise
5270
     * @return \PHPPgAdmin\Database\A 0 success
5271
     */
5272 View Code Duplication
    public function dropDomainConstraint($domain, $constraint, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5273
    {
5274
        $f_schema = $this->_schema;
5275
        $this->fieldClean($f_schema);
5276
        $this->fieldClean($domain);
5277
        $this->fieldClean($constraint);
5278
5279
        $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP CONSTRAINT \"{$constraint}\"";
5280
        if ($cascade) {
5281
            $sql .= ' CASCADE';
5282
        }
5283
5284
        return $this->execute($sql);
5285
    }
5286
5287
    // Rule functions
5288
5289
    /**
5290
     * Returns an array containing a function's properties
5291
     *
5292
     * @param $f The array of data for the function
5293
     * @return An array containing the properties
5294
     */
5295
    public function getFunctionProperties($f)
5296
    {
5297
        $temp = [];
5298
5299
        // Volatility
5300
        if ($f['provolatile'] == 'v') {
5301
            $temp[] = 'VOLATILE';
5302
        } elseif ($f['provolatile'] == 'i') {
5303
            $temp[] = 'IMMUTABLE';
5304
        } elseif ($f['provolatile'] == 's') {
5305
            $temp[] = 'STABLE';
5306
        } else {
5307
            return -1;
5308
        }
5309
5310
        // Null handling
5311
        $f['proisstrict'] = $this->phpBool($f['proisstrict']);
5312
        if ($f['proisstrict']) {
5313
            $temp[] = 'RETURNS NULL ON NULL INPUT';
5314
        } else {
5315
            $temp[] = 'CALLED ON NULL INPUT';
5316
        }
5317
5318
        // Security
5319
        $f['prosecdef'] = $this->phpBool($f['prosecdef']);
5320
        if ($f['prosecdef']) {
5321
            $temp[] = 'SECURITY DEFINER';
5322
        } else {
5323
            $temp[] = 'SECURITY INVOKER';
5324
        }
5325
5326
        return $temp;
5327
    }
5328
5329
    /**
5330
     * Updates (replaces) a function.
5331
     *
5332
     * @param $function_oid The OID of the function
5333
     * @param $funcname     The name of the function to create
5334
     * @param $newname      The new name for the function
5335
     * @param $args         The array of argument types
5336
     * @param $returns      The return type
5337
     * @param $definition   The definition for the new function
5338
     * @param $language     The language the function is written for
5339
     * @param $flags        An array of optional flags
5340
     * @param $setof        True if returns a set, false otherwise
5341
     * @param $funcown
5342
     * @param $newown
5343
     * @param $funcschema
5344
     * @param $newschema
5345
     * @param $cost
5346
     * @param $rows
5347
     * @param $comment      The comment on the function
5348
     * @return bool|int 0 success
5349
     */
5350
    public function setFunction(
5351
        $function_oid,
5352
        $funcname,
5353
        $newname,
5354
        $args,
5355
        $returns,
5356
        $definition,
5357
        $language,
5358
        $flags,
5359
        $setof,
5360
        $funcown,
5361
        $newown,
5362
        $funcschema,
5363
        $newschema,
5364
        $cost,
5365
        $rows,
5366
        $comment
5367
    ) {
5368
        // Begin a transaction
5369
        $status = $this->beginTransaction();
5370
        if ($status != 0) {
5371
            $this->rollbackTransaction();
5372
5373
            return -1;
5374
        }
5375
5376
        // Replace the existing function
5377
        $status = $this->createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, true);
5378
        if ($status != 0) {
5379
            $this->rollbackTransaction();
5380
5381
            return $status;
5382
        }
5383
5384
        $f_schema = $this->_schema;
5385
        $this->fieldClean($f_schema);
5386
5387
        // Rename the function, if necessary
5388
        $this->fieldClean($newname);
5389
        /* $funcname is escaped in createFunction */
5390
        if ($funcname != $newname) {
5391
            $sql    = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) RENAME TO \"{$newname}\"";
5392
            $status = $this->execute($sql);
5393
            if ($status != 0) {
5394
                $this->rollbackTransaction();
5395
5396
                return -5;
5397
            }
5398
5399
            $funcname = $newname;
5400
        }
5401
5402
        // Alter the owner, if necessary
5403
        if ($this->hasFunctionAlterOwner()) {
5404
            $this->fieldClean($newown);
5405
            if ($funcown != $newown) {
5406
                $sql    = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) OWNER TO \"{$newown}\"";
5407
                $status = $this->execute($sql);
5408
                if ($status != 0) {
5409
                    $this->rollbackTransaction();
5410
5411
                    return -6;
5412
                }
5413
            }
5414
        }
5415
5416
        // Alter the schema, if necessary
5417
        if ($this->hasFunctionAlterSchema()) {
5418
            $this->fieldClean($newschema);
5419
            /* $funcschema is escaped in createFunction */
5420
            if ($funcschema != $newschema) {
5421
                $sql    = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) SET SCHEMA \"{$newschema}\"";
5422
                $status = $this->execute($sql);
5423
                if ($status != 0) {
5424
                    $this->rollbackTransaction();
5425
5426
                    return -7;
5427
                }
5428
            }
5429
        }
5430
5431
        return $this->endTransaction();
5432
    }
5433
5434
    /**
5435
     * Creates a new function.
5436
     *
5437
     * @param      $funcname   The name of the function to create
5438
     * @param      $args       A comma separated string of types
5439
     * @param      $returns    The return type
5440
     * @param      $definition The definition for the new function
5441
     * @param      $language   The language the function is written for
5442
     * @param      $flags      An array of optional flags
5443
     * @param      $setof      True if it returns a set, false otherwise
5444
     * @param      $cost       cost the planner should use in the function execution step
5445
     * @param      $rows       number of rows planner should estimate will be returned
5446
     * @param      $comment    Comment for the function
5447
     * @param bool $replace    (optional) True if OR REPLACE, false for normal
5448
     * @return bool|int 0 success
5449
     */
5450
    public function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, $replace = false)
5451
    {
5452
5453
        // Begin a transaction
5454
        $status = $this->beginTransaction();
5455
        if ($status != 0) {
5456
            $this->rollbackTransaction();
5457
5458
            return -1;
5459
        }
5460
5461
        $this->fieldClean($funcname);
5462
        $this->clean($args);
5463
        $this->fieldClean($language);
5464
        $this->arrayClean($flags);
5465
        $this->clean($cost);
5466
        $this->clean($rows);
5467
        $f_schema = $this->_schema;
5468
        $this->fieldClean($f_schema);
5469
5470
        $sql = 'CREATE';
5471
        if ($replace) {
5472
            $sql .= ' OR REPLACE';
5473
        }
5474
5475
        $sql .= " FUNCTION \"{$f_schema}\".\"{$funcname}\" (";
5476
5477
        if ($args != '') {
5478
            $sql .= $args;
5479
        }
5480
5481
        // For some reason, the returns field cannot have quotes...
5482
        $sql .= ') RETURNS ';
5483
        if ($setof) {
5484
            $sql .= 'SETOF ';
5485
        }
5486
5487
        $sql .= "{$returns} AS ";
5488
5489 View Code Duplication
        if (is_array($definition)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5490
            $this->arrayClean($definition);
5491
            $sql .= "'" . $definition[0] . "'";
5492
            if ($definition[1]) {
5493
                $sql .= ",'" . $definition[1] . "'";
5494
            }
5495
        } else {
5496
            $this->clean($definition);
5497
            $sql .= "'" . $definition . "'";
5498
        }
5499
5500
        $sql .= " LANGUAGE \"{$language}\"";
5501
5502
        // Add costs
5503
        if (!empty($cost)) {
5504
            $sql .= " COST {$cost}";
5505
        }
5506
5507
        if ($rows != 0) {
5508
            $sql .= " ROWS {$rows}";
5509
        }
5510
5511
        // Add flags
5512
        foreach ($flags as $v) {
5513
            // Skip default flags
5514
            if ($v == '') {
5515
                continue;
5516
            }
5517
5518
            $sql .= "\n{$v}";
5519
        }
5520
5521
        $status = $this->execute($sql);
5522
        if ($status != 0) {
5523
            $this->rollbackTransaction();
5524
5525
            return -3;
5526
        }
5527
5528
        /* set the comment */
5529
        $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment);
5530
        if ($status != 0) {
5531
            $this->rollbackTransaction();
5532
5533
            return -4;
5534
        }
5535
5536
        return $this->endTransaction();
5537
    }
5538
5539
    public function hasFunctionAlterOwner()
5540
    {
5541
        return true;
5542
    }
5543
5544
    // Trigger functions
5545
5546
    public function hasFunctionAlterSchema()
5547
    {
5548
        return true;
5549
    }
5550
5551
    /**
5552
     * Drops a function.
5553
     *
5554
     * @param $function_oid The OID of the function to drop
5555
     * @param $cascade      True to cascade drop, false to restrict
5556
     * @return \PHPPgAdmin\Database\A 0 success
5557
     */
5558
    public function dropFunction($function_oid, $cascade)
5559
    {
5560
        // Function comes in with $object as function OID
5561
        $fn       = $this->getFunction($function_oid);
5562
        $f_schema = $this->_schema;
5563
        $this->fieldClean($f_schema);
5564
        $this->fieldClean($fn->fields['proname']);
5565
5566
        $sql = "DROP FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})";
5567
        if ($cascade) {
5568
            $sql .= ' CASCADE';
5569
        }
5570
5571
        return $this->execute($sql);
5572
    }
5573
5574
    /**
5575
     * Returns all details for a particular function
5576
     *
5577
     * @param $function_oid
5578
     * @return \PHPPgAdmin\Database\Function info
5579
     * @internal param \PHPPgAdmin\Database\The $func name of the function to retrieve
5580
     */
5581
    public function getFunction($function_oid)
5582
    {
5583
        $this->clean($function_oid);
5584
5585
        $sql = "
5586
			SELECT
5587
				pc.oid AS prooid, proname,
5588
				pg_catalog.pg_get_userbyid(proowner) AS proowner,
5589
				nspname as proschema, lanname as prolanguage, procost, prorows,
5590
				pg_catalog.format_type(prorettype, NULL) as proresult, prosrc,
5591
				probin, proretset, proisstrict, provolatile, prosecdef,
5592
				pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments,
5593
				proargnames AS proargnames,
5594
				pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment,
5595
				proconfig,
5596
				(select array_agg( (select typname from pg_type pt
5597
					where pt.oid = p.oid) ) from unnest(proallargtypes) p)
5598
				AS proallarguments,
5599
				proargmodes
5600
			FROM
5601
				pg_catalog.pg_proc pc, pg_catalog.pg_language pl,
5602
				pg_catalog.pg_namespace pn
5603
			WHERE
5604
				pc.oid = '{$function_oid}'::oid AND pc.prolang = pl.oid
5605
				AND pc.pronamespace = pn.oid
5606
			";
5607
5608
        return $this->selectSet($sql);
5609
    }
5610
5611
    /**
5612
     * Returns all details for a particular type
5613
     *
5614
     * @param $typname The name of the view to retrieve
5615
     * @return Type info
5616
     */
5617
    public function getType($typname)
5618
    {
5619
        $this->clean($typname);
5620
5621
        $sql = "SELECT typtype, typbyval, typname, typinput AS typin, typoutput AS typout, typlen, typalign
5622
			FROM pg_type WHERE typname='{$typname}'";
5623
5624
        return $this->selectSet($sql);
5625
    }
5626
5627
    /**
5628
     * Returns a list of all types in the database
5629
     *
5630
     * @param bool|\PHPPgAdmin\Database\If $all        If true, will find all available types, if false just those in search path
5631
     * @param bool|\PHPPgAdmin\Database\If $tabletypes If true, will include table types
5632
     * @param bool|\PHPPgAdmin\Database\If $domains    If true, will include domains
5633
     * @return \PHPPgAdmin\Database\A recordet
5634
     */
5635
    public function getTypes($all = false, $tabletypes = false, $domains = false)
5636
    {
5637
        if ($all) {
5638
            $where = '1 = 1';
5639
        } else {
5640
            $c_schema = $this->_schema;
5641
            $this->clean($c_schema);
5642
            $where = "n.nspname = '{$c_schema}'";
5643
        }
5644
        // Never show system table types
5645
        $where2 = "AND c.relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname LIKE 'pg@_%' ESCAPE '@')";
5646
5647
        // Create type filter
5648
        $tqry = "'c'";
5649
        if ($tabletypes) {
5650
            $tqry .= ", 'r', 'v'";
5651
        }
5652
5653
        // Create domain filter
5654
        if (!$domains) {
5655
            $where .= " AND t.typtype != 'd'";
5656
        }
5657
5658
        $sql = "SELECT
5659
				t.typname AS basename,
5660
				pg_catalog.format_type(t.oid, NULL) AS typname,
5661
				pu.usename AS typowner,
5662
				t.typtype,
5663
				pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment
5664
			FROM (pg_catalog.pg_type t
5665
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace)
5666
				LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid
5667
			WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2}))
5668
			AND t.typname !~ '^_'
5669
			AND {$where}
5670
			ORDER BY typname
5671
		";
5672
5673
        return $this->selectSet($sql);
5674
    }
5675
5676
    /**
5677
     * Creates a new type
5678
     *
5679
     * @param $typname
5680
     * @param $typin
5681
     * @param $typout
5682
     * @param $typlen
5683
     * @param $typdef
5684
     * @param $typelem
5685
     * @param $typdelim
5686
     * @param $typbyval
5687
     * @param $typalign
5688
     * @param $typstorage
5689
     * @return \PHPPgAdmin\Database\A 0 success
5690
     * @internal param $ ...
5691
     */
5692
    public function createType(
5693
        $typname,
5694
        $typin,
5695
        $typout,
5696
        $typlen,
5697
        $typdef,
5698
        $typelem,
5699
        $typdelim,
5700
        $typbyval,
5701
        $typalign,
5702
        $typstorage
5703
    ) {
5704
        $f_schema = $this->_schema;
5705
        $this->fieldClean($f_schema);
5706
        $this->fieldClean($typname);
5707
        $this->fieldClean($typin);
5708
        $this->fieldClean($typout);
5709
5710
        $sql = "
5711
			CREATE TYPE \"{$f_schema}\".\"{$typname}\" (
5712
				INPUT = \"{$typin}\",
5713
				OUTPUT = \"{$typout}\",
5714
				INTERNALLENGTH = {$typlen}";
5715
        if ($typdef != '') {
5716
            $sql .= ", DEFAULT = {$typdef}";
5717
        }
5718
5719
        if ($typelem != '') {
5720
            $sql .= ", ELEMENT = {$typelem}";
5721
        }
5722
5723
        if ($typdelim != '') {
5724
            $sql .= ", DELIMITER = {$typdelim}";
5725
        }
5726
5727
        if ($typbyval) {
5728
            $sql .= ', PASSEDBYVALUE, ';
5729
        }
5730
5731
        if ($typalign != '') {
5732
            $sql .= ", ALIGNMENT = {$typalign}";
5733
        }
5734
5735
        if ($typstorage != '') {
5736
            $sql .= ", STORAGE = {$typstorage}";
5737
        }
5738
5739
        $sql .= ')';
5740
5741
        return $this->execute($sql);
5742
    }
5743
5744
    /**
5745
     * Drops a type.
5746
     *
5747
     * @param $typname The name of the type to drop
5748
     * @param $cascade True to cascade drop, false to restrict
5749
     * @return \PHPPgAdmin\Database\A 0 success
5750
     */
5751 View Code Duplication
    public function dropType($typname, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5752
    {
5753
        $f_schema = $this->_schema;
5754
        $this->fieldClean($f_schema);
5755
        $this->fieldClean($typname);
5756
5757
        $sql = "DROP TYPE \"{$f_schema}\".\"{$typname}\"";
5758
        if ($cascade) {
5759
            $sql .= ' CASCADE';
5760
        }
5761
5762
        return $this->execute($sql);
5763
    }
5764
5765
    /**
5766
     * Creates a new enum type in the database
5767
     *
5768
     * @param $name       The name of the type
5769
     * @param $values     An array of values
5770
     * @param $typcomment Type comment
5771
     * @return bool|int 0 success
5772
     */
5773
    public function createEnumType($name, $values, $typcomment)
5774
    {
5775
        $f_schema = $this->_schema;
5776
        $this->fieldClean($f_schema);
5777
        $this->fieldClean($name);
5778
5779
        if (empty($values)) {
5780
            return -2;
5781
        }
5782
5783
        $status = $this->beginTransaction();
5784
        if ($status != 0) {
5785
            return -1;
5786
        }
5787
5788
        $values = array_unique($values);
5789
5790
        $nbval = count($values);
5791
5792
        for ($i = 0; $i < $nbval; $i++) {
5793
            $this->clean($values[$i]);
5794
        }
5795
5796
        $sql = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS ENUM ('";
5797
        $sql .= implode("','", $values);
5798
        $sql .= "')";
5799
5800
        $status = $this->execute($sql);
5801
        if ($status) {
5802
            $this->rollbackTransaction();
5803
5804
            return -1;
5805
        }
5806
5807 View Code Duplication
        if ($typcomment != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5808
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
5809
            if ($status) {
5810
                $this->rollbackTransaction();
5811
5812
                return -1;
5813
            }
5814
        }
5815
5816
        return $this->endTransaction();
5817
    }
5818
5819
    /**
5820
     * Get defined values for a given enum
5821
     *
5822
     * @param $name
5823
     * @return \PHPPgAdmin\Database\A recordset
5824
     */
5825
    public function getEnumValues($name)
5826
    {
5827
        $this->clean($name);
5828
5829
        $sql = "SELECT enumlabel AS enumval
5830
		FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON (t.oid=e.enumtypid)
5831
		WHERE t.typname = '{$name}' ORDER BY e.oid";
5832
5833
        return $this->selectSet($sql);
5834
    }
5835
5836
    // Operator functions
5837
5838
    /**
5839
     * Creates a new composite type in the database
5840
     *
5841
     * @param $name       The name of the type
5842
     * @param $fields     The number of fields
5843
     * @param $field      An array of field names
5844
     * @param $type       An array of field types
5845
     * @param $array      An array of '' or '[]' for each type if it's an array or not
5846
     * @param $length     An array of field lengths
5847
     * @param $colcomment An array of comments
5848
     * @param $typcomment Type comment
5849
     * @return bool|int 0 success
5850
     */
5851
    public function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment)
5852
    {
5853
        $f_schema = $this->_schema;
5854
        $this->fieldClean($f_schema);
5855
        $this->fieldClean($name);
5856
5857
        $status = $this->beginTransaction();
5858
        if ($status != 0) {
5859
            return -1;
5860
        }
5861
5862
        $found       = false;
5863
        $first       = true;
5864
        $comment_sql = ''; // Accumulate comments for the columns
5865
        $sql         = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS (";
5866
        for ($i = 0; $i < $fields; $i++) {
5867
            $this->fieldClean($field[$i]);
5868
            $this->clean($type[$i]);
5869
            $this->clean($length[$i]);
5870
            $this->clean($colcomment[$i]);
5871
5872
            // Skip blank columns - for user convenience
5873
            if ($field[$i] == '' || $type[$i] == '') {
5874
                continue;
5875
            }
5876
5877
            // If not the first column, add a comma
5878
            if (!$first) {
5879
                $sql .= ', ';
5880
            } else {
5881
                $first = false;
5882
            }
5883
5884 View Code Duplication
            switch ($type[$i]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5885
                // Have to account for weird placing of length for with/without
5886
                // time zone types
5887
                case 'timestamp with time zone':
5888
                case 'timestamp without time zone':
5889
                    $qual = substr($type[$i], 9);
5890
                    $sql .= "\"{$field[$i]}\" timestamp";
5891
                    if ($length[$i] != '') {
5892
                        $sql .= "({$length[$i]})";
5893
                    }
5894
5895
                    $sql .= $qual;
5896
                    break;
5897
                case 'time with time zone':
5898
                case 'time without time zone':
5899
                    $qual = substr($type[$i], 4);
5900
                    $sql .= "\"{$field[$i]}\" time";
5901
                    if ($length[$i] != '') {
5902
                        $sql .= "({$length[$i]})";
5903
                    }
5904
5905
                    $sql .= $qual;
5906
                    break;
5907
                default:
5908
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
5909
                    if ($length[$i] != '') {
5910
                        $sql .= "({$length[$i]})";
5911
                    }
5912
            }
5913
            // Add array qualifier if necessary
5914
            if ($array[$i] == '[]') {
5915
                $sql .= '[]';
5916
            }
5917
5918 View Code Duplication
            if ($colcomment[$i] != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5919
                $comment_sql .= "COMMENT ON COLUMN \"{$f_schema}\".\"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
5920
            }
5921
5922
            $found = true;
5923
        }
5924
5925
        if (!$found) {
5926
            return -1;
5927
        }
5928
5929
        $sql .= ')';
5930
5931
        $status = $this->execute($sql);
5932
        if ($status) {
5933
            $this->rollbackTransaction();
5934
5935
            return -1;
5936
        }
5937
5938 View Code Duplication
        if ($typcomment != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5939
            $status = $this->setComment('TYPE', $name, '', $typcomment, true);
5940
            if ($status) {
5941
                $this->rollbackTransaction();
5942
5943
                return -1;
5944
            }
5945
        }
5946
5947 View Code Duplication
        if ($comment_sql != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5948
            $status = $this->execute($comment_sql);
5949
            if ($status) {
5950
                $this->rollbackTransaction();
5951
5952
                return -1;
5953
            }
5954
        }
5955
5956
        return $this->endTransaction();
5957
    }
5958
5959
    /**
5960
     * Returns a list of all casts in the database
5961
     *
5962
     * @return All casts
5963
     */
5964 View Code Duplication
    public function getCasts()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5965
    {
5966
        $conf = $this->conf;
5967
5968
        if ($conf['show_system']) {
5969
            $where = '';
5970
        } else {
5971
            $where = '
5972
				AND n1.nspname NOT LIKE $$pg\_%$$
5973
				AND n2.nspname NOT LIKE $$pg\_%$$
5974
				AND n3.nspname NOT LIKE $$pg\_%$$
5975
			';
5976
        }
5977
5978
        $sql = "
5979
			SELECT
5980
				c.castsource::pg_catalog.regtype AS castsource,
5981
				c.casttarget::pg_catalog.regtype AS casttarget,
5982
				CASE WHEN c.castfunc=0 THEN NULL
5983
				ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc,
5984
				c.castcontext,
5985
				obj_description(c.oid, 'pg_cast') as castcomment
5986
			FROM
5987
				(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),
5988
				pg_catalog.pg_type t1,
5989
				pg_catalog.pg_type t2,
5990
				pg_catalog.pg_namespace n1,
5991
				pg_catalog.pg_namespace n2
5992
			WHERE
5993
				c.castsource=t1.oid
5994
				AND c.casttarget=t2.oid
5995
				AND t1.typnamespace=n1.oid
5996
				AND t2.typnamespace=n2.oid
5997
				{$where}
5998
			ORDER BY 1, 2
5999
		";
6000
6001
        return $this->selectSet($sql);
6002
    }
6003
6004
    /**
6005
     * Returns a list of all conversions in the database
6006
     *
6007
     * @return All conversions
6008
     */
6009
    public function getConversions()
6010
    {
6011
        $c_schema = $this->_schema;
6012
        $this->clean($c_schema);
6013
        $sql = "
6014
			SELECT
6015
			       c.conname,
6016
			       pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding,
6017
			       pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding,
6018
			       c.condefault,
6019
			       pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment
6020
			FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n
6021
			WHERE n.oid = c.connamespace
6022
			      AND n.nspname='{$c_schema}'
6023
			ORDER BY 1;
6024
		";
6025
6026
        return $this->selectSet($sql);
6027
    }
6028
6029
    // Operator Class functions
6030
6031
    /**
6032
     * Edits a rule on a table OR view
6033
     *
6034
     * @param $name    The name of the new rule
6035
     * @param $event   SELECT, INSERT, UPDATE or DELETE
6036
     * @param $table   Table on which to create the rule
6037
     * @param $where   When to execute the rule, '' indicates always
6038
     * @param $instead True if an INSTEAD rule, false otherwise
6039
     * @param $type    NOTHING for a do nothing rule, SOMETHING to use given action
6040
     * @param $action  The action to take
6041
     * @return \PHPPgAdmin\Database\A 0 success
6042
     */
6043
    public function setRule($name, $event, $table, $where, $instead, $type, $action)
6044
    {
6045
        return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true);
6046
    }
6047
6048
    // FTS functions
6049
6050
    /**
6051
     * Creates a rule
6052
     *
6053
     * @param      $name    The name of the new rule
6054
     * @param      $event   SELECT, INSERT, UPDATE or DELETE
6055
     * @param      $table   Table on which to create the rule
6056
     * @param      $where   When to execute the rule, '' indicates always
6057
     * @param      $instead True if an INSTEAD rule, false otherwise
6058
     * @param      $type    NOTHING for a do nothing rule, SOMETHING to use given action
6059
     * @param      $action  The action to take
6060
     * @param bool $replace (optional) True to replace existing rule, false otherwise
6061
     * @return \PHPPgAdmin\Database\A 0 success
6062
     */
6063
    public function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false)
6064
    {
6065
        $f_schema = $this->_schema;
6066
        $this->fieldClean($f_schema);
6067
        $this->fieldClean($name);
6068
        $this->fieldClean($table);
6069
        if (!in_array($event, $this->rule_events)) {
6070
            return -1;
6071
        }
6072
6073
        $sql = 'CREATE';
6074
        if ($replace) {
6075
            $sql .= ' OR REPLACE';
6076
        }
6077
6078
        $sql .= " RULE \"{$name}\" AS ON {$event} TO \"{$f_schema}\".\"{$table}\"";
6079
        // Can't escape WHERE clause
6080
        if ($where != '') {
6081
            $sql .= " WHERE {$where}";
6082
        }
6083
6084
        $sql .= ' DO';
6085
        if ($instead) {
6086
            $sql .= ' INSTEAD';
6087
        }
6088
6089
        if ($type == 'NOTHING') {
6090
            $sql .= ' NOTHING';
6091
        } else {
6092
            $sql .= " ({$action})";
6093
        }
6094
6095
        return $this->execute($sql);
6096
    }
6097
6098
    /**
6099
     * Removes a rule from a table OR view
6100
     *
6101
     * @param $rule     The rule to drop
6102
     * @param $relation The relation from which to drop
6103
     * @param $cascade  True to cascade drop, false to restrict
6104
     * @return \PHPPgAdmin\Database\A 0 success
6105
     */
6106 View Code Duplication
    public function dropRule($rule, $relation, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6107
    {
6108
        $f_schema = $this->_schema;
6109
        $this->fieldClean($f_schema);
6110
        $this->fieldClean($rule);
6111
        $this->fieldClean($relation);
6112
6113
        $sql = "DROP RULE \"{$rule}\" ON \"{$f_schema}\".\"{$relation}\"";
6114
        if ($cascade) {
6115
            $sql .= ' CASCADE';
6116
        }
6117
6118
        return $this->execute($sql);
6119
    }
6120
6121
    /**
6122
     * Grabs a single trigger
6123
     *
6124
     * @param $table   The name of a table whose triggers to retrieve
6125
     * @param $trigger The name of the trigger to retrieve
6126
     * @return A recordset
6127
     */
6128 View Code Duplication
    public function getTrigger($table, $trigger)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6129
    {
6130
        $c_schema = $this->_schema;
6131
        $this->clean($c_schema);
6132
        $this->clean($table);
6133
        $this->clean($trigger);
6134
6135
        $sql = "
6136
			SELECT * FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c
6137
			WHERE t.tgrelid=c.oid AND c.relname='{$table}' AND t.tgname='{$trigger}'
6138
				AND c.relnamespace=(
6139
					SELECT oid FROM pg_catalog.pg_namespace
6140
					WHERE nspname='{$c_schema}')";
6141
6142
        return $this->selectSet($sql);
6143
    }
6144
6145
    /**
6146
     * A helper function for getTriggers that translates
6147
     * an array of attribute numbers to an array of field names.
6148
     * Note: Only needed for pre-7.4 servers, this function is deprecated
6149
     *
6150
     * @param $trigger An array containing fields from the trigger table
6151
     * @return The trigger definition string
6152
     */
6153
    public function getTriggerDef($trigger)
6154
    {
6155
        $this->fieldArrayClean($trigger);
6156
        // Constants to figure out tgtype
6157
        if (!defined('TRIGGER_TYPE_ROW')) {
6158
            define('TRIGGER_TYPE_ROW', 1 << 0);
6159
        }
6160
6161
        if (!defined('TRIGGER_TYPE_BEFORE')) {
6162
            define('TRIGGER_TYPE_BEFORE', 1 << 1);
6163
        }
6164
6165
        if (!defined('TRIGGER_TYPE_INSERT')) {
6166
            define('TRIGGER_TYPE_INSERT', 1 << 2);
6167
        }
6168
6169
        if (!defined('TRIGGER_TYPE_DELETE')) {
6170
            define('TRIGGER_TYPE_DELETE', 1 << 3);
6171
        }
6172
6173
        if (!defined('TRIGGER_TYPE_UPDATE')) {
6174
            define('TRIGGER_TYPE_UPDATE', 1 << 4);
6175
        }
6176
6177
        $trigger['tgisconstraint'] = $this->phpBool($trigger['tgisconstraint']);
6178
        $trigger['tgdeferrable']   = $this->phpBool($trigger['tgdeferrable']);
6179
        $trigger['tginitdeferred'] = $this->phpBool($trigger['tginitdeferred']);
6180
6181
        // Constraint trigger or normal trigger
6182
        if ($trigger['tgisconstraint']) {
6183
            $tgdef = 'CREATE CONSTRAINT TRIGGER ';
6184
        } else {
6185
            $tgdef = 'CREATE TRIGGER ';
6186
        }
6187
6188
        $tgdef .= "\"{$trigger['tgname']}\" ";
6189
6190
        // Trigger type
6191
        $findx = 0;
6192
        if (($trigger['tgtype'] & TRIGGER_TYPE_BEFORE) == TRIGGER_TYPE_BEFORE) {
6193
            $tgdef .= 'BEFORE';
6194
        } else {
6195
            $tgdef .= 'AFTER';
6196
        }
6197
6198
        if (($trigger['tgtype'] & TRIGGER_TYPE_INSERT) == TRIGGER_TYPE_INSERT) {
6199
            $tgdef .= ' INSERT';
6200
            $findx++;
6201
        }
6202 View Code Duplication
        if (($trigger['tgtype'] & TRIGGER_TYPE_DELETE) == TRIGGER_TYPE_DELETE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6203
            if ($findx > 0) {
6204
                $tgdef .= ' OR DELETE';
6205
            } else {
6206
                $tgdef .= ' DELETE';
6207
                $findx++;
6208
            }
6209
        }
6210 View Code Duplication
        if (($trigger['tgtype'] & TRIGGER_TYPE_UPDATE) == TRIGGER_TYPE_UPDATE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6211
            if ($findx > 0) {
6212
                $tgdef .= ' OR UPDATE';
6213
            } else {
6214
                $tgdef .= ' UPDATE';
6215
            }
6216
        }
6217
6218
        $f_schema = $this->_schema;
6219
        $this->fieldClean($f_schema);
6220
        // Table name
6221
        $tgdef .= " ON \"{$f_schema}\".\"{$trigger['relname']}\" ";
6222
6223
        // Deferrability
6224
        if ($trigger['tgisconstraint']) {
6225
            if ($trigger['tgconstrrelid'] != 0) {
6226
                // Assume constrelname is not null
6227
                $tgdef .= " FROM \"{$trigger['tgconstrrelname']}\" ";
6228
            }
6229
            if (!$trigger['tgdeferrable']) {
6230
                $tgdef .= 'NOT ';
6231
            }
6232
6233
            $tgdef .= 'DEFERRABLE INITIALLY ';
6234
            if ($trigger['tginitdeferred']) {
6235
                $tgdef .= 'DEFERRED ';
6236
            } else {
6237
                $tgdef .= 'IMMEDIATE ';
6238
            }
6239
        }
6240
6241
        // Row or statement
6242
        if ($trigger['tgtype'] & TRIGGER_TYPE_ROW == TRIGGER_TYPE_ROW) {
6243
            $tgdef .= 'FOR EACH ROW ';
6244
        } else {
6245
            $tgdef .= 'FOR EACH STATEMENT ';
6246
        }
6247
6248
        // Execute procedure
6249
        $tgdef .= "EXECUTE PROCEDURE \"{$trigger['tgfname']}\"(";
6250
6251
        // Parameters
6252
        // Escape null characters
6253
        $v = addcslashes($trigger['tgargs'], "\0");
6254
        // Split on escaped null characters
6255
        $params = explode('\\000', $v);
6256
        for ($findx = 0; $findx < $trigger['tgnargs']; $findx++) {
6257
            $param = "'" . str_replace('\'', '\\\'', $params[$findx]) . "'";
6258
            $tgdef .= $param;
6259
            if ($findx < ($trigger['tgnargs'] - 1)) {
6260
                $tgdef .= ', ';
6261
            }
6262
        }
6263
6264
        // Finish it off
6265
        $tgdef .= ')';
6266
6267
        return $tgdef;
6268
    }
6269
6270
    /**
6271
     * Returns a list of all functions that can be used in triggers
6272
     */
6273
    public function getTriggerFunctions()
6274
    {
6275
        return $this->getFunctions(true, 'trigger');
6276
    }
6277
6278
    /**
6279
     * Returns a list of all functions in the database
6280
     *
6281
     * @param bool|\PHPPgAdmin\Database\If $all  If true, will find all available functions, if false just those in search path
6282
     * @param                              $type If not null, will find all functions with return value = type
6283
     * @return \PHPPgAdmin\Database\All functions
6284
     */
6285
    public function getFunctions($all = false, $type = null)
6286
    {
6287
        if ($all) {
6288
            $where    = 'pg_catalog.pg_function_is_visible(p.oid)';
6289
            $distinct = 'DISTINCT ON (p.proname)';
6290
6291
            if ($type) {
6292
                $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') ";
6293
            }
6294
        } else {
6295
            $c_schema = $this->_schema;
6296
            $this->clean($c_schema);
6297
            $where    = "n.nspname = '{$c_schema}'";
6298
            $distinct = '';
6299
        }
6300
6301
        $sql = "
6302
			SELECT
6303
				{$distinct}
6304
				p.oid AS prooid,
6305
				p.proname,
6306
				p.proretset,
6307
				pg_catalog.format_type(p.prorettype, NULL) AS proresult,
6308
				pg_catalog.oidvectortypes(p.proargtypes) AS proarguments,
6309
				pl.lanname AS prolanguage,
6310
				pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment,
6311
				p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
6312
				CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns,
6313
				u.usename AS proowner
6314
			FROM pg_catalog.pg_proc p
6315
				INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
6316
				INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang
6317
				LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner
6318
			WHERE NOT p.proisagg
6319
				AND {$where}
6320
			ORDER BY p.proname, proresult
6321
			";
6322
6323
        return $this->selectSet($sql);
6324
    }
6325
6326
    /**
6327
     * Creates a trigger
6328
     *
6329
     * @param $tgname  The name of the trigger to create
6330
     * @param $table   The name of the table
6331
     * @param $tgproc  The function to execute
6332
     * @param $tgtime  BEFORE or AFTER
6333
     * @param $tgevent Event
6334
     * @param $tgfrequency
6335
     * @param $tgargs  The function arguments
6336
     * @return \PHPPgAdmin\Database\A 0 success
6337
     */
6338
    public function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs)
6339
    {
6340
        $f_schema = $this->_schema;
6341
        $this->fieldClean($f_schema);
6342
        $this->fieldClean($tgname);
6343
        $this->fieldClean($table);
6344
        $this->fieldClean($tgproc);
6345
6346
        /* No Statement Level Triggers in PostgreSQL (by now) */
6347
        $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime}
6348
				{$tgevent} ON \"{$f_schema}\".\"{$table}\"
6349
				FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})";
6350
6351
        return $this->execute($sql);
6352
    }
6353
6354
    /**
6355
     * Alters a trigger
6356
     *
6357
     * @param $table   The name of the table containing the trigger
6358
     * @param $trigger The name of the trigger to alter
6359
     * @param $name    The new name for the trigger
6360
     * @return \PHPPgAdmin\Database\A 0 success
6361
     */
6362
    public function alterTrigger($table, $trigger, $name)
6363
    {
6364
        $f_schema = $this->_schema;
6365
        $this->fieldClean($f_schema);
6366
        $this->fieldClean($table);
6367
        $this->fieldClean($trigger);
6368
        $this->fieldClean($name);
6369
6370
        $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_schema}\".\"{$table}\" RENAME TO \"{$name}\"";
6371
6372
        return $this->execute($sql);
6373
    }
6374
6375
    /**
6376
     * Drops a trigger
6377
     *
6378
     * @param $tgname  The name of the trigger to drop
6379
     * @param $table   The table from which to drop the trigger
6380
     * @param $cascade True to cascade drop, false to restrict
6381
     * @return \PHPPgAdmin\Database\A 0 success
6382
     */
6383 View Code Duplication
    public function dropTrigger($tgname, $table, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6384
    {
6385
        $f_schema = $this->_schema;
6386
        $this->fieldClean($f_schema);
6387
        $this->fieldClean($tgname);
6388
        $this->fieldClean($table);
6389
6390
        $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\"";
6391
        if ($cascade) {
6392
            $sql .= ' CASCADE';
6393
        }
6394
6395
        return $this->execute($sql);
6396
    }
6397
6398
    /**
6399
     * Enables a trigger
6400
     *
6401
     * @param $tgname The name of the trigger to enable
6402
     * @param $table  The table in which to enable the trigger
6403
     * @return \PHPPgAdmin\Database\A 0 success
6404
     */
6405 View Code Duplication
    public function enableTrigger($tgname, $table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6406
    {
6407
        $f_schema = $this->_schema;
6408
        $this->fieldClean($f_schema);
6409
        $this->fieldClean($tgname);
6410
        $this->fieldClean($table);
6411
6412
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\"";
6413
6414
        return $this->execute($sql);
6415
    }
6416
6417
    /**
6418
     * Disables a trigger
6419
     *
6420
     * @param $tgname The name of the trigger to disable
6421
     * @param $table  The table in which to disable the trigger
6422
     * @return \PHPPgAdmin\Database\A 0 success
6423
     */
6424 View Code Duplication
    public function disableTrigger($tgname, $table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6425
    {
6426
        $f_schema = $this->_schema;
6427
        $this->fieldClean($f_schema);
6428
        $this->fieldClean($tgname);
6429
        $this->fieldClean($table);
6430
6431
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\"";
6432
6433
        return $this->execute($sql);
6434
    }
6435
6436
    /**
6437
     * Returns a list of all operators in the database
6438
     *
6439
     * @return All operators
6440
     */
6441
    public function getOperators()
6442
    {
6443
        $c_schema = $this->_schema;
6444
        $this->clean($c_schema);
6445
        // We stick with the subselects here, as you cannot ORDER BY a regtype
6446
        $sql = "
6447
			SELECT
6448
            	po.oid,	po.oprname,
6449
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname,
6450
				(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname,
6451
				po.oprresult::pg_catalog.regtype AS resultname,
6452
		        pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment
6453
			FROM
6454
				pg_catalog.pg_operator po
6455
			WHERE
6456
				po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')
6457
			ORDER BY
6458
				po.oprname, oprleftname, oprrightname
6459
		";
6460
6461
        return $this->selectSet($sql);
6462
    }
6463
6464
    /**
6465
     * Drops an operator
6466
     *
6467
     * @param $operator_oid The OID of the operator to drop
6468
     * @param $cascade      True to cascade drop, false to restrict
6469
     * @return \PHPPgAdmin\Database\A 0 success
6470
     */
6471
    public function dropOperator($operator_oid, $cascade)
6472
    {
6473
        // Function comes in with $object as operator OID
6474
        $opr      = $this->getOperator($operator_oid);
6475
        $f_schema = $this->_schema;
6476
        $this->fieldClean($f_schema);
6477
        $this->fieldClean($opr->fields['oprname']);
6478
6479
        $sql = "DROP OPERATOR \"{$f_schema}\".{$opr->fields['oprname']} (";
6480
        // Quoting or formatting here???
6481
        if ($opr->fields['oprleftname'] !== null) {
6482
            $sql .= $opr->fields['oprleftname'] . ', ';
6483
        } else {
6484
            $sql .= 'NONE, ';
6485
        }
6486
6487
        if ($opr->fields['oprrightname'] !== null) {
6488
            $sql .= $opr->fields['oprrightname'] . ')';
6489
        } else {
6490
            $sql .= 'NONE)';
6491
        }
6492
6493
        if ($cascade) {
6494
            $sql .= ' CASCADE';
6495
        }
6496
6497
        return $this->execute($sql);
6498
    }
6499
6500
    /**
6501
     * Returns all details for a particular operator
6502
     *
6503
     * @param $operator_oid The oid of the operator
6504
     * @return Function info
6505
     */
6506
    public function getOperator($operator_oid)
6507
    {
6508
        $this->clean($operator_oid);
6509
6510
        $sql = "
6511
			SELECT
6512
            	po.oid, po.oprname,
6513
				oprleft::pg_catalog.regtype AS oprleftname,
6514
				oprright::pg_catalog.regtype AS oprrightname,
6515
				oprresult::pg_catalog.regtype AS resultname,
6516
				po.oprcanhash,
6517
				oprcanmerge,
6518
				oprcom::pg_catalog.regoperator AS oprcom,
6519
				oprnegate::pg_catalog.regoperator AS oprnegate,
6520
				po.oprcode::pg_catalog.regproc AS oprcode,
6521
				po.oprrest::pg_catalog.regproc AS oprrest,
6522
				po.oprjoin::pg_catalog.regproc AS oprjoin
6523
			FROM
6524
				pg_catalog.pg_operator po
6525
			WHERE
6526
				po.oid='{$operator_oid}'
6527
		";
6528
6529
        return $this->selectSet($sql);
6530
    }
6531
6532
    /**
6533
     *  Gets all opclasses
6534
     *
6535
     * @return A recordset
6536
     */
6537
6538
    public function getOpClasses()
6539
    {
6540
        $c_schema = $this->_schema;
6541
        $this->clean($c_schema);
6542
        $sql = "
6543
			SELECT
6544
				pa.amname, po.opcname,
6545
				po.opcintype::pg_catalog.regtype AS opcintype,
6546
				po.opcdefault,
6547
				pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment
6548
			FROM
6549
				pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn
6550
			WHERE
6551
				po.opcmethod=pa.oid
6552
				AND po.opcnamespace=pn.oid
6553
				AND pn.nspname='{$c_schema}'
6554
			ORDER BY 1,2
6555
			";
6556
6557
        return $this->selectSet($sql);
6558
    }
6559
6560
    /**
6561
     * Creates a new FTS configuration.
6562
     *
6563
     * @param string $cfgname  The name of the FTS configuration to create
6564
     * @param string $parser   The parser to be used in new FTS configuration
6565
     * @param string $template The existing FTS configuration to be used as template for the new one
6566
     * @param string $comment  If omitted, defaults to nothing
6567
     * @return bool|int 0 success
6568
     * @internal param string $locale Locale of the FTS configuration
6569
     * @internal param string $withmap Should we copy whole map of existing FTS configuration to the new one
6570
     * @internal param string $makeDefault Should this configuration be the default for locale given
6571
     */
6572
    public function createFtsConfiguration($cfgname, $parser = '', $template = '', $comment = '')
6573
    {
6574
        $f_schema = $this->_schema;
6575
        $this->fieldClean($f_schema);
6576
        $this->fieldClean($cfgname);
6577
6578
        $sql = "CREATE TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$cfgname}\" (";
6579 View Code Duplication
        if ($parser != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6580
            $this->fieldClean($parser['schema']);
6581
            $this->fieldClean($parser['parser']);
6582
            $parser = "\"{$parser['schema']}\".\"{$parser['parser']}\"";
6583
            $sql .= " PARSER = {$parser}";
6584
        }
6585 View Code Duplication
        if ($template != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6586
            $this->fieldClean($template['schema']);
6587
            $this->fieldClean($template['name']);
6588
            $sql .= " COPY = \"{$template['schema']}\".\"{$template['name']}\"";
6589
        }
6590
        $sql .= ')';
6591
6592
        if ($comment != '') {
6593
            $status = $this->beginTransaction();
6594
            if ($status != 0) {
6595
                return -1;
6596
            }
6597
        }
6598
6599
        // Create the FTS configuration
6600
        $status = $this->execute($sql);
6601
        if ($status != 0) {
6602
            $this->rollbackTransaction();
6603
6604
            return -1;
6605
        }
6606
6607
        // Set the comment
6608 View Code Duplication
        if ($comment != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6609
            $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment);
6610
            if ($status != 0) {
6611
                $this->rollbackTransaction();
6612
6613
                return -1;
6614
            }
6615
6616
            return $this->endTransaction();
6617
        }
6618
6619
        return 0;
6620
    }
6621
6622
    // Language functions
6623
6624
    /**
6625
     * Returns available FTS configurations
6626
     *
6627
     * @param bool|\PHPPgAdmin\Database\if $all if false, returns schema qualified FTS confs
6628
     * @return \PHPPgAdmin\Database\A recordset
6629
     */
6630
    public function getFtsConfigurations($all = true)
6631
    {
6632
        $c_schema = $this->_schema;
6633
        $this->clean($c_schema);
6634
        $sql = "
6635
			SELECT
6636
				n.nspname as schema,
6637
				c.cfgname as name,
6638
				pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment
6639
			FROM
6640
				pg_catalog.pg_ts_config c
6641
				JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace
6642
			WHERE
6643
				pg_catalog.pg_ts_config_is_visible(c.oid)";
6644
6645
        if (!$all) {
6646
            $sql .= " AND  n.nspname='{$c_schema}'\n";
6647
        }
6648
6649
        $sql .= 'ORDER BY name';
6650
6651
        return $this->selectSet($sql);
6652
    }
6653
6654
    // Aggregate functions
6655
6656
    /**
6657
     * Returns the map of FTS configuration given
6658
     * (list of mappings (tokens) and their processing dictionaries)
6659
     *
6660
     * @param string $ftscfg Name of the FTS configuration
6661
     *
6662
     * @return RecordSet
6663
     */
6664
    public function getFtsConfigurationMap($ftscfg)
6665
    {
6666
        $c_schema = $this->_schema;
6667
        $this->clean($c_schema);
6668
        $this->fieldClean($ftscfg);
6669
6670
        $oidSet = $this->selectSet("SELECT c.oid
6671
			FROM pg_catalog.pg_ts_config AS c
6672
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.cfgnamespace)
6673
			WHERE c.cfgname = '{$ftscfg}'
6674
				AND n.nspname='{$c_schema}'");
6675
6676
        $oid = $oidSet->fields['oid'];
6677
6678
        $sql = "
6679
 			SELECT
6680
    			(SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name,
6681
        		(SELECT t.description FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS description,
6682
				c.cfgname AS cfgname, n.nspname ||'.'|| d.dictname as dictionaries
6683
			FROM
6684
				pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d,
6685
				pg_catalog.pg_namespace n
6686
			WHERE
6687
				c.oid = {$oid}
6688
				AND m.mapcfg = c.oid
6689
				AND m.mapdict = d.oid
6690
				AND d.dictnamespace = n.oid
6691
			ORDER BY name
6692
			";
6693
6694
        return $this->selectSet($sql);
6695
    }
6696
6697
    /**
6698
     * Returns FTS parsers available
6699
     *
6700
     * @param bool|\PHPPgAdmin\Database\if $all if false, return only Parsers from the current schema
6701
     * @return \PHPPgAdmin\Database\RecordSet
6702
     */
6703
    public function getFtsParsers($all = true)
6704
    {
6705
        $c_schema = $this->_schema;
6706
        $this->clean($c_schema);
6707
        $sql = "
6708
			SELECT
6709
			   n.nspname as schema,
6710
			   p.prsname as name,
6711
			   pg_catalog.obj_description(p.oid, 'pg_ts_parser') as comment
6712
			FROM pg_catalog.pg_ts_parser p
6713
				LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = p.prsnamespace)
6714
			WHERE pg_catalog.pg_ts_parser_is_visible(p.oid)";
6715
6716
        if (!$all) {
6717
            $sql .= " AND n.nspname='{$c_schema}'\n";
6718
        }
6719
6720
        $sql .= 'ORDER BY name';
6721
6722
        return $this->selectSet($sql);
6723
    }
6724
6725
    /**
6726
     * Returns FTS dictionaries available
6727
     *
6728
     * @param bool|\PHPPgAdmin\Database\if $all if false, return only Dics from the current schema
6729
     * @return \PHPPgAdmin\Database\RecordSet
6730
     */
6731
    public function getFtsDictionaries($all = true)
6732
    {
6733
        $c_schema = $this->_schema;
6734
        $this->clean($c_schema);
6735
        $sql = "
6736
 			SELECT
6737
				n.nspname as schema, d.dictname as name,
6738
				pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment
6739
			FROM pg_catalog.pg_ts_dict d
6740
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace
6741
			WHERE pg_catalog.pg_ts_dict_is_visible(d.oid)";
6742
6743
        if (!$all) {
6744
            $sql .= " AND n.nspname='{$c_schema}'\n";
6745
        }
6746
6747
        $sql .= 'ORDER BY name;';
6748
6749
        return $this->selectSet($sql);
6750
    }
6751
6752
    /**
6753
     * Returns all FTS dictionary templates available
6754
     */
6755
    public function getFtsDictionaryTemplates()
6756
    {
6757
        $sql = "
6758
 			SELECT
6759
				n.nspname as schema,
6760
				t.tmplname as name,
6761
				( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname
6762
					FROM pg_catalog.pg_proc p
6763
					LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace
6764
					WHERE t.tmplinit = p.oid ) AS  init,
6765
				( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname
6766
					FROM pg_catalog.pg_proc p
6767
					LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace
6768
					WHERE t.tmpllexize = p.oid ) AS  lexize,
6769
				pg_catalog.obj_description(t.oid, 'pg_ts_template') as comment
6770
			FROM pg_catalog.pg_ts_template t
6771
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace
6772
			WHERE pg_catalog.pg_ts_template_is_visible(t.oid)
6773
			ORDER BY name;";
6774
6775
        return $this->selectSet($sql);
6776
    }
6777
6778
    /**
6779
     * Drops FTS coniguration
6780
     *
6781
     * @param $ftscfg  The configuration's name
6782
     * @param $cascade Cascade to dependenced objects
6783
     * @return \PHPPgAdmin\Database\A 0 on success
6784
     */
6785 View Code Duplication
    public function dropFtsConfiguration($ftscfg, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6786
    {
6787
        $f_schema = $this->_schema;
6788
        $this->fieldClean($f_schema);
6789
        $this->fieldClean($ftscfg);
6790
6791
        $sql = "DROP TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\"";
6792
        if ($cascade) {
6793
            $sql .= ' CASCADE';
6794
        }
6795
6796
        return $this->execute($sql);
6797
    }
6798
6799
    /**
6800
     * Drops FTS dictionary
6801
     *
6802
     * @param $ftsdict The dico's name
6803
     * @param $cascade Cascade to dependenced objects
6804
     * @return \PHPPgAdmin\Database\A 0 on success
6805
     * @todo Support of dictionary templates dropping
6806
     */
6807 View Code Duplication
    public function dropFtsDictionary($ftsdict, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6808
    {
6809
        $f_schema = $this->_schema;
6810
        $this->fieldClean($f_schema);
6811
        $this->fieldClean($ftsdict);
6812
6813
        $sql = 'DROP TEXT SEARCH DICTIONARY';
6814
        $sql .= " \"{$f_schema}\".\"{$ftsdict}\"";
6815
        if ($cascade) {
6816
            $sql .= ' CASCADE';
6817
        }
6818
6819
        return $this->execute($sql);
6820
    }
6821
6822
    /**
6823
     * Alters FTS configuration
6824
     *
6825
     * @param $cfgname The conf's name
6826
     * @param $comment A comment on for the conf
6827
     * @param $name    The new conf name
6828
     * @return bool|int 0 on success
6829
     */
6830 View Code Duplication
    public function updateFtsConfiguration($cfgname, $comment, $name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6831
    {
6832
        $status = $this->beginTransaction();
6833
        if ($status != 0) {
6834
            $this->rollbackTransaction();
6835
6836
            return -1;
6837
        }
6838
6839
        $this->fieldClean($cfgname);
6840
6841
        $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment);
6842
        if ($status != 0) {
6843
            $this->rollbackTransaction();
6844
6845
            return -1;
6846
        }
6847
6848
        // Only if the name has changed
6849
        if ($name != $cfgname) {
6850
            $f_schema = $this->_schema;
6851
            $this->fieldClean($f_schema);
6852
            $this->fieldClean($name);
6853
6854
            $sql    = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$cfgname}\" RENAME TO \"{$name}\"";
6855
            $status = $this->execute($sql);
6856
            if ($status != 0) {
6857
                $this->rollbackTransaction();
6858
6859
                return -1;
6860
            }
6861
        }
6862
6863
        return $this->endTransaction();
6864
    }
6865
6866
    /**
6867
     * Creates a new FTS dictionary or FTS dictionary template.
6868
     *
6869
     * @param string  $dictname   The name of the FTS dictionary to create
6870
     * @param boolean $isTemplate Flag whether we create usual dictionary or dictionary template
6871
     * @param string  $template   The existing FTS dictionary to be used as template for the new one
6872
     * @param string  $lexize     The name of the function, which does transformation of input word
6873
     * @param string  $init       The name of the function, which initializes dictionary
6874
     * @param string  $option     Usually, it stores various options required for the dictionary
6875
     * @param string  $comment    If omitted, defaults to nothing
6876
     * @return bool|int 0 success
6877
     */
6878
    public function createFtsDictionary(
6879
        $dictname,
6880
        $isTemplate = false,
6881
        $template = '',
6882
        $lexize = '',
6883
        $init = '',
6884
        $option = '',
6885
        $comment = ''
6886
    ) {
6887
        $f_schema = $this->_schema;
6888
        $this->fieldClean($f_schema);
6889
        $this->fieldClean($dictname);
6890
        $this->fieldClean($template);
6891
        $this->fieldClean($lexize);
6892
        $this->fieldClean($init);
6893
        $this->fieldClean($option);
6894
6895
        $sql = 'CREATE TEXT SEARCH';
6896
        if ($isTemplate) {
6897
            $sql .= " TEMPLATE \"{$f_schema}\".\"{$dictname}\" (";
6898
            if ($lexize != '') {
6899
                $sql .= " LEXIZE = {$lexize}";
6900
            }
6901
6902
            if ($init != '') {
6903
                $sql .= ", INIT = {$init}";
6904
            }
6905
6906
            $sql .= ')';
6907
            $whatToComment = 'TEXT SEARCH TEMPLATE';
6908
        } else {
6909
            $sql .= " DICTIONARY \"{$f_schema}\".\"{$dictname}\" (";
6910 View Code Duplication
            if ($template != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6911
                $this->fieldClean($template['schema']);
6912
                $this->fieldClean($template['name']);
6913
                $template = "\"{$template['schema']}\".\"{$template['name']}\"";
6914
6915
                $sql .= " TEMPLATE = {$template}";
6916
            }
6917
            if ($option != '') {
6918
                $sql .= ", {$option}";
6919
            }
6920
6921
            $sql .= ')';
6922
            $whatToComment = 'TEXT SEARCH DICTIONARY';
6923
        }
6924
6925
        /* if comment, begin a transaction to
6926
         * run both commands */
6927
        if ($comment != '') {
6928
            $status = $this->beginTransaction();
6929
            if ($status != 0) {
6930
                return -1;
6931
            }
6932
        }
6933
6934
        // Create the FTS dictionary
6935
        $status = $this->execute($sql);
6936
        if ($status != 0) {
6937
            $this->rollbackTransaction();
6938
6939
            return -1;
6940
        }
6941
6942
        // Set the comment
6943 View Code Duplication
        if ($comment != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6944
            $status = $this->setComment($whatToComment, $dictname, '', $comment);
6945
            if ($status != 0) {
6946
                $this->rollbackTransaction();
6947
6948
                return -1;
6949
            }
6950
        }
6951
6952
        return $this->endTransaction();
6953
    }
6954
6955
    // Role, User/Group functions
6956
6957
    /**
6958
     * Alters FTS dictionary or dictionary template
6959
     *
6960
     * @param $dictname The dico's name
6961
     * @param $comment  The comment
6962
     * @param $name     The new dico's name
6963
     * @return bool|int 0 on success
6964
     */
6965 View Code Duplication
    public function updateFtsDictionary($dictname, $comment, $name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6966
    {
6967
        $status = $this->beginTransaction();
6968
        if ($status != 0) {
6969
            $this->rollbackTransaction();
6970
6971
            return -1;
6972
        }
6973
6974
        $this->fieldClean($dictname);
6975
        $status = $this->setComment('TEXT SEARCH DICTIONARY', $dictname, '', $comment);
6976
        if ($status != 0) {
6977
            $this->rollbackTransaction();
6978
6979
            return -1;
6980
        }
6981
6982
        // Only if the name has changed
6983
        if ($name != $dictname) {
6984
            $f_schema = $this->_schema;
6985
            $this->fieldClean($f_schema);
6986
            $this->fieldClean($name);
6987
6988
            $sql    = "ALTER TEXT SEARCH DICTIONARY \"{$f_schema}\".\"{$dictname}\" RENAME TO \"{$name}\"";
6989
            $status = $this->execute($sql);
6990
            if ($status != 0) {
6991
                $this->rollbackTransaction();
6992
6993
                return -1;
6994
            }
6995
        }
6996
6997
        return $this->endTransaction();
6998
    }
6999
7000
    /**
7001
     * Return all information relating to a FTS dictionary
7002
     *
7003
     * @param $ftsdict The name of the FTS dictionary
7004
     *
7005
     * @return RecordSet of FTS dictionary information
7006
     */
7007
    public function getFtsDictionaryByName($ftsdict)
7008
    {
7009
        $c_schema = $this->_schema;
7010
        $this->clean($c_schema);
7011
        $this->clean($ftsdict);
7012
7013
        $sql = "SELECT
7014
			   n.nspname as schema,
7015
			   d.dictname as name,
7016
			   ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM
7017
				 pg_catalog.pg_ts_template t
7018
									  LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace
7019
									  WHERE d.dicttemplate = t.oid ) AS  template,
7020
			   d.dictinitoption as init,
7021
			   pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment
7022
			FROM pg_catalog.pg_ts_dict d
7023
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace
7024
			WHERE d.dictname = '{$ftsdict}'
7025
			   AND pg_catalog.pg_ts_dict_is_visible(d.oid)
7026
			   AND n.nspname='{$c_schema}'
7027
			ORDER BY name";
7028
7029
        return $this->selectSet($sql);
7030
    }
7031
7032
    /**
7033
     * Creates/updates/deletes FTS mapping.
7034
     *
7035
     * @param        $ftscfg
7036
     * @param array  $mapping  Array of tokens' names
7037
     * @param string $action   What to do with the mapping: add, alter or drop
7038
     * @param string $dictname Dictionary that will process tokens given or null in case of drop action
7039
     * @return int|\PHPPgAdmin\Database\A 0 success
7040
     * @internal param string $cfgname The name of the FTS configuration to alter
7041
     */
7042
    public function changeFtsMapping($ftscfg, $mapping, $action, $dictname = null)
7043
    {
7044
        if (count($mapping) > 0) {
7045
            $f_schema = $this->_schema;
7046
            $this->fieldClean($f_schema);
7047
            $this->fieldClean($ftscfg);
7048
            $this->fieldClean($dictname);
7049
            $this->arrayClean($mapping);
7050
7051
            switch ($action) {
7052
                case 'alter':
7053
                    $whatToDo = 'ALTER';
7054
                    break;
7055
                case 'drop':
7056
                    $whatToDo = 'DROP';
7057
                    break;
7058
                default:
7059
                    $whatToDo = 'ADD';
7060
                    break;
7061
            }
7062
7063
            $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\" {$whatToDo} MAPPING FOR ";
7064
            $sql .= implode(',', $mapping);
7065
            if ($action != 'drop' && !empty($dictname)) {
7066
                $sql .= " WITH {$dictname}";
7067
            }
7068
7069
            return $this->execute($sql);
7070
        }
7071
7072
        return -1;
7073
    }
7074
7075
    /**
7076
     * Return all information related to a given FTS configuration's mapping
7077
     *
7078
     * @param $ftscfg  The name of the FTS configuration
7079
     * @param $mapping The name of the mapping
7080
     *
7081
     * @return FTS configuration information
7082
     */
7083
    public function getFtsMappingByName($ftscfg, $mapping)
7084
    {
7085
        $c_schema = $this->_schema;
7086
        $this->clean($c_schema);
7087
        $this->clean($ftscfg);
7088
        $this->clean($mapping);
7089
7090
        $oidSet = $this->selectSet("SELECT c.oid, cfgparser
7091
			FROM pg_catalog.pg_ts_config AS c
7092
				LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = c.cfgnamespace
7093
			WHERE c.cfgname = '{$ftscfg}'
7094
				AND n.nspname='{$c_schema}'");
7095
7096
        $oid       = $oidSet->fields['oid'];
7097
        $cfgparser = $oidSet->fields['cfgparser'];
7098
7099
        $tokenIdSet = $this->selectSet("SELECT tokid
7100
			FROM pg_catalog.ts_token_type({$cfgparser})
7101
			WHERE alias = '{$mapping}'");
7102
7103
        $tokid = $tokenIdSet->fields['tokid'];
7104
7105
        $sql = "SELECT
7106
			    (SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name,
7107
    	            d.dictname as dictionaries
7108
			FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d
7109
			WHERE c.oid = {$oid} AND m.mapcfg = c.oid AND m.maptokentype = {$tokid} AND m.mapdict = d.oid
7110
			LIMIT 1;";
7111
7112
        return $this->selectSet($sql);
7113
    }
7114
7115
    /**
7116
     * Return list of FTS mappings possible for given parser
7117
     * (specified by given configuration since configuration can only have 1 parser)
7118
     *
7119
     * @param $ftscfg The config's name that use the parser
7120
     * @return \PHPPgAdmin\Database\A 0 on success
7121
     */
7122
    public function getFtsMappings($ftscfg)
7123
    {
7124
        $cfg = $this->getFtsConfigurationByName($ftscfg);
7125
7126
        $sql = "SELECT alias AS name, description
7127
			FROM pg_catalog.ts_token_type({$cfg->fields['parser_id']})
7128
			ORDER BY name";
7129
7130
        return $this->selectSet($sql);
7131
    }
7132
7133
    /**
7134
     * Return all information related to a FTS configuration
7135
     *
7136
     * @param $ftscfg The name of the FTS configuration
7137
     *
7138
     * @return FTS configuration information
7139
     */
7140
    public function getFtsConfigurationByName($ftscfg)
7141
    {
7142
        $c_schema = $this->_schema;
7143
        $this->clean($c_schema);
7144
        $this->clean($ftscfg);
7145
        $sql = "
7146
			SELECT
7147
				n.nspname as schema,
7148
				c.cfgname as name,
7149
				p.prsname as parser,
7150
				c.cfgparser as parser_id,
7151
				pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment
7152
			FROM pg_catalog.pg_ts_config c
7153
				LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace
7154
				LEFT JOIN pg_catalog.pg_ts_parser p ON p.oid = c.cfgparser
7155
			WHERE pg_catalog.pg_ts_config_is_visible(c.oid)
7156
				AND c.cfgname = '{$ftscfg}'
7157
				AND n.nspname='{$c_schema}'";
7158
7159
        return $this->selectSet($sql);
7160
    }
7161
7162
    /**
7163
     * Gets all languages
7164
     *
7165
     * @param bool|True $all True to get all languages, regardless of show_system
7166
     * @return \PHPPgAdmin\Database\A recordset
7167
     */
7168 View Code Duplication
    public function getLanguages($all = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7169
    {
7170
        $conf = $this->conf;
7171
7172
        if ($conf['show_system'] || $all) {
7173
            $where = '';
7174
        } else {
7175
            $where = 'WHERE lanispl';
7176
        }
7177
7178
        $sql = "
7179
			SELECT
7180
				lanname, lanpltrusted,
7181
				lanplcallfoid::pg_catalog.regproc AS lanplcallf
7182
			FROM
7183
				pg_catalog.pg_language
7184
			{$where}
7185
			ORDER BY lanname
7186
		";
7187
7188
        return $this->selectSet($sql);
7189
    }
7190
7191
    /**
7192
     * Creates a new aggregate in the database
7193
     *
7194
     * @param $name     The name of the aggregate
7195
     * @param $basetype The input data type of the aggregate
7196
     * @param $sfunc    The name of the state transition function for the aggregate
7197
     * @param $stype    The data type for the aggregate's state value
7198
     * @param $ffunc    The name of the final function for the aggregate
7199
     * @param $initcond The initial setting for the state value
7200
     * @param $sortop   The sort operator for the aggregate
7201
     * @param $comment  Aggregate comment
7202
     * @return bool|int 0 success
7203
     */
7204
    public function createAggregate($name, $basetype, $sfunc, $stype, $ffunc, $initcond, $sortop, $comment)
7205
    {
7206
        $f_schema = $this->_schema;
7207
        $this->fieldClean($f_schema);
7208
        $this->fieldClean($name);
7209
        $this->fieldClean($basetype);
7210
        $this->fieldClean($sfunc);
7211
        $this->fieldClean($stype);
7212
        $this->fieldClean($ffunc);
7213
        $this->fieldClean($initcond);
7214
        $this->fieldClean($sortop);
7215
7216
        $this->beginTransaction();
7217
7218
        $sql = "CREATE AGGREGATE \"{$f_schema}\".\"{$name}\" (BASETYPE = \"{$basetype}\", SFUNC = \"{$sfunc}\", STYPE = \"{$stype}\"";
7219
        if (trim($ffunc) != '') {
7220
            $sql .= ", FINALFUNC = \"{$ffunc}\"";
7221
        }
7222
7223
        if (trim($initcond) != '') {
7224
            $sql .= ", INITCOND = \"{$initcond}\"";
7225
        }
7226
7227
        if (trim($sortop) != '') {
7228
            $sql .= ", SORTOP = \"{$sortop}\"";
7229
        }
7230
7231
        $sql .= ')';
7232
7233
        $status = $this->execute($sql);
7234
        if ($status) {
7235
            $this->rollbackTransaction();
7236
7237
            return -1;
7238
        }
7239
7240 View Code Duplication
        if (trim($comment) != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7241
            $status = $this->setComment('AGGREGATE', $name, '', $comment, $basetype);
7242
            if ($status) {
7243
                $this->rollbackTransaction();
7244
7245
                return -1;
7246
            }
7247
        }
7248
7249
        return $this->endTransaction();
7250
    }
7251
7252
    /**
7253
     * Removes an aggregate function from the database
7254
     *
7255
     * @param $aggrname The name of the aggregate
7256
     * @param $aggrtype The input data type of the aggregate
7257
     * @param $cascade  True to cascade drop, false to restrict
7258
     * @return \PHPPgAdmin\Database\A 0 success
7259
     */
7260 View Code Duplication
    public function dropAggregate($aggrname, $aggrtype, $cascade)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7261
    {
7262
        $f_schema = $this->_schema;
7263
        $this->fieldClean($f_schema);
7264
        $this->fieldClean($aggrname);
7265
        $this->fieldClean($aggrtype);
7266
7267
        $sql = "DROP AGGREGATE \"{$f_schema}\".\"{$aggrname}\" (\"{$aggrtype}\")";
7268
        if ($cascade) {
7269
            $sql .= ' CASCADE';
7270
        }
7271
7272
        return $this->execute($sql);
7273
    }
7274
7275
    /**
7276
     * Gets all information for an aggregate
7277
     *
7278
     * @param $name     The name of the aggregate
7279
     * @param $basetype The input data type of the aggregate
7280
     * @return A recordset
7281
     */
7282
    public function getAggregate($name, $basetype)
7283
    {
7284
        $c_schema = $this->_schema;
7285
        $this->clean($c_schema);
7286
        $this->fieldClean($name);
7287
        $this->fieldClean($basetype);
7288
7289
        $sql = "
7290
			SELECT p.proname, CASE p.proargtypes[0]
7291
				WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL
7292
				ELSE pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes,
7293
				a.aggtransfn, format_type(a.aggtranstype, NULL) AS aggstype, a.aggfinalfn,
7294
				a.agginitval, a.aggsortop, u.usename, pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment
7295
			FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a
7296
			WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid
7297
				AND p.proisagg AND n.nspname='{$c_schema}'
7298
				AND p.proname='" . $name . "'
7299
				AND CASE p.proargtypes[0]
7300
					WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN ''
7301
					ELSE pg_catalog.format_type(p.proargtypes[0], NULL)
7302
				END ='" . $basetype . "'";
7303
7304
        return $this->selectSet($sql);
7305
    }
7306
7307
    /**
7308
     * Gets all aggregates
7309
     *
7310
     * @return A recordset
7311
     */
7312
    public function getAggregates()
7313
    {
7314
        $c_schema = $this->_schema;
7315
        $this->clean($c_schema);
7316
        $sql = "SELECT p.proname, CASE p.proargtypes[0] WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL ELSE
7317
			   pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes, a.aggtransfn, u.usename,
7318
			   pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment
7319
			   FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a
7320
			   WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid
7321
			   AND p.proisagg AND n.nspname='{$c_schema}' ORDER BY 1, 2";
7322
7323
        return $this->selectSet($sql);
7324
    }
7325
7326
    /**
7327
     * Alters an aggregate
7328
     *
7329
     * @param $aggrname       The actual name of the aggregate
7330
     * @param $aggrtype       The actual input data type of the aggregate
7331
     * @param $aggrowner      The actual owner of the aggregate
7332
     * @param $aggrschema     The actual schema the aggregate belongs to
7333
     * @param $aggrcomment    The actual comment for the aggregate
7334
     * @param $newaggrname    The new name of the aggregate
7335
     * @param $newaggrowner   The new owner of the aggregate
7336
     * @param $newaggrschema  The new schema where the aggregate will belong to
7337
     * @param $newaggrcomment The new comment for the aggregate
7338
     * @return bool|int 0 success
7339
     */
7340
    public function alterAggregate(
7341
        $aggrname,
7342
        $aggrtype,
7343
        $aggrowner,
7344
        $aggrschema,
7345
        $aggrcomment,
7346
        $newaggrname,
7347
        $newaggrowner,
7348
        $newaggrschema,
7349
        $newaggrcomment
7350
    ) {
7351
        // Clean fields
7352
        $this->fieldClean($aggrname);
7353
        $this->fieldClean($aggrtype);
7354
        $this->fieldClean($aggrowner);
7355
        $this->fieldClean($aggrschema);
7356
        $this->fieldClean($newaggrname);
7357
        $this->fieldClean($newaggrowner);
7358
        $this->fieldClean($newaggrschema);
7359
7360
        $this->beginTransaction();
7361
7362
        // Change the owner, if it has changed
7363
        if ($aggrowner != $newaggrowner) {
7364
            $status = $this->changeAggregateOwner($aggrname, $aggrtype, $newaggrowner);
7365
            if ($status != 0) {
7366
                $this->rollbackTransaction();
7367
7368
                return -1;
7369
            }
7370
        }
7371
7372
        // Set the comment, if it has changed
7373
        if ($aggrcomment != $newaggrcomment) {
7374
            $status = $this->setComment('AGGREGATE', $aggrname, '', $newaggrcomment, $aggrtype);
7375
            if ($status) {
7376
                $this->rollbackTransaction();
7377
7378
                return -2;
7379
            }
7380
        }
7381
7382
        // Change the schema, if it has changed
7383
        if ($aggrschema != $newaggrschema) {
7384
            $status = $this->changeAggregateSchema($aggrname, $aggrtype, $newaggrschema);
7385
            if ($status != 0) {
7386
                $this->rollbackTransaction();
7387
7388
                return -3;
7389
            }
7390
        }
7391
7392
        // Rename the aggregate, if it has changed
7393
        if ($aggrname != $newaggrname) {
7394
            $status = $this->renameAggregate($newaggrschema, $aggrname, $aggrtype, $newaggrname);
7395
            if ($status != 0) {
7396
                $this->rollbackTransaction();
7397
7398
                return -4;
7399
            }
7400
        }
7401
7402
        return $this->endTransaction();
7403
    }
7404
7405
    /**
7406
     * Changes the owner of an aggregate function
7407
     *
7408
     * @param $aggrname     The name of the aggregate
7409
     * @param $aggrtype     The input data type of the aggregate
7410
     * @param $newaggrowner The new owner of the aggregate
7411
     * @return \PHPPgAdmin\Database\A 0 success
7412
     */
7413 View Code Duplication
    public function changeAggregateOwner($aggrname, $aggrtype, $newaggrowner)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7414
    {
7415
        $f_schema = $this->_schema;
7416
        $this->fieldClean($f_schema);
7417
        $this->fieldClean($aggrname);
7418
        $this->fieldClean($newaggrowner);
7419
        $sql = "ALTER AGGREGATE \"{$f_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") OWNER TO \"{$newaggrowner}\"";
7420
7421
        return $this->execute($sql);
7422
    }
7423
7424
    /**
7425
     * Changes the schema of an aggregate function
7426
     *
7427
     * @param $aggrname      The name of the aggregate
7428
     * @param $aggrtype      The input data type of the aggregate
7429
     * @param $newaggrschema The new schema for the aggregate
7430
     * @return \PHPPgAdmin\Database\A 0 success
7431
     */
7432 View Code Duplication
    public function changeAggregateSchema($aggrname, $aggrtype, $newaggrschema)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7433
    {
7434
        $f_schema = $this->_schema;
7435
        $this->fieldClean($f_schema);
7436
        $this->fieldClean($aggrname);
7437
        $this->fieldClean($newaggrschema);
7438
        $sql = "ALTER AGGREGATE \"{$f_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") SET SCHEMA  \"{$newaggrschema}\"";
7439
7440
        return $this->execute($sql);
7441
    }
7442
7443
    /**
7444
     * Renames an aggregate function
7445
     *
7446
     * @param $aggrschema
7447
     * @param $aggrname    The actual name of the aggregate
7448
     * @param $aggrtype    The actual input data type of the aggregate
7449
     * @param $newaggrname The new name of the aggregate
7450
     * @return \PHPPgAdmin\Database\A 0 success
7451
     */
7452
    public function renameAggregate($aggrschema, $aggrname, $aggrtype, $newaggrname)
7453
    {
7454
        /* this function is called from alterAggregate where params are cleaned */
7455
        $sql = "ALTER AGGREGATE \"{$aggrschema}\"" . '.' . "\"{$aggrname}\" (\"{$aggrtype}\") RENAME TO \"{$newaggrname}\"";
7456
7457
        return $this->execute($sql);
7458
    }
7459
7460
    /**
7461
     * Returns all roles in the database cluster
7462
     *
7463
     * @param $rolename (optional) The role name to exclude from the select
7464
     * @return All roles
7465
     */
7466
    public function getRoles($rolename = '')
7467
    {
7468
        $sql = '
7469
			SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit,
7470
				rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig
7471
			FROM pg_catalog.pg_roles';
7472
        if ($rolename) {
7473
            $sql .= " WHERE rolname!='{$rolename}'";
7474
        }
7475
7476
        $sql .= ' ORDER BY rolname';
7477
7478
        return $this->selectSet($sql);
7479
    }
7480
7481
    /**
7482
     * Returns information about a single role
7483
     *
7484
     * @param $rolename The name of the role to retrieve
7485
     * @return The role's data
7486
     */
7487
    public function getRole($rolename)
7488
    {
7489
        $this->clean($rolename);
7490
7491
        $sql = "
7492
			SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit,
7493
				rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig
7494
			FROM pg_catalog.pg_roles WHERE rolname='{$rolename}'";
7495
7496
        return $this->selectSet($sql);
7497
    }
7498
7499
    /**
7500
     * Returns all users in the database cluster
7501
     *
7502
     * @return All users
7503
     */
7504
    public function getUsers()
7505
    {
7506
        $sql = 'SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig
7507
			FROM pg_user
7508
			ORDER BY usename';
7509
7510
        return $this->selectSet($sql);
7511
    }
7512
7513
    /**
7514
     * Returns information about a single user
7515
     *
7516
     * @param $username The username of the user to retrieve
7517
     * @return The user's data
7518
     */
7519
    public function getUser($username)
7520
    {
7521
        $this->clean($username);
7522
7523
        $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig
7524
			FROM pg_user
7525
			WHERE usename='{$username}'";
7526
7527
        return $this->selectSet($sql);
7528
    }
7529
7530
    /**
7531
     * Creates a new role
7532
     *
7533
     * @param $rolename     The name of the role to create
7534
     * @param $password     A password for the role
7535
     * @param $superuser    Boolean whether or not the role is a superuser
7536
     * @param $createdb     Boolean whether or not the role can create databases
7537
     * @param $createrole   Boolean whether or not the role can create other roles
7538
     * @param $inherits     Boolean whether or not the role inherits the privileges from parent roles
7539
     * @param $login        Boolean whether or not the role will be allowed to login
7540
     * @param $connlimit    Number of concurrent connections the role can make
7541
     * @param $expiry       String Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire
7542
     * @param $memberof     (array) Roles to which the new role will be immediately added as a new member
7543
     * @param $members      (array) Roles which are automatically added as members of the new role
7544
     * @param $adminmembers (array) Roles which are automatically added as admin members of the new role
7545
     * @return \PHPPgAdmin\Database\A 0 success
7546
     */
7547
    public function createRole(
7548
        $rolename,
7549
        $password,
7550
        $superuser,
7551
        $createdb,
7552
        $createrole,
7553
        $inherits,
7554
        $login,
7555
        $connlimit,
7556
        $expiry,
7557
        $memberof,
7558
        $members,
7559
        $adminmembers
7560
    ) {
7561
        $enc = $this->_encryptPassword($rolename, $password);
7562
        $this->fieldClean($rolename);
7563
        $this->clean($enc);
7564
        $this->clean($connlimit);
7565
        $this->clean($expiry);
7566
        $this->fieldArrayClean($memberof);
7567
        $this->fieldArrayClean($members);
7568
        $this->fieldArrayClean($adminmembers);
7569
7570
        $sql = "CREATE ROLE \"{$rolename}\"";
7571
        if ($password != '') {
7572
            $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'";
7573
        }
7574
7575
        $sql .= $superuser ? ' SUPERUSER' : ' NOSUPERUSER';
7576
        $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB';
7577
        $sql .= $createrole ? ' CREATEROLE' : ' NOCREATEROLE';
7578
        $sql .= $inherits ? ' INHERIT' : ' NOINHERIT';
7579
        $sql .= $login ? ' LOGIN' : ' NOLOGIN';
7580
        if ($connlimit != '') {
7581
            $sql .= " CONNECTION LIMIT {$connlimit}";
7582
        } else {
7583
            $sql .= ' CONNECTION LIMIT -1';
7584
        }
7585
7586
        if ($expiry != '') {
7587
            $sql .= " VALID UNTIL '{$expiry}'";
7588
        } else {
7589
            $sql .= " VALID UNTIL 'infinity'";
7590
        }
7591
7592
        if (is_array($memberof) && sizeof($memberof) > 0) {
7593
            $sql .= ' IN ROLE "' . join('", "', $memberof) . '"';
7594
        }
7595
7596
        if (is_array($members) && sizeof($members) > 0) {
7597
            $sql .= ' ROLE "' . join('", "', $members) . '"';
7598
        }
7599
7600
        if (is_array($adminmembers) && sizeof($adminmembers) > 0) {
7601
            $sql .= ' ADMIN "' . join('", "', $adminmembers) . '"';
7602
        }
7603
7604
        return $this->execute($sql);
7605
    }
7606
7607
    /**
7608
     * Helper function that computes encypted PostgreSQL passwords
7609
     *
7610
     * @param $username The username
7611
     * @param $password The password
7612
     * @return string
7613
     */
7614
    public function _encryptPassword($username, $password)
7615
    {
7616
        return 'md5' . md5($password . $username);
7617
    }
7618
7619
    /**
7620
     * Adjusts a role's info and renames it
7621
     *
7622
     * @param $rolename        The name of the role to adjust
7623
     * @param $password        A password for the role
7624
     * @param $superuser       Boolean whether or not the role is a superuser
7625
     * @param $createdb        Boolean whether or not the role can create databases
7626
     * @param $createrole      Boolean whether or not the role can create other roles
7627
     * @param $inherits        Boolean whether or not the role inherits the privileges from parent roles
7628
     * @param $login           Boolean whether or not the role will be allowed to login
7629
     * @param $connlimit       Number of concurrent connections the role can make
7630
     * @param $expiry          string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire
7631
     * @param $memberof        (array) Roles to which the role will be immediately added as a new member
7632
     * @param $members         (array) Roles which are automatically added as members of the role
7633
     * @param $adminmembers    (array) Roles which are automatically added as admin members of the role
7634
     * @param $memberofold     (array) Original roles whose the role belongs to
7635
     * @param $membersold      (array) Original roles that are members of the role
7636
     * @param $adminmembersold (array) Original roles that are admin members of the role
7637
     * @param $newrolename     The new name of the role
7638
     * @return bool|int 0 success
7639
     */
7640
    public function setRenameRole(
7641
        $rolename,
7642
        $password,
7643
        $superuser,
7644
        $createdb,
7645
        $createrole,
7646
        $inherits,
7647
        $login,
7648
        $connlimit,
7649
        $expiry,
7650
        $memberof,
7651
        $members,
7652
        $adminmembers,
7653
        $memberofold,
7654
        $membersold,
7655
        $adminmembersold,
7656
        $newrolename
7657
    ) {
7658
        $status = $this->beginTransaction();
7659
        if ($status != 0) {
7660
            return -1;
7661
        }
7662
7663
        if ($rolename != $newrolename) {
7664
            $status = $this->renameRole($rolename, $newrolename);
7665
            if ($status != 0) {
7666
                $this->rollbackTransaction();
7667
7668
                return -3;
7669
            }
7670
            $rolename = $newrolename;
7671
        }
7672
7673
        $status =
7674
        $this->setRole($rolename, $password, $superuser, $createdb, $createrole, $inherits, $login, $connlimit, $expiry, $memberof, $members,
7675
            $adminmembers, $memberofold, $membersold, $adminmembersold);
7676
        if ($status != 0) {
7677
            $this->rollbackTransaction();
7678
7679
            return -2;
7680
        }
7681
7682
        return $this->endTransaction();
7683
    }
7684
7685
    /**
7686
     * Renames a role
7687
     *
7688
     * @param $rolename    The name of the role to rename
7689
     * @param $newrolename The new name of the role
7690
     * @return \PHPPgAdmin\Database\A 0 success
7691
     */
7692
    public function renameRole($rolename, $newrolename)
7693
    {
7694
        $this->fieldClean($rolename);
7695
        $this->fieldClean($newrolename);
7696
7697
        $sql = "ALTER ROLE \"{$rolename}\" RENAME TO \"{$newrolename}\"";
7698
7699
        return $this->execute($sql);
7700
    }
7701
7702
    /**
7703
     * Adjusts a role's info
7704
     *
7705
     * @param $rolename        The name of the role to adjust
7706
     * @param $password        A password for the role
7707
     * @param $superuser       Boolean whether or not the role is a superuser
7708
     * @param $createdb        Boolean whether or not the role can create databases
7709
     * @param $createrole      Boolean whether or not the role can create other roles
7710
     * @param $inherits        Boolean whether or not the role inherits the privileges from parent roles
7711
     * @param $login           Boolean whether or not the role will be allowed to login
7712
     * @param $connlimit       Number of concurrent connections the role can make
7713
     * @param $expiry          string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire
7714
     * @param $memberof        (array) Roles to which the role will be immediately added as a new member
7715
     * @param $members         (array) Roles which are automatically added as members of the role
7716
     * @param $adminmembers    (array) Roles which are automatically added as admin members of the role
7717
     * @param $memberofold     (array) Original roles whose the role belongs to
7718
     * @param $membersold      (array) Original roles that are members of the role
7719
     * @param $adminmembersold (array) Original roles that are admin members of the role
7720
     * @return int|\PHPPgAdmin\Database\A 0 success
7721
     */
7722
    public function setRole(
7723
        $rolename,
7724
        $password,
7725
        $superuser,
7726
        $createdb,
7727
        $createrole,
7728
        $inherits,
7729
        $login,
7730
        $connlimit,
7731
        $expiry,
7732
        $memberof,
7733
        $members,
7734
        $adminmembers,
7735
        $memberofold,
7736
        $membersold,
7737
        $adminmembersold
7738
    ) {
7739
        $enc = $this->_encryptPassword($rolename, $password);
7740
        $this->fieldClean($rolename);
7741
        $this->clean($enc);
7742
        $this->clean($connlimit);
7743
        $this->clean($expiry);
7744
        $this->fieldArrayClean($memberof);
7745
        $this->fieldArrayClean($members);
7746
        $this->fieldArrayClean($adminmembers);
7747
7748
        $sql = "ALTER ROLE \"{$rolename}\"";
7749
        if ($password != '') {
7750
            $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'";
7751
        }
7752
7753
        $sql .= $superuser ? ' SUPERUSER' : ' NOSUPERUSER';
7754
        $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB';
7755
        $sql .= $createrole ? ' CREATEROLE' : ' NOCREATEROLE';
7756
        $sql .= $inherits ? ' INHERIT' : ' NOINHERIT';
7757
        $sql .= $login ? ' LOGIN' : ' NOLOGIN';
7758
        if ($connlimit != '') {
7759
            $sql .= " CONNECTION LIMIT {$connlimit}";
7760
        } else {
7761
            $sql .= ' CONNECTION LIMIT -1';
7762
        }
7763
7764
        if ($expiry != '') {
7765
            $sql .= " VALID UNTIL '{$expiry}'";
7766
        } else {
7767
            $sql .= " VALID UNTIL 'infinity'";
7768
        }
7769
7770
        $status = $this->execute($sql);
7771
7772
        if ($status != 0) {
7773
            return -1;
7774
        }
7775
7776
        //memberof
7777
        $old = explode(',', $memberofold);
7778
        foreach ($memberof as $m) {
7779
            if (!in_array($m, $old)) {
7780
                $status = $this->grantRole($m, $rolename);
7781
                if ($status != 0) {
7782
                    return -1;
7783
                }
7784
            }
7785
        }
7786 View Code Duplication
        if ($memberofold) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7787
            foreach ($old as $o) {
7788
                if (!in_array($o, $memberof)) {
7789
                    $status = $this->revokeRole($o, $rolename, 0, 'CASCADE');
7790
                    if ($status != 0) {
7791
                        return -1;
7792
                    }
7793
                }
7794
            }
7795
        }
7796
7797
        //members
7798
        $old = explode(',', $membersold);
7799
        foreach ($members as $m) {
7800
            if (!in_array($m, $old)) {
7801
                $status = $this->grantRole($rolename, $m);
7802
                if ($status != 0) {
7803
                    return -1;
7804
                }
7805
            }
7806
        }
7807 View Code Duplication
        if ($membersold) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7808
            foreach ($old as $o) {
7809
                if (!in_array($o, $members)) {
7810
                    $status = $this->revokeRole($rolename, $o, 0, 'CASCADE');
7811
                    if ($status != 0) {
7812
                        return -1;
7813
                    }
7814
                }
7815
            }
7816
        }
7817
7818
        //adminmembers
7819
        $old = explode(',', $adminmembersold);
7820
        foreach ($adminmembers as $m) {
7821
            if (!in_array($m, $old)) {
7822
                $status = $this->grantRole($rolename, $m, 1);
7823
                if ($status != 0) {
7824
                    return -1;
7825
                }
7826
            }
7827
        }
7828 View Code Duplication
        if ($adminmembersold) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7829
            foreach ($old as $o) {
7830
                if (!in_array($o, $adminmembers)) {
7831
                    $status = $this->revokeRole($rolename, $o, 1, 'CASCADE');
7832
                    if ($status != 0) {
7833
                        return -1;
7834
                    }
7835
                }
7836
            }
7837
        }
7838
7839
        return $status;
7840
    }
7841
7842
    /**
7843
     * Grants membership in a role
7844
     *
7845
     * @param     $role     The name of the target role
7846
     * @param     $rolename The name of the role that will belong to the target role
7847
     * @param int $admin    (optional) Flag to grant the admin option
7848
     * @return \PHPPgAdmin\Database\A 0 success
7849
     */
7850 View Code Duplication
    public function grantRole($role, $rolename, $admin = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7851
    {
7852
        $this->fieldClean($role);
7853
        $this->fieldClean($rolename);
7854
7855
        $sql = "GRANT \"{$role}\" TO \"{$rolename}\"";
7856
        if ($admin == 1) {
7857
            $sql .= ' WITH ADMIN OPTION';
7858
        }
7859
7860
        return $this->execute($sql);
7861
    }
7862
7863
    /**
7864
     * Revokes membership in a role
7865
     *
7866
     * @param        $role     The name of the target role
7867
     * @param        $rolename The name of the role that will not belong to the target role
7868
     * @param int    $admin    (optional) Flag to revoke only the admin option
7869
     * @param string $type     (optional) Type of revoke: RESTRICT | CASCADE
7870
     * @return \PHPPgAdmin\Database\A 0 success
7871
     */
7872 View Code Duplication
    public function revokeRole($role, $rolename, $admin = 0, $type = 'RESTRICT')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7873
    {
7874
        $this->fieldClean($role);
7875
        $this->fieldClean($rolename);
7876
7877
        $sql = 'REVOKE ';
7878
        if ($admin == 1) {
7879
            $sql .= 'ADMIN OPTION FOR ';
7880
        }
7881
7882
        $sql .= "\"{$role}\" FROM \"{$rolename}\" {$type}";
7883
7884
        return $this->execute($sql);
7885
    }
7886
7887
    /**
7888
     * Removes a role
7889
     *
7890
     * @param $rolename The name of the role to drop
7891
     * @return \PHPPgAdmin\Database\A 0 success
7892
     */
7893
    public function dropRole($rolename)
7894
    {
7895
        $this->fieldClean($rolename);
7896
7897
        $sql = "DROP ROLE \"{$rolename}\"";
7898
7899
        return $this->execute($sql);
7900
    }
7901
7902
    /**
7903
     * Creates a new user
7904
     *
7905
     * @param $username   The username of the user to create
7906
     * @param $password   A password for the user
7907
     * @param $createdb   boolean Whether or not the user can create databases
7908
     * @param $createuser boolean Whether or not the user can create other users
7909
     * @param $expiry     string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire
7910
     * @param $groups
7911
     * @return \PHPPgAdmin\Database\A 0 success
7912
     * @internal param $group (array) The groups to create the user in
7913
     */
7914
    public function createUser($username, $password, $createdb, $createuser, $expiry, $groups)
7915
    {
7916
        $enc = $this->_encryptPassword($username, $password);
7917
        $this->fieldClean($username);
7918
        $this->clean($enc);
7919
        $this->clean($expiry);
7920
        $this->fieldArrayClean($groups);
7921
7922
        $sql = "CREATE USER \"{$username}\"";
7923
        if ($password != '') {
7924
            $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'";
7925
        }
7926
7927
        $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB';
7928
        $sql .= $createuser ? ' CREATEUSER' : ' NOCREATEUSER';
7929
        if (is_array($groups) && sizeof($groups) > 0) {
7930
            $sql .= ' IN GROUP "' . join('", "', $groups) . '"';
7931
        }
7932
7933
        if ($expiry != '') {
7934
            $sql .= " VALID UNTIL '{$expiry}'";
7935
        } else {
7936
            $sql .= " VALID UNTIL 'infinity'";
7937
        }
7938
7939
        return $this->execute($sql);
7940
    }
7941
7942
    /**
7943
     * Adjusts a user's info and renames the user
7944
     *
7945
     * @param $username   The username of the user to modify
7946
     * @param $password   A new password for the user
7947
     * @param $createdb   boolean Whether or not the user can create databases
7948
     * @param $createuser boolean Whether or not the user can create other users
7949
     * @param $expiry     string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire.
7950
     * @param $newname    The new name of the user
7951
     * @return bool|int 0 success
7952
     */
7953
    public function setRenameUser($username, $password, $createdb, $createuser, $expiry, $newname)
7954
    {
7955
        $status = $this->beginTransaction();
7956
        if ($status != 0) {
7957
            return -1;
7958
        }
7959
7960
        if ($username != $newname) {
7961
            $status = $this->renameUser($username, $newname);
7962
            if ($status != 0) {
7963
                $this->rollbackTransaction();
7964
7965
                return -3;
7966
            }
7967
            $username = $newname;
7968
        }
7969
7970
        $status = $this->setUser($username, $password, $createdb, $createuser, $expiry);
7971
        if ($status != 0) {
7972
            $this->rollbackTransaction();
7973
7974
            return -2;
7975
        }
7976
7977
        return $this->endTransaction();
7978
    }
7979
7980
    /**
7981
     * Renames a user
7982
     *
7983
     * @param $username The username of the user to rename
7984
     * @param $newname  The new name of the user
7985
     * @return \PHPPgAdmin\Database\A 0 success
7986
     */
7987
    public function renameUser($username, $newname)
7988
    {
7989
        $this->fieldClean($username);
7990
        $this->fieldClean($newname);
7991
7992
        $sql = "ALTER USER \"{$username}\" RENAME TO \"{$newname}\"";
7993
7994
        return $this->execute($sql);
7995
    }
7996
7997
    // Tablespace functions
7998
7999
    /**
8000
     * Adjusts a user's info
8001
     *
8002
     * @param $username   The username of the user to modify
8003
     * @param $password   A new password for the user
8004
     * @param $createdb   boolean Whether or not the user can create databases
8005
     * @param $createuser boolean Whether or not the user can create other users
8006
     * @param $expiry     string Format 'YYYY-MM-DD HH:MM:SS'.  '' means never expire.
8007
     * @return \PHPPgAdmin\Database\A 0 success
8008
     */
8009
    public function setUser($username, $password, $createdb, $createuser, $expiry)
8010
    {
8011
        $enc = $this->_encryptPassword($username, $password);
8012
        $this->fieldClean($username);
8013
        $this->clean($enc);
8014
        $this->clean($expiry);
8015
8016
        $sql = "ALTER USER \"{$username}\"";
8017
        if ($password != '') {
8018
            $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'";
8019
        }
8020
8021
        $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB';
8022
        $sql .= $createuser ? ' CREATEUSER' : ' NOCREATEUSER';
8023
        if ($expiry != '') {
8024
            $sql .= " VALID UNTIL '{$expiry}'";
8025
        } else {
8026
            $sql .= " VALID UNTIL 'infinity'";
8027
        }
8028
8029
        return $this->execute($sql);
8030
    }
8031
8032
    /**
8033
     * Removes a user
8034
     *
8035
     * @param $username The username of the user to drop
8036
     * @return \PHPPgAdmin\Database\A 0 success
8037
     */
8038
    public function dropUser($username)
8039
    {
8040
        $this->fieldClean($username);
8041
8042
        $sql = "DROP USER \"{$username}\"";
8043
8044
        return $this->execute($sql);
8045
    }
8046
8047
    /**
8048
     * Changes a role's password
8049
     *
8050
     * @param $rolename The role name
8051
     * @param $password The new password
8052
     * @return \PHPPgAdmin\Database\A 0 success
8053
     */
8054 View Code Duplication
    public function changePassword($rolename, $password)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
8055
    {
8056
        $enc = $this->_encryptPassword($rolename, $password);
8057
        $this->fieldClean($rolename);
8058
        $this->clean($enc);
8059
8060
        $sql = "ALTER ROLE \"{$rolename}\" WITH ENCRYPTED PASSWORD '{$enc}'";
8061
8062
        return $this->execute($sql);
8063
    }
8064
8065
    /**
8066
     * Adds a group member
8067
     *
8068
     * @param $groname The name of the group
8069
     * @param $user    The name of the user to add to the group
8070
     * @return \PHPPgAdmin\Database\A 0 success
8071
     */
8072
    public function addGroupMember($groname, $user)
8073
    {
8074
        $this->fieldClean($groname);
8075
        $this->fieldClean($user);
8076
8077
        $sql = "ALTER GROUP \"{$groname}\" ADD USER \"{$user}\"";
8078
8079
        return $this->execute($sql);
8080
    }
8081
8082
    /**
8083
     * Returns all role names which the role belongs to
8084
     *
8085
     * @param $rolename The role name
8086
     * @return All role names
8087
     */
8088
    public function getMemberOf($rolename)
8089
    {
8090
        $this->clean($rolename);
8091
8092
        $sql = "
8093
			SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M
8094
			WHERE R.oid=M.roleid
8095
				AND member IN (
8096
					SELECT oid FROM pg_catalog.pg_roles
8097
					WHERE rolname='{$rolename}')
8098
			ORDER BY rolname";
8099
8100
        return $this->selectSet($sql);
8101
    }
8102
8103
    // Administration functions
8104
8105
    /**
8106
     * Returns all role names that are members of a role
8107
     *
8108
     * @param $rolename The role name
8109
     * @param $admin    (optional) Find only admin members
8110
     * @return All role names
8111
     */
8112
    public function getMembers($rolename, $admin = 'f')
8113
    {
8114
        $this->clean($rolename);
8115
8116
        $sql = "
8117
			SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M
8118
			WHERE R.oid=M.member AND admin_option='{$admin}'
8119
				AND roleid IN (SELECT oid FROM pg_catalog.pg_roles
8120
					WHERE rolname='{$rolename}')
8121
			ORDER BY rolname";
8122
8123
        return $this->selectSet($sql);
8124
    }
8125
8126
    /**
8127
     * Removes a group member
8128
     *
8129
     * @param $groname The name of the group
8130
     * @param $user    The name of the user to remove from the group
8131
     * @return \PHPPgAdmin\Database\A 0 success
8132
     */
8133
    public function dropGroupMember($groname, $user)
8134
    {
8135
        $this->fieldClean($groname);
8136
        $this->fieldClean($user);
8137
8138
        $sql = "ALTER GROUP \"{$groname}\" DROP USER \"{$user}\"";
8139
8140
        return $this->execute($sql);
8141
    }
8142
8143
    /**
8144
     * Return users in a specific group
8145
     *
8146
     * @param $groname The name of the group
8147
     * @return All users in the group
8148
     */
8149
    public function getGroup($groname)
8150
    {
8151
        $this->clean($groname);
8152
8153
        $sql = "
8154
			SELECT s.usename FROM pg_catalog.pg_user s, pg_catalog.pg_group g
8155
			WHERE g.groname='{$groname}' AND s.usesysid = ANY (g.grolist)
8156
			ORDER BY s.usename";
8157
8158
        return $this->selectSet($sql);
8159
    }
8160
8161
    /**
8162
     * Returns all groups in the database cluser
8163
     *
8164
     * @return All groups
8165
     */
8166
    public function getGroups()
8167
    {
8168
        $sql = 'SELECT groname FROM pg_group ORDER BY groname';
8169
8170
        return $this->selectSet($sql);
8171
    }
8172
8173
    /**
8174
     * Creates a new group
8175
     *
8176
     * @param $groname The name of the group
8177
     * @param $users   An array of users to add to the group
8178
     * @return \PHPPgAdmin\Database\A 0 success
8179
     */
8180
    public function createGroup($groname, $users)
8181
    {
8182
        $this->fieldClean($groname);
8183
8184
        $sql = "CREATE GROUP \"{$groname}\"";
8185
8186
        if (is_array($users) && sizeof($users) > 0) {
8187
            $this->fieldArrayClean($users);
8188
            $sql .= ' WITH USER "' . join('", "', $users) . '"';
8189
        }
8190
8191
        return $this->execute($sql);
8192
    }
8193
8194
    /**
8195
     * Removes a group
8196
     *
8197
     * @param $groname The name of the group to drop
8198
     * @return \PHPPgAdmin\Database\A 0 success
8199
     */
8200
    public function dropGroup($groname)
8201
    {
8202
        $this->fieldClean($groname);
8203
8204
        $sql = "DROP GROUP \"{$groname}\"";
8205
8206
        return $this->execute($sql);
8207
    }
8208
8209
    /**
8210
     * Grants a privilege to a user, group or public
8211
     *
8212
     * @param $mode        'GRANT' or 'REVOKE';
8213
     * @param $type        The type of object
8214
     * @param $object      The name of the object
8215
     * @param $public      True to grant to public, false otherwise
8216
     * @param $usernames   The array of usernames to grant privs to.
8217
     * @param $groupnames  The array of group names to grant privs to.
8218
     * @param $privileges  The array of privileges to grant (eg. ('SELECT', 'ALL PRIVILEGES', etc.) )
8219
     * @param $grantoption True if has grant option, false otherwise
8220
     * @param $cascade     True for cascade revoke, false otherwise
8221
     * @param $table       the column's table if type=column
8222
     * @return \PHPPgAdmin\Database\A 0 success
8223
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'GRANT' at position 0 could not be parsed: Unknown type name ''GRANT'' at position 0 in 'GRANT'.
Loading history...
8224
    public function setPrivileges(
8225
        $mode,
8226
        $type,
8227
        $object,
8228
        $public,
8229
        $usernames,
8230
        $groupnames,
8231
        $privileges,
8232
        $grantoption,
8233
        $cascade,
8234
        $table
8235
    ) {
8236
        $f_schema = $this->_schema;
8237
        $this->fieldClean($f_schema);
8238
        $this->fieldArrayClean($usernames);
8239
        $this->fieldArrayClean($groupnames);
8240
8241
        // Input checking
8242
        if (!is_array($privileges) || sizeof($privileges) == 0) {
8243
            return -3;
8244
        }
8245
8246
        if (!is_array($usernames) || !is_array($groupnames) ||
8247
            (!$public && sizeof($usernames) == 0 && sizeof($groupnames) == 0)) {
8248
            return -4;
8249
        }
8250
8251
        if ($mode != 'GRANT' && $mode != 'REVOKE') {
8252
            return -5;
8253
        }
8254
8255
        $sql = $mode;
8256
8257
        // Grant option
8258
        if ($this->hasGrantOption() && $mode == 'REVOKE' && $grantoption) {
8259
            $sql .= ' GRANT OPTION FOR';
8260
        }
8261
8262
        if (in_array('ALL PRIVILEGES', $privileges)) {
8263
            $sql .= ' ALL PRIVILEGES';
8264
        } else {
8265
            if ($type == 'column') {
8266
                $this->fieldClean($object);
8267
                $sql .= ' ' . join(" (\"{$object}\"), ", $privileges);
8268
            } else {
8269
                $sql .= ' ' . join(', ', $privileges);
8270
            }
8271
        }
8272
8273
        switch ($type) {
8274
            case 'column':
8275
                $sql .= " (\"{$object}\")";
8276
                $object = $table;
8277
            case 'table':
8278
            case 'view':
8279
            case 'sequence':
8280
                $this->fieldClean($object);
8281
                $sql .= " ON \"{$f_schema}\".\"{$object}\"";
8282
                break;
8283
            case 'database':
8284
                $this->fieldClean($object);
8285
                $sql .= " ON DATABASE \"{$object}\"";
8286
                break;
8287
            case 'function':
8288
                // Function comes in with $object as function OID
8289
                $fn = $this->getFunction($object);
8290
                $this->fieldClean($fn->fields['proname']);
8291
                $sql .= " ON FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})";
8292
                break;
8293
            case 'language':
8294
                $this->fieldClean($object);
8295
                $sql .= " ON LANGUAGE \"{$object}\"";
8296
                break;
8297
            case 'schema':
8298
                $this->fieldClean($object);
8299
                $sql .= " ON SCHEMA \"{$object}\"";
8300
                break;
8301
            case 'tablespace':
8302
                $this->fieldClean($object);
8303
                $sql .= " ON TABLESPACE \"{$object}\"";
8304
                break;
8305
            default:
8306
                return -1;
8307
        }
8308
8309
        // Dump PUBLIC
8310
        $first = true;
8311
        $sql .= ($mode == 'GRANT') ? ' TO ' : ' FROM ';
8312
        if ($public) {
8313
            $sql .= 'PUBLIC';
8314
            $first = false;
8315
        }
8316
        // Dump users
8317
        foreach ($usernames as $v) {
8318
            if ($first) {
8319
                $sql .= "\"{$v}\"";
8320
                $first = false;
8321
            } else {
8322
                $sql .= ", \"{$v}\"";
8323
            }
8324
        }
8325
        // Dump groups
8326
        foreach ($groupnames as $v) {
8327
            if ($first) {
8328
                $sql .= "GROUP \"{$v}\"";
8329
                $first = false;
8330
            } else {
8331
                $sql .= ", GROUP \"{$v}\"";
8332
            }
8333
        }
8334
8335
        // Grant option
8336
        if ($this->hasGrantOption() && $mode == 'GRANT' && $grantoption) {
8337
            $sql .= ' WITH GRANT OPTION';
8338
        }
8339
8340
        // Cascade revoke
8341
        if ($this->hasGrantOption() && $mode == 'REVOKE' && $cascade) {
8342
            $sql .= ' CASCADE';
8343
        }
8344
8345
        return $this->execute($sql);
8346
    }
8347
8348
    /**
8349
     * Retrieves information for all tablespaces
8350
     *
8351
     * @param bool|\PHPPgAdmin\Database\Include $all Include all tablespaces (necessary when moving objects back to the default space)
8352
     * @return \PHPPgAdmin\Database\A recordset
8353
     */
8354
    public function getTablespaces($all = false)
8355
    {
8356
        $conf = $this->conf;
8357
8358
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
8359
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
8360
					FROM pg_catalog.pg_tablespace";
8361
8362
        if (!$conf['show_system'] && !$all) {
8363
            $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$';
8364
        }
8365
8366
        $sql .= ' ORDER BY spcname';
8367
8368
        return $this->selectSet($sql);
8369
    }
8370
8371
    // Misc functions
8372
8373
    /**
8374
     * Retrieves a tablespace's information
8375
     *
8376
     * @param $spcname
8377
     * @return \PHPPgAdmin\Database\A recordset
8378
     */
8379
    public function getTablespace($spcname)
8380
    {
8381
        $this->clean($spcname);
8382
8383
        $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation,
8384
                    (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment
8385
					FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'";
8386
8387
        return $this->selectSet($sql);
8388
    }
8389
8390
    /**
8391
     * Creates a tablespace
8392
     *
8393
     * @param        $spcname  The name of the tablespace to create
8394
     * @param        $spcowner The owner of the tablespace. '' for current
8395
     * @param        $spcloc   The directory in which to create the tablespace
8396
     * @param string $comment
8397
     * @return int 0 success
8398
     */
8399
    public function createTablespace($spcname, $spcowner, $spcloc, $comment = '')
8400
    {
8401
        $this->fieldClean($spcname);
8402
        $this->clean($spcloc);
8403
8404
        $sql = "CREATE TABLESPACE \"{$spcname}\"";
8405
8406
        if ($spcowner != '') {
8407
            $this->fieldClean($spcowner);
8408
            $sql .= " OWNER \"{$spcowner}\"";
8409
        }
8410
8411
        $sql .= " LOCATION '{$spcloc}'";
8412
8413
        $status = $this->execute($sql);
8414
        if ($status != 0) {
8415
            return -1;
8416
        }
8417
8418 View Code Duplication
        if ($comment != '' && $this->hasSharedComments()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
8419
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
8420
            if ($status != 0) {
8421
                return -2;
8422
            }
8423
        }
8424
8425
        return 0;
8426
    }
8427
8428
    /**
8429
     * Alters a tablespace
8430
     *
8431
     * @param        $spcname The name of the tablespace
8432
     * @param        $name    The new name for the tablespace
8433
     * @param        $owner   The new owner for the tablespace
8434
     * @param string $comment
8435
     * @return bool|int 0 success
8436
     */
8437
    public function alterTablespace($spcname, $name, $owner, $comment = '')
8438
    {
8439
        $this->fieldClean($spcname);
8440
        $this->fieldClean($name);
8441
        $this->fieldClean($owner);
8442
8443
        // Begin transaction
8444
        $status = $this->beginTransaction();
8445
        if ($status != 0) {
8446
            return -1;
8447
        }
8448
8449
        // Owner
8450
        $sql    = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\"";
8451
        $status = $this->execute($sql);
8452
        if ($status != 0) {
8453
            $this->rollbackTransaction();
8454
8455
            return -2;
8456
        }
8457
8458
        // Rename (only if name has changed)
8459
        if ($name != $spcname) {
8460
            $sql    = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\"";
8461
            $status = $this->execute($sql);
8462
            if ($status != 0) {
8463
                $this->rollbackTransaction();
8464
8465
                return -3;
8466
            }
8467
8468
            $spcname = $name;
8469
        }
8470
8471
        // Set comment if it has changed
8472 View Code Duplication
        if (trim($comment) != '' && $this->hasSharedComments()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
8473
            $status = $this->setComment('TABLESPACE', $spcname, '', $comment);
8474
            if ($status != 0) {
8475
                return -4;
8476
            }
8477
        }
8478
8479
        return $this->endTransaction();
8480
    }
8481
8482
    /**
8483
     * Drops a tablespace
8484
     *
8485
     * @param $spcname The name of the domain to drop
8486
     * @return \PHPPgAdmin\Database\A 0 success
8487
     */
8488
    public function dropTablespace($spcname)
8489
    {
8490
        $this->fieldClean($spcname);
8491
8492
        $sql = "DROP TABLESPACE \"{$spcname}\"";
8493
8494
        return $this->execute($sql);
8495
    }
8496
8497
    /**
8498
     * Analyze a database
8499
     *
8500
     * @param $table (optional) The table to analyze
8501
     * @return \PHPPgAdmin\Database\A
8502
     */
8503 View Code Duplication
    public function analyzeDB($table = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
8504
    {
8505
        if ($table != '') {
8506
            $f_schema = $this->_schema;
8507
            $this->fieldClean($f_schema);
8508
            $this->fieldClean($table);
8509
8510
            $sql = "ANALYZE \"{$f_schema}\".\"{$table}\"";
8511
        } else {
8512
            $sql = 'ANALYZE';
8513
        }
8514
8515
        return $this->execute($sql);
8516
    }
8517
8518
    /**
8519
     * Vacuums a database
8520
     *
8521
     * @param \PHPPgAdmin\Database\The|string $table   The table to vacuum
8522
     * @param bool|\PHPPgAdmin\Database\If    $analyze If true, also does analyze
8523
     * @param bool|\PHPPgAdmin\Database\If    $full    If true, selects "full" vacuum
8524
     * @param bool|\PHPPgAdmin\Database\If    $freeze  If true, selects aggressive "freezing" of tuples
8525
     * @return \PHPPgAdmin\Database\A
8526
     */
8527
    public function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false)
8528
    {
8529
        $sql = 'VACUUM';
8530
        if ($full) {
8531
            $sql .= ' FULL';
8532
        }
8533
8534
        if ($freeze) {
8535
            $sql .= ' FREEZE';
8536
        }
8537
8538
        if ($analyze) {
8539
            $sql .= ' ANALYZE';
8540
        }
8541
8542
        if ($table != '') {
8543
            $f_schema = $this->_schema;
8544
            $this->fieldClean($f_schema);
8545
            $this->fieldClean($table);
8546
            $sql .= " \"{$f_schema}\".\"{$table}\"";
8547
        }
8548
8549
        return $this->execute($sql);
8550
    }
8551
8552
    /**
8553
     * Returns all autovacuum global configuration
8554
     *
8555
     * @return associative array array( param => value, ...)
8556
     */
8557
    public function getAutovacuum()
8558
    {
8559
        $_defaults = $this->selectSet("SELECT name, setting
8560
			FROM pg_catalog.pg_settings
8561
			WHERE
8562
				name = 'autovacuum'
8563
				OR name = 'autovacuum_vacuum_threshold'
8564
				OR name = 'autovacuum_vacuum_scale_factor'
8565
				OR name = 'autovacuum_analyze_threshold'
8566
				OR name = 'autovacuum_analyze_scale_factor'
8567
				OR name = 'autovacuum_vacuum_cost_delay'
8568
				OR name = 'autovacuum_vacuum_cost_limit'
8569
				OR name = 'vacuum_freeze_min_age'
8570
				OR name = 'autovacuum_freeze_max_age'
8571
			"
8572
        );
8573
8574
        $ret = [];
8575
        while (!$_defaults->EOF) {
8576
            $ret[$_defaults->fields['name']] = $_defaults->fields['setting'];
8577
            $_defaults->moveNext();
8578
        }
8579
8580
        return $ret;
8581
    }
8582
8583
    /**
8584
     * Returns all available autovacuum per table information.
8585
     *
8586
     * @param $table
8587
     * @param $vacenabled
8588
     * @param $vacthreshold
8589
     * @param $vacscalefactor
8590
     * @param $anathresold
8591
     * @param $anascalefactor
8592
     * @param $vaccostdelay
8593
     * @param $vaccostlimit
8594
     * @return \PHPPgAdmin\Database\A recordset
8595
     */
8596
    public function saveAutovacuum(
8597
        $table,
8598
        $vacenabled,
8599
        $vacthreshold,
8600
        $vacscalefactor,
8601
        $anathresold,
8602
        $anascalefactor,
8603
        $vaccostdelay,
8604
        $vaccostlimit
8605
    ) {
8606
        $f_schema = $this->_schema;
8607
        $this->fieldClean($f_schema);
8608
        $this->fieldClean($table);
8609
8610
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
8611
8612
        if (!empty($vacenabled)) {
8613
            $this->clean($vacenabled);
8614
            $params[] = "autovacuum_enabled='{$vacenabled}'";
8615
        }
8616
        if (!empty($vacthreshold)) {
8617
            $this->clean($vacthreshold);
8618
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
8619
        }
8620
        if (!empty($vacscalefactor)) {
8621
            $this->clean($vacscalefactor);
8622
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
8623
        }
8624
        if (!empty($anathresold)) {
8625
            $this->clean($anathresold);
8626
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
8627
        }
8628
        if (!empty($anascalefactor)) {
8629
            $this->clean($anascalefactor);
8630
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
8631
        }
8632
        if (!empty($vaccostdelay)) {
8633
            $this->clean($vaccostdelay);
8634
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
8635
        }
8636
        if (!empty($vaccostlimit)) {
8637
            $this->clean($vaccostlimit);
8638
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
8639
        }
8640
8641
        $sql = $sql . implode(',', $params) . ');';
8642
8643
        return $this->execute($sql);
8644
    }
8645
8646
    // Type conversion routines
8647
8648
    public function dropAutovacuum($table)
8649
    {
8650
        $f_schema = $this->_schema;
8651
        $this->fieldClean($f_schema);
8652
        $this->fieldClean($table);
8653
8654
        return $this->execute("
8655
			ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
8656
				autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
8657
				autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
8658
			);"
8659
        );
8660
    }
8661
8662
    /**
8663
     * Returns all available process information.
8664
     *
8665
     * @param $database (optional) Find only connections to specified database
8666
     * @return A recordset
8667
     */
8668
    public function getProcesses($database = null)
8669
    {
8670
        if ($database === null) {
8671
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
8672
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
8673
				FROM pg_catalog.pg_stat_activity
8674
				ORDER BY datname, usename, pid";
8675
        } else {
8676
            $this->clean($database);
8677
            $sql = "SELECT datname, usename, pid, waiting, state_change as query_start,
8678
                  case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query
8679
				FROM pg_catalog.pg_stat_activity
8680
				WHERE datname='{$database}'
8681
				ORDER BY usename, pid";
8682
        }
8683
8684
        return $this->selectSet($sql);
8685
    }
8686
8687
    // interfaces Statistics collector functions
8688
8689
    /**
8690
     * Returns table locks information in the current database
8691
     *
8692
     * @return A recordset
8693
     */
8694
8695 View Code Duplication
    public function getLocks()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
8696
    {
8697
        $conf = $this->conf;
8698
8699
        if (!$conf['show_system']) {
8700
            $where = 'AND pn.nspname NOT LIKE $$pg\_%$$';
8701
        } else {
8702
            $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
8703
        }
8704
8705
        $sql = "
8706
			SELECT
8707
				pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction,
8708
				(select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid'
8709
					and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction
8710
			FROM
8711
				pg_catalog.pg_locks pl,
8712
				pg_catalog.pg_class pc,
8713
				pg_catalog.pg_namespace pn
8714
			WHERE
8715
				pl.relation = pc.oid AND pc.relnamespace=pn.oid
8716
			{$where}
8717
			ORDER BY pid,nspname,tablename";
8718
8719
        return $this->selectSet($sql);
8720
    }
8721
8722
    /**
8723
     * Sends a cancel or kill command to a process
8724
     *
8725
     * @param $pid    The ID of the backend process
8726
     * @param $signal 'CANCEL'
8727
     * @return int 0 success
8728
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'CANCEL' at position 0 could not be parsed: Unknown type name ''CANCEL'' at position 0 in 'CANCEL'.
Loading history...
8729
    public function sendSignal($pid, $signal)
8730
    {
8731
        // Clean
8732
        $pid = (int) $pid;
8733
8734
        if ($signal == 'CANCEL') {
8735
            $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val";
8736
        } elseif ($signal == 'KILL') {
8737
            $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val";
8738
        } else {
8739
            return -1;
8740
        }
8741
8742
        // Execute the query
8743
        $val = $this->selectField($sql, 'val');
8744
8745
        if ($val === 'f') {
8746
            return -1;
8747
        }
8748
8749
        if ($val === 't') {
8750
            return 0;
8751
        } else {
8752
            return -1;
8753
        }
8754
    }
8755
8756
    /**
8757
     * Executes an SQL script as a series of SQL statements.  Returns
8758
     * the result of the final step.  This is a very complicated lexer
8759
     * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in
8760
     * the PostgreSQL source code.
8761
     * XXX: It does not handle multibyte languages properly.
8762
     *
8763
     * @param $name     Entry in $_FILES to use
8764
     * @param $callback (optional) Callback function to call with each query,
8765
     *                  its result and line number.
8766
     * @return True for general success, false on any failure.
8767
     */
8768
    public function executeScript($name, $callback = null)
0 ignored issues
show
Coding Style introduced by
executeScript uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
8769
    {
8770
8771
        // This whole function isn't very encapsulated, but hey...
8772
        $conn = $this->conn->_connectionID;
8773
        if (!is_uploaded_file($_FILES[$name]['tmp_name'])) {
8774
            return false;
8775
        }
8776
8777
        $fd = fopen($_FILES[$name]['tmp_name'], 'rb');
8778
        if (!$fd) {
8779
            return false;
8780
        }
8781
8782
        // Build up each SQL statement, they can be multiline
8783
        $query_buf    = null;
8784
        $query_start  = 0;
8785
        $in_quote     = 0;
8786
        $in_xcomment  = 0;
8787
        $bslash_count = 0;
8788
        $dol_quote    = null;
8789
        $paren_level  = 0;
8790
        $len          = 0;
8791
        $i            = 0;
8792
        $prevlen      = 0;
8793
        $thislen      = 0;
8794
        $lineno       = 0;
8795
8796
        // Loop over each line in the file
8797
        while (!feof($fd)) {
8798
            $line = fgets($fd);
8799
            $lineno++;
8800
8801
            // Nothing left on line? Then ignore...
8802
            if (trim($line) == '') {
8803
                continue;
8804
            }
8805
8806
            $len         = strlen($line);
8807
            $query_start = 0;
8808
8809
            /*
8810
             * Parse line, looking for command separators.
8811
             *
8812
             * The current character is at line[i], the prior character at line[i
8813
             * - prevlen], the next character at line[i + thislen].
8814
             */
8815
            $prevlen = 0;
8816
            $thislen = ($len > 0) ? 1 : 0;
8817
8818
            for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) {
8819
8820
                /* was the previous character a backslash? */
8821
                if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') {
8822
                    $bslash_count++;
8823
                } else {
8824
                    $bslash_count = 0;
8825
                }
8826
8827
                /*
8828
                 * It is important to place the in_* test routines before the
8829
                 * in_* detection routines. i.e. we have to test if we are in
8830
                 * a quote before testing for comments.
8831
                 */
8832
8833
                /* in quote? */
8834
                if ($in_quote !== 0) {
8835
                    /*
8836
                     * end of quote if matching non-backslashed character.
8837
                     * backslashes don't count for double quotes, though.
8838
                     */
8839
                    if (substr($line, $i, 1) == $in_quote &&
8840
                        ($bslash_count % 2 == 0 || $in_quote == '"')) {
8841
                        $in_quote = 0;
8842
                    }
8843
                } /* in or end of $foo$ type quote? */
8844
                else {
8845
                    if ($dol_quote) {
8846
                        if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) {
8847
                            $this->advance_1($i, $prevlen, $thislen);
8848
                            while (substr($line, $i, 1) != '$') {
8849
                                $this->advance_1($i, $prevlen, $thislen);
8850
                            }
8851
8852
                            $dol_quote = null;
8853
                        }
8854
                    } /* start of extended comment? */
8855
                    else {
8856
                        if (substr($line, $i, 2) == '/*') {
8857
                            $in_xcomment++;
8858
                            if ($in_xcomment == 1) {
8859
                                $this->advance_1($i, $prevlen, $thislen);
8860
                            }
8861
                        } /* in or end of extended comment? */
8862
                        else {
8863
                            if ($in_xcomment) {
8864
                                if (substr($line, $i, 2) == '*/' && !--$in_xcomment) {
8865
                                    $this->advance_1($i, $prevlen, $thislen);
8866
                                }
8867
                            } /* start of quote? */
8868
                            else {
8869
                                if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
8870
                                    $in_quote = substr($line, $i, 1);
8871
                                } /*
8872
                                 * start of $foo$ type quote?
8873
                                 */
8874
                                else {
8875
                                    if (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
8876
                                        $dol_end   = strpos(substr($line, $i + 1), '$');
8877
                                        $dol_quote = substr($line, $i, $dol_end + 1);
8878
                                        $this->advance_1($i, $prevlen, $thislen);
8879
                                        while (substr($line, $i, 1) != '$') {
8880
                                            $this->advance_1($i, $prevlen, $thislen);
8881
                                        }
8882
                                    } /* single-line comment? truncate line */
8883
                                    else {
8884
                                        if (substr($line, $i, 2) == '--') {
8885
                                            $line = substr($line, 0, $i); /* remove comment */
8886
                                            break;
8887
                                        } /* count nested parentheses */
8888
8889
                                        if (substr($line, $i, 1) == '(') {
8890
                                            $paren_level++;
8891
                                        } else {
8892
                                            if (substr($line, $i, 1) == ')' && $paren_level > 0) {
8893
                                                $paren_level--;
8894
                                            } /* semicolon? then send query */
8895
                                            else {
8896
                                                if (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) {
8897
                                                    $subline = substr(substr($line, 0, $i), $query_start);
8898
                                                    /*
8899
                                                     * insert a cosmetic newline, if this is not the first
8900
                                                     * line in the buffer
8901
                                                     */
8902
                                                    if (strlen($query_buf) > 0) {
8903
                                                        $query_buf .= "\n";
8904
                                                    }
8905
8906
                                                    /* append the line to the query buffer */
8907
                                                    $query_buf .= $subline;
8908
                                                    /* is there anything in the query_buf? */
8909
                                                    if (trim($query_buf)) {
8910
                                                        $query_buf .= ';';
8911
8912
                                                        // Execute the query. PHP cannot execute
8913
                                                        // empty queries, unlike libpq
8914
                                                        $res = @pg_query($conn, $query_buf);
8915
8916
                                                        // Call the callback function for display
8917
                                                        if ($callback !== null) {
8918
                                                            $callback($query_buf, $res, $lineno);
8919
                                                        }
8920
8921
                                                        // Check for COPY request
8922 View Code Duplication
                                                        if (pg_result_status($res) == 4) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
8923
                                                            // 4 == PGSQL_COPY_FROM
8924
                                                            while (!feof($fd)) {
8925
                                                                $copy = fgets($fd, 32768);
8926
                                                                $lineno++;
8927
                                                                pg_put_line($conn, $copy);
8928
                                                                if ($copy == "\\.\n" || $copy == "\\.\r\n") {
8929
                                                                    pg_end_copy($conn);
8930
                                                                    break;
8931
                                                                }
8932
                                                            }
8933
                                                        }
8934
                                                    }
8935
                                                    $query_buf   = null;
8936
                                                    $query_start = $i + $thislen;
8937
                                                }
8938
8939
                                                /*
8940
                                                 * keyword or identifier?
8941
                                                 * We grab the whole string so that we don't
8942
                                                 * mistakenly see $foo$ inside an identifier as the start
8943
                                                 * of a dollar quote.
8944
                                                 */
8945
                                                // XXX: multibyte here
8946
                                                else {
8947
                                                    if (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) {
8948
                                                        $sub = substr($line, $i, $thislen);
8949
                                                        while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) {
8950
                                                            /* keep going while we still have identifier chars */
8951
                                                            $this->advance_1($i, $prevlen, $thislen);
8952
                                                            $sub = substr($line, $i, $thislen);
8953
                                                        }
8954
                                                        // Since we're now over the next character to be examined, it is necessary
8955
                                                        // to move back one space.
8956
                                                        $i -= $prevlen;
8957
                                                    }
8958
                                                }
8959
                                            }
8960
                                        }
8961
                                    }
8962
                                }
8963
                            }
8964
                        }
8965
                    }
8966
                }
8967
            } // end for
8968
8969
            /* Put the rest of the line in the query buffer. */
8970
            $subline = substr($line, $query_start);
8971
            if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) {
8972
                if (strlen($query_buf) > 0) {
8973
                    $query_buf .= "\n";
8974
                }
8975
8976
                $query_buf .= $subline;
8977
            }
8978
8979
            $line = null;
8980
        } // end while
8981
8982
        /*
8983
         * Process query at the end of file without a semicolon, so long as
8984
         * it's non-empty.
8985
         */
8986
        if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) {
8987
            // Execute the query
8988
            $res = @pg_query($conn, $query_buf);
8989
8990
            // Call the callback function for display
8991
            if ($callback !== null) {
8992
                $callback($query_buf, $res, $lineno);
8993
            }
8994
8995
            // Check for COPY request
8996 View Code Duplication
            if (pg_result_status($res) == 4) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
8997
                // 4 == PGSQL_COPY_FROM
8998
                while (!feof($fd)) {
8999
                    $copy = fgets($fd, 32768);
9000
                    $lineno++;
9001
                    pg_put_line($conn, $copy);
9002
                    if ($copy == "\\.\n" || $copy == "\\.\r\n") {
9003
                        pg_end_copy($conn);
9004
                        break;
9005
                    }
9006
                }
9007
            }
9008
        }
9009
9010
        fclose($fd);
9011
9012
        return true;
9013
    }
9014
9015
    /**
9016
     * A private helper method for executeScript that advances the
9017
     * character by 1.  In psql this is careful to take into account
9018
     * multibyte languages, but we don't at the moment, so this function
9019
     * is someone redundant, since it will always advance by 1
9020
     *
9021
     * @param &$i       The current character position in the line
9022
     * @param &$prevlen Length of previous character (ie. 1)
9023
     * @param &$thislen Length of current character (ie. 1)
9024
     */
9025
    private function advance_1(&$i, &$prevlen, &$thislen)
9026
    {
9027
        $prevlen = $thislen;
9028
        $i += $thislen;
9029
        $thislen = 1;
9030
    }
9031
9032
    /**
9033
     * Private helper method to detect a valid $foo$ quote delimiter at
9034
     * the start of the parameter dquote
9035
     *
9036
     * @param $dquote
9037
     * @return True if valid, false otherwise
9038
     */
9039
    private function valid_dolquote($dquote)
9040
    {
9041
        // XXX: support multibyte
9042
        return (preg_match('/^[$][$]/', $dquote) || preg_match('/^[$][_[:alpha:]][_[:alnum:]]*[$]/', $dquote));
9043
    }
9044
9045
    // Capabilities
9046
9047
    /**
9048
     * Returns a recordset of all columns in a query.  Supports paging.
9049
     *
9050
     * @param $type       Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier,
9051
     *                    or 'SELECT" if it's a select query
9052
     * @param $table      The base table of the query.  NULL for no table.
9053
     * @param $query      The query that is being executed.  NULL for no query.
9054
     * @param $sortkey    The column number to sort by, or '' or null for no sorting
9055
     * @param $sortdir    The direction in which to sort the specified column ('asc' or 'desc')
9056
     * @param $page       The page of the relation to retrieve
9057
     * @param $page_size  The number of rows per page
9058
     * @param &$max_pages (return-by-ref) The max number of pages in the relation
9059
     * @return A recordset on success
9060
     * @return -1 transaction error
9061
     * @return -2 counting error
9062
     * @return -3 page or page_size invalid
9063
     * @return -4 unknown type
9064
     * @return -5 failed setting transaction read only
9065
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment (return-by-ref) at position 1 could not be parsed: Unknown type name 'return-by-ref' at position 1 in (return-by-ref).
Loading history...
9066
    public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages)
9067
    {
9068
        // Check that we're not going to divide by zero
9069
        if (!is_numeric($page_size) || $page_size != (int) $page_size || $page_size <= 0) {
9070
            return -3;
9071
        }
9072
9073
        // If $type is TABLE, then generate the query
9074
        switch ($type) {
9075
            case 'TABLE':
9076
                if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
9077
                    $orderby = [$sortkey => $sortdir];
9078
                } else {
9079
                    $orderby = [];
9080
                }
9081
9082
                $query = $this->getSelectSQL($table, [], [], [], $orderby);
9083
                break;
9084
            case 'QUERY':
9085
            case 'SELECT':
9086
                // Trim query
9087
                $query = trim($query);
9088
                // Trim off trailing semi-colon if there is one
9089
                if (substr($query, strlen($query) - 1, 1) == ';') {
9090
                    $query = substr($query, 0, strlen($query) - 1);
9091
                }
9092
9093
                break;
9094
            default:
9095
                return -4;
9096
        }
9097
9098
        // Generate count query
9099
        $count = "SELECT COUNT(*) AS total FROM ($query) AS sub";
9100
9101
        // Open a transaction
9102
        $status = $this->beginTransaction();
9103
        if ($status != 0) {
9104
            return -1;
9105
        }
9106
9107
        // If backend supports read only queries, then specify read only mode
9108
        // to avoid side effects from repeating queries that do writes.
9109
        if ($this->hasReadOnlyQueries()) {
9110
            $status = $this->execute('SET TRANSACTION READ ONLY');
9111
            if ($status != 0) {
9112
                $this->rollbackTransaction();
9113
9114
                return -5;
9115
            }
9116
        }
9117
9118
        // Count the number of rows
9119
        $total = $this->browseQueryCount($query, $count);
9120
        if ($total < 0) {
9121
            $this->rollbackTransaction();
9122
9123
            return -2;
9124
        }
9125
9126
        // Calculate max pages
9127
        $max_pages = ceil($total / $page_size);
9128
9129
        // Check that page is less than or equal to max pages
9130
        if (!is_numeric($page) || $page != (int) $page || $page > $max_pages || $page < 1) {
9131
            $this->rollbackTransaction();
9132
9133
            return -3;
9134
        }
9135
9136
        // Set fetch mode to NUM so that duplicate field names are properly returned
9137
        // for non-table queries.  Since the SELECT feature only allows selecting one
9138
        // table, duplicate fields shouldn't appear.
9139
        if ($type == 'QUERY') {
9140
            $this->conn->setFetchMode(ADODB_FETCH_NUM);
9141
        }
9142
9143
        // Figure out ORDER BY.  Sort key is always the column number (based from one)
9144
        // of the column to order by.  Only need to do this for non-TABLE queries
9145
        if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) {
9146
            $orderby = " ORDER BY {$sortkey}";
9147
            // Add sort order
9148
            if ($sortdir == 'desc') {
9149
                $orderby .= ' DESC';
9150
            } else {
9151
                $orderby .= ' ASC';
9152
            }
9153
        } else {
9154
            $orderby = '';
9155
        }
9156
9157
        // Actually retrieve the rows, with offset and limit
9158
        $rs     = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size);
9159
        $status = $this->endTransaction();
9160
        if ($status != 0) {
9161
            $this->rollbackTransaction();
9162
9163
            return -1;
9164
        }
9165
9166
        return $rs;
9167
    }
9168
9169
    /**
9170
     * Generates the SQL for the 'select' function
9171
     *
9172
     * @param $table   The table from which to select
9173
     * @param $show    An array of columns to show.  Empty array means all columns.
9174
     * @param $values  An array mapping columns to values
9175
     * @param $ops     An array of the operators to use
9176
     * @param $orderby (optional) An array of column numbers or names (one based)
9177
     *                 mapped to sort direction (asc or desc or '' or null) to order by
9178
     * @return The SQL query
9179
     */
9180
    public function getSelectSQL($table, $show, $values, $ops, $orderby = [])
0 ignored issues
show
Coding Style introduced by
getSelectSQL uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
9181
    {
9182
        $this->fieldArrayClean($show);
9183
9184
        // If an empty array is passed in, then show all columns
9185
        if (sizeof($show) == 0) {
9186
            if ($this->hasObjectID($table)) {
9187
                $sql = "SELECT \"{$this->id}\", * FROM ";
9188
            } else {
9189
                $sql = 'SELECT * FROM ';
9190
            }
9191
        } else {
9192
            // Add oid column automatically to results for editing purposes
9193
            if (!in_array($this->id, $show) && $this->hasObjectID($table)) {
9194
                $sql = "SELECT \"{$this->id}\", \"";
9195
            } else {
9196
                $sql = 'SELECT "';
9197
            }
9198
9199
            $sql .= join('","', $show) . '" FROM ';
9200
        }
9201
9202
        $this->fieldClean($table);
9203
9204
        if (isset($_REQUEST['schema'])) {
9205
            $f_schema = $_REQUEST['schema'];
9206
            $this->fieldClean($f_schema);
9207
            $sql .= "\"{$f_schema}\".";
9208
        }
9209
        $sql .= "\"{$table}\"";
9210
9211
        // If we have values specified, add them to the WHERE clause
9212
        $first = true;
9213
        if (is_array($values) && sizeof($values) > 0) {
9214
            foreach ($values as $k => $v) {
9215
                if ($v != '' || $this->selectOps[$ops[$k]] == 'p') {
9216
                    $this->fieldClean($k);
9217
                    if ($first) {
9218
                        $sql .= ' WHERE ';
9219
                        $first = false;
9220
                    } else {
9221
                        $sql .= ' AND ';
9222
                    }
9223
                    // Different query format depending on operator type
9224
                    switch ($this->selectOps[$ops[$k]]) {
9225
                        case 'i':
9226
                            // Only clean the field for the inline case
9227
                            // this is because (x), subqueries need to
9228
                            // to allow 'a','b' as input.
9229
                            $this->clean($v);
9230
                            $sql .= "\"{$k}\" {$ops[$k]} '{$v}'";
9231
                            break;
9232
                        case 'p':
9233
                            $sql .= "\"{$k}\" {$ops[$k]}";
9234
                            break;
9235
                        case 'x':
9236
                            $sql .= "\"{$k}\" {$ops[$k]} ({$v})";
9237
                            break;
9238
                        case 't':
9239
                            $sql .= "\"{$k}\" {$ops[$k]}('{$v}')";
9240
                            break;
9241
                        default:
9242
                            // Shouldn't happen
9243
                    }
9244
                }
9245
            }
9246
        }
9247
9248
        // ORDER BY
9249
        if (is_array($orderby) && sizeof($orderby) > 0) {
9250
            $sql .= ' ORDER BY ';
9251
            $first = true;
9252
            foreach ($orderby as $k => $v) {
9253
                if ($first) {
9254
                    $first = false;
9255
                } else {
9256
                    $sql .= ', ';
9257
                }
9258
9259
                if (preg_match('/^[0-9]+$/', $k)) {
9260
                    $sql .= $k;
9261
                } else {
9262
                    $this->fieldClean($k);
9263
                    $sql .= '"' . $k . '"';
9264
                }
9265
                if (strtoupper($v) == 'DESC') {
9266
                    $sql .= ' DESC';
9267
                }
9268
            }
9269
        }
9270
9271
        return $sql;
9272
    }
9273
9274
    public function hasReadOnlyQueries()
9275
    {
9276
        return true;
9277
    }
9278
9279
    /**
9280
     * Finds the number of rows that would be returned by a
9281
     * query.
9282
     *
9283
     * @param $query The SQL query
9284
     * @param $count The count query
9285
     * @return The count of rows
9286
     * @return -1 error
9287
     */
9288
    public function browseQueryCount($query, $count)
9289
    {
9290
        return $this->selectField($count, 'total');
9291
    }
9292
9293
    /**
9294
     * Returns a recordset of all columns in a table
9295
     *
9296
     * @param $table The name of a table
9297
     * @param $key   The associative array holding the key to retrieve
9298
     * @return A recordset
9299
     */
9300
    public function browseRow($table, $key)
9301
    {
9302
        $f_schema = $this->_schema;
9303
        $this->fieldClean($f_schema);
9304
        $this->fieldClean($table);
9305
9306
        $sql = "SELECT * FROM \"{$f_schema}\".\"{$table}\"";
9307
        if (is_array($key) && sizeof($key) > 0) {
9308
            $sql .= ' WHERE true';
9309
            foreach ($key as $k => $v) {
9310
                $this->fieldClean($k);
9311
                $this->clean($v);
9312
                $sql .= " AND \"{$k}\"='{$v}'";
9313
            }
9314
        }
9315
9316
        return $this->selectSet($sql);
9317
    }
9318
9319
    /**
9320
     * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false
9321
     *
9322
     * @param $parameter the parameter
9323
     * @return \PHPPgAdmin\Database\the|string
9324
     */
9325
    public function dbBool(&$parameter)
9326
    {
9327
        if ($parameter) {
9328
            $parameter = 't';
9329
        } else {
9330
            $parameter = 'f';
9331
        }
9332
9333
        return $parameter;
9334
    }
9335
9336
    /**
9337
     * Fetches statistics for a database
9338
     *
9339
     * @param $database The database to fetch stats for
9340
     * @return A recordset
9341
     */
9342
    public function getStatsDatabase($database)
9343
    {
9344
        $this->clean($database);
9345
9346
        $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'";
9347
9348
        return $this->selectSet($sql);
9349
    }
9350
9351
    /**
9352
     * Fetches tuple statistics for a table
9353
     *
9354
     * @param $table The table to fetch stats for
9355
     * @return A recordset
9356
     */
9357 View Code Duplication
    public function getStatsTableTuples($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
9358
    {
9359
        $c_schema = $this->_schema;
9360
        $this->clean($c_schema);
9361
        $this->clean($table);
9362
9363
        $sql = "SELECT * FROM pg_stat_all_tables
9364
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
9365
9366
        return $this->selectSet($sql);
9367
    }
9368
9369
    /**
9370
     * Fetches I/0 statistics for a table
9371
     *
9372
     * @param $table The table to fetch stats for
9373
     * @return A recordset
9374
     */
9375 View Code Duplication
    public function getStatsTableIO($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
9376
    {
9377
        $c_schema = $this->_schema;
9378
        $this->clean($c_schema);
9379
        $this->clean($table);
9380
9381
        $sql = "SELECT * FROM pg_statio_all_tables
9382
			WHERE schemaname='{$c_schema}' AND relname='{$table}'";
9383
9384
        return $this->selectSet($sql);
9385
    }
9386
9387
    /**
9388
     * Fetches tuple statistics for all indexes on a table
9389
     *
9390
     * @param $table The table to fetch index stats for
9391
     * @return A recordset
9392
     */
9393 View Code Duplication
    public function getStatsIndexTuples($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
9394
    {
9395
        $c_schema = $this->_schema;
9396
        $this->clean($c_schema);
9397
        $this->clean($table);
9398
9399
        $sql = "SELECT * FROM pg_stat_all_indexes
9400
			WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname";
9401
9402
        return $this->selectSet($sql);
9403
    }
9404
9405
    /**
9406
     * Fetches I/0 statistics for all indexes on a table
9407
     *
9408
     * @param $table The table to fetch index stats for
9409
     * @return A recordset
9410
     */
9411 View Code Duplication
    public function getStatsIndexIO($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
9412
    {
9413
        $c_schema = $this->_schema;
9414
        $this->clean($c_schema);
9415
        $this->clean($table);
9416
9417
        $sql = "SELECT * FROM pg_statio_all_indexes
9418
			WHERE schemaname='{$c_schema}' AND relname='{$table}'
9419
			ORDER BY indexrelname";
9420
9421
        return $this->selectSet($sql);
9422
    }
9423
9424
    public function hasAggregateSortOp()
9425
    {
9426
        return true;
9427
    }
9428
9429
    public function hasAlterAggregate()
9430
    {
9431
        return true;
9432
    }
9433
9434
    public function hasAlterColumnType()
9435
    {
9436
        return true;
9437
    }
9438
9439
    public function hasAlterDatabaseOwner()
9440
    {
9441
        return true;
9442
    }
9443
9444
    public function hasAlterSchema()
9445
    {
9446
        return true;
9447
    }
9448
9449
    public function hasAlterSchemaOwner()
9450
    {
9451
        return true;
9452
    }
9453
9454
    public function hasAlterSequenceSchema()
9455
    {
9456
        return true;
9457
    }
9458
9459
    public function hasAlterSequenceStart()
9460
    {
9461
        return true;
9462
    }
9463
9464
    public function hasAlterTableSchema()
9465
    {
9466
        return true;
9467
    }
9468
9469
    public function hasAutovacuum()
9470
    {
9471
        return true;
9472
    }
9473
9474
    public function hasCreateTableLike()
9475
    {
9476
        return true;
9477
    }
9478
9479
    public function hasDisableTriggers()
9480
    {
9481
        return true;
9482
    }
9483
9484
    public function hasAlterDomains()
9485
    {
9486
        return true;
9487
    }
9488
9489
    public function hasEnumTypes()
9490
    {
9491
        return true;
9492
    }
9493
9494
    public function hasFTS()
9495
    {
9496
        return true;
9497
    }
9498
9499
    public function hasFunctionCosting()
9500
    {
9501
        return true;
9502
    }
9503
9504
    public function hasFunctionGUC()
9505
    {
9506
        return true;
9507
    }
9508
9509
    public function hasNamedParams()
9510
    {
9511
        return true;
9512
    }
9513
9514
    public function hasPrepare()
9515
    {
9516
        return true;
9517
    }
9518
9519
    public function hasPreparedXacts()
9520
    {
9521
        return true;
9522
    }
9523
9524
    public function hasRecluster()
9525
    {
9526
        return true;
9527
    }
9528
9529
    public function hasServerAdminFuncs()
9530
    {
9531
        return true;
9532
    }
9533
9534
    public function hasQueryCancel()
9535
    {
9536
        return true;
9537
    }
9538
9539
    public function hasUserRename()
9540
    {
9541
        return true;
9542
    }
9543
9544
    public function hasUserSignals()
9545
    {
9546
        return true;
9547
    }
9548
9549
    public function hasVirtualTransactionId()
9550
    {
9551
        return true;
9552
    }
9553
9554
    public function hasAlterDatabase()
9555
    {
9556
        return $this->hasAlterDatabaseRename();
9557
    }
9558
9559
    public function hasAlterDatabaseRename()
9560
    {
9561
        return true;
9562
    }
9563
9564
    public function hasDatabaseCollation()
9565
    {
9566
        return true;
9567
    }
9568
9569
    public function hasMagicTypes()
9570
    {
9571
        return true;
9572
    }
9573
9574
    public function hasQueryKill()
9575
    {
9576
        return true;
9577
    }
9578
9579
    public function hasConcurrentIndexBuild()
9580
    {
9581
        return true;
9582
    }
9583
9584
    public function hasForceReindex()
9585
    {
9586
        return false;
9587
    }
9588
9589
    public function hasByteaHexDefault()
9590
    {
9591
        return true;
9592
    }
9593
}
9594