Passed
Push — develop ( ce7da4...69770d )
by Felipe
05:27
created

TableTrait::alterTableSchema()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 2
nop 2
1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.40
5
 */
6
7
namespace PHPPgAdmin\Database;
8
9
/**
10
 * Common trait for tables manipulation.
11
 */
12
trait TableTrait
13
{
14
15
    /**
16
     * Return all tables in current database (and schema).
17
     *
18
     * @param bool|true $all True to fetch all tables, false for just in current schema
19
     *
20
     * @return \PHPPgAdmin\ADORecordSet All tables, sorted alphabetically
21
     */
22
    public function getTables($all = false)
23
    {
24
        $c_schema = $this->_schema;
25
        $this->clean($c_schema);
0 ignored issues
show
Bug introduced by
It seems like clean() 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

25
        $this->/** @scrutinizer ignore-call */ 
26
               clean($c_schema);
Loading history...
26
        if ($all) {
27
            // Exclude pg_catalog and information_schema tables
28
            $sql = "SELECT
29
                        schemaname AS nspname,
30
                        tablename AS relname,
31
                        tableowner AS relowner
32
                    FROM pg_catalog.pg_tables
33
                    WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
34
                    ORDER BY schemaname, tablename";
35
        } else {
36
            $sql = "
37
                SELECT c.relname,
38
                    pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
39
                    pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
40
                    reltuples::bigint,
41
                    pt.spcname as tablespace,
42
                    pg_size_pretty(pg_total_relation_size(c.oid)) as table_size
43
                FROM pg_catalog.pg_class c
44
                LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
45
                LEFT JOIN  pg_catalog.pg_tablespace pt ON  pt.oid=c.reltablespace
46
                WHERE c.relkind = 'r'
47
                AND nspname='{$c_schema}'
48
                ORDER BY c.relname";
49
        }
50
51
        return $this->selectSet($sql);
0 ignored issues
show
Bug introduced by
It seems like selectSet() 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

51
        return $this->/** @scrutinizer ignore-call */ selectSet($sql);
Loading history...
52
    }
53
54
    /**
55
     * Finds the names and schemas of parent tables (in order).
56
     *
57
     * @param $table The table to find the parents for
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
58
     *
59
     * @return \PHPPgAdmin\ADORecordSet A recordset
60
     */
61
    public function getTableParents($table)
62
    {
63
        $c_schema = $this->_schema;
64
        $this->clean($c_schema);
65
        $this->clean($table);
66
67
        $sql = "
68
            SELECT
69
                pn.nspname, relname
70
            FROM
71
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
72
            WHERE
73
                pc.oid=pi.inhparent
74
                AND pc.relnamespace=pn.oid
75
                AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
76
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
77
            ORDER BY
78
                pi.inhseqno
79
        ";
80
81
        return $this->selectSet($sql);
82
    }
83
84
    /**
85
     * Finds the names and schemas of child tables.
86
     *
87
     * @param $table The table to find the children for
88
     *
89
     * @return \PHPPgAdmin\ADORecordSet A recordset
90
     */
91
    public function getTableChildren($table)
92
    {
93
        $c_schema = $this->_schema;
94
        $this->clean($c_schema);
95
        $this->clean($table);
96
97
        $sql = "
98
            SELECT
99
                pn.nspname, relname
100
            FROM
101
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
102
            WHERE
103
                pc.oid=pi.inhrelid
104
                AND pc.relnamespace=pn.oid
105
                AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
106
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
107
        ";
108
109
        return $this->selectSet($sql);
110
    }
111
112
    /**
113
     * Returns the SQL definition for the table.
114
     *
115
     * @pre MUST be run within a transaction
116
     *
117
     * @param           $table The table to define
1 ignored issue
show
Coding Style introduced by
Parameter tags must be defined first in a doc comment
Loading history...
118
     * @param bool|true $clean True to issue drop command, false otherwise
119
     *
120
     * @return string A string containing the formatted SQL code
121
     */
122
    public function getTableDefPrefix($table, $clean = false)
123
    {
124
        // Fetch table
125
        $t = $this->getTable($table);
126
        if (!is_object($t) || $t->recordCount() != 1) {
0 ignored issues
show
introduced by
The condition $t->recordCount() != 1 is always true.
Loading history...
127
            $this->rollbackTransaction();
0 ignored issues
show
Bug introduced by
It seems like rollbackTransaction() 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

127
            $this->/** @scrutinizer ignore-call */ 
128
                   rollbackTransaction();
Loading history...
128
129
            return null;
130
        }
131
        $this->fieldClean($t->fields['relname']);
132
        $this->fieldClean($t->fields['nspname']);
133
134
        // Fetch attributes
135
        $atts = $this->getTableAttributes($table);
136
        if (!is_object($atts)) {
137
            $this->rollbackTransaction();
138
139
            return null;
140
        }
141
142
        // Fetch constraints
143
        $cons = $this->getConstraints($table);
144
        if (!is_object($cons)) {
145
            $this->rollbackTransaction();
146
147
            return null;
148
        }
149
150
        // Output a reconnect command to create the table as the correct user
151
        $sql = $this->getChangeUserSQL($t->fields['relowner']) . "\n\n";
152
153
        // Set schema search path
154
        $sql .= "SET search_path = \"{$t->fields['nspname']}\", pg_catalog;\n\n";
155
156
        // Begin CREATE TABLE definition
157
        $sql .= "-- Definition\n\n";
158
        // DROP TABLE must be fully qualified in case a table with the same name exists
159
        // in pg_catalog.
160
        if (!$clean) {
161
            $sql .= '-- ';
162
        }
163
164
        $sql .= 'DROP TABLE ';
165
        $sql .= "\"{$t->fields['nspname']}\".\"{$t->fields['relname']}\";\n";
166
        $sql .= "CREATE TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" (\n";
167
168
        // Output all table columns
169
        $col_comments_sql = ''; // Accumulate comments on columns
170
        $num              = $atts->recordCount() + $cons->recordCount();
171
        $i                = 1;
172
        while (!$atts->EOF) {
173
            $this->fieldClean($atts->fields['attname']);
174
            $sql .= "    \"{$atts->fields['attname']}\"";
175
            // Dump SERIAL and BIGSERIAL columns correctly
176
            if ($this->phpBool($atts->fields['attisserial']) &&
177
                ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) {
2 ignored issues
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
178
                if ($atts->fields['type'] == 'integer') {
179
                    $sql .= ' SERIAL';
180
                } else {
181
                    $sql .= ' BIGSERIAL';
182
                }
183
            } else {
184
                $sql .= ' ' . $this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
185
186
                // Add NOT NULL if necessary
187
                if ($this->phpBool($atts->fields['attnotnull'])) {
188
                    $sql .= ' NOT NULL';
189
                }
190
191
                // Add default if necessary
192
                if ($atts->fields['adsrc'] !== null) {
193
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
194
                }
195
            }
196
197
            // Output comma or not
198
            if ($i < $num) {
199
                $sql .= ",\n";
200
            } else {
201
                $sql .= "\n";
202
            }
203
204
            // Does this column have a comment?
205
            if ($atts->fields['comment'] !== null) {
206
                $this->clean($atts->fields['comment']);
207
                $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
208
            }
209
210
            $atts->moveNext();
211
            ++$i;
212
        }
213
        // Output all table constraints
214
        while (!$cons->EOF) {
215
            $this->fieldClean($cons->fields['conname']);
216
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
217
            // Nasty hack to support pre-7.4 PostgreSQL
218
            if ($cons->fields['consrc'] !== null) {
219
                $sql .= $cons->fields['consrc'];
220
            } else {
221
                switch ($cons->fields['contype']) {
222
                    case 'p':
223
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
224
                        $sql .= 'PRIMARY KEY (' . join(',', $keys) . ')';
225
226
                        break;
227
                    case 'u':
228
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
229
                        $sql .= 'UNIQUE (' . join(',', $keys) . ')';
230
231
                        break;
232
                    default:
233
                        // Unrecognised constraint
234
                        $this->rollbackTransaction();
235
236
                        return null;
237
                }
238
            }
239
240
            // Output comma or not
241
            if ($i < $num) {
242
                $sql .= ",\n";
243
            } else {
244
                $sql .= "\n";
245
            }
246
247
            $cons->moveNext();
248
            ++$i;
249
        }
250
251
        $sql .= ')';
252
253
        // @@@@ DUMP CLUSTERING INFORMATION
254
255
        // Inherits
256
        /*
257
         * XXX: This is currently commented out as handling inheritance isn't this simple.
258
         * You also need to make sure you don't dump inherited columns and defaults, as well
259
         * as inherited NOT NULL and CHECK constraints.  So for the time being, we just do
260
         * not claim to support inheritance.
261
        $parents = $this->getTableParents($table);
262
        if ($parents->recordCount() > 0) {
263
        $sql .= " INHERITS (";
264
        while (!$parents->EOF) {
265
        $this->fieldClean($parents->fields['relname']);
266
        // Qualify the parent table if it's in another schema
267
        if ($parents->fields['schemaname'] != $this->_schema) {
268
        $this->fieldClean($parents->fields['schemaname']);
269
        $sql .= "\"{$parents->fields['schemaname']}\".";
270
        }
271
        $sql .= "\"{$parents->fields['relname']}\"";
272
273
        $parents->moveNext();
274
        if (!$parents->EOF) $sql .= ', ';
275
        }
276
        $sql .= ")";
277
        }
278
         */
279
280
        // Handle WITHOUT OIDS
281
        if ($this->hasObjectID($table)) {
282
            $sql .= ' WITH OIDS';
283
        } else {
284
            $sql .= ' WITHOUT OIDS';
285
        }
286
287
        $sql .= ";\n";
288
289
        // Column storage and statistics
290
        $atts->moveFirst();
291
        $first = true;
292
        while (!$atts->EOF) {
293
            $this->fieldClean($atts->fields['attname']);
294
            // Statistics first
295
            if ($atts->fields['attstattarget'] >= 0) {
296
                if ($first) {
297
                    $sql .= "\n";
298
                    $first = false;
299
                }
300
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
301
            }
302
            // Then storage
303
            if ($atts->fields['attstorage'] != $atts->fields['typstorage']) {
304
                switch ($atts->fields['attstorage']) {
305
                    case 'p':
306
                        $storage = 'PLAIN';
307
308
                        break;
309
                    case 'e':
310
                        $storage = 'EXTERNAL';
311
312
                        break;
313
                    case 'm':
314
                        $storage = 'MAIN';
315
316
                        break;
317
                    case 'x':
318
                        $storage = 'EXTENDED';
319
320
                        break;
321
                    default:
322
                        // Unknown storage type
323
                        $this->rollbackTransaction();
324
325
                        return null;
326
                }
327
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
328
            }
329
330
            $atts->moveNext();
331
        }
332
333
        // Comment
334
        if ($t->fields['relcomment'] !== null) {
335
            $this->clean($t->fields['relcomment']);
336
            $sql .= "\n-- Comment\n\n";
337
            $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n";
338
        }
339
340
        // Add comments on columns, if any
341
        if ($col_comments_sql != '') {
342
            $sql .= $col_comments_sql;
343
        }
344
345
        // Privileges
346
        $privs = $this->getPrivileges($table, 'table');
347
        if (!is_array($privs)) {
348
            $this->rollbackTransaction();
349
350
            return null;
351
        }
352
353
        if (sizeof($privs) > 0) {
354
            $sql .= "\n-- Privileges\n\n";
355
            /*
356
             * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
357
             * wire-in knowledge about the default public privileges for different
358
             * kinds of objects.
359
             */
360
            $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n";
361
            foreach ($privs as $v) {
362
                // Get non-GRANT OPTION privs
363
                $nongrant = array_diff($v[2], $v[4]);
364
365
                // Skip empty or owner ACEs
366
                if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) {
367
                    continue;
368
                }
369
370
                // Change user if necessary
371
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
372
                    $grantor = $v[3];
373
                    $this->clean($grantor);
374
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
375
                }
376
377
                // Output privileges with no GRANT OPTION
378
                $sql .= 'GRANT ' . join(', ', $nongrant) . " ON TABLE \"{$t->fields['relname']}\" TO ";
379
                switch ($v[0]) {
380
                    case 'public':
381
                        $sql .= "PUBLIC;\n";
382
383
                        break;
384
                    case 'user':
385
                        $this->fieldClean($v[1]);
386
                        $sql .= "\"{$v[1]}\";\n";
387
388
                        break;
389
                    case 'group':
390
                        $this->fieldClean($v[1]);
391
                        $sql .= "GROUP \"{$v[1]}\";\n";
392
393
                        break;
394
                    default:
395
                        // Unknown privilege type - fail
396
                        $this->rollbackTransaction();
397
398
                        return null;
399
                }
400
401
                // Reset user if necessary
402
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
403
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
404
                }
405
406
                // Output privileges with GRANT OPTION
407
408
                // Skip empty or owner ACEs
409
                if (!$this->hasGrantOption() || sizeof($v[4]) == 0) {
410
                    continue;
411
                }
412
413
                // Change user if necessary
414
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
415
                    $grantor = $v[3];
416
                    $this->clean($grantor);
417
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
418
                }
419
420
                $sql .= 'GRANT ' . join(', ', $v[4]) . " ON \"{$t->fields['relname']}\" TO ";
421
                switch ($v[0]) {
422
                    case 'public':
423
                        $sql .= 'PUBLIC';
424
425
                        break;
426
                    case 'user':
427
                        $this->fieldClean($v[1]);
428
                        $sql .= "\"{$v[1]}\"";
429
430
                        break;
431
                    case 'group':
432
                        $this->fieldClean($v[1]);
433
                        $sql .= "GROUP \"{$v[1]}\"";
434
435
                        break;
436
                    default:
437
                        // Unknown privilege type - fail
438
                        return null;
439
                }
440
                $sql .= " WITH GRANT OPTION;\n";
441
442
                // Reset user if necessary
443
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
444
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
445
                }
