Passed
Push — master ( 16b665...cbef8e )
by Felipe
11:43 queued 06:53
created

TableTrait::getTableAutovacuum()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 50
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 21
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 50
rs 9.584
1
<?php
2
3
/**
4
 * PHPPgAdmin 6.1.0
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
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
627
628
                    if ('' !== $length[$i]) {
629
                        $sql .= "({$length[$i]})";
630
                    }
631
            }
632
            // Add array qualifier if necessary
633
            if ('[]' === $array[$i]) {
634
                $sql .= '[]';
635
            }
636
637
            // Add other qualifiers
638
            if (!isset($primarykey[$i])) {
639
                if (isset($uniquekey[$i])) {
640
                    $sql .= ' UNIQUE';
641
                }
642
643
                if (isset($notnull[$i])) {
644
                    $sql .= ' NOT NULL';
645
                }
646
            }
647
648
            if ('' !== $default[$i]) {
649
                $sql .= " DEFAULT {$default[$i]}";
650
            }
651
652
            if ('' !== $colcomment[$i]) {
653
                $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
654
            }
655
656
            $found = true;
657
        }
658
659
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
660
            return -1;
661
        }
662
663
        // PRIMARY KEY
664
        $primarykeycolumns = [];
665
666
        for ($i = 0; $i < $fields; ++$i) {
667
            if (isset($primarykey[$i])) {
668
                $primarykeycolumns[] = "\"{$field[$i]}\"";
669
            }
670
        }
671
672
        if (0 < \count($primarykeycolumns)) {
673
            $sql .= ', PRIMARY KEY (' . \implode(', ', $primarykeycolumns) . ')';
674
        }
675
676
        $sql .= ')';
677
678
        // WITHOUT OIDS
679
        if ($withoutoids) {
680
            $sql .= ' WITHOUT OIDS';
681
        } else {
682
            $sql .= ' WITH OIDS';
683
        }
684
685
        // Tablespace
686
        if ($this->hasTablespaces() && '' !== $tablespace) {
687
            $this->fieldClean($tablespace);
688
            $sql .= " TABLESPACE \"{$tablespace}\"";
689
        }
690
691
        $status = $this->execute($sql);
692
693
        if ($status) {
694
            $this->rollbackTransaction();
695
696
            return -1;
697
        }
698
699
        if ('' !== $tblcomment) {
700
            $status = $this->setComment('TABLE', '', $name, $tblcomment, true);
701
702
            if ($status) {
703
                $this->rollbackTransaction();
704
705
                return -1;
706
            }
707
        }
708
709
        if ('' !== $comment_sql) {
710
            $status = $this->execute($comment_sql);
711
712
            if ($status) {
713
                $this->rollbackTransaction();
714
715
                return -1;
716
            }
717
        }
718
719
        return $this->endTransaction();
720
    }
721
722
    /**
723
     * Creates a new table in the database copying attribs and other properties from another table.
724
     *
725
     * @param string $name        The name of the table
726
     * @param array  $like        an array giving the schema ans the name of the table from which attribs are copying
727
     *                            from: array(
728
     *                            'table' => table name,
729
     *                            'schema' => the schema name,
730
     *                            )
731
     * @param bool   $defaults    if true, copy the defaults values as well
732
     * @param bool   $constraints if true, copy the constraints as well (CHECK on table & attr)
733
     * @param bool   $idx
734
     * @param string $tablespace  The tablespace name ('' means none/default)
735
     *
736
     * @return bool|int
737
     */
738
    public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '')
739
    {
740
        $f_schema = $this->_schema;
741
        $this->fieldClean($f_schema);
742
        $this->fieldClean($name);
743
        $this->fieldClean($like['schema']);
744
        $this->fieldClean($like['table']);
745
        $like = "\"{$like['schema']}\".\"{$like['table']}\"";
746
747
        $status = $this->beginTransaction();
748
749
        if (0 !== $status) {
750
            return -1;
751
        }
752
753
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}";
754
755
        if ($defaults) {
756
            $sql .= ' INCLUDING DEFAULTS';
757
        }
