TableTrait::_dumpPrivileges()   F
last analyzed

Complexity

Conditions 24
Paths 309

Size

Total Lines 107
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 24
eloc 57
c 2
b 0
f 0
nc 309
nop 3
dl 0
loc 107
rs 1.9708

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * PHPPgAdmin 6.1.3
5
 */
6
7
namespace PHPPgAdmin\Database\Traits;
8
9
/**
10
 * Common trait for tables manipulation.
11
 */
12
trait TableTrait
13
{
14
    use \PHPPgAdmin\Database\Traits\ColumnTrait;
15
    use \PHPPgAdmin\Database\Traits\RowTrait;
16
    use \PHPPgAdmin\Database\Traits\TriggerTrait;
17
18
    /**
19
     * Return all tables in current database excluding schemas 'pg_catalog', 'information_schema' and 'pg_toast'.
20
     *
21
     * @return int|\PHPPgAdmin\ADORecordSet
22
     */
23
    public function getAllTables()
24
    {
25
        $sql = "SELECT
26
                        schemaname AS nspname,
27
                        tablename AS relname,
28
                        tableowner AS relowner
29
                    FROM pg_catalog.pg_tables
30
                    WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
31
                    ORDER BY schemaname, tablename";
32
33
        return $this->selectSet($sql);
34
    }
35
36
    /**
37
     * Return all tables in current database (and schema).
38
     *
39
     * @return int|\PHPPgAdmin\ADORecordSet
40
     */
41
    public function getTables()
42
    {
43
        $c_schema = $this->_schema;
44
        $this->clean($c_schema);
45
46
        $sql = "
47
                SELECT c.relname,
48
                    pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
49
                    pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
50
                    reltuples::bigint as reltuples,
51
                    pt.spcname as tablespace, ";
52
53
        /*
54
         * Either display_sizes is true for tables and schemas,
55
         * or we must check if said config is an associative array
56
         */
57
        if ($this->conf['display_sizes']['tables']) {
58
            $sql .= ' pg_size_pretty(pg_total_relation_size(c.oid)) as table_size ';
59
        } else {
60
            $sql .= "   'N/A' as table_size ";
61
        }
62
63
        $sql .= " FROM pg_catalog.pg_class c
64
                LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
65
                LEFT JOIN  pg_catalog.pg_tablespace pt ON  pt.oid=c.reltablespace
66
                WHERE c.relkind = 'r'
67
                AND nspname='{$c_schema}'
68
                ORDER BY c.relname";
69
70
        return $this->selectSet($sql);
71
    }
72
73
    /**
74
     * Finds the names and schemas of parent tables (in order).
75
     *
76
     * @param string $table The table to find the parents for
77
     *
78
     * @return int|\PHPPgAdmin\ADORecordSet
79
     */
80
    public function getTableParents($table)
81
    {
82
        $c_schema = $this->_schema;
83
        $this->clean($c_schema);
84
        $this->clean($table);
85
86
        $sql = "
87
            SELECT
88
                pn.nspname, relname
89
            FROM
90
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
91
            WHERE
92
                pc.oid=pi.inhparent
93
                AND pc.relnamespace=pn.oid
94
                AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
95
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
96
            ORDER BY
97
                pi.inhseqno
98
        ";
99
100
        return $this->selectSet($sql);
101
    }
102
103
    /**
104
     * Finds the names and schemas of child tables.
105
     *
106
     * @param string $table The table to find the children for
107
     *
108
     * @return int|\PHPPgAdmin\ADORecordSet
109
     */
110
    public function getTableChildren($table)
111
    {
112
        $c_schema = $this->_schema;
113
        $this->clean($c_schema);
114
        $this->clean($table);
115
116
        $sql = "
117
            SELECT
118
                pn.nspname, relname
119
            FROM
120
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
121
            WHERE
122
                pc.oid=pi.inhrelid
123
                AND pc.relnamespace=pn.oid
124
                AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
125
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
126
        ";
127
128
        return $this->selectSet($sql);
129
    }
130
131
    /**
132
     * Returns the SQL definition for the table.
133
     * MUST be run within a transaction.
134
     *
135
     * @param string $table       The table to define
136
     * @param string $cleanprefix set to '-- ' to avoid issuing DROP statement
137
     */
138
    public function getTableDefPrefix($table, $cleanprefix = ''): ?string
139
    {
140
        // Fetch table
141
        $t = $this->getTable($table);
142
143
        if (!\is_object($t) || 1 !== $t->recordCount()) {
144
            $this->rollbackTransaction();
145
146
            return null;
147
        }
148
        $this->fieldClean($t->fields['relname']);
149
        $this->fieldClean($t->fields['nspname']);
150
151
        // Fetch attributes
152
        $atts = $this->getTableAttributes($table);
153
154
        if (!\is_object($atts)) {
155
            $this->rollbackTransaction();
156
157
            return null;
158
        }
159
160
        // Fetch constraints
161
        $cons = $this->getConstraints($table);
162
163
        if (!\is_object($cons)) {
164
            $this->rollbackTransaction();
165
166
            return null;
167
        }
168
169
        // Output a reconnect command to create the table as the correct user
170
        $sql = "-- PHPPgAdmin\n" . $this->getChangeUserSQL($t->fields['relowner']) . "\n\n";
171
172
        $sql = $this->_dumpCreate($t, $sql, $cleanprefix);
173
174
        // Output all table columns
175
        $col_comments_sql = ''; // Accumulate comments on columns
176
        $num = $atts->recordCount() + $cons->recordCount();
177
        $i = 1;
178
179
        $sql = $this->_dumpSerials($atts, $t, $sql, $col_comments_sql, $i, $num);
180
181
        $consOutput = $this->_dumpConstraints($cons, $table, $sql, $i, $num);
182
183
        if (null === $consOutput) {
184
            return null;
185
        }
186
        $sql = $consOutput;
187
188
        $sql .= ')';
189
190
        // Handle WITHOUT OIDS
191
        if ($this->hasObjectID($table)) {
192
            $sql .= ' WITH OIDS';
193
        } else {
194
            $sql .= ' WITHOUT OIDS';
195
        }
196
197
        $sql .= ";\n";
198
199
        $colStorage = $this->_dumpColStats($atts, $t, $sql);
200
201
        if (null === $colStorage) {
202
            return null;
203
        }
204
        $sql = $colStorage;
205
206
        // Comment
207
        if (null !== $t->fields['relcomment']) {
208
            $this->clean($t->fields['relcomment']);
209
            $sql .= "\n-- Comment\n\n";
210
            $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n";
211
        }
212
213
        // Add comments on columns, if any
214
        if ('' !== $col_comments_sql) {
215
            $sql .= $col_comments_sql;
216
        }
217
218
        // Privileges
219
        $privs = $this->getPrivileges($table, 'table');
0 ignored issues
show
Bug introduced by
It seems like getPrivileges() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

219
        /** @scrutinizer ignore-call */ 
220
        $privs = $this->getPrivileges($table, 'table');
Loading history...
220
221
        if (!\is_array($privs)) {
222
            $this->rollbackTransaction();
223
224
            return null;
225
        }
226
227
        $privsOutput = $this->_dumpPrivileges($privs, $t, $sql);
0 ignored issues
show
Bug introduced by
$privs of type array is incompatible with the type PHPPgAdmin\ADORecordSet expected by parameter $privs of PHPPgAdmin\Database\Trai...rait::_dumpPrivileges(). ( Ignorable by Annotation )

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

227
        $privsOutput = $this->_dumpPrivileges(/** @scrutinizer ignore-type */ $privs, $t, $sql);
Loading history...
228
229
        if (null === $privsOutput) {
230
            return null;
231
        }
232
        $sql = $privsOutput;
233
234
        // Add a newline to separate data that follows (if any)
235
        $sql .= "\n";
236
237
        return $sql;
238
    }
239
240
    /**
241
     * Returns table information.
242
     *
243
     * @param string $table The name of the table
244
     *
245
     * @return int|\PHPPgAdmin\ADORecordSet
246
     */
247
    public function getTable($table)
248
    {
249
        $c_schema = $this->_schema;
250
        $this->clean($c_schema);
251
        $this->clean($table);
252
253
        $sql = '
254
            SELECT
255
              c.relname, n.nspname, ';
256
257
        $sql .= ($this->hasRoles() ? ' coalesce(u.usename,r.rolname) ' : ' u.usename') . " AS relowner,
258
              pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
259
              pt.spcname  AS tablespace
260
            FROM pg_catalog.pg_class c
261
                LEFT JOIN pg_catalog.pg_tablespace pt ON pt.oid=c.reltablespace
262
                 LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
263
                 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace ";
264
265
        $sql .= ($this->hasRoles() ? ' LEFT JOIN pg_catalog.pg_roles r ON c.relowner = r.oid ' : '') .
266
            " WHERE c.relkind = 'r'
267
                  AND n.nspname = '{$c_schema}'
268
                  AND n.oid = c.relnamespace
269
                  AND c.relname = '{$table}'";
270
271
        return $this->selectSet($sql);
272
    }
273
274
    /**
275
     * Retrieve the attribute definition of a table.
276
     *
277
     * @param string $table The name of the table
278
     * @param string $field (optional) The name of a field to return
279
     *
280
     * @return \PHPPgAdmin\ADORecordSet All attributes in order
281
     */
282
    public function getTableAttributes($table, $field = '')
283
    {
284
        $c_schema = $this->_schema;
285
        $this->clean($c_schema);
286
        $this->clean($table);
287
288
        if ('' === $field) {
289
            // This query is made much more complex by the addition of the 'attisserial' field.
290
            // The subquery to get that field checks to see if there is an internally dependent
291
            // sequence on the field.
292
            return $this->_getTableAttributesAll($table, $c_schema);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_getTableA...sAll($table, $c_schema) also could return the type integer which is incompatible with the documented return type PHPPgAdmin\ADORecordSet.
Loading history...
293
        }
294
        $this->clean($field);
295
296
        return $this->_getTableAttribute($table, $c_schema, $field);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_getTableA...ble, $c_schema, $field) also could return the type integer which is incompatible with the documented return type PHPPgAdmin\ADORecordSet.
Loading history...
297
    }