446
            }
447
        }
448
449
        // Add a newline to separate data that follows (if any)
450
        $sql .= "\n";
451
452
        return $sql;
453
    }
454
455
    /**
456
     * Returns table information.
457
     *
458
     * @param $table The name of the table
459
     *
460
     * @return \PHPPgAdmin\ADORecordSet A recordset
461
     */
462
    public function getTable($table)
463
    {
464
        $c_schema = $this->_schema;
465
        $this->clean($c_schema);
466
        $this->clean($table);
467
468
        $sql = "
469
            SELECT
470
              c.relname, n.nspname, u.usename AS relowner,
471
              pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
472
              (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
473
            FROM pg_catalog.pg_class c
474
                 LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
475
                 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
476
            WHERE c.relkind = 'r'
477
                  AND n.nspname = '{$c_schema}'
478
                  AND n.oid = c.relnamespace
479
                  AND c.relname = '{$table}'";
480
481
        return $this->selectSet($sql);
482
    }
483
484
    /**
485
     * Retrieve the attribute definition of a table.
486
     *
487
     * @param $table The name of the table
488
     * @param $field (optional) The name of a field to return
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\optional was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
489
     *
490
     * @return All attributes in order
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\All was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
491
     */
492
    public function getTableAttributes($table, $field = '')
493
    {
494
        $c_schema = $this->_schema;
495
        $this->clean($c_schema);
496
        $this->clean($table);
497
        $this->clean($field);
498
499
        if ($field == '') {
500
            // This query is made much more complex by the addition of the 'attisserial' field.
501
            // The subquery to get that field checks to see if there is an internally dependent
502
            // sequence on the field.
503
            $sql = "
504
                SELECT
505
                    a.attname, a.attnum,
506
                    pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
507
                    a.atttypmod,
508
                    a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
509
                    a.attstattarget, a.attstorage, t.typstorage,
510
                    (
511
                        SELECT 1 FROM pg_catalog.pg_depend pd, pg_catalog.pg_class pc
512
                        WHERE pd.objid=pc.oid
513
                        AND pd.classid=pc.tableoid
514
                        AND pd.refclassid=pc.tableoid
515
                        AND pd.refobjid=a.attrelid
516
                        AND pd.refobjsubid=a.attnum
517
                        AND pd.deptype='i'
518
                        AND pc.relkind='S'
519
                    ) IS NOT NULL AS attisserial,
520
                    pg_catalog.col_description(a.attrelid, a.attnum) AS comment
521
                FROM
522
                    pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
523
                    ON a.attrelid=adef.adrelid
524
                    AND a.attnum=adef.adnum
525
                    LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
526
                WHERE
527
                    a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
528
                        AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
529
                        nspname = '{$c_schema}'))
530
                    AND a.attnum > 0 AND NOT a.attisdropped
531
                ORDER BY a.attnum";
532
        } else {
533
            $sql = "
534
                SELECT
535
                    a.attname, a.attnum,
536
                    pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
537
                    pg_catalog.format_type(a.atttypid, NULL) as base_type,
538
                    a.atttypmod,
539
                    a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
540
                    a.attstattarget, a.attstorage, t.typstorage,
541
                    pg_catalog.col_description(a.attrelid, a.attnum) AS comment
542
                FROM
543
                    pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
544
                    ON a.attrelid=adef.adrelid
545
                    AND a.attnum=adef.adnum
546
                    LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
547
                WHERE
548
                    a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
549
                        AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
550
                        nspname = '{$c_schema}'))