758
759
        if ($this->hasCreateTableLikeWithConstraints() && $constraints) {
760
            $sql .= ' INCLUDING CONSTRAINTS';
761
        }
762
763
        if ($this->hasCreateTableLikeWithIndexes() && $idx) {
764
            $sql .= ' INCLUDING INDEXES';
765
        }
766
767
        $sql .= ')';
768
769
        if ($this->hasTablespaces() && '' !== $tablespace) {
770
            $this->fieldClean($tablespace);
771
            $sql .= " TABLESPACE \"{$tablespace}\"";
772
        }
773
774
        $status = $this->execute($sql);
775
776
        if ($status) {
777
            $this->rollbackTransaction();
778
779
            return -1;
780
        }
781
782
        return $this->endTransaction();
783
    }
784
785
    /**
786
     * Alter table properties.
787
     *
788
     * @param string $table      The name of the table
789
     * @param string $name       The new name for the table
790
     * @param string $owner      The new owner for the table
791
     * @param string $schema     The new schema for the table
792
     * @param string $comment    The comment on the table
793
     * @param string $tablespace The new tablespace for the table ('' means leave as is)
794
     * @param bool   $with_oids  If set to FALSE, will drop oids column
795
     *
796
     * @return bool|int 0 success
797
     */
798
    public function alterTable($table, $name, $owner, $schema, $comment, $tablespace, bool $with_oids = true)
799
    {
800
        $tblrs = $this->getTable($table);
801
802
        if (1 !== $tblrs->recordCount()) {
803
            return -2;
804
        }
805
806
        $status = $this->beginTransaction();
807
        //dump(['beginTransaction' => $status]);
808
809
        if (0 !== $status) {
810
            $this->rollbackTransaction();
811
812
            return -1;
813
        }
814
        $sql_sentence = "ALTER TABLE \"{$this->_schema}\".\"{$tblrs->fields['relname']}\" ";
815
        $status = $this->_alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace, !$with_oids, $sql_sentence);
0 ignored issues
show
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

815
        $status = $this->_alterTable(/** @scrutinizer ignore-type */ $tblrs, $name, $owner, $schema, $comment, $tablespace, !$with_oids, $sql_sentence);
Loading history...
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

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

1345
        /** @scrutinizer ignore-call */ 
1346
        $this->lastExecutedSql = $this->getLastExecutedSQL();
Loading history...
1346
1347
        if (0 !== $status) {
1348
            return -4;
1349
        }
1350
1351
        // Owner
1352
        $this->fieldClean($owner);
1353
        [$TableOwnerStatus,$TableOwnerSQL] = $this->alterTableOwner($tblrs, $owner);
1354
        $this->lastExecutedSql .= $TableOwnerSQL ? \sprintf('%s%s', \PHP_EOL, $TableOwnerSQL) : '';
1355
        //dump(['alterTableOwner' => [$TableOwnerStatus,$TableOwnerSQL]]);
1356
1357
        if (0 !== $TableOwnerStatus) {
1358
            return -5;
1359
        }
1360
1361
        // Tablespace
1362
        $this->fieldClean($tablespace);
1363
        [$TableTablespaceStatus,$TableTablespaceSQL] = $this->alterTableTablespace($tblrs, $tablespace);
1364
        $this->lastExecutedSql .= $TableTablespaceSQL ? \sprintf('%s%s', \PHP_EOL, $TableTablespaceSQL) : '';
1365
        //dump(['alterTableTablespace' => [$TableTablespaceStatus,$TableTablespaceSQL]]);
1366
1367
        if (0 !== $TableTablespaceStatus) {
1368
            return -6;
1369
        }
1370
1371
        // Rename
1372
        $this->fieldClean($name);
1373
        [$TableNameStatus,$TableNameSQL] = $this->alterTableName($tblrs, $name);
1374
        $this->lastExecutedSql .= $TableNameSQL ? \sprintf('%s%s', \PHP_EOL, $TableNameSQL) : '';
1375
        //dump(['alterTableName' => [$TableNameStatus,$TableNameSQL]]);
1376
1377
        if (0 !== $TableNameStatus) {
1378
            return -3;
1379
        }
1380
1381
        // Schema