298
299
    /**
300
     * Returns a list of all constraints on a table.
301
     *
302
     * @param string $table The table to find rules for
303
     *
304
     * @return int|\PHPPgAdmin\ADORecordSet
305
     */
306
    public function getConstraints($table)
307
    {
308
        $c_schema = $this->_schema;
309
        $this->clean($c_schema);
310
        $this->clean($table);
311
312
        // This SQL is greatly complicated by the need to retrieve
313
        // index clustering information for primary and unique constraints
314
        $sql = "SELECT
315
                pc.conname,
316
                pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc,
317
                pc.contype,
318
                CASE WHEN pc.contype='u' OR pc.contype='p' THEN (
319
                    SELECT
320
                        indisclustered
321
                    FROM
322
                        pg_catalog.pg_depend pd,
323
                        pg_catalog.pg_class pl,
324
                        pg_catalog.pg_index pi
325
                    WHERE
326
                        pd.refclassid=pc.tableoid
327
                        AND pd.refobjid=pc.oid
328
                        AND pd.objid=pl.oid
329
                        AND pl.oid=pi.indexrelid
330
                ) ELSE
331
                    NULL
332
                END AS indisclustered
333
            FROM
334
                pg_catalog.pg_constraint pc
335
            WHERE
336
                pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
337
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
338
                    WHERE nspname='{$c_schema}'))
339
            ORDER BY
340
                1
341
        ";
342
343
        return $this->selectSet($sql);
344
    }
345
346
    /**
347
     * Checks to see whether or not a table has a unique id column.
348
     *
349
     * @param string $table The table name
350
     *
351
     * @return bool true if it has a unique id, false otherwise
352
     */
353
    public function hasObjectID($table)
354
    {
355
        $c_schema = $this->_schema;
356
        $this->clean($c_schema);
357
        $this->clean($table);
358
359
        $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}'
360
            AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')";
361
362
        $rs = $this->selectSet($sql);
363
364
        if (1 !== $rs->recordCount()) {
365
            return false;
366
        }
367
368
        $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']);
369
370
        return $rs->fields['relhasoids'];
371
    }
372
373
    /**
374
     * Returns extra table definition information that is most usefully
375
     * dumped after the table contents for speed and efficiency reasons.
376
     *
377
     * @param string $table The table to define
378
     */
379
    public function getTableDefSuffix($table): ?string
380
    {
381
        $sql = '';
382
383
        // Indexes
384
        $indexes = $this->getIndexes($table);
385
386
        if (!\is_object($indexes)) {
387
            $this->rollbackTransaction();
388
389
            return null;
390
        }
391
392
        if (0 < $indexes->recordCount()) {
393
            $sql .= "\n-- Indexes\n\n";
394
395
            while (!$indexes->EOF) {
396
                $sql .= $indexes->fields['inddef'] . ";\n";
397
398
                $indexes->moveNext();
399
            }
400
        }
401
402
        // Triggers
403
        $triggers = $this->getTriggers($table);
404
405
        if (!\is_object($triggers)) {
406
            $this->rollbackTransaction();
407
408
            return null;
409
        }
410
411
        if (0 < $triggers->recordCount()) {
412
            $sql .= "\n-- Triggers\n\n";
413
414
            while (!$triggers->EOF) {
415
                $sql .= $triggers->fields['tgdef'];
416
                $sql .= ";\n";
417
418
                $triggers->moveNext();
419
            }
420
        }
421
422
        // Rules
423
        $rules = $this->getRules($table);
424
425
        if (!\is_object($rules)) {
426
            $this->rollbackTransaction();
427
428
            return null;
429
        }
430
431
        if (0 < $rules->recordCount()) {
432
            $sql .= "\n-- Rules\n\n";
433
434
            while (!$rules->EOF) {
435
                $sql .= $rules->fields['definition'] . "\n";
436
437
                $rules->moveNext();
438
            }
439
        }
440
441
        return $sql;
442
    }
443
444
    /**
445
     * Grabs a list of indexes for a table.
446
     *
447
     * @param string $table  The name of a table whose indexes to retrieve
448
     * @param bool   $unique Only get unique/pk indexes
449
     *
450
     * @return int|\PHPPgAdmin\ADORecordSet
451
     */
452
    public function getIndexes($table = '', $unique = false)
453
    {
454
        $this->clean($table);
455
456
        $sql = "
457
            SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
458
                pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef
459
            FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
460
            WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid)
461
                AND c.oid = i.indrelid AND i.indexrelid = c2.oid
462
        ";
463
464
        if ($unique) {
465
            $sql .= ' AND i.indisunique ';
466
        }
467
468
        $sql .= ' ORDER BY c2.relname';
469
470
        return $this->selectSet($sql);
471
    }
472
473
    /**
474
     * Grabs a list of triggers on a table.
475
     *
476
     * @param string $table The name of a table whose triggers to retrieve
477
     *
478
     * @return int|\PHPPgAdmin\ADORecordSet
479
     */
480
    public function getTriggers($table = '')