551
                    AND a.attname = '{$field}'";
552
        }
553
554
        return $this->selectSet($sql);
555
    }
556
557
    /**
558
     * Returns a list of all constraints on a table.
559
     *
560
     * @param $table The table to find rules for
561
     *
562
     * @return \PHPPgAdmin\ADORecordSet A recordset
563
     */
564
    public function getConstraints($table)
565
    {
566
        $c_schema = $this->_schema;
567
        $this->clean($c_schema);
568
        $this->clean($table);
569
570
        // This SQL is greatly complicated by the need to retrieve
571
        // index clustering information for primary and unique constraints
572
        $sql = "SELECT
573
                pc.conname,
574
                pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc,
575
                pc.contype,
576
                CASE WHEN pc.contype='u' OR pc.contype='p' THEN (
577
                    SELECT
578
                        indisclustered
579
                    FROM
580
                        pg_catalog.pg_depend pd,
581
                        pg_catalog.pg_class pl,
582
                        pg_catalog.pg_index pi
583
                    WHERE
584
                        pd.refclassid=pc.tableoid
585
                        AND pd.refobjid=pc.oid
586
                        AND pd.objid=pl.oid
587
                        AND pl.oid=pi.indexrelid
588
                ) ELSE
589
                    NULL
590
                END AS indisclustered
591
            FROM
592
                pg_catalog.pg_constraint pc
593
            WHERE
594
                pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
595
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
596
                    WHERE nspname='{$c_schema}'))
597
            ORDER BY
598
                1
599
        ";
600
601
        return $this->selectSet($sql);
602
    }
603
604
    /**
605
     * Checks to see whether or not a table has a unique id column.
606
     *
607
     * @param $table The table name
608
     *
609
     * @return true if it has a unique id, false otherwise
610
     */
611
    public function hasObjectID($table)
612
    {
613
        $c_schema = $this->_schema;
614
        $this->clean($c_schema);
615
        $this->clean($table);
616
617
        $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}'
618
            AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')";
619
620
        $rs = $this->selectSet($sql);
621
        if ($rs->recordCount() != 1) {
622
            return null;
623
        }
624
625
        $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']);
0 ignored issues
show
Bug introduced by
It seems like phpBool() 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

625
        /** @scrutinizer ignore-call */ 
626
        $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']);
Loading history...
626
627
        return $rs->fields['relhasoids'];
628
    }
629
630
    /**
631
     * Returns extra table definition information that is most usefully
632
     * dumped after the table contents for speed and efficiency reasons.
633
     *
634
     * @param $table The table to define
635
     *
636
     * @return A string containing the formatted SQL code
637
     */
638
    public function getTableDefSuffix($table)
639
    {
640
        $sql = '';
641
642
        // Indexes
643
        $indexes = $this->getIndexes($table);
644
        if (!is_object($indexes)) {
645
            $this->rollbackTransaction();
646
647
            return null;
648
        }
649
650
        if ($indexes->recordCount() > 0) {
651
            $sql .= "\n-- Indexes\n\n";
652
            while (!$indexes->EOF) {
653
                $sql .= $indexes->fields['inddef'] . ";\n";
654
655
                $indexes->moveNext();
656
            }
657
        }
658
659
        // Triggers
660
        $triggers = $this->getTriggers($table);
661
        if (!is_object($triggers)) {
662
            $this->rollbackTransaction();
663
664
            return null;
665
        }
666
667
        if ($triggers->recordCount() > 0) {
668
            $sql .= "\n-- Triggers\n\n";
669
            while (!$triggers->EOF) {
670
                $sql .= $triggers->fields['tgdef'];
671
                $sql .= ";\n";
672
673
                $triggers->moveNext();
674
            }
675
        }
676
677
        // Rules
678
        $rules = $this->getRules($table);
679
        if (!is_object($rules)) {
680
            $this->rollbackTransaction();
681
682
            return null;
683
        }
684
685
        if ($rules->recordCount() > 0) {
686
            $sql .= "\n-- Rules\n\n";
687
            while (!$rules->EOF) {
688
                $sql .= $rules->fields['definition'] . "\n";
689
690
                $rules->moveNext();
691
            }
692
        }
693
694
        return $sql;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sql returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\A.
Loading history...
695
    }
696
697
    /**
698
     * Grabs a list of indexes for a table.
699
     *
700
     * @param string                         $table  The name of a table whose indexes to retrieve
701
     * @param bool|\PHPPgAdmin\Database\Only $unique Only get unique/pk indexes
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Only was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
702
     *
703
     * @return \PHPPgAdmin\ADORecordSet A recordset
704
     */
705
    public function getIndexes($table = '', $unique = false)
706
    {
707
        $this->clean($table);
708
709
        $sql = "
710
            SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
711
                pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef
712
            FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
713
            WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid)
714
                AND c.oid = i.indrelid AND i.indexrelid = c2.oid
715
        ";
716
        if ($unique) {
717
            $sql .= ' AND i.indisunique ';
718
        }
719
720
        $sql .= ' ORDER BY c2.relname';
721
722
        return $this->selectSet($sql);
723
    }
724
725
    /**
726
     * Grabs a list of triggers on a table.
727
     *
728
     * @param string $table The name of a table whose triggers to retrieve
729
     *
730
     * @return \PHPPgAdmin\ADORecordSet A recordset
731
     */
732
    public function getTriggers($table = '')
733
    {
734
        $c_schema = $this->_schema;
735
        $this->clean($c_schema);
736
        $this->clean($table);
737
738
        $sql = "SELECT
739
                t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef,
740
                CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid,
741
                p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
742
                ns.nspname AS pronamespace
743
            FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns
744
            WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
745
                AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
746
                AND ( tgconstraint = 0 OR NOT EXISTS
747
                        (SELECT 1 FROM pg_catalog.pg_depend d    JOIN pg_catalog.pg_constraint c
748
                            ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
749
                        WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))
750
                AND p.oid=t.tgfoid
751
                AND p.pronamespace = ns.oid";
752
753
        return $this->selectSet($sql);
754
    }
755
756
    /**
757
     * Returns a list of all rules on a table OR view.
758
     *
759
     * @param $table The table to find rules for
760
     *
761
     * @return \PHPPgAdmin\ADORecordSet A recordset
762
     */
763
    public function getRules($table)
764
    {
765
        $c_schema = $this->_schema;
766
        $this->clean($c_schema);
767
        $this->clean($table);
768
769
        $sql = "
770
            SELECT *
771
            FROM pg_catalog.pg_rules
772
            WHERE
773
                schemaname='{$c_schema}' AND tablename='{$table}'
774
            ORDER BY rulename
775
        ";
776
777
        return $this->selectSet($sql);
778
    }
779
780
    /**
781
     * Creates a new table in the database.
782
     *
783
     * @param $name        The name of the table
784
     * @param $fields      The number of fields
785
     * @param $field       An array of field names
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\An was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
786
     * @param $type        An array of field types
787
     * @param $array       An array of '' or '[]' for each type if it's an array or not
788
     * @param $length      An array of field lengths
789
     * @param $notnull     An array of not null
790
     * @param $default     An array of default values
791
     * @param $withoutoids True if WITHOUT OIDS, false otherwise
792
     * @param $colcomment  An array of comments
793
     * @param $tblcomment
794
     * @param $tablespace  The tablespace name ('' means none/default)
795
     * @param $uniquekey   An Array indicating the fields that are unique (those indexes that are set)
796
     * @param $primarykey  An Array indicating the field used for the primarykey (those indexes that are set)
797
     *
798
     * @return bool|int 0 success
799
     *
800
     * @internal param \PHPPgAdmin\Database\Table $comment comment
801
     */