1382
        $this->fieldClean($schema);
1383
        [$TableSchemaStatus,$TableSchemaSQL] = $this->alterTableSchema($tblrs, $schema);
1384
        $this->lastExecutedSql .= $TableSchemaSQL ? \sprintf('%s%s', \PHP_EOL, $TableSchemaSQL) : '';
1385
        //dump(['alterTableSchema' => [$TableSchemaStatus,$TableSchemaSQL]]);
1386
1387
        if (0 !== $TableSchemaStatus) {
1388
            return -7;
1389
        }
1390
        [$TableOidsStatus,$TableOidsSQL] = $this->alterTableOids($tblrs, $withoutoids);
1391
        $this->lastExecutedSql .= $TableOidsSQL ? \sprintf('%s%s', \PHP_EOL, $TableOidsSQL) : '';
1392
        //dump(['alterTableOids' => [$TableOidsStatus,$TableOidsSQL]]);
1393
        if (0 !== $TableOidsStatus) {
1394
            return -7;
1395
        }
1396
1397
        return 0;
1398
    }
1399
1400
    /**
1401
     * Dumps serial-like columns in the table.
1402
     *
1403
     * @param \PHPPgAdmin\ADORecordSet $atts             table attributes
1404
     * @param \PHPPgAdmin\ADORecordSet $tblfields        table fields object
1405
     * @param string                   $sql              The sql sentence
1406
     *                                                   generated so far
1407
     * @param string                   $col_comments_sql Column comments,
1408
     *                                                   passed by reference
1409
     * @param int                      $i                current counter to
1410
     *                                                   know if we should
1411
     *                                                   append a comma to the
1412
     *                                                   sentence
1413
     * @param int                      $num              Table attributes
1414
     *                                                   count + table
1415
     *                                                   constraints count
1416
     *
1417
     * @return string original $sql plus appended strings
1418
     */
1419
    private function _dumpSerials($atts, $tblfields, $sql, &$col_comments_sql, $i, $num)
1420
    {
1421
        while (!$atts->EOF) {
1422
            $this->fieldClean($atts->fields['attname']);
1423
            $sql .= "    \"{$atts->fields['attname']}\"";
1424
            // Dump SERIAL and BIGSERIAL columns correctly
1425
            if ($this->phpBool($atts->fields['attisserial']) &&
1426
                ('integer' === $atts->fields['type'] || 'bigint' === $atts->fields['type'])
1427
            ) {
1428
                if ('integer' === $atts->fields['type']) {
1429
                    $sql .= ' SERIAL';
1430
                } else {
1431
                    $sql .= ' BIGSERIAL';
1432
                }
1433
            } else {
1434
                $sql .= ' ' . $this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
1435
1436
                // Add NOT NULL if necessary
1437
                if ($this->phpBool($atts->fields['attnotnull'])) {
1438
                    $sql .= ' NOT NULL';
1439
                }
1440
1441
                // Add default if necessary
1442
                if (null !== $atts->fields['adsrc']) {
1443
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
1444
                }
1445
            }
1446
1447
            // Output comma or not
1448
            if ($i < $num) {
1449
                $sql .= ",\n";
1450
            } else {
1451
                $sql .= "\n";
1452
            }
1453
1454
            // Does this column have a comment?
1455
            if (null !== $atts->fields['comment']) {
1456
                $this->clean($atts->fields['comment']);
1457
                $col_comments_sql .= "COMMENT ON COLUMN \"{$tblfields->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
1458
            }
1459
1460
            $atts->moveNext();
1461
            ++$i;
1462
        }
1463
1464
        return $sql;
1465
    }
1466
1467
    /**
1468
     * Dumps constraints.
1469
     *
1470
     * @param \PHPPgAdmin\ADORecordSet $cons  The table constraints
1471
     * @param string                   $table The table to define
1472
     * @param string                   $sql   The sql sentence generated so
1473
     *                                        far
1474
     * @param mixed                    $i
1475
     * @param int                      $num   Table attributes count + table
1476
     *                                        constraints count
1477
     */
1478
    private function _dumpConstraints($cons, $table, $sql, $i, $num): ?string