481
    {
482
        $c_schema = $this->_schema;
483
        $this->clean($c_schema);
484
        $this->clean($table);
485
486
        $sql = "SELECT
487
                t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef,
488
                CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid,
489
                p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
490
                ns.nspname AS pronamespace
491
            FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns
492
            WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
493
                AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
494
                AND ( tgconstraint = 0 OR NOT EXISTS
495
                        (SELECT 1 FROM pg_catalog.pg_depend d    JOIN pg_catalog.pg_constraint c
496
                            ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
497
                        WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))
498
                AND p.oid=t.tgfoid
499
                AND p.pronamespace = ns.oid";
500
501
        return $this->selectSet($sql);
502
    }
503
504
    /**
505
     * Returns a list of all rules on a table OR view.
506
     *
507
     * @param string $table The table to find rules for
508
     *
509
     * @return int|\PHPPgAdmin\ADORecordSet
510
     */
511
    public function getRules($table)
512
    {
513
        $c_schema = $this->_schema;
514
        $this->clean($c_schema);
515
        $this->clean($table);
516
517
        $sql = "
518
            SELECT *
519
            FROM pg_catalog.pg_rules
520
            WHERE
521
                schemaname='{$c_schema}' AND tablename='{$table}'
522
            ORDER BY rulename
523
        ";
524
525
        return $this->selectSet($sql);
526
    }
527
528
    /**
529
     * Creates a new table in the database.
530
     *
531
     * @param string $name        The name of the table
532
     * @param int    $fields      The number of fields
533
     * @param array  $field       An array of field names
534
     * @param array  $type        An array of field types
535
     * @param array  $array       An array of '' or '[]' for each type if it's an array or not
536
     * @param array  $length      An array of field lengths
537
     * @param array  $notnull     An array of not null
538
     * @param array  $default     An array of default values
539
     * @param bool   $withoutoids True if WITHOUT OIDS, false otherwise
540
     * @param array  $colcomment  An array of comments
541
     * @param string $tblcomment  the comment for the table
542
     * @param string $tablespace  The tablespace name ('' means none/default)
543
     * @param array  $uniquekey   An Array indicating the fields that are unique (those indexes that are set)
544
     * @param array  $primarykey  An Array indicating the field used for the primarykey (those indexes that are set)
545
     *
546
     * @return bool|int 0 success
547
     */
548
    public function createTable(
549
        $name,
550
        $fields,
551
        $field,
552
        $type,
553
        $array,
554
        $length,
555
        $notnull,
556
        $default,
557
        $withoutoids,
558
        $colcomment,
559
        $tblcomment,
560
        $tablespace,
561
        $uniquekey,
562
        $primarykey
563
    ) {
564
        $f_schema = $this->_schema;
565
        $this->fieldClean($f_schema);
566
        $this->fieldClean($name);
567
568
        $status = $this->beginTransaction();
569
570
        if (0 !== $status) {
571
            return -1;
572
        }
573
574
        $found = false;
575
        $first = true;
576
        $comment_sql = ''; //Accumulate comments for the columns
577
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (";
578
579
        for ($i = 0; $i < $fields; ++$i) {
580
            $this->fieldClean($field[$i]);
581
            $this->clean($type[$i]);
582
            $this->clean($length[$i]);
583
            $this->clean($colcomment[$i]);
584
585
            // Skip blank columns - for user convenience
586
            if ('' === $field[$i] || '' === $type[$i]) {
587
                continue;
588
            }
589
590
            // If not the first column, add a comma
591
            if (!$first) {
592
                $sql .= ', ';
593
            } else {
594
                $first = false;
595
            }
596
597
            switch ($type[$i]) {
598
                    // Have to account for weird placing of length for with/without
599
                    // time zone types
600
                case 'timestamp with time zone':
601
                case 'timestamp without time zone':
602
                    $qual = \mb_substr($type[$i], 9);
603
                    $sql .= "\"{$field[$i]}\" timestamp";
604
605
                    if ('' !== $length[$i] ?? '') {
606
                        $sql .= "({$length[$i]})";
607
                    }
608
609
                    $sql .= $qual;
610
611
                    break;
612
                case 'time with time zone':
613
                case 'time without time zone':
614
                    $qual = \mb_substr($type[$i], 4);
615
                    $sql .= "\"{$field[$i]}\" time";
616
617
                    if ('' !== $length[$i]) {
618
                        $sql .= "({$length[$i]})";
619
                    }
620
621
                    $sql .= $qual;
622
623
                    break;
624
625
                default:
626
                    $length[$i] = $length[$i] ?? null;
627
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
628
629
                    if ('' !== $length[$i] && null !== $length[$i]) {
630
                        $sql .= "({$length[$i]})";
631
                    }
632
            }
633
            // Add array qualifier if necessary
634
            if ('[]' === $array[$i]) {
635
                $sql .= '[]';
636
            }
637
638
            // Add other qualifiers
639
            if (!isset($primarykey[$i])) {
640
                if (isset($uniquekey[$i])) {
641
                    $sql .= ' UNIQUE';
642
                }
643
644
                if (isset($notnull[$i])) {
645
                    $sql .= ' NOT NULL';
646
                }
647
            }
648
649
            if ('' !== $default[$i]) {
650
                $sql .= " DEFAULT {$default[$i]}";
651
            }
652
653
            if ('' !== $colcomment[$i]) {
654
                $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
655
            }
656
657
            $found = true;
658
        }
659
660
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
661
            return -1;
662
        }
663
664
        // PRIMARY KEY
665
        $primarykeycolumns = [];
666
667
        for ($i = 0; $i < $fields; ++$i) {
668
            if (isset($primarykey[$i])) {
669
                $primarykeycolumns[] = "\"{$field[$i]}\"";
670
            }
671
        }
672
673
        if (0 < \count($primarykeycolumns)) {
674
            $sql .= ', PRIMARY KEY (' . \implode(', ', $primarykeycolumns) . ')';
675
        }
676
677
        $sql .= ')';
678
679
        // WITHOUT OIDS
680
        if ($withoutoids) {
681
            $sql .= ' WITHOUT OIDS';
682
        } else {
683
            $sql .= ' WITH OIDS';
684
        }
685
686
        // Tablespace
687
        if ($this->hasTablespaces() && '' !== $tablespace) {
688
            $this->fieldClean($tablespace);
689
            $sql .= " TABLESPACE \"{$tablespace}\"";
690
        }
691
692
        $status = $this->execute($sql);
693
694
        if ($status) {
695
            $this->rollbackTransaction();
696
697
            return -1;
698
        }
699
700
        if ('' !== $tblcomment) {
701
            $status = $this->setComment('TABLE', '', $name, $tblcomment, true);
702
703
            if ($status) {
704
                $this->rollbackTransaction();
705
706
                return -1;
707
            }
708
        }
709
710
        if ('' !== $comment_sql) {
711
            $status = $this->execute($comment_sql);
712
713
            if ($status) {
714
                $this->rollbackTransaction();
715
716
                return -1;
717
            }
718
        }
719
720
        return $this->endTransaction();
721
    }
722
723
    /**
724
     * Creates a new table in the database copying attribs and other properties from another table.
725
     *
726
     * @param string $name        The name of the table
727
     * @param array  $like        an array giving the schema ans the name of the table from which attribs are copying
728
     *                            from: array(
729
     *                            'table' => table name,
730
     *                            'schema' => the schema name,
731
     *                            )
732
     * @param bool   $defaults    if true, copy the defaults values as well
733
     * @param bool   $constraints if true, copy the constraints as well (CHECK on table & attr)
734
     * @param bool   $idx
735
     * @param string $tablespace  The tablespace name ('' means none/default)
736
     *
737
     * @return bool|int
738
     */
739
    public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '')
740
    {
741
        $f_schema = $this->_schema;
742
        $this->fieldClean($f_schema);
743
        $this->fieldClean($name);
744
        $this->fieldClean($like['schema']);
745
        $this->fieldClean($like['table']);
746
        $like = "\"{$like['schema']}\".\"{$like['table']}\"";
747
748
        $status = $this->beginTransaction();
749
750
        if (0 !== $status) {
751
            return -1;
752
        }
753
754
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}";
755
756
        if ($defaults) {
757
            $sql .= ' INCLUDING DEFAULTS';
758
        }