802
    public function createTable(
803
        $name,
804
        $fields,
805
        $field,
806
        $type,
807
        $array,
808
        $length,
809
        $notnull,
810
        $default,
811
        $withoutoids,
812
        $colcomment,
813
        $tblcomment,
814
        $tablespace,
815
        $uniquekey,
816
        $primarykey
817
    ) {
818
        $f_schema = $this->_schema;
819
        $this->fieldClean($f_schema);
0 ignored issues
show
Bug introduced by
It seems like fieldClean() 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

819
        $this->/** @scrutinizer ignore-call */ 
820
               fieldClean($f_schema);
Loading history...
820
        $this->fieldClean($name);
821
822
        $status = $this->beginTransaction();
0 ignored issues
show
Bug introduced by
It seems like beginTransaction() 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

822
        /** @scrutinizer ignore-call */ 
823
        $status = $this->beginTransaction();
Loading history...
823
        if ($status != 0) {
824
            return -1;
825
        }
826
827
        $found       = false;
828
        $first       = true;
829
        $comment_sql = ''; //Accumulate comments for the columns
830
        $sql         = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (";
831
        for ($i = 0; $i < $fields; ++$i) {
832
            $this->fieldClean($field[$i]);
833
            $this->clean($type[$i]);
834
            $this->clean($length[$i]);
835
            $this->clean($colcomment[$i]);
836
837
            // Skip blank columns - for user convenience
838
            if ($field[$i] == '' || $type[$i] == '') {
839
                continue;
840
            }
841
842
            // If not the first column, add a comma
843
            if (!$first) {
844
                $sql .= ', ';
845
            } else {
846
                $first = false;
847
            }
848
849
            switch ($type[$i]) {
850
                // Have to account for weird placing of length for with/without
851
                // time zone types
852
                case 'timestamp with time zone':
853
                case 'timestamp without time zone':
854
                    $qual = substr($type[$i], 9);
855
                    $sql .= "\"{$field[$i]}\" timestamp";
856
                    if ($length[$i] != '') {
857
                        $sql .= "({$length[$i]})";
858
                    }
859
860
                    $sql .= $qual;
861
862
                    break;
863
                case 'time with time zone':
864
                case 'time without time zone':
865
                    $qual = substr($type[$i], 4);
866
                    $sql .= "\"{$field[$i]}\" time";
867
                    if ($length[$i] != '') {
868
                        $sql .= "({$length[$i]})";
869
                    }
870
871
                    $sql .= $qual;
872
873
                    break;
874
                default:
875
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
876
                    if ($length[$i] != '') {
877
                        $sql .= "({$length[$i]})";
878
                    }
879
            }
880
            // Add array qualifier if necessary
881
            if ($array[$i] == '[]') {
882
                $sql .= '[]';
883
            }
884
885
            // Add other qualifiers
886
            if (!isset($primarykey[$i])) {
887
                if (isset($uniquekey[$i])) {
888
                    $sql .= ' UNIQUE';
889
                }
890
891
                if (isset($notnull[$i])) {
892
                    $sql .= ' NOT NULL';
893
                }
894
            }
895
            if ($default[$i] != '') {
896
                $sql .= " DEFAULT {$default[$i]}";
897
            }
898
899
            if ($colcomment[$i] != '') {
900
                $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
901
            }
902
903
            $found = true;
904
        }
905
906
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
907
            return -1;
908
        }
909
910
        // PRIMARY KEY
911
        $primarykeycolumns = [];
912
        for ($i = 0; $i < $fields; ++$i) {
913
            if (isset($primarykey[$i])) {
914
                $primarykeycolumns[] = "\"{$field[$i]}\"";
915
            }
916
        }
917
        if (count($primarykeycolumns) > 0) {
918
            $sql .= ', PRIMARY KEY (' . implode(', ', $primarykeycolumns) . ')';
919
        }
920
921
        $sql .= ')';
922
923
        // WITHOUT OIDS
924
        if ($withoutoids) {
925
            $sql .= ' WITHOUT OIDS';
926
        } else {
927
            $sql .= ' WITH OIDS';
928
        }
929
930
        // Tablespace
931
        if ($this->hasTablespaces() && $tablespace != '') {
932
            $this->fieldClean($tablespace);
933
            $sql .= " TABLESPACE \"{$tablespace}\"";
934
        }
935
936
        $status = $this->execute($sql);
937
        if ($status) {
938
            $this->rollbackTransaction();
939
940
            return -1;
941
        }
942
943
        if ($tblcomment != '') {
944
            $status = $this->setComment('TABLE', '', $name, $tblcomment, true);
945
            if ($status) {
946
                $this->rollbackTransaction();
947
948
                return -1;
949
            }
950
        }
951
952
        if ($comment_sql != '') {
953
            $status = $this->execute($comment_sql);
954
            if ($status) {
955
                $this->rollbackTransaction();
956
957
                return -1;
958
            }
959
        }
960
961
        return $this->endTransaction();
962
    }
963
964
    /**
965
     * Creates a new table in the database copying attribs and other properties from another table.
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\an was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
966
     *
967
     * @param        $name        The name of the table
968
     * @param        $like        an array giving the schema ans the name of the table from which attribs are copying
969
     *                            from: array(
970
     *                            'table' => table name,
971
     *                            'schema' => the schema name,
972
     *                            )
973
     * @param bool   $defaults    if true, copy the defaults values as well
974
     * @param bool   $constraints if true, copy the constraints as well (CHECK on table & attr)
975
     * @param bool   $idx
976
     * @param string $tablespace  The tablespace name ('' means none/default)
977
     *
978
     * @return bool|int
979
     */
980
    public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '')
981
    {
982
        $f_schema = $this->_schema;
983
        $this->fieldClean($f_schema);
984
        $this->fieldClean($name);
985
        $this->fieldClean($like['schema']);
986
        $this->fieldClean($like['table']);
987
        $like = "\"{$like['schema']}\".\"{$like['table']}\"";
988
989
        $status = $this->beginTransaction();
990
        if ($status != 0) {
991
            return -1;
992
        }
993
994
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}";
995
996
        if ($defaults) {
997
            $sql .= ' INCLUDING DEFAULTS';
998
        }
999
1000
        if ($this->hasCreateTableLikeWithConstraints() && $constraints) {
0 ignored issues
show
Bug introduced by
It seems like hasCreateTableLikeWithConstraints() 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

1000
        if ($this->/** @scrutinizer ignore-call */ hasCreateTableLikeWithConstraints() && $constraints) {
Loading history...
1001
            $sql .= ' INCLUDING CONSTRAINTS';
1002
        }
1003
1004
        if ($this->hasCreateTableLikeWithIndexes() && $idx) {
0 ignored issues
show
Bug introduced by
It seems like hasCreateTableLikeWithIndexes() 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

1004
        if ($this->/** @scrutinizer ignore-call */ hasCreateTableLikeWithIndexes() && $idx) {
Loading history...
1005
            $sql .= ' INCLUDING INDEXES';
1006
        }
1007
1008
        $sql .= ')';
1009
1010
        if ($this->hasTablespaces() && $tablespace != '') {
0 ignored issues
show
Bug introduced by
It seems like hasTablespaces() 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

1010
        if ($this->/** @scrutinizer ignore-call */ hasTablespaces() && $tablespace != '') {
Loading history...
1011
            $this->fieldClean($tablespace);
1012
            $sql .= " TABLESPACE \"{$tablespace}\"";
1013
        }
1014
1015
        $status = $this->execute($sql);
0 ignored issues
show
Bug introduced by
It seems like execute() 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

1015
        /** @scrutinizer ignore-call */ 
1016
        $status = $this->execute($sql);
Loading history...
1016
        if ($status) {
1017
            $this->rollbackTransaction();
1018
1019
            return -1;
1020
        }
1021
1022
        return $this->endTransaction();
0 ignored issues
show
Bug introduced by
It seems like endTransaction() 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

1022
        return $this->/** @scrutinizer ignore-call */ endTransaction();
Loading history...
1023
    }
1024
1025
    /**
1026
     * Alter table properties.
1027
     *
1028
     * @param $table      The name of the table
1029
     * @param $name       The new name for the table
1030
     * @param $owner      The new owner for the table
1031
     * @param $schema     The new schema for the table
1032
     * @param $comment    The comment on the table
1033
     * @param $tablespace The new tablespace for the table ('' means leave as is)
1034
     *
1035
     * @return bool|int 0 success
1036
     */
1037
    public function alterTable($table, $name, $owner, $schema, $comment, $tablespace)
1038
    {
1039
        $data = $this->getTable($table);
1040
1041
        if ($data->recordCount() != 1) {
0 ignored issues
show
introduced by
The condition $data->recordCount() != 1 is always true.
Loading history...
1042
            return -2;
1043
        }
1044
1045
        $status = $this->beginTransaction();
1046
        if ($status != 0) {
1047
            $this->rollbackTransaction();
1048
1049
            return -1;
1050
        }
1051
1052
        $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace);
1053
1054
        if ($status != 0) {
1055
            $this->rollbackTransaction();
1056
1057
            return $status;
1058
        }
1059
1060
        return $this->endTransaction();
1061
    }
1062
1063
    /**
1064
     * Protected method which alter a table
1065
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION.
1066
     *
1067
     * @param $tblrs      The table recordSet returned by getTable()
1068
     * @param $name       The new name for the table
1069
     * @param $owner      The new owner for the table
1070
     * @param $schema     The new schema for the table
1071
     * @param $comment    The comment on the table
1072
     * @param $tablespace The new tablespace for the table ('' means leave as is)
1073
     *
1074
     * @return int 0 success
1075
     */
1076
    protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace)