1479
    {
1480
        // Output all table constraints
1481
        while (!$cons->EOF) {
1482
            $this->fieldClean($cons->fields['conname']);
1483
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
1484
            // Nasty hack to support pre-7.4 PostgreSQL
1485
            if (null !== $cons->fields['consrc']) {
1486
                $sql .= $cons->fields['consrc'];
1487
            } else {
1488
                switch ($cons->fields['contype']) {
1489
                    case 'p':
1490
                        $keys = $this->getAttributeNames($table, \explode(' ', $cons->fields['indkey']));
1491
                        $sql .= 'PRIMARY KEY (' . \implode(',', $keys) . ')';
1492
1493
                        break;
1494
                    case 'u':
1495
                        $keys = $this->getAttributeNames($table, \explode(' ', $cons->fields['indkey']));
1496
                        $sql .= 'UNIQUE (' . \implode(',', $keys) . ')';
1497
1498
                        break;
1499
1500
                    default:
1501
                        // Unrecognised constraint
1502
                        $this->rollbackTransaction();
1503
1504
                        return null;
1505
                }
1506
            }
1507
1508
            // Output comma or not
1509
            if ($i < $num) {
1510
                $sql .= ",\n";
1511
            } else {
1512
                $sql .= "\n";
1513
            }
1514
1515
            $cons->moveNext();
1516
            ++$i;
1517
        }
1518
1519
        return $sql;
1520
    }
1521
1522
    /**
1523
     * Dumps col statistics.
1524
     *
1525
     * @param \PHPPgAdmin\ADORecordSet $atts      table attributes
1526
     * @param \PHPPgAdmin\ADORecordSet $tblfields table field attributes
1527
     * @param string                   $sql       The sql sentence generated so far
1528
     */
1529
    private function _dumpColStats($atts, $tblfields, $sql): ?string
1530
    {
1531
        // Column storage and statistics
1532
        $atts->moveFirst();
1533
        $first = true;
1534
1535
        while (!$atts->EOF) {
1536
            $this->fieldClean($atts->fields['attname']);
1537
            // Statistics first
1538
            if (0 <= $atts->fields['attstattarget']) {
1539
                if ($first) {
1540
                    $sql .= "\n";
1541
                    $first = false;
1542
                }
1543
                $sql .= "ALTER TABLE ONLY \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
1544
            }
1545
            // Then storage
1546
            if ($atts->fields['attstorage'] !== $atts->fields['typstorage']) {
1547
                switch ($atts->fields['attstorage']) {
1548
                    case 'p':
1549
                        $storage = 'PLAIN';
1550
1551
                        break;
1552
                    case 'e':
1553
                        $storage = 'EXTERNAL';
1554
1555
                        break;
1556
                    case 'm':
1557
                        $storage = 'MAIN';
1558
1559
                        break;
1560
                    case 'x':
1561
                        $storage = 'EXTENDED';
1562
1563
                        break;
1564
1565
                    default:
1566
                        // Unknown storage type
1567
                        $this->rollbackTransaction();
1568
1569
                        return null;
1570
                }
1571
                $sql .= "ALTER TABLE ONLY \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
1572
            }
1573
1574
            $atts->moveNext();
1575
        }
1576
1577
        return $sql;
1578
    }
1579
1580
    /**
1581
     * Dumps privileges.
1582
     *
1583
     * @param \PHPPgAdmin\ADORecordSet $privs     The table privileges
1584
     * @param \PHPPgAdmin\ADORecordSet $tblfields The table fields definition
1585
     * @param string                   $sql       The sql sentence generated so far
1586
     */
1587
    private function _dumpPrivileges($privs, $tblfields, $sql): ?string