759
760
        if ($this->hasCreateTableLikeWithConstraints() && $constraints) {
761
            $sql .= ' INCLUDING CONSTRAINTS';
762
        }
763
764
        if ($this->hasCreateTableLikeWithIndexes() && $idx) {
765
            $sql .= ' INCLUDING INDEXES';
766
        }
767
768
        $sql .= ')';
769
770
        if ($this->hasTablespaces() && '' !== $tablespace) {
771
            $this->fieldClean($tablespace);
772
            $sql .= " TABLESPACE \"{$tablespace}\"";
773
        }
774
775
        $status = $this->execute($sql);
776
777
        if ($status) {
778
            $this->rollbackTransaction();
779
780
            return -1;
781
        }
782
783
        return $this->endTransaction();
784
    }
785
786
    /**
787
     * Alter table properties.
788
     *
789
     * @param string $table      The name of the table
790
     * @param string $name       The new name for the table
791
     * @param string $owner      The new owner for the table
792
     * @param string $schema     The new schema for the table
793
     * @param string $comment    The comment on the table
794
     * @param string $tablespace The new tablespace for the table ('' means leave as is)
795
     * @param bool   $with_oids  If set to FALSE, will drop oids column
796
     *
797
     * @return bool|int 0 success
798
     */
799
    public function alterTable($table, $name, $owner, $schema, $comment, $tablespace, bool $with_oids = true)
800
    {
801
        $tblrs = $this->getTable($table);
802
803
        if (1 !== $tblrs->recordCount()) {
804
            return -2;
805
        }
806
807
        $status = $this->beginTransaction();
808
        //dump(['beginTransaction' => $status]);
809
810
        if (0 !== $status) {
811
            $this->rollbackTransaction();
812
813
            return -1;
814
        }
815
        $sql_sentence = "ALTER TABLE \"{$this->_schema}\".\"{$tblrs->fields['relname']}\" ";
816
        $status = $this->_alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace, !$with_oids, $sql_sentence);
0 ignored issues
show
Unused Code introduced by
The call to PHPPgAdmin\Database\Trai...bleTrait::_alterTable() has too many arguments starting with $sql_sentence. ( Ignorable by Annotation )

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

816
        /** @scrutinizer ignore-call */ 
817
        $status = $this->_alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace, !$with_oids, $sql_sentence);

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

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

Loading history...
Bug introduced by
It seems like $tblrs can also be of type integer; however, parameter $tblrs of PHPPgAdmin\Database\Trai...bleTrait::_alterTable() does only seem to accept PHPPgAdmin\ADORecordSet, maybe add an additional type check? ( Ignorable by Annotation )

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

816
        $status = $this->_alterTable(/** @scrutinizer ignore-type */ $tblrs, $name, $owner, $schema, $comment, $tablespace, !$with_oids, $sql_sentence);
Loading history...
817
        //dump(['_alterTable' => [$status, $sql_sentence.$this->getLastExecutedSQL()]]);
818
819
        if (0 !== $status) {
820
            $this->rollbackTransaction();
821
822
            return $status;
823
        }
824
825
        return $this->endTransaction();
826
    }
827
828
    /**
829
     * Enables or disables the oid system column to a table a table's owner
830
     * /!\ this function is called from _alterTable which take care of escaping fields.
831
     *
832
     * @param \PHPPgAdmin\ADORecordSet $tblrs       The table RecordSet returned by getTable()
833
     * @param null|string              $owner
834
     * @param bool                     $withoutoids
835
     *
836
     * @return array{0:int,1:string} [status:0 if successful, change_sql: changed attribute]
837
     */
838
    public function alterTableOids($tblrs, bool $withoutoids = false): array
839
    {
840
        $status = 0;
841
        $change_sql = '';
842
        // no changes. Return 0
843
        if ((bool) ($this->hasObjectID($tblrs->fields['relname'])) !== !$withoutoids) {
844
            /* vars cleaned in _alterTable */
845
            $f_schema = $this->_schema;
846
            $this->fieldClean($f_schema);
847
848
            $alter_sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" ";
849
            $change_sql = '   SET ';
850
851
            if ($withoutoids) {
852
                $change_sql .= ' WITHOUT OIDS';
853
            } else {
854
                $change_sql .= ' WITH OIDS';
855
            }
856
            $sql = \implode(' ', [$alter_sql, $change_sql]);
857
858
            $status = $this->execute($sql);
859
        }
860
861
        return [$status, $change_sql];
862
    }
863
864
    /**
865
     * Alter a table's owner
866
     * /!\ this function is called from _alterTable which take care of escaping fields.
867
     *
868
     * @param \PHPPgAdmin\ADORecordSet $tblrs The table RecordSet returned by getTable()
869
     * @param null|string              $owner
870
     *
871
     * @return array{0:int,1:string} [status:0 if successful, change_sql: changed attribute]
872
     */
873
    public function alterTableOwner($tblrs, $owner = null): array
874
    {
875
        $status = 0;
876
        $change_sql = '';
877
        /* vars cleaned in _alterTable */
878
        if (!empty($owner) && ($tblrs->fields['relowner'] !== $owner)) {
879
            $f_schema = $this->_schema;
880
            $this->fieldClean($f_schema);
881
            // If owner has been changed, then do the alteration.  We are
882
            // careful to avoid this generally as changing owner is a
883
            // superuser only function.
884
            $alter_sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" ";
885
            $change_sql = " OWNER TO \"{$owner}\"";
886
            $sql = \implode(' ', [$alter_sql, $change_sql]);
887
888
            $status = $this->execute($sql);
889
        }
890
891
        return [$status, $change_sql];
892
    }
893
894
    /**
895
     * Alter a table's tablespace
896
     * /!\ this function is called from _alterTable which take care of escaping fields.
897
     *
898
     * @param \PHPPgAdmin\ADORecordSet $tblrs      The table RecordSet returned by getTable()
899
     * @param null|string              $tablespace
900
     *
901
     * @return array{0:int,1:string} [status:0 if successful, change_sql: changed attribute]
902
     */
903
    public function alterTableTablespace($tblrs, $tablespace = null): array
904
    {
905
        $status = 0;
906
        $change_sql = '';
907
        /* vars cleaned in _alterTable */
908
        if (!empty($tablespace) && ($tblrs->fields['tablespace'] !== $tablespace)) {
909
            $f_schema = $this->_schema;
910
            $this->fieldClean($f_schema);
911
912
            // If tablespace has been changed, then do the alteration.  We
913
            // don't want to do this unnecessarily.
914
            $alter_sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" ";
915
            $change_sql = " SET TABLESPACE \"{$tablespace}\"";
916
            $sql = \implode(' ', [$alter_sql, $change_sql]);
917
918
            $status = $this->execute($sql);
919
        }
920
921
        return [$status, $change_sql];
922
    }
923
924
    /**
925
     * Alter a table's name
926
     * /!\ this function is called from _alterTable which take care of escaping fields.
927
     *
928
     * @param \PHPPgAdmin\ADORecordSet $tblrs The table RecordSet returned by getTable()
929
     * @param string                   $name  The new table's name
930
     *
931
     *  @return array{0:int,1:string} [status:0 if successful, change_sql: changed attribute]
932
     */
933
    public function alterTableName($tblrs, $name = null): array