1 ignored issue
show
Coding Style introduced by
Protected method name "TableTrait::_alterTable" must not be prefixed with an underscore
Loading history...
1077
    {
1078
        $this->fieldArrayClean($tblrs->fields);
0 ignored issues
show
Bug introduced by
It seems like fieldArrayClean() 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

1078
        $this->/** @scrutinizer ignore-call */ 
1079
               fieldArrayClean($tblrs->fields);
Loading history...
1079
1080
        // Comment
1081
        $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment);
0 ignored issues
show
Bug introduced by
It seems like setComment() 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

1081
        /** @scrutinizer ignore-call */ 
1082
        $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment);
Loading history...
1082
        if ($status != 0) {
1083
            return -4;
1084
        }
1085
1086
        // Owner
1087
        $this->fieldClean($owner);
1088
        $status = $this->alterTableOwner($tblrs, $owner);
1089
        if ($status != 0) {
1090
            return -5;
1091
        }
1092
1093
        // Tablespace
1094
        $this->fieldClean($tablespace);
1095
        $status = $this->alterTableTablespace($tblrs, $tablespace);
1096
        if ($status != 0) {
1097
            return -6;
1098
        }
1099
1100
        // Rename
1101
        $this->fieldClean($name);
1102
        $status = $this->alterTableName($tblrs, $name);
1103
        if ($status != 0) {
1104
            return -3;
1105
        }
1106
1107
        // Schema
1108
        $this->fieldClean($schema);
1109
        $status = $this->alterTableSchema($tblrs, $schema);
1110
        if ($status != 0) {
1111
            return -7;
1112
        }
1113
1114
        return 0;
1115
    }
1116
1117
    /**
1118
     * Alter a table's owner
1119
     * /!\ this function is called from _alterTable which take care of escaping fields.
1120
     *
1121
     * @param      $tblrs The table RecordSet returned by getTable()
1122
     * @param null $owner
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $owner is correct as it would always require null to be passed?
Loading history...
1123
     *
1124
     * @return int 0 if operation was successful
1125
     *
1126
     * @internal param \PHPPgAdmin\Database\The $name new table's owner
1127
     */
1128
    public function alterTableOwner($tblrs, $owner = null)
1129
    {
1130
        /* vars cleaned in _alterTable */
1131
        if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) {
1132
            $f_schema = $this->_schema;
1133
            $this->fieldClean($f_schema);
1134
            // If owner has been changed, then do the alteration.  We are
1135
            // careful to avoid this generally as changing owner is a
1136
            // superuser only function.
1137
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\"";
1138
1139
            return $this->execute($sql);
1140
        }
1141
1142
        return 0;
1143
    }
1144
1145
    /**
1146
     * Alter a table's tablespace
1147
     * /!\ this function is called from _alterTable which take care of escaping fields.
1148
     *
1149
     * @param      $tblrs      The table RecordSet returned by getTable()
1150
     * @param null $tablespace
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $tablespace is correct as it would always require null to be passed?
Loading history...
1151
     *
1152
     * @return int 0 if operation was successful
1153
     *
1154
     * @internal param \PHPPgAdmin\Database\The $name new table's tablespace
1155
     */
1156
    public function alterTableTablespace($tblrs, $tablespace = null)
1157
    {
1158
        /* vars cleaned in _alterTable */
1159
        if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) {
1160
            $f_schema = $this->_schema;
1161
            $this->fieldClean($f_schema);
1162
1163
            // If tablespace has been changed, then do the alteration.  We
1164
            // don't want to do this unnecessarily.
1165
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\"";
1166
1167
            return $this->execute($sql);
1168
        }
1169
1170
        return 0;
1171
    }
1172
1173
    /**
1174
     * Alter a table's name
1175
     * /!\ this function is called from _alterTable which take care of escaping fields.
1176
     *
1177
     * @param $tblrs The table RecordSet returned by getTable()
1178
     * @param $name  The new table's name
1179
     *
1180
     * @return int 0 if operation was successful
1181
     */
1182
    public function alterTableName($tblrs, $name = null)
1183
    {
1184
        /* vars cleaned in _alterTable */
1185
        // Rename (only if name has changed)
1186
        if (!empty($name) && ($name != $tblrs->fields['relname'])) {
1187
            $f_schema = $this->_schema;
1188
            $this->fieldClean($f_schema);
1189
1190
            $sql    = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" RENAME TO \"{$name}\"";
1191
            $status = $this->execute($sql);
1192
            if ($status == 0) {
1193
                $tblrs->fields['relname'] = $name;
1194
            } else {
1195
                return $status;
1196
            }
1197
        }
1198
1199
        return 0;
1200
    }
1201
1202
    // Row functions
1203
1204
    /**
1205
     * Alter a table's schema
1206
     * /!\ this function is called from _alterTable which take care of escaping fields.
1207
     *
1208
     * @param      $tblrs  The table RecordSet returned by getTable()
1209
     * @param null $schema
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $schema is correct as it would always require null to be passed?
Loading history...
1210
     *
1211
     * @return int 0 if operation was successful
1212
     *
1213
     * @internal param \PHPPgAdmin\Database\The $name new table's schema
1214
     */
1215
    public function alterTableSchema($tblrs, $schema = null)
1216
    {
1217
        /* vars cleaned in _alterTable */
1218
        if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) {
1219
            $f_schema = $this->_schema;
1220
            $this->fieldClean($f_schema);
1221
            // If tablespace has been changed, then do the alteration.  We
1222
            // don't want to do this unnecessarily.
1223
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET SCHEMA \"{$schema}\"";
1224
1225
            return $this->execute($sql);
1226
        }
1227
1228
        return 0;
1229
    }
1230
1231
    /**
1232
     * Empties a table in the database.
1233
     *
1234
     * @param $table The table to be emptied
1235
     * @param $cascade True to cascade truncate, false to restrict
1236
     *
1237
     * @return int 0 if operation was successful
1238
     */
1239
    public function emptyTable($table, $cascade)
1240
    {
1241
        $f_schema = $this->_schema;
1242
        $this->fieldClean($f_schema);
1243
        $this->fieldClean($table);
1244
1245
        $sql = "TRUNCATE TABLE \"{$f_schema}\".\"{$table}\" ";
1246
        if ($cascade) {
1247
            $sql = $sql . ' CASCADE';
1248
        }
1249
1250
        $status = $this->execute($sql);
1251
1252
        return [$status, $sql];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($status, $sql) returns the type array<integer,mixed|string> which is incompatible with the documented return type integer.
Loading history...
1253
    }
1254
1255
    /**
1256
     * Removes a table from the database.
1257
     *
1258
     * @param $table   The table to drop
1259
     * @param $cascade True to cascade drop, false to restrict
1260
     *
1261
     * @return int 0 if operation was successful
1262
     */
1263
    public function dropTable($table, $cascade)
1264
    {
1265
        $f_schema = $this->_schema;
1266
        $this->fieldClean($f_schema);
1267
        $this->fieldClean($table);
1268
1269
        $sql = "DROP TABLE \"{$f_schema}\".\"{$table}\"";
1270
        if ($cascade) {
1271
            $sql .= ' CASCADE';
1272
        }
1273
1274
        return $this->execute($sql);
1275
    }
1276
1277
    /**
1278
     * Add a new column to a table.
1279
     *
1280
     * @param $table   The table to add to
1281
     * @param $column  The name of the new column
1282
     * @param $type    The type of the column
1283
     * @param $array   True if array type, false otherwise
1284
     * @param $length  The optional size of the column (ie. 30 for varchar(30))
1285
     * @param $notnull True if NOT NULL, false otherwise
1286
     * @param $default The default for the column.  '' for none.
1287
     * @param $comment
1288
     *
1289
     * @return bool|int 0 success
1290
     */
1291
    public function addColumn($table, $column, $type, $array, $length, $notnull, $default, $comment)
