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

TableTrait::createTable()   F

Complexity

Conditions 31
Paths > 20000

Size

Total Lines 172
Code Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 31
eloc 85
c 0
b 0
f 0
nc 154787
nop 14
dl 0
loc 172
rs 0

How to fix   Long Method    Complexity    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.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