934
    {
935
        $status = 0;
936
        $change_sql = '';
937
        /* vars cleaned in _alterTable */
938
        // Rename (only if name has changed)
939
        if (!empty($name) && ($name !== $tblrs->fields['relname'])) {
940
            $f_schema = $this->_schema;
941
            $this->fieldClean($f_schema);
942
943
            $alter_sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" ";
944
            $change_sql = " RENAME TO \"{$name}\"";
945
            $sql = \implode(' ', [$alter_sql, $change_sql]);
946
            $status = $this->execute($sql);
947
948
            if (0 === $status) {
949
                $tblrs->fields['relname'] = $name;
950
            }
951
        }
952
953
        return [$status, $change_sql];
954
    }
955
956
    // Row functions
957
958
    /**
959
     * Alter a table's schema
960
     * /!\ this function is called from _alterTable which take care of escaping fields.
961
     *
962
     * @param \PHPPgAdmin\ADORecordSet $tblrs  The table RecordSet returned by getTable()
963
     * @param null|string              $schema
964
     *
965
     *  @return array{0:int,1:string} [status:0 if successful, change_sql: changed attribute]
966
     */
967
    public function alterTableSchema($tblrs, $schema = null): array
968
    {
969
        $status = 0;
970
        $change_sql = '';
971
        /* vars cleaned in _alterTable */
972
        if (!empty($schema) && ($tblrs->fields['nspname'] !== $schema)) {
973
            $f_schema = $this->_schema;
974
            $this->fieldClean($f_schema);
975
            // If tablespace has been changed, then do the alteration.  We
976
            // don't want to do this unnecessarily.
977
            $alter_sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" ";
978
            $change_sql = " SET SCHEMA \"{$schema}\"";
979
            $sql = \implode(' ', [$alter_sql, $change_sql]);
980
981
            $status = $this->execute($sql);
982
        }
983
984
        return [$status, $change_sql];
985
    }
986
987
    /**
988
     * Empties a table in the database.
989
     *
990
     * @param string $table   The table to be emptied
991
     * @param bool   $cascade True to cascade truncate, false to restrict
992
     *
993
     * @return array<integer,mixed|string> 0 if operation was successful
994
     */
995
    public function emptyTable($table, $cascade)
996
    {
997
        $f_schema = $this->_schema;
998
        $this->fieldClean($f_schema);
999
        $this->fieldClean($table);
1000
1001
        $sql = "TRUNCATE TABLE \"{$f_schema}\".\"{$table}\" ";
1002
1003
        if ($cascade) {
1004
            $sql = $sql . ' CASCADE';
1005
        }
1006
1007
        $status = $this->execute($sql);
1008
1009
        return [$status, $sql];
1010
    }
1011
1012
    /**
1013
     * Removes a table from the database.
1014
     *
1015
     * @param string $table   The table to drop
1016
     * @param bool   $cascade True to cascade drop, false to restrict
1017
     *
1018
     * @return int|\PHPPgAdmin\ADORecordSet
1019
     */
1020
    public function dropTable($table, $cascade)
1021
    {
1022
        $f_schema = $this->_schema;
1023
        $this->fieldClean($f_schema);
1024
        $this->fieldClean($table);
1025
1026
        $sql = "DROP TABLE \"{$f_schema}\".\"{$table}\"";
1027
1028
        if ($cascade) {
1029
            $sql .= ' CASCADE';
1030
        }
1031
1032
        return $this->execute($sql);
1033
    }
1034
1035
    /**
1036
     * Sets up the data object for a dump.  eg. Starts the appropriate
1037
     * transaction, sets variables, etc.
1038
     *
1039
     * @return int 0 success
1040
     */
1041
    public function beginDump()
1042
    {
1043
        // Begin serializable transaction (to dump consistent data)
1044
        $status = $this->beginTransaction();
1045
1046
        if (0 !== $status) {
1047
            return -1;
1048
        }
1049
1050
        // Set serializable
1051
        $sql = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE';
1052
        $status = $this->execute($sql);
1053
1054
        if (0 !== $status) {
1055
            $this->rollbackTransaction();
1056
1057
            return -1;
1058
        }
1059
1060
        // Set datestyle to ISO
1061
        $sql = 'SET DATESTYLE = ISO';
1062
        $status = $this->execute($sql);
1063
1064
        if (0 !== $status) {
1065
            $this->rollbackTransaction();
1066
1067
            return -1;
1068
        }
1069
1070
        // Set extra_float_digits to 2
1071
        $sql = 'SET extra_float_digits TO 2';
1072
        $status = $this->execute($sql);
1073
1074
        if (0 !== $status) {
1075
            $this->rollbackTransaction();
1076
1077
            return -1;
1078
        }
1079
1080
        return 0;
1081
    }
1082
1083
    /**
1084
     * Ends the data object for a dump.
1085
     *
1086
     * @return bool 0 success
1087
     */
1088
    public function endDump()
1089
    {
1090
        return $this->endTransaction();
1091
    }
1092
1093
    /**
1094
     * Returns a recordset of all columns in a relation.  Used for data export.
1095
     *
1096
     * @@ Note: Really needs to use a cursor
1097
     *
1098
     * @param string $relation The name of a relation
1099
     * @param bool   $oids     true to dump also the oids
1100
     *
1101
     * @return int|\PHPPgAdmin\ADORecordSet
1102
     */
1103
    public function dumpRelation($relation, $oids)
1104
    {
1105
        $this->fieldClean($relation);
1106
1107
        // Actually retrieve the rows
1108
        if ($oids) {
1109
            $oid_str = $this->id . ', ';
1110
        } else {
1111
            $oid_str = '';
1112
        }
1113
1114
        return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\"");
1115
    }
1116
1117
    /**
1118
     * Returns all available autovacuum per table information.
1119
     *
1120
     * @param string $table if given, return autovacuum info for the given table or return all informations for all table
1121
     *
1122
     * @return \PHPPgAdmin\ArrayRecordSet A recordset
1123
     */
1124
    public function getTableAutovacuum($table = '')
1125
    {
1126
        $sql = '';
1127
1128
        if ('' !== $table) {
1129
            $this->clean($table);
1130
            $c_schema = $this->_schema;
1131
            $this->clean($c_schema);
1132
1133
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1134
                FROM pg_class c
1135
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1136
                WHERE c.relkind = 'r'::\"char\"
1137
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1138
                    AND c.reloptions IS NOT NULL
1139
                    AND c.relname = '{$table}' AND n.nspname = '{$c_schema}'
1140
                ORDER BY nspname, relname";
1141
        } else {
1142
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1143
                FROM pg_class c
1144
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1145
                WHERE c.relkind = 'r'::\"char\"
1146
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1147
                    AND c.reloptions IS NOT NULL
1148
                ORDER BY nspname, relname";
1149
        }
1150
1151
        /* tmp var to parse the results */
1152
        $_autovacs = $this->selectSet($sql);
1153
1154
        /* result aray to return as RS */
1155
        $autovacs = [];
1156
1157
        while (!$_autovacs->EOF) {
1158
            $_ = [
1159
                'nspname' => $_autovacs->fields['nspname'],
1160
                'relname' => $_autovacs->fields['relname'],
1161
            ];
1162
1163
            foreach (\explode(',', $_autovacs->fields['reloptions']) as $var) {
1164
                [$o, $v] = \explode('=', $var);
1165
                $_[$o] = $v;
1166
            }
1167
1168
            $autovacs[] = $_;
1169
1170
            $_autovacs->moveNext();
1171
        }
1172
1173
        return new \PHPPgAdmin\ArrayRecordSet($autovacs);
1174
    }
1175
1176
    /**
1177
     * Returns the SQL for changing the current user.
1178
     *
1179
     * @param string $user The user to change to
1180
     *
1181
     * @return string The SQL
1182
     */
1183
    public function getChangeUserSQL($user)
1184
    {
1185
        $this->clean($user);
1186
1187
        return "SET SESSION AUTHORIZATION '{$user}';";
1188
    }
