TableTrait::saveAutovacuum()   B
last analyzed

Complexity

Conditions 8
Paths 128

Size

Total Lines 56
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 28
c 1
b 0
f 0
nc 128
nop 8
dl 0
loc 56
rs 8.2111

How to fix   Long Method    Many Parameters   

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:

Many Parameters

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

There are several approaches to avoid long parameter lists:

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