1292
    {
1293
        $f_schema = $this->_schema;
1294
        $this->fieldClean($f_schema);
1295
        $this->fieldClean($table);
1296
        $this->fieldClean($column);
1297
        $this->clean($type);
1298
        $this->clean($length);
1299
1300
        if ($length == '') {
1301
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}";
1302
        } else {
1303
            switch ($type) {
1304
                // Have to account for weird placing of length for with/without
1305
                // time zone types
1306
                case 'timestamp with time zone':
1307
                case 'timestamp without time zone':
1308
                    $qual = substr($type, 9);
1309
                    $sql  = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" timestamp({$length}){$qual}";
1310
1311
                    break;
1312
                case 'time with time zone':
1313
                case 'time without time zone':
1314
                    $qual = substr($type, 4);
1315
                    $sql  = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" time({$length}){$qual}";
1316
1317
                    break;
1318
                default:
1319
                    $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}({$length})";
1320
            }
1321
        }
1322
1323
        // Add array qualifier, if requested
1324
        if ($array) {
1325
            $sql .= '[]';
1326
        }
1327
1328
        // If we have advanced column adding, add the extra qualifiers
1329
        if ($this->hasCreateFieldWithConstraints()) {
0 ignored issues
show
Bug introduced by
It seems like hasCreateFieldWithConstraints() 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

1329
        if ($this->/** @scrutinizer ignore-call */ hasCreateFieldWithConstraints()) {
Loading history...
1330
            // NOT NULL clause
1331
            if ($notnull) {
1332
                $sql .= ' NOT NULL';
1333
            }
1334
1335
            // DEFAULT clause
1336
            if ($default != '') {
1337
                $sql .= ' DEFAULT ' . $default;
1338
            }
1339
        }
1340
1341
        $status = $this->beginTransaction();
1342
        if ($status != 0) {
1343
            return -1;
1344
        }
1345
1346
        $status = $this->execute($sql);
1347
        if ($status != 0) {
1348
            $this->rollbackTransaction();
1349
1350
            return -1;
1351
        }
1352
1353
        $status = $this->setComment('COLUMN', $column, $table, $comment);
1354
        if ($status != 0) {
1355
            $this->rollbackTransaction();
1356
1357
            return -1;
1358
        }
1359
1360
        return $this->endTransaction();
1361
    }
1362
1363
    /**
1364
     * Alters a column in a table.
1365
     *
1366
     * @param $table      The table in which the column resides
1367
     * @param $column     The column to alter
1368
     * @param $name       The new name for the column
1369
     * @param $notnull    (boolean) True if not null, false otherwise
1370
     * @param $oldnotnull (boolean) True if column is already not null, false otherwise
1371
     * @param $default    The new default for the column
1372
     * @param $olddefault The old default for the column
1373
     * @param $type       The new type for the column
1374
     * @param $length     The optional size of the column (ie. 30 for varchar(30))
1375
     * @param $array      True if array type, false otherwise
1376
     * @param $oldtype    The old type for the column
1377
     * @param $comment    Comment for the column
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\Comment was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1378
     *
1379
     * @return array 0 success
1380
     */
1381
    public function alterColumn(
1382
        $table,
1383
        $column,
1384
        $name,
1385
        $notnull,
1386
        $oldnotnull,
1387
        $default,
1388
        $olddefault,
1389
        $type,
1390
        $length,
1391
        $array,
1392
        $oldtype,
1393
        $comment
1394
    ) {
1395
        // Begin transaction
1396
        $status = $this->beginTransaction();
1397
        $sql    = '';
1398
        if ($status != 0) {
1399
            $this->rollbackTransaction();
1400
1401
            return [-6, $sql];
1402
        }
1403
1404
        // Rename the column, if it has been changed
1405
        if ($column != $name) {
1406
            $status = $this->renameColumn($table, $column, $name);
1407
            if ($status != 0) {
1408
                $this->rollbackTransaction();
1409
1410
                return [-4, $sql];
1411
            }
1412
        }
1413
1414
        $f_schema = $this->_schema;
1415
        $this->fieldClean($f_schema);
1416
        $this->fieldClean($name);
1417
        $this->fieldClean($table);
1418
        $this->fieldClean($column);
1419
1420
        $toAlter = [];
1421
        // Create the command for changing nullability
1422
        if ($notnull != $oldnotnull) {
1423
            $toAlter[] = "ALTER COLUMN \"{$name}\" " . ($notnull ? 'SET' : 'DROP') . ' NOT NULL';
1424
        }
1425
1426
        // Add default, if it has changed
1427
        if ($default != $olddefault) {
1428
            if ($default == '') {
1429
                $toAlter[] = "ALTER COLUMN \"{$name}\" DROP DEFAULT";
1430
            } else {
1431
                $toAlter[] = "ALTER COLUMN \"{$name}\" SET DEFAULT {$default}";
1432
            }
1433
        }
1434
1435
        // Add type, if it has changed
1436
        if ($length == '') {
1437
            $ftype = $type;
1438
        } else {
1439
            switch ($type) {
1440
                // Have to account for weird placing of length for with/without
1441
                // time zone types
1442
                case 'timestamp with time zone':
1443
                case 'timestamp without time zone':
1444
                    $qual  = substr($type, 9);
1445
                    $ftype = "timestamp({$length}){$qual}";
1446
1447
                    break;
1448
                case 'time with time zone':
1449
                case 'time without time zone':
1450
                    $qual  = substr($type, 4);
1451
                    $ftype = "time({$length}){$qual}";
1452
1453
                    break;
1454
                default:
1455
                    $ftype = "{$type}({$length})";
1456
            }
1457
        }
1458
1459
        // Add array qualifier, if requested
1460
        if ($array) {
1461
            $ftype .= '[]';
1462
        }
1463
1464
        if ($ftype != $oldtype) {
1465
            $toAlter[] = "ALTER COLUMN \"{$name}\" TYPE {$ftype}";
1466
        }
1467
1468
        // Attempt to process the batch alteration, if anything has been changed
1469
        if (!empty($toAlter)) {
1470
            // Initialise an empty SQL string
1471
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" "
1472
            . implode(',', $toAlter);
1473
1474
            $status = $this->execute($sql);
1475
            if ($status != 0) {
1476
                $this->rollbackTransaction();
1477
1478
                return [-1, $sql];
1479
            }
1480
        }
1481
1482
        // Update the comment on the column
1483
        $status = $this->setComment('COLUMN', $name, $table, $comment);
1484
        if ($status != 0) {
1485
            $this->rollbackTransaction();
1486
1487
            return [-5, $sql];
1488
        }
1489
1490
        return [$this->endTransaction(), $sql];
1491
    }
1492
1493
    /**
1494
     * Renames a column in a table.
1495
     *
1496
     * @param $table   The table containing the column to be renamed
1497
     * @param $column  The column to be renamed
1498
     * @param $newName The new name for the column
1499
     *
1500
     * @return int 0 if operation was successful
1501
     */
1502
    public function renameColumn($table, $column, $newName)
1503
    {
1504
        $f_schema = $this->_schema;
1505
        $this->fieldClean($f_schema);
1506
        $this->fieldClean($table);
1507
        $this->fieldClean($column);
1508
        $this->fieldClean($newName);
1509
1510
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" RENAME COLUMN \"{$column}\" TO \"{$newName}\"";
1511
1512
        return $this->execute($sql);
1513
    }
1514
1515
    /**
1516
     * Sets default value of a column.
1517
     *
1518
     * @param $table   The table from which to drop
1519
     * @param $column  The column name to set
1520
     * @param $default The new default value
1521
     *
1522
     * @return int 0 if operation was successful
1523
     */
1524
    public function setColumnDefault($table, $column, $default)
1525
    {
1526
        $f_schema = $this->_schema;
1527
        $this->fieldClean($f_schema);
1528
        $this->fieldClean($table);
1529
        $this->fieldClean($column);
1530
1531
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" SET DEFAULT {$default}";
1532
1533
        return $this->execute($sql);
1534
    }
1535
1536
    /**
1537
     * Sets whether or not a column can contain NULLs.
1538
     *
1539
     * @param $table  The table that contains the column
1540
     * @param $column The column to alter
1541
     * @param $state  True to set null, false to set not null
1542
     *
1543
     * @return int 0 if operation was successful
1544
     */
1545
    public function setColumnNull($table, $column, $state)
1546
    {
1547
        $f_schema = $this->_schema;
1548
        $this->fieldClean($f_schema);
1549
        $this->fieldClean($table);
1550
        $this->fieldClean($column);
1551
1552
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . ($state ? 'DROP' : 'SET') . ' NOT NULL';
1553
1554
        return $this->execute($sql);
1555
    }