1189
1190
    /**
1191
     * Returns all available autovacuum per table information.
1192
     *
1193
     * @param string $table          table name
1194
     * @param bool   $vacenabled     true if vacuum is enabled
1195
     * @param int    $vacthreshold   vacuum threshold
1196
     * @param int    $vacscalefactor vacuum scalefactor
1197
     * @param int    $anathresold    analyze threshold
1198
     * @param int    $anascalefactor analyze scale factor
1199
     * @param int    $vaccostdelay   vacuum cost delay
1200
     * @param int    $vaccostlimit   vacuum cost limit
1201
     *
1202
     * @return int|\PHPPgAdmin\ADORecordSet
1203
     */
1204
    public function saveAutovacuum(
1205
        $table,
1206
        $vacenabled,
1207
        $vacthreshold,
1208
        $vacscalefactor,
1209
        $anathresold,
1210
        $anascalefactor,
1211
        $vaccostdelay,
1212
        $vaccostlimit
1213
    ) {
1214
        $f_schema = $this->_schema;
1215
        $this->fieldClean($f_schema);
1216
        $this->fieldClean($table);
1217
1218
        $params = [];
1219
1220
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
1221
1222
        if (!empty($vacenabled)) {
1223
            $this->clean($vacenabled);
1224
            $params[] = "autovacuum_enabled='{$vacenabled}'";
1225
        }
1226
1227
        if (!empty($vacthreshold)) {
1228
            $this->clean($vacthreshold);
1229
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
1230
        }
1231
1232
        if (!empty($vacscalefactor)) {
1233
            $this->clean($vacscalefactor);
1234
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
1235
        }
1236
1237
        if (!empty($anathresold)) {
1238
            $this->clean($anathresold);
1239
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
1240
        }
1241
1242
        if (!empty($anascalefactor)) {
1243
            $this->clean($anascalefactor);
1244
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
1245
        }
1246
1247
        if (!empty($vaccostdelay)) {
1248
            $this->clean($vaccostdelay);
1249
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
1250
        }
1251
1252
        if (!empty($vaccostlimit)) {
1253
            $this->clean($vaccostlimit);
1254
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
1255
        }
1256
1257
        $sql = $sql . \implode(',', $params) . ');';
1258
1259
        return $this->execute($sql);
1260
    }
1261
1262
    // Type conversion routines
1263
1264
    /**
1265
     * Drops autovacuum config for a table.
1266
     *
1267
     * @param string $table The table
1268
     *
1269
     * @return int|\PHPPgAdmin\ADORecordSet
1270
     */
1271
    public function dropAutovacuum($table)
1272
    {
1273
        $f_schema = $this->_schema;
1274
        $this->fieldClean($f_schema);
1275
        $this->fieldClean($table);
1276
1277
        return $this->execute(
1278
            "
1279
            ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
1280
                autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
1281
                autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
1282
            );"
1283
        );
1284
    }
1285
1286
    abstract public function formatType($typname, $typmod);
1287
1288
    abstract public function hasGrantOption();
1289
1290
    abstract public function hasRoles();
1291
1292
    abstract public function fieldClean(&$str);
1293
1294
    abstract public function beginTransaction();
1295
1296
    abstract public function rollbackTransaction();
1297
1298
    abstract public function endTransaction();
1299
1300
    abstract public function execute($sql);
1301
1302
    abstract public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null);
1303
1304
    abstract public function selectSet($sql);
1305
1306
    abstract public function clean(&$str);
1307
1308
    abstract public function phpBool($parameter);
1309
1310
    abstract public function hasCreateTableLikeWithConstraints();
1311
1312
    abstract public function hasCreateTableLikeWithIndexes();
1313
1314
    abstract public function hasTablespaces();
1315
1316
    abstract public function delete($table, $conditions, $schema = '');
1317
1318
    abstract public function fieldArrayClean(&$arr);
1319
1320
    abstract public function hasCreateFieldWithConstraints();
1321
1322
    abstract public function getAttributeNames($table, $atts);
1323
1324
    /**
1325
     * Protected method which alter a table
1326
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION.
1327
     *
1328
     * @param \PHPPgAdmin\ADORecordSet $tblrs       The table recordSet returned by getTable()
1329
     * @param string                   $name        The new name for the table
1330
     * @param string                   $owner       The new owner for the table
1331
     * @param string                   $schema      The new schema for the table
1332
     * @param string                   $comment     The comment on the table
1333
     * @param string                   $tablespace  The new tablespace for the table ('' means leave as is)
1334
     * @param bool                     $withoutoids If set to TRUE, will drop oids column
1335
     *
1336
     * @return int 0 success
1337
     */
1338
    protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace, bool $withoutoids = false)
1339
    {
1340
        $this->fieldArrayClean($tblrs->fields);
1341
1342
        // Comment
1343
        $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment);
1344
        //dump(['setComment' => $this->getLastExecutedSQL()]);
1345
1346
        $this->lastExecutedSql = $this->getLastExecutedSQL();
0 ignored issues
show
Bug introduced by
It seems like getLastExecutedSQL() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

1346
        /** @scrutinizer ignore-call */ 
1347
        $this->lastExecutedSql = $this->getLastExecutedSQL();
Loading history...
1347
1348
        if (0 !== $status) {
1349
            return -4;
1350
        }
1351
1352
        // Owner
1353
        $this->fieldClean($owner);
1354
        [$TableOwnerStatus,$TableOwnerSQL] = $this->alterTableOwner($tblrs, $owner);
1355
        $this->lastExecutedSql .= $TableOwnerSQL ? \sprintf('%s%s', \PHP_EOL, $TableOwnerSQL) : '';
1356
        //dump(['alterTableOwner' => [$TableOwnerStatus,$TableOwnerSQL]]);
1357
1358
        if (0 !== $TableOwnerStatus) {
1359
            return -5;
1360
        }
1361
1362
        // Tablespace
1363
        $this->fieldClean($tablespace);
1364
        [$TableTablespaceStatus,$TableTablespaceSQL] = $this->alterTableTablespace($tblrs, $tablespace);
1365
        $this->lastExecutedSql .= $TableTablespaceSQL ? \sprintf('%s%s', \PHP_EOL, $TableTablespaceSQL) : '';
1366
        //dump(['alterTableTablespace' => [$TableTablespaceStatus,$TableTablespaceSQL]]);
1367
1368
        if (0 !== $TableTablespaceStatus) {
1369
            return -6;
1370
        }
1371
1372
        // Rename
1373
        $this->fieldClean($name);
1374
        [$TableNameStatus,$TableNameSQL] = $this->alterTableName($tblrs, $name);
1375
        $this->lastExecutedSql .= $TableNameSQL ? \sprintf('%s%s', \PHP_EOL, $TableNameSQL) : '';
1376
        //dump(['alterTableName' => [$TableNameStatus,$TableNameSQL]]);
1377
1378
        if (0 !== $TableNameStatus) {
1379
            return -3;
1380
        }
1381
1382
        // Schema
1383
        $this->fieldClean($schema);
1384
        [$TableSchemaStatus,$TableSchemaSQL] = $this->alterTableSchema($tblrs, $schema);
1385
        $this->lastExecutedSql .= $TableSchemaSQL ? \sprintf('%s%s', \PHP_EOL, $TableSchemaSQL) : '';
1386
        //dump(['alterTableSchema' => [$TableSchemaStatus,$TableSchemaSQL]]);
1387
1388
        if (0 !== $TableSchemaStatus) {
1389
            return -7;
1390
        }
1391
        [$TableOidsStatus,$TableOidsSQL] = $this->alterTableOids($tblrs, $withoutoids);
1392
        $this->lastExecutedSql .= $TableOidsSQL ? \sprintf('%s%s', \PHP_EOL, $TableOidsSQL) : '';