1588
    {
1589
        if (0 >= \count($privs)) {
1590
            return $sql;
1591
        }
1592
        $sql .= "\n-- Privileges\n\n";
1593
        /*
1594
         * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
1595
         * wire-in knowledge about the default public privileges for different
1596
         * kinds of objects.
1597
         */
1598
        $sql .= "REVOKE ALL ON TABLE \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" FROM PUBLIC;\n";
1599
1600
        foreach ($privs as $v) {
1601
            // Get non-GRANT OPTION privs
1602
            $nongrant = \array_diff($v[2], $v[4]);
1603
1604
            // Skip empty or owner ACEs
1605
            if (0 === \count($v[2]) || ('user' === $v[0] && $v[1] === $tblfields->fields['relowner'])) {
1606
                continue;
1607
            }
1608
1609
            // Change user if necessary
1610
            if ($this->hasGrantOption() && $v[3] !== $tblfields->fields['relowner']) {
1611
                $grantor = $v[3];
1612
                $this->clean($grantor);
1613
                $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
1614
            }
1615
1616
            // Output privileges with no GRANT OPTION
1617
            $sql .= 'GRANT ' . \implode(', ', $nongrant) . " ON TABLE \"{$tblfields->fields['relname']}\" TO ";
1618
1619
            switch ($v[0]) {
1620
                case 'public':
1621
                    $sql .= "PUBLIC;\n";
1622
1623
                    break;
1624
                case 'user':
1625
                case 'role':
1626
                    $this->fieldClean($v[1]);
1627
                    $sql .= "\"{$v[1]}\";\n";
1628
1629
                    break;
1630
                case 'group':
1631
                    $this->fieldClean($v[1]);
1632
                    $sql .= "GROUP \"{$v[1]}\";\n";
1633
1634
                    break;
1635
1636
                default:
1637
                    // Unknown privilege type - fail
1638
                    $this->rollbackTransaction();
1639
1640
                    return null;
1641
            }
1642
1643
            // Reset user if necessary
1644
            if ($this->hasGrantOption() && $v[3] !== $tblfields->fields['relowner']) {
1645
                $sql .= "RESET SESSION AUTHORIZATION;\n";
1646
            }
1647
1648
            // Output privileges with GRANT OPTION
1649
1650
            // Skip empty or owner ACEs
1651
            if (!$this->hasGrantOption() || 0 === \count($v[4])) {
1652
                continue;
1653
            }
1654
1655
            // Change user if necessary
1656
            if ($this->hasGrantOption() && $v[3] !== $tblfields->fields['relowner']) {
1657
                $grantor = $v[3];
1658
                $this->clean($grantor);
1659
                $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
1660
            }
1661
1662
            $sql .= 'GRANT ' . \implode(', ', $v[4]) . " ON \"{$tblfields->fields['relname']}\" TO ";
1663
1664
            switch ($v[0]) {
1665
                case 'public':
1666
                    $sql .= 'PUBLIC';
1667
1668
                    break;
1669
                case 'user':
1670
                case 'role':
1671
                    $this->fieldClean($v[1]);
1672
                    $sql .= "\"{$v[1]}\"";
1673
1674
                    break;
1675
                case 'group':
1676
                    $this->fieldClean($v[1]);
1677
                    $sql .= "GROUP \"{$v[1]}\"";
1678
1679
                    break;
1680
1681
                default:
1682
                    // Unknown privilege type - fail
1683
                    return null;
1684
            }
1685
            $sql .= " WITH GRANT OPTION;\n";
1686
1687
            // Reset user if necessary
1688
            if ($this->hasGrantOption() && $v[3] !== $tblfields->fields['relowner']) {
1689
                $sql .= "RESET SESSION AUTHORIZATION;\n";
1690
            }
1691
        }
1692
1693
        return $sql;
1694
    }
1695
1696
    /**
1697
     * Dumps a create.
1698
     *
1699
     * @param \PHPPgAdmin\ADORecordSet $tblfields   table fields object
1700
     * @param string                   $sql         The sql sentence generated so far
1701
     * @param string                   $cleanprefix set to '-- ' to avoid issuing DROP statement
1702
     * @param mixed                    $fields
1703
     *
1704
     * @return string original $sql plus appended strings
1705
     */
1706
    private function _dumpCreate($tblfields, $sql, $cleanprefix)