1556
1557
    /**
1558
     * Drops a column from a table.
1559
     *
1560
     * @param $table   The table from which to drop a column
1561
     * @param $column  The column to be dropped
1562
     * @param $cascade True to cascade drop, false to restrict
1563
     *
1564
     * @return int 0 if operation was successful
1565
     */
1566
    public function dropColumn($table, $column, $cascade)
1567
    {
1568
        $f_schema = $this->_schema;
1569
        $this->fieldClean($f_schema);
1570
        $this->fieldClean($table);
1571
        $this->fieldClean($column);
1572
1573
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DROP COLUMN \"{$column}\"";
1574
        if ($cascade) {
1575
            $sql .= ' CASCADE';
1576
        }
1577
1578
        return $this->execute($sql);
1579
    }
1580
1581
    /**
1582
     * Drops default value of a column.
1583
     *
1584
     * @param $table  The table from which to drop
1585
     * @param $column The column name to drop default
1586
     *
1587
     * @return int 0 if operation was successful
1588
     */
1589
    public function dropColumnDefault($table, $column)
1590
    {
1591
        $f_schema = $this->_schema;
1592
        $this->fieldClean($f_schema);
1593
        $this->fieldClean($table);
1594
        $this->fieldClean($column);
1595
1596
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT";
1597
1598
        return $this->execute($sql);
1599
    }
1600
1601
    /**
1602
     * Sets up the data object for a dump.  eg. Starts the appropriate
1603
     * transaction, sets variables, etc.
1604
     *
1605
     * @return int 0 success
1606
     */
1607
    public function beginDump()
1608
    {
1609
        // Begin serializable transaction (to dump consistent data)
1610
        $status = $this->beginTransaction();
1611
        if ($status != 0) {
1612
            return -1;
1613
        }
1614
1615
        // Set serializable
1616
        $sql    = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE';
1617
        $status = $this->execute($sql);
1618
        if ($status != 0) {
1619
            $this->rollbackTransaction();
1620
1621
            return -1;
1622
        }
1623
1624
        // Set datestyle to ISO
1625
        $sql    = 'SET DATESTYLE = ISO';
1626
        $status = $this->execute($sql);
1627
        if ($status != 0) {
1628
            $this->rollbackTransaction();
1629
1630
            return -1;
1631
        }
1632
1633
        // Set extra_float_digits to 2
1634
        $sql    = 'SET extra_float_digits TO 2';
1635
        $status = $this->execute($sql);
1636
        if ($status != 0) {
1637
            $this->rollbackTransaction();
1638
1639
            return -1;
1640
        }
1641
1642
        return 0;
1643
    }
1644
1645
    /**
1646
     * Ends the data object for a dump.
1647
     *
1648
     * @return bool 0 success
1649
     */
1650
    public function endDump()
1651
    {
1652
        return $this->endTransaction();
1653
    }
1654
1655
    /**
1656
     * Returns a recordset of all columns in a relation.  Used for data export.
1657
     *
1658
     * @@ Note: Really needs to use a cursor
1659
     *
1660
     * @param $relation The name of a relation
1 ignored issue
show
Coding Style introduced by
Parameter tags must be defined first in a doc comment
Loading history...
1661
     * @param $oids
1662
     *
1663
     * @return \PHPPgAdmin\ADORecordSet A recordset on success
1664
     */
1665
    public function dumpRelation($relation, $oids)
1666
    {
1667
        $this->fieldClean($relation);
1668
1669
        // Actually retrieve the rows
1670
        if ($oids) {
1671
            $oid_str = $this->id . ', ';
1672
        } else {
1673
            $oid_str = '';
1674
        }
1675
1676
        return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\"");
1677
    }
1678
1679
    /**
1680
     * Returns all available autovacuum per table information.
1681
     *
1682
     * @param \PHPPgAdmin\Database\if|string $table if given, return autovacuum info for the given table or return all informations for all table
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\if was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1683
     *
1684
     * @return \PHPPgAdmin\ADORecordSet A recordset
1685
     */
1686
    public function getTableAutovacuum($table = '')
1687
    {
1688
        $sql = '';
1689
1690
        if ($table !== '') {
1691
            $this->clean($table);
1692
            $c_schema = $this->_schema;
1693
            $this->clean($c_schema);
1694
1695
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1696
                FROM pg_class c
1697
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1698
                WHERE c.relkind = 'r'::\"char\"
1699
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1700
                    AND c.reloptions IS NOT NULL
1701
                    AND c.relname = '{$table}' AND n.nspname = '{$c_schema}'
1702
                ORDER BY nspname, relname";
1703
        } else {
1704
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1705
                FROM pg_class c
1706
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1707
                WHERE c.relkind = 'r'::\"char\"
1708
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1709
                    AND c.reloptions IS NOT NULL
1710
                ORDER BY nspname, relname";
1711
        }
1712
1713
        /* tmp var to parse the results */
1714
        $_autovacs = $this->selectSet($sql);
1715
1716
        /* result aray to return as RS */
1717
        $autovacs = [];
1718
        while (!$_autovacs->EOF) {
1719
            $_ = [
1720
                'nspname' => $_autovacs->fields['nspname'],
1721
                'relname' => $_autovacs->fields['relname'],
1722
            ];
1723
1724
            foreach (explode(',', $_autovacs->fields['reloptions']) as $var) {
1725
                list($o, $v) = explode('=', $var);
1726
                $_[$o]       = $v;
1727
            }
1728
1729
            $autovacs[] = $_;
1730
1731
            $_autovacs->moveNext();
1732
        }
1733
1734
        return new \PHPPgAdmin\ArrayRecordSet($autovacs);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new PHPPgAdmin\ArrayRecordSet($autovacs) returns the type PHPPgAdmin\ArrayRecordSet which is incompatible with the documented return type PHPPgAdmin\ADORecordSet.
Loading history...
1735
    }
1736
1737
    /**
1738
     * Get the fields for uniquely identifying a row in a table.
1739
     *
1740
     * @param $table The table for which to retrieve the identifier
1741
     *
1742
     * @return An array mapping attribute number to attribute name, empty for no identifiers
1743
     * @return -1 error
0 ignored issues
show
Coding Style introduced by
Only 1 @return tag is allowed in a function comment
Loading history...
1744
     */
1745
    public function getRowIdentifier($table)
1746
    {
1747
        $oldtable = $table;
1748
        $c_schema = $this->_schema;
1749
        $this->clean($c_schema);
1750
        $this->clean($table);
1751
1752
        $status = $this->beginTransaction();
1753
        if ($status != 0) {
1754
            return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type PHPPgAdmin\Database\An.
Loading history...
1755
        }
1756
1757
        // Get the first primary or unique index (sorting primary keys first) that
1758
        // is NOT a partial index.
1759
        $sql = "
1760
            SELECT indrelid, indkey
1761
            FROM pg_catalog.pg_index
1762
            WHERE indisunique AND indrelid=(
1763
                SELECT oid FROM pg_catalog.pg_class
1764
                WHERE relname='{$table}' AND relnamespace=(
1765
                    SELECT oid FROM pg_catalog.pg_namespace
1766
                    WHERE nspname='{$c_schema}'
1767
                )
1768
            ) AND indpred IS NULL AND indexprs IS NULL
1769
            ORDER BY indisprimary DESC LIMIT 1";
1770
        $rs = $this->selectSet($sql);
1771
1772
        // If none, check for an OID column.  Even though OIDs can be duplicated, the edit and delete row
1773
        // functions check that they're only modiying a single row.  Otherwise, return empty array.
1774
        if ($rs->recordCount() == 0) {
1775
            // Check for OID column
1776
            $temp = [];
1777
            if ($this->hasObjectID($table)) {
1778
                $temp = ['oid'];
1779
            }
1780
            $this->endTransaction();
1781
1782
            return $temp;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $temp returns the type array<integer,string>|array which is incompatible with the documented return type PHPPgAdmin\Database\An.
Loading history...
1783
        } // Otherwise find the names of the keys
1784
1785
        $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey']));
0 ignored issues
show
Bug introduced by
It seems like getAttributeNames() 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

1785
        /** @scrutinizer ignore-call */ 
1786
        $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey']));
Loading history...
1786
        if (!is_array($attnames)) {
1787
            $this->rollbackTransaction();
1788
1789
            return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type PHPPgAdmin\Database\An.
Loading history...
1790
        }
1791
1792
        $this->endTransaction();
1793
1794
        return $attnames;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $attnames returns the type array which is incompatible with the documented return type PHPPgAdmin\Database\An.
Loading history...
1795
    }