1393
        //dump(['alterTableOids' => [$TableOidsStatus,$TableOidsSQL]]);
1394
        if (0 !== $TableOidsStatus) {
1395
            return -7;
1396
        }
1397
1398
        return 0;
1399
    }
1400
1401
    /**
1402
     * Dumps serial-like columns in the table.
1403
     *
1404
     * @param \PHPPgAdmin\ADORecordSet $atts             table attributes
1405
     * @param \PHPPgAdmin\ADORecordSet $tblfields        table fields object
1406
     * @param string                   $sql              The sql sentence
1407
     *                                                   generated so far
1408
     * @param string                   $col_comments_sql Column comments,
1409
     *                                                   passed by reference
1410
     * @param int                      $i                current counter to
1411
     *                                                   know if we should
1412
     *                                                   append a comma to the
1413
     *                                                   sentence
1414
     * @param int                      $num              Table attributes
1415
     *                                                   count + table
1416
     *                                                   constraints count
1417
     *
1418
     * @return string original $sql plus appended strings
1419
     */
1420
    private function _dumpSerials($atts, $tblfields, $sql, &$col_comments_sql, $i, $num)
1421
    {
1422
        while (!$atts->EOF) {
1423
            $this->fieldClean($atts->fields['attname']);
1424
            $sql .= "    \"{$atts->fields['attname']}\"";
1425
            // Dump SERIAL and BIGSERIAL columns correctly
1426
            if ($this->phpBool($atts->fields['attisserial']) &&
1427
                ('integer' === $atts->fields['type'] || 'bigint' === $atts->fields['type'])
1428
            ) {
1429
                if ('integer' === $atts->fields['type']) {
1430
                    $sql .= ' SERIAL';
1431
                } else {
1432
                    $sql .= ' BIGSERIAL';
1433
                }
1434
            } else {
1435
                $sql .= ' ' . $this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
1436
1437
                // Add NOT NULL if necessary
1438
                if ($this->phpBool($atts->fields['attnotnull'])) {
1439
                    $sql .= ' NOT NULL';
1440
                }
1441
1442
                // Add default if necessary
1443
                if (null !== $atts->fields['adsrc']) {
1444
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
1445
                }
1446
            }
1447
1448
            // Output comma or not
1449
            if ($i < $num) {
1450
                $sql .= ",\n";
1451
            } else {
1452
                $sql .= "\n";
1453
            }
1454
1455
            // Does this column have a comment?
1456
            if (null !== $atts->fields['comment']) {
1457
                $this->clean($atts->fields['comment']);
1458
                $col_comments_sql .= "COMMENT ON COLUMN \"{$tblfields->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
1459
            }
1460
1461
            $atts->moveNext();
1462
            ++$i;
1463
        }
1464
1465
        return $sql;
1466
    }
1467
1468
    /**
1469
     * Dumps constraints.
1470
     *
1471
     * @param \PHPPgAdmin\ADORecordSet $cons  The table constraints
1472
     * @param string                   $table The table to define
1473
     * @param string                   $sql   The sql sentence generated so
1474
     *                                        far
1475
     * @param mixed                    $i
1476
     * @param int                      $num   Table attributes count + table
1477
     *                                        constraints count
1478
     */
1479
    private function _dumpConstraints($cons, $table, $sql, $i, $num): ?string
1480
    {
1481
        // Output all table constraints
1482
        while (!$cons->EOF) {
1483
            $this->fieldClean($cons->fields['conname']);
1484
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
1485
            // Nasty hack to support pre-7.4 PostgreSQL
1486
            if (null !== $cons->fields['consrc']) {
1487
                $sql .= $cons->fields['consrc'];
1488
            } else {
1489
                switch ($cons->fields['contype']) {
1490
                    case 'p':
1491
                        $keys = $this->getAttributeNames($table, \explode(' ', $cons->fields['indkey']));
1492
                        $sql .= 'PRIMARY KEY (' . \implode(',', $keys) . ')';
1493
1494
                        break;
1495
                    case 'u':
1496
                        $keys = $this->getAttributeNames($table, \explode(' ', $cons->fields['indkey']));
1497
                        $sql .= 'UNIQUE (' . \implode(',', $keys) . ')';
1498
1499
                        break;
1500
1501
                    default:
1502
                        // Unrecognised constraint
1503
                        $this->rollbackTransaction();
1504
1505
                        return null;
1506
                }
1507
            }
1508
1509
            // Output comma or not
1510
            if ($i < $num) {
1511
                $sql .= ",\n";
1512
            } else {
1513
                $sql .= "\n";
1514
            }
1515
1516
            $cons->moveNext();
1517
            ++$i;
1518
        }
1519
1520
        return $sql;
1521
    }
1522
1523
    /**
1524
     * Dumps col statistics.
1525
     *
1526
     * @param \PHPPgAdmin\ADORecordSet $atts      table attributes
1527
     * @param \PHPPgAdmin\ADORecordSet $tblfields table field attributes
1528
     * @param string                   $sql       The sql sentence generated so far
1529
     */
1530
    private function _dumpColStats($atts, $tblfields, $sql): ?string
1531
    {
1532
        // Column storage and statistics
1533
        $atts->moveFirst();
1534
        $first = true;
1535
1536
        while (!$atts->EOF) {
1537
            $this->fieldClean($atts->fields['attname']);
1538
            // Statistics first
1539
            if (0 <= $atts->fields['attstattarget']) {
1540
                if ($first) {
1541
                    $sql .= "\n";
1542
                    $first = false;
1543
                }
1544
                $sql .= "ALTER TABLE ONLY \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 190 characters; contains 210 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
1545
            }
1546
            // Then storage
1547
            if ($atts->fields['attstorage'] !== $atts->fields['typstorage']) {
1548
                switch ($atts->fields['attstorage']) {
1549
                    case 'p':
1550
                        $storage = 'PLAIN';
1551
1552
                        break;
1553
                    case 'e':
1554
                        $storage = 'EXTERNAL';
1555
1556
                        break;
1557
                    case 'm':
1558
                        $storage = 'MAIN';
1559
1560
                        break;
1561
                    case 'x':
1562
                        $storage = 'EXTENDED';
1563
1564
                        break;
1565
1566
                    default:
1567
                        // Unknown storage type
1568
                        $this->rollbackTransaction();
1569
1570
                        return null;
1571
                }
1572
                $sql .= "ALTER TABLE ONLY \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
1573
            }
1574
1575
            $atts->moveNext();
1576
        }
1577
1578
        return $sql;
1579
    }
1580
1581
    /**
1582
     * Dumps privileges.
1583
     *
1584
     * @param \PHPPgAdmin\ADORecordSet $privs     The table privileges
1585
     * @param \PHPPgAdmin\ADORecordSet $tblfields The table fields definition
1586
     * @param string                   $sql       The sql sentence generated so far
1587
     */
1588
    private function _dumpPrivileges($privs, $tblfields, $sql): ?string