1707
    {
1708
        // Set schema search path
1709
        $sql .= "SET search_path = \"{$tblfields->fields['nspname']}\", pg_catalog;\n\n";
1710
1711
        // Begin CREATE TABLE definition
1712
        $sql .= "-- Definition\n\n";
1713
        // DROP TABLE must be fully qualified in case a table with the same name exists
1714
        $sql .= $cleanprefix . 'DROP TABLE ';
1715
        $sql .= "\"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\";\n";
1716
        $sql .= "CREATE TABLE \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" (\n";
1717
1718
        return $sql;
1719
    }
1720
1721
    /**
1722
     * Retrieve all attributes definition of a table.
1723
     *
1724
     * @param string $table    The name of the table
1725
     * @param string $c_schema The name of the schema
1726
     *
1727
     * @return int|\PHPPgAdmin\ADORecordSet
1728
     */
1729
    private function _getTableAttributesAll($table, $c_schema)
1730
    {
1731
        $sql = "
1732
            SELECT
1733
                a.attname,
1734
                a.attnum,
1735
                pg_catalog.format_type(a.atttypid, a.atttypmod) AS TYPE,
1736
                a.atttypmod,
1737
                a.attnotnull,
1738
                a.atthasdef,
1739
                pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, TRUE) AS adsrc,
1740
                a.attstattarget,
1741
                a.attstorage,
1742
                t.typstorage,
1743
                CASE
1744
                WHEN pc.oid IS NULL THEN FALSE
1745
                ELSE TRUE
1746
                END AS attisserial,
1747
                pg_catalog.col_description(a.attrelid, a.attnum) AS COMMENT
1748
1749
            FROM pg_catalog.pg_tables tbl
1750
            JOIN pg_catalog.pg_class tbl_class ON tbl.tablename=tbl_class.relname
1751
            JOIN  pg_catalog.pg_attribute a ON tbl_class.oid = a.attrelid
1752
            JOIN pg_catalog.pg_namespace    ON pg_namespace.oid = tbl_class.relnamespace
1753
                                            AND pg_namespace.nspname=tbl.schemaname
1754
            LEFT JOIN pg_catalog.pg_attrdef adef    ON a.attrelid=adef.adrelid
1755
                                                    AND a.attnum=adef.adnum
1756
            LEFT JOIN pg_catalog.pg_type t  ON a.atttypid=t.oid
1757
            LEFT JOIN  pg_catalog.pg_depend pd  ON pd.refobjid=a.attrelid
1758
                                                AND pd.refobjsubid=a.attnum
1759
                                                AND pd.deptype='i'
1760
            LEFT JOIN pg_catalog.pg_class pc ON pd.objid=pc.oid
1761
                                            AND pd.classid=pc.tableoid
1762
                                            AND pd.refclassid=pc.tableoid
1763
                                            AND pc.relkind='S'
1764
            WHERE tbl.tablename='{$table}'
1765
            AND tbl.schemaname='{$c_schema}'
1766
            AND a.attnum > 0 AND NOT a.attisdropped
1767
            ORDER BY a.attnum";
1768
1769
        return $this->selectSet($sql);
1770
    }
1771
1772
    /**
1773
     * Retrieve single attribute definition of a table.
1774
     *
1775
     * @param string $table    The name of the table
1776
     * @param string $c_schema The schema of the table
1777
     * @param string $field    (optional) The name of a field to return
1778
     *
1779
     * @return int|\PHPPgAdmin\ADORecordSet
1780
     */
1781
    private function _getTableAttribute($table, $c_schema, $field)
1782
    {
1783
        $sql = "
1784
                SELECT
1785
                    a.attname, a.attnum,
1786
                    pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
1787
                    pg_catalog.format_type(a.atttypid, NULL) as base_type,
1788
                    a.atttypmod,
1789
                    a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
1790
                    a.attstattarget, a.attstorage, t.typstorage,
1791
                    pg_catalog.col_description(a.attrelid, a.attnum) AS comment
1792
                FROM
1793
                    pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
1794
                    ON a.attrelid=adef.adrelid
1795
                    AND a.attnum=adef.adnum
1796
                    LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
1797
                WHERE
1798
                    a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
1799
                        AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
1800
                        nspname = '{$c_schema}'))
1801
                    AND a.attname = '{$field}'";
1802
1803
        return $this->selectSet($sql);
1804
    }
1805
}
1806