1796
1797
    /**
1798
     * Adds a new row to a table.
1799
     *
1800
     * @param $table  The table in which to insert
1801
     * @param $fields Array of given field in values
1802
     * @param $values Array of new values for the row
1803
     * @param $nulls  An array mapping column => something if it is to be null
1804
     * @param $format An array of the data type (VALUE or EXPRESSION)
1805
     * @param $types  An array of field types
1806
     *
1807
     * @return int 0 if operation was successful
1808
     */
1809
    public function insertRow($table, $fields, $values, $nulls, $format, $types)
1810
    {
1811
        if (!is_array($fields) || !is_array($values) || !is_array($nulls)
1812
            || !is_array($format) || !is_array($types)
1813
            || (count($fields) != count($values))
1814
        ) {
1815
            return -1;
1816
        }
1817
1818
        // Build clause
1819
        if (count($values) > 0) {
1820
            // Escape all field names
1821
            $fields   = array_map(['\PHPPgAdmin\Database\Postgres', 'fieldClean'], $fields);
1822
            $f_schema = $this->_schema;
1823
            $this->fieldClean($table);
1824
            $this->fieldClean($f_schema);
1825
1826
            $sql = '';
1827
            foreach ($values as $i => $value) {
1828
                // Handle NULL values
1829
                if (isset($nulls[$i])) {
1830
                    $sql .= ',NULL';
1831
                } else {
1832
                    $sql .= ',' . $this->formatValue($types[$i], $format[$i], $value);
1833
                }
1834
            }
1835
1836
            $sql = "INSERT INTO \"{$f_schema}\".\"{$table}\" (\"" . implode('","', $fields) . '")
1837
                VALUES (' . substr($sql, 1) . ')';
1838
1839
            return $this->execute($sql);
1840
        }
1841
1842
        return -1;
1843
    }
1844
1845
    /**
1846
     * Formats a value or expression for sql purposes.
1847
     *
1848
     * @param $type   The type of the field
1849
     * @param $format VALUE or EXPRESSION
0 ignored issues
show
Bug introduced by
The type PHPPgAdmin\Database\VALUE was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1850
     * @param $value  The actual value entered in the field.  Can be NULL
1851
     *
1852
     * @return The suitably quoted and escaped value
1853
     */
1854
    public function formatValue($type, $format, $value)
1855
    {
1856
        switch ($type) {
1857
            case 'bool':
1858
            case 'boolean':
1859
                if ($value == 't') {
1860
                    return 'TRUE';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'TRUE' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
1861
                }
1862
1863
                if ($value == 'f') {
1864
                    return 'FALSE';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'FALSE' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
1865
                }
1866
                if ($value == '') {
1867
                    return 'NULL';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'NULL' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
1868
                }
1869
1870
                return $value;
1871
                break;
1 ignored issue
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1872
            default:
1873
                // Checking variable fields is difficult as there might be a size
1874
                // attribute...
1875
                if (strpos($type, 'time') === 0) {
1876
                    // Assume it's one of the time types...
1877
                    if ($value == '') {
1878
                        return "''";
0 ignored issues
show
Bug Best Practice introduced by
The expression return '''' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
1879
                    }
1880
1881
                    if (strcasecmp($value, 'CURRENT_TIMESTAMP') == 0
1882
                        || strcasecmp($value, 'CURRENT_TIME') == 0
1883
                        || strcasecmp($value, 'CURRENT_DATE') == 0
1884
                        || strcasecmp($value, 'LOCALTIME') == 0
1885
                        || strcasecmp($value, 'LOCALTIMESTAMP') == 0) {
1 ignored issue
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
1886
                        return $value;
1887
                    }
1888
                    if ($format == 'EXPRESSION') {
1889
                        return $value;
1890
                    }
1891
                    $this->clean($value);
1892
1893
                    return "'{$value}'";
0 ignored issues
show
Bug Best Practice introduced by
The expression return '''.$value.''' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
1894
                }
1895
                if ($format == 'VALUE') {
1896
                    $this->clean($value);
1897
1898
                    return "'{$value}'";
0 ignored issues
show
Bug Best Practice introduced by
The expression return '''.$value.''' returns the type string which is incompatible with the documented return type PHPPgAdmin\Database\The.
Loading history...
1899
                }
1900
1901
                return $value;
1902
        }
1903
    }
1904
1905
    // View functions
1906
1907
    /**
1908
     * Updates a row in a table.
1909
     *
1910
     * @param $table  The table in which to update
1911
     * @param $vars   An array mapping new values for the row
1912
     * @param $nulls  An array mapping column => something if it is to be null
1913
     * @param $format An array of the data type (VALUE or EXPRESSION)
1914
     * @param $types  An array of field types
1915
     * @param $keyarr An array mapping column => value to update
1916
     *
1917
     * @return bool|int 0 success
1918
     */
1919
    public function editRow($table, $vars, $nulls, $format, $types, $keyarr)
1920
    {
1921
        if (!is_array($vars) || !is_array($nulls) || !is_array($format) || !is_array($types)) {
1922
            return -1;
1923
        }
1924
1925
        $f_schema = $this->_schema;
1926
        $this->fieldClean($f_schema);
1927
        $this->fieldClean($table);
1928
1929
        // Build clause
1930
        if (sizeof($vars) > 0) {
1931
            foreach ($vars as $key => $value) {
1932
                $this->fieldClean($key);
1933
1934
                // Handle NULL values
1935
                if (isset($nulls[$key])) {
1936
                    $tmp = 'NULL';
1937
                } else {
1938
                    $tmp = $this->formatValue($types[$key], $format[$key], $value);
1939
                }
1940
1941
                if (isset($sql)) {
1942
                    $sql .= ", \"{$key}\"={$tmp}";
1943
                } else {
1944
                    $sql = "UPDATE \"{$f_schema}\".\"{$table}\" SET \"{$key}\"={$tmp}";
1945
                }
1946
            }
1947
            $first = true;
1948
            foreach ($keyarr as $k => $v) {
1949
                $this->fieldClean($k);
1950
                $this->clean($v);
1951
                if ($first) {
1952
                    $sql .= " WHERE \"{$k}\"='{$v}'";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sql does not seem to be defined for all execution paths leading up to this point.
Loading history...
1953
                    $first = false;
1954
                } else {
1955
                    $sql .= " AND \"{$k}\"='{$v}'";
1956
                }
1957
            }
1958
        }
1959
1960
        // Begin transaction.  We do this so that we can ensure only one row is
1961
        // edited
1962
        $status = $this->beginTransaction();
1963
        if ($status != 0) {
1964
            $this->rollbackTransaction();
1965
1966
            return -1;
1967
        }
1968
1969
        $status = $this->execute($sql);
1970
        if ($status != 0) {
1971
            // update failed
1972
            $this->rollbackTransaction();
1973
1974
            return -1;
1975
        }
1976
1977
        if ($this->conn->Affected_Rows() != 1) {
1978
            // more than one row could be updated
1979
            $this->rollbackTransaction();
1980
1981
            return -2;
1982
        }
1983
1984
        // End transaction
1985
        return $this->endTransaction();
1986
    }
1987
1988
    /**
1989
     * Delete a row from a table.
1990
     *
1991
     * @param      $table  The table from which to delete
1992
     * @param      $key    An array mapping column => value to delete
1993
     * @param bool $schema
1994
     *
1995
     * @return bool|int 0 success
1996
     */
1997
    public function deleteRow($table, $key, $schema = false)
1998
    {
1999
        if (!is_array($key)) {
2000
            return -1;
2001
        }
2002
2003
        // Begin transaction.  We do this so that we can ensure only one row is
2004
        // deleted
2005
        $status = $this->beginTransaction();
2006
        if ($status != 0) {
2007
            $this->rollbackTransaction();
2008
2009
            return -1;
2010
        }
2011
2012
        if ($schema === false) {
2013
            $schema = $this->_schema;
2014
        }
2015
2016
        $status = $this->delete($table, $key, $schema);
0 ignored issues
show
Bug introduced by
The method delete() does not exist on PHPPgAdmin\Database\TableTrait. Did you maybe mean deleteRow()? ( Ignorable by Annotation )

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

2016
        /** @scrutinizer ignore-call */ 
2017
        $status = $this->delete($table, $key, $schema);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2017
        if ($status != 0 || $this->conn->Affected_Rows() != 1) {
2018
            $this->rollbackTransaction();
2019
2020
            return -2;
2021
        }
2022
2023
        // End transaction
2024
        return $this->endTransaction();
2025
    }
2026
2027
}
2028