1589
    {
1590
        if (0 >= \count($privs)) {
1591
            return $sql;
1592
        }
1593
        $sql .= "\n-- Privileges\n\n";
1594
        /*
1595
         * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
1596
         * wire-in knowledge about the default public privileges for different
1597
         * kinds of objects.
1598
         */
1599
        $sql .= "REVOKE ALL ON TABLE \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" FROM PUBLIC;\n";
1600
1601
        foreach ($privs as $v) {
1602
            // Get non-GRANT OPTION privs
1603
            $nongrant = \array_diff($v[2], $v[4]);
1604
1605
            // Skip empty or owner ACEs
1606
            if (0 === \count($v[2]) || ('user' === $v[0] && $v[1] === $tblfields->fields['relowner'])) {
1607
                continue;
1608
            }
1609
1610
            // Change user if necessary
1611
            if ($this->hasGrantOption() && $v[3] !== $tblfields->fields['relowner']) {
1612
                $grantor = $v[3];
1613
                $this->clean($grantor);
1614
                $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
1615
            }
1616
1617
            // Output privileges with no GRANT OPTION
1618
            $sql .= 'GRANT ' . \implode(', ', $nongrant) . " ON TABLE \"{$tblfields->fields['relname']}\" TO ";
1619
1620
            switch ($v[0]) {
1621
                case 'public':
1622
                    $sql .= "PUBLIC;\n";
1623
1624
                    break;
1625
                case 'user':
1626
                case 'role':
1627
                    $this->fieldClean($v[1]);
1628
                    $sql .= "\"{$v[1]}\";\n";
1629
1630
                    break;
1631
                case 'group':
1632
                    $this->fieldClean($v[1]);
1633
                    $sql .= "GROUP \"{$v[1]}\";\n";
1634
1635
                    break;
1636
1637
                default:
1638
                    // Unknown privilege type - fail
1639
                    $this->rollbackTransaction();
1640
1641
                    return null;
1642
            }
1643
1644
            // Reset user if necessary
1645
            if ($this->hasGrantOption() && $v[3] !== $tblfields->fields['relowner']) {
1646
                $sql .= "RESET SESSION AUTHORIZATION;\n";
1647
            }
1648
1649
            // Output privileges with GRANT OPTION
1650
1651
            // Skip empty or owner ACEs
1652
            if (!$this->hasGrantOption() || 0 === \count($v[4])) {
1653
                continue;
1654
            }
1655
1656
            // Change user if necessary
1657
            if ($this->hasGrantOption() && $v[3] !== $tblfields->fields['relowner']) {
1658
                $grantor = $v[3];
1659
                $this->clean($grantor);
1660
                $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
1661
            }
1662
1663
            $sql .= 'GRANT ' . \implode(', ', $v[4]) . " ON \"{$tblfields->fields['relname']}\" TO ";
1664
1665
            switch ($v[0]) {
1666
                case 'public':
1667
                    $sql .= 'PUBLIC';
1668
1669
                    break;
1670
                case 'user':
1671
                case 'role':
1672
                    $this->fieldClean($v[1]);
1673
                    $sql .= "\"{$v[1]}\"";
1674
1675
                    break;
1676
                case 'group':
1677
                    $this->fieldClean($v[1]);
1678
                    $sql .= "GROUP \"{$v[1]}\"";
1679
1680
                    break;
1681
1682
                default:
1683
                    // Unknown privilege type - fail
1684
                    return null;
1685
            }
1686
            $sql .= " WITH GRANT OPTION;\n";
1687
1688
            // Reset user if necessary
1689
            if ($this->hasGrantOption() && $v[3] !== $tblfields->fields['relowner']) {
1690
                $sql .= "RESET SESSION AUTHORIZATION;\n";
1691
            }
1692
        }
1693
1694
        return $sql;
1695
    }
1696
1697
    /**
1698
     * Dumps a create.
1699
     *
1700
     * @param \PHPPgAdmin\ADORecordSet $tblfields   table fields object
1701
     * @param string                   $sql         The sql sentence generated so far
1702
     * @param string                   $cleanprefix set to '-- ' to avoid issuing DROP statement
1703
     * @param mixed                    $fields
1704
     *
1705
     * @return string original $sql plus appended strings
1706
     */
1707
    private function _dumpCreate($tblfields, $sql, $cleanprefix)
1708
    {
1709
        // Set schema search path
1710
        $sql .= "SET search_path = \"{$tblfields->fields['nspname']}\", pg_catalog;\n\n";
1711
1712
        // Begin CREATE TABLE definition
1713
        $sql .= "-- Definition\n\n";
1714
        // DROP TABLE must be fully qualified in case a table with the same name exists
1715
        $sql .= $cleanprefix . 'DROP TABLE ';
1716
        $sql .= "\"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\";\n";
1717
        $sql .= "CREATE TABLE \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" (\n";
1718
1719
        return $sql;
1720
    }
1721
1722
    /**
1723
     * Retrieve all attributes definition of a table.
1724
     *
1725
     * @param string $table    The name of the table
1726
     * @param string $c_schema The name of the schema
1727
     *
1728
     * @return int|\PHPPgAdmin\ADORecordSet
1729
     */
1730
    private function _getTableAttributesAll($table, $c_schema)
1731
    {
1732
        $sql = "
1733
            SELECT
1734
                a.attname,
1735
                a.attnum,
1736
                pg_catalog.format_type(a.atttypid, a.atttypmod) AS TYPE,
1737
                a.atttypmod,
1738
                a.attnotnull,
1739
                a.atthasdef,
1740
                pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, TRUE) AS adsrc,
1741
                a.attstattarget,
1742
                a.attstorage,
1743
                t.typstorage,
1744
                CASE
1745
                WHEN pc.oid IS NULL THEN FALSE
1746
                ELSE TRUE
1747
                END AS attisserial,
1748
                pg_catalog.col_description(a.attrelid, a.attnum) AS COMMENT
1749
1750
            FROM pg_catalog.pg_tables tbl
1751
            JOIN pg_catalog.pg_class tbl_class ON tbl.tablename=tbl_class.relname
1752
            JOIN  pg_catalog.pg_attribute a ON tbl_class.oid = a.attrelid
1753
            JOIN pg_catalog.pg_namespace    ON pg_namespace.oid = tbl_class.relnamespace
1754
                                            AND pg_namespace.nspname=tbl.schemaname
1755
            LEFT JOIN pg_catalog.pg_attrdef adef    ON a.attrelid=adef.adrelid
1756
                                                    AND a.attnum=adef.adnum
1757
            LEFT JOIN pg_catalog.pg_type t  ON a.atttypid=t.oid
1758
            LEFT JOIN  pg_catalog.pg_depend pd  ON pd.refobjid=a.attrelid
1759
                                                AND pd.refobjsubid=a.attnum
1760
                                                AND pd.deptype='i'
1761
            LEFT JOIN pg_catalog.pg_class pc ON pd.objid=pc.oid
1762
                                            AND pd.classid=pc.tableoid
1763
                                            AND pd.refclassid=pc.tableoid
1764
                                            AND pc.relkind='S'
1765
            WHERE tbl.tablename='{$table}'
1766
            AND tbl.schemaname='{$c_schema}'
1767
            AND a.attnum > 0 AND NOT a.attisdropped
1768
            ORDER BY a.attnum";
1769
1770
        return $this->selectSet($sql);
1771
    }
1772
1773
    /**
1774
     * Retrieve single attribute definition of a table.
1775
     *
1776
     * @param string $table    The name of the table
1777
     * @param string $c_schema The schema of the table
1778
     * @param string $field    (optional) The name of a field to return
1779
     *
1780
     * @return int|\PHPPgAdmin\ADORecordSet
1781
     */
1782
    private function _getTableAttribute($table, $c_schema, $field)
1783
    {
1784
        $sql = "
1785
                SELECT
1786
                    a.attname, a.attnum,
1787
                    pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
1788
                    pg_catalog.format_type(a.atttypid, NULL) as base_type,
1789
                    a.atttypmod,
1790
                    a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
1791
                    a.attstattarget, a.attstorage, t.typstorage,
1792
                    pg_catalog.col_description(a.attrelid, a.attnum) AS comment
1793
                FROM
1794
                    pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
1795
                    ON a.attrelid=adef.adrelid
1796
                    AND a.attnum=adef.adnum
1797
                    LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
1798
                WHERE
1799
                    a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
1800
                        AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
1801
                        nspname = '{$c_schema}'))
1802
                    AND a.attname = '{$field}'";
1803
1804
        return $this->selectSet($sql);
1805
    }
1806
}
1807