Passed
Pull Request — develop (#141)
by Felipe
09:20 queued 02:01
created

TableTrait::getRowIdentifier()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 50
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 8.6315
c 0
b 0
f 0
cc 5
eloc 23
nc 5
nop 1
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);
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);
52
    }
53
54
    /**
55
     * Finds the names and schemas of parent tables (in order).
56
     *
57
     * @param string $table The table to find the parents for
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 string $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 string    $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();
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 string $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 string $table The name of the table
488
     * @param string $field (optional) The name of a field to return
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 string $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 string $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']);
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 string $table The table to define
635
     *
636
     * @return string 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;
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 string $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 string $name        The name of the table
784
     * @param int $fields      The number of fields
785
     * @param array $field       An array of field names
786
     * @param array $type        An array of field types
787
     * @param array $array       An array of '' or '[]' for each type if it's an array or not
788
     * @param array $length      An array of field lengths
789
     * @param array $notnull     An array of not null
790
     * @param array $default     An array of default values
791
     * @param bool $withoutoids True if WITHOUT OIDS, false otherwise
792
     * @param array $colcomment  An array of comments
793
     * @param string $tblcomment the comment for the table
794
     * @param string $tablespace  The tablespace name ('' means none/default)
795
     * @param array $uniquekey   An Array indicating the fields that are unique (those indexes that are set)
796
     * @param array $primarykey  An Array indicating the field used for the primarykey (those indexes that are set)
797
     *
798
     * @return bool|int 0 success
799
     *
800
     */
0 ignored issues
show
Coding Style introduced by
Additional blank lines found at end of doc comment
Loading history...
801
    public function createTable(
802
        $name,
803
        $fields,
804
        $field,
805
        $type,
806
        $array,
807
        $length,
808
        $notnull,
809
        $default,
810
        $withoutoids,
811
        $colcomment,
812
        $tblcomment,
813
        $tablespace,
814
        $uniquekey,
815
        $primarykey
816
    ) {
817
        $f_schema = $this->_schema;
818
        $this->fieldClean($f_schema);
819
        $this->fieldClean($name);
820
821
        $status = $this->beginTransaction();
822
        if ($status != 0) {
823
            return -1;
824
        }
825
826
        $found       = false;
827
        $first       = true;
828
        $comment_sql = ''; //Accumulate comments for the columns
829
        $sql         = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (";
830
        for ($i = 0; $i < $fields; ++$i) {
831
            $this->fieldClean($field[$i]);
832
            $this->clean($type[$i]);
833
            $this->clean($length[$i]);
834
            $this->clean($colcomment[$i]);
835
836
            // Skip blank columns - for user convenience
837
            if ($field[$i] == '' || $type[$i] == '') {
838
                continue;
839
            }
840
841
            // If not the first column, add a comma
842
            if (!$first) {
843
                $sql .= ', ';
844
            } else {
845
                $first = false;
846
            }
847
848
            switch ($type[$i]) {
849
                // Have to account for weird placing of length for with/without
850
                // time zone types
851
                case 'timestamp with time zone':
852
                case 'timestamp without time zone':
853
                    $qual = substr($type[$i], 9);
854
                    $sql .= "\"{$field[$i]}\" timestamp";
855
                    if ($length[$i] != '') {
856
                        $sql .= "({$length[$i]})";
857
                    }
858
859
                    $sql .= $qual;
860
861
                    break;
862
                case 'time with time zone':
863
                case 'time without time zone':
864
                    $qual = substr($type[$i], 4);
865
                    $sql .= "\"{$field[$i]}\" time";
866
                    if ($length[$i] != '') {
867
                        $sql .= "({$length[$i]})";
868
                    }
869
870
                    $sql .= $qual;
871
872
                    break;
873
                default:
874
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
875
                    if ($length[$i] != '') {
876
                        $sql .= "({$length[$i]})";
877
                    }
878
            }
879
            // Add array qualifier if necessary
880
            if ($array[$i] == '[]') {
881
                $sql .= '[]';
882
            }
883
884
            // Add other qualifiers
885
            if (!isset($primarykey[$i])) {
886
                if (isset($uniquekey[$i])) {
887
                    $sql .= ' UNIQUE';
888
                }
889
890
                if (isset($notnull[$i])) {
891
                    $sql .= ' NOT NULL';
892
                }
893
            }
894
            if ($default[$i] != '') {
895
                $sql .= " DEFAULT {$default[$i]}";
896
            }
897
898
            if ($colcomment[$i] != '') {
899
                $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
900
            }
901
902
            $found = true;
903
        }
904
905
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
906
            return -1;
907
        }
908
909
        // PRIMARY KEY
910
        $primarykeycolumns = [];
911
        for ($i = 0; $i < $fields; ++$i) {
912
            if (isset($primarykey[$i])) {
913
                $primarykeycolumns[] = "\"{$field[$i]}\"";
914
            }
915
        }
916
        if (count($primarykeycolumns) > 0) {
917
            $sql .= ', PRIMARY KEY (' . implode(', ', $primarykeycolumns) . ')';
918
        }
919
920
        $sql .= ')';
921
922
        // WITHOUT OIDS
923
        if ($withoutoids) {
924
            $sql .= ' WITHOUT OIDS';
925
        } else {
926
            $sql .= ' WITH OIDS';
927
        }
928
929
        // Tablespace
930
        if ($this->hasTablespaces() && $tablespace != '') {
931
            $this->fieldClean($tablespace);
932
            $sql .= " TABLESPACE \"{$tablespace}\"";
933
        }
934
935
        $status = $this->execute($sql);
936
        if ($status) {
937
            $this->rollbackTransaction();
938
939
            return -1;
940
        }
941
942
        if ($tblcomment != '') {
943
            $status = $this->setComment('TABLE', '', $name, $tblcomment, true);
944
            if ($status) {
945
                $this->rollbackTransaction();
946
947
                return -1;
948
            }
949
        }
950
951
        if ($comment_sql != '') {
952
            $status = $this->execute($comment_sql);
953
            if ($status) {
954
                $this->rollbackTransaction();
955
956
                return -1;
957
            }
958
        }
959
960
        return $this->endTransaction();
961
    }
962
963
    /**
964
     * Creates a new table in the database copying attribs and other properties from another table.
965
     *
966
     * @param string $name        The name of the table
967
     * @param array $like        an array giving the schema ans the name of the table from which attribs are copying
968
     *                            from: array(
969
     *                            'table' => table name,
970
     *                            'schema' => the schema name,
971
     *                            )
972
     * @param bool   $defaults    if true, copy the defaults values as well
973
     * @param bool   $constraints if true, copy the constraints as well (CHECK on table & attr)
974
     * @param bool   $idx
975
     * @param string $tablespace  The tablespace name ('' means none/default)
976
     *
977
     * @return bool|int
978
     */
979
    public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '')
980
    {
981
        $f_schema = $this->_schema;
982
        $this->fieldClean($f_schema);
983
        $this->fieldClean($name);
984
        $this->fieldClean($like['schema']);
985
        $this->fieldClean($like['table']);
986
        $like = "\"{$like['schema']}\".\"{$like['table']}\"";
987
988
        $status = $this->beginTransaction();
989
        if ($status != 0) {
990
            return -1;
991
        }
992
993
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}";
994
995
        if ($defaults) {
996
            $sql .= ' INCLUDING DEFAULTS';
997
        }
998
999
        if ($this->hasCreateTableLikeWithConstraints() && $constraints) {
1000
            $sql .= ' INCLUDING CONSTRAINTS';
1001
        }
1002
1003
        if ($this->hasCreateTableLikeWithIndexes() && $idx) {
1004
            $sql .= ' INCLUDING INDEXES';
1005
        }
1006
1007
        $sql .= ')';
1008
1009
        if ($this->hasTablespaces() && $tablespace != '') {
1010
            $this->fieldClean($tablespace);
1011
            $sql .= " TABLESPACE \"{$tablespace}\"";
1012
        }
1013
1014
        $status = $this->execute($sql);
1015
        if ($status) {
1016
            $this->rollbackTransaction();
1017
1018
            return -1;
1019
        }
1020
1021
        return $this->endTransaction();
1022
    }
1023
1024
    /**
1025
     * Alter table properties.
1026
     *
1027
     * @param string $table      The name of the table
1028
     * @param string $name       The new name for the table
1029
     * @param string $owner      The new owner for the table
1030
     * @param string $schema     The new schema for the table
1031
     * @param string $comment    The comment on the table
1032
     * @param string $tablespace The new tablespace for the table ('' means leave as is)
1033
     *
1034
     * @return bool|int 0 success
1035
     */
1036
    public function alterTable($table, $name, $owner, $schema, $comment, $tablespace)
1037
    {
1038
        $data = $this->getTable($table);
1039
1040
        if ($data->recordCount() != 1) {
0 ignored issues
show
introduced by
The condition $data->recordCount() != 1 is always true.
Loading history...
1041
            return -2;
1042
        }
1043
1044
        $status = $this->beginTransaction();
1045
        if ($status != 0) {
1046
            $this->rollbackTransaction();
1047
1048
            return -1;
1049
        }
1050
1051
        $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace);
1052
1053
        if ($status != 0) {
1054
            $this->rollbackTransaction();
1055
1056
            return $status;
1057
        }
1058
1059
        return $this->endTransaction();
1060
    }
1061
1062
    /**
1063
     * Protected method which alter a table
1064
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION.
1065
     *
1066
     * @param \PHPPgAdmin\ADORecordSet $tblrs      The table recordSet returned by getTable()
1067
     * @param string $name       The new name for the table
1068
     * @param string $owner      The new owner for the table
1069
     * @param string $schema     The new schema for the table
1070
     * @param string $comment    The comment on the table
1071
     * @param string $tablespace The new tablespace for the table ('' means leave as is)
1072
     *
1073
     * @return int 0 success
1074
     */
1075
    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...
1076
    {
1077
        $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

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

1325
        if ($this->/** @scrutinizer ignore-call */ hasCreateFieldWithConstraints()) {
Loading history...
1326
            // NOT NULL clause
1327
            if ($notnull) {
1328
                $sql .= ' NOT NULL';
1329
            }
1330
1331
            // DEFAULT clause
1332
            if ($default != '') {
1333
                $sql .= ' DEFAULT ' . $default;
1334
            }
1335
        }
1336
1337
        $status = $this->beginTransaction();
1338
        if ($status != 0) {
1339
            return -1;
1340
        }
1341
1342
        $status = $this->execute($sql);
1343
        if ($status != 0) {
1344
            $this->rollbackTransaction();
1345
1346
            return -1;
1347
        }
1348
1349
        $status = $this->setComment('COLUMN', $column, $table, $comment);
1350
        if ($status != 0) {
1351
            $this->rollbackTransaction();
1352
1353
            return -1;
1354
        }
1355
1356
        return $this->endTransaction();
1357
    }
1358
1359
    /**
1360
     * Alters a column in a table.
1361
     *
1362
     * @param string $table      The table in which the column resides
1363
     * @param string $column     The column to alter
1364
     * @param string $name       The new name for the column
1365
     * @param boolean $notnull    (boolean) True if not null, false otherwise
1366
     * @param boolean $oldnotnull (boolean) True if column is already not null, false otherwise
1367
     * @param mixed $default    The new default for the column
1368
     * @param mixed $olddefault The old default for the column
1369
     * @param string $type       The new type for the column
1370
     * @param integer $length     The optional size of the column (ie. 30 for varchar(30))
1371
     * @param boolean $array      True if array type, false otherwise
1372
     * @param string $oldtype    The old type for the column
1373
     * @param string $comment    Comment for the column
1374
     *
1375
     * @return array 0 success
1376
     */
1377
    public function alterColumn(
1378
        $table,
1379
        $column,
1380
        $name,
1381
        $notnull,
1382
        $oldnotnull,
1383
        $default,
1384
        $olddefault,
1385
        $type,
1386
        $length,
1387
        $array,
1388
        $oldtype,
1389
        $comment
1390
    ) {
1391
        // Begin transaction
1392
        $status = $this->beginTransaction();
1393
        $sql    = '';
1394
        if ($status != 0) {
1395
            $this->rollbackTransaction();
1396
1397
            return [-6, $sql];
1398
        }
1399
1400
        // Rename the column, if it has been changed
1401
        if ($column != $name) {
1402
            $status = $this->renameColumn($table, $column, $name);
1403
            if ($status != 0) {
1404
                $this->rollbackTransaction();
1405
1406
                return [-4, $sql];
1407
            }
1408
        }
1409
1410
        $f_schema = $this->_schema;
1411
        $this->fieldClean($f_schema);
1412
        $this->fieldClean($name);
1413
        $this->fieldClean($table);
1414
        $this->fieldClean($column);
1415
1416
        $toAlter = [];
1417
        // Create the command for changing nullability
1418
        if ($notnull != $oldnotnull) {
1419
            $toAlter[] = "ALTER COLUMN \"{$name}\" " . ($notnull ? 'SET' : 'DROP') . ' NOT NULL';
1420
        }
1421
1422
        // Add default, if it has changed
1423
        if ($default != $olddefault) {
1424
            if ($default == '') {
1425
                $toAlter[] = "ALTER COLUMN \"{$name}\" DROP DEFAULT";
1426
            } else {
1427
                $toAlter[] = "ALTER COLUMN \"{$name}\" SET DEFAULT {$default}";
1428
            }
1429
        }
1430
1431
        // Add type, if it has changed
1432
        if ($length == '') {
1433
            $ftype = $type;
1434
        } else {
1435
            switch ($type) {
1436
                // Have to account for weird placing of length for with/without
1437
                // time zone types
1438
                case 'timestamp with time zone':
1439
                case 'timestamp without time zone':
1440
                    $qual  = substr($type, 9);
1441
                    $ftype = "timestamp({$length}){$qual}";
1442
1443
                    break;
1444
                case 'time with time zone':
1445
                case 'time without time zone':
1446
                    $qual  = substr($type, 4);
1447
                    $ftype = "time({$length}){$qual}";
1448
1449
                    break;
1450
                default:
1451
                    $ftype = "{$type}({$length})";
1452
            }
1453
        }
1454
1455
        // Add array qualifier, if requested
1456
        if ($array) {
1457
            $ftype .= '[]';
1458
        }
1459
1460
        if ($ftype != $oldtype) {
1461
            $toAlter[] = "ALTER COLUMN \"{$name}\" TYPE {$ftype}";
1462
        }
1463
1464
        // Attempt to process the batch alteration, if anything has been changed
1465
        if (!empty($toAlter)) {
1466
            // Initialise an empty SQL string
1467
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" "
1468
            . implode(',', $toAlter);
1469
1470
            $status = $this->execute($sql);
1471
            if ($status != 0) {
1472
                $this->rollbackTransaction();
1473
1474
                return [-1, $sql];
1475
            }
1476
        }
1477
1478
        // Update the comment on the column
1479
        $status = $this->setComment('COLUMN', $name, $table, $comment);
1480
        if ($status != 0) {
1481
            $this->rollbackTransaction();
1482
1483
            return [-5, $sql];
1484
        }
1485
1486
        return [$this->endTransaction(), $sql];
1487
    }
1488
1489
    /**
1490
     * Renames a column in a table.
1491
     *
1492
     * @param string $table   The table containing the column to be renamed
1493
     * @param string $column  The column to be renamed
1494
     * @param string $newName The new name for the column
1495
     *
1496
     * @return int 0 if operation was successful
1497
     */
1498
    public function renameColumn($table, $column, $newName)
1499
    {
1500
        $f_schema = $this->_schema;
1501
        $this->fieldClean($f_schema);
1502
        $this->fieldClean($table);
1503
        $this->fieldClean($column);
1504
        $this->fieldClean($newName);
1505
1506
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" RENAME COLUMN \"{$column}\" TO \"{$newName}\"";
1507
1508
        return $this->execute($sql);
1509
    }
1510
1511
    /**
1512
     * Sets default value of a column.
1513
     *
1514
     * @param string $table   The table from which to drop
1515
     * @param string $column  The column name to set
1516
     * @param mixed $default The new default value
1517
     *
1518
     * @return int 0 if operation was successful
1519
     */
1520
    public function setColumnDefault($table, $column, $default)
1521
    {
1522
        $f_schema = $this->_schema;
1523
        $this->fieldClean($f_schema);
1524
        $this->fieldClean($table);
1525
        $this->fieldClean($column);
1526
1527
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" SET DEFAULT {$default}";
1528
1529
        return $this->execute($sql);
1530
    }
1531
1532
    /**
1533
     * Sets whether or not a column can contain NULLs.
1534
     *
1535
     * @param string $table  The table that contains the column
1536
     * @param string $column The column to alter
1537
     * @param bool $state  True to set null, false to set not null
1538
     *
1539
     * @return int 0 if operation was successful
1540
     */
1541
    public function setColumnNull($table, $column, $state)
1542
    {
1543
        $f_schema = $this->_schema;
1544
        $this->fieldClean($f_schema);
1545
        $this->fieldClean($table);
1546
        $this->fieldClean($column);
1547
1548
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . ($state ? 'DROP' : 'SET') . ' NOT NULL';
1549
1550
        return $this->execute($sql);
1551
    }
1552
1553
    /**
1554
     * Drops a column from a table.
1555
     *
1556
     * @param string $table   The table from which to drop a column
1557
     * @param string $column  The column to be dropped
1558
     * @param bool $cascade True to cascade drop, false to restrict
1559
     *
1560
     * @return int 0 if operation was successful
1561
     */
1562
    public function dropColumn($table, $column, $cascade)
1563
    {
1564
        $f_schema = $this->_schema;
1565
        $this->fieldClean($f_schema);
1566
        $this->fieldClean($table);
1567
        $this->fieldClean($column);
1568
1569
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DROP COLUMN \"{$column}\"";
1570
        if ($cascade) {
1571
            $sql .= ' CASCADE';
1572
        }
1573
1574
        return $this->execute($sql);
1575
    }
1576
1577
    /**
1578
     * Drops default value of a column.
1579
     *
1580
     * @param string $table  The table from which to drop
1581
     * @param string $column The column name to drop default
1582
     *
1583
     * @return int 0 if operation was successful
1584
     */
1585
    public function dropColumnDefault($table, $column)
1586
    {
1587
        $f_schema = $this->_schema;
1588
        $this->fieldClean($f_schema);
1589
        $this->fieldClean($table);
1590
        $this->fieldClean($column);
1591
1592
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT";
1593
1594
        return $this->execute($sql);
1595
    }
1596
1597
    /**
1598
     * Sets up the data object for a dump.  eg. Starts the appropriate
1599
     * transaction, sets variables, etc.
1600
     *
1601
     * @return int 0 success
1602
     */
1603
    public function beginDump()
1604
    {
1605
        // Begin serializable transaction (to dump consistent data)
1606
        $status = $this->beginTransaction();
1607
        if ($status != 0) {
1608
            return -1;
1609
        }
1610
1611
        // Set serializable
1612
        $sql    = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE';
1613
        $status = $this->execute($sql);
1614
        if ($status != 0) {
1615
            $this->rollbackTransaction();
1616
1617
            return -1;
1618
        }
1619
1620
        // Set datestyle to ISO
1621
        $sql    = 'SET DATESTYLE = ISO';
1622
        $status = $this->execute($sql);
1623
        if ($status != 0) {
1624
            $this->rollbackTransaction();
1625
1626
            return -1;
1627
        }
1628
1629
        // Set extra_float_digits to 2
1630
        $sql    = 'SET extra_float_digits TO 2';
1631
        $status = $this->execute($sql);
1632
        if ($status != 0) {
1633
            $this->rollbackTransaction();
1634
1635
            return -1;
1636
        }
1637
1638
        return 0;
1639
    }
1640
1641
    /**
1642
     * Ends the data object for a dump.
1643
     *
1644
     * @return bool 0 success
1645
     */
1646
    public function endDump()
1647
    {
1648
        return $this->endTransaction();
1649
    }
1650
1651
    /**
1652
     * Returns a recordset of all columns in a relation.  Used for data export.
1653
     *
1654
     * @@ Note: Really needs to use a cursor
1655
     *
1656
     * @param string $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...
1657
     * @param bool $oids true to dump also the oids
1658
     *
1659
     * @return \PHPPgAdmin\ADORecordSet A recordset on success
1660
     */
1661
    public function dumpRelation($relation, $oids)
1662
    {
1663
        $this->fieldClean($relation);
1664
1665
        // Actually retrieve the rows
1666
        if ($oids) {
1667
            $oid_str = $this->id . ', ';
1668
        } else {
1669
            $oid_str = '';
1670
        }
1671
1672
        return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\"");
1673
    }
1674
1675
    /**
1676
     * Returns all available autovacuum per table information.
1677
     *
1678
     * @param string $table if given, return autovacuum info for the given table or return all informations for all table
1679
     *
1680
     * @return \PHPPgAdmin\ADORecordSet A recordset
1681
     */
1682
    public function getTableAutovacuum($table = '')
1683
    {
1684
        $sql = '';
1685
1686
        if ($table !== '') {
1687
            $this->clean($table);
1688
            $c_schema = $this->_schema;
1689
            $this->clean($c_schema);
1690
1691
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1692
                FROM pg_class c
1693
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1694
                WHERE c.relkind = 'r'::\"char\"
1695
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1696
                    AND c.reloptions IS NOT NULL
1697
                    AND c.relname = '{$table}' AND n.nspname = '{$c_schema}'
1698
                ORDER BY nspname, relname";
1699
        } else {
1700
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1701
                FROM pg_class c
1702
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1703
                WHERE c.relkind = 'r'::\"char\"
1704
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1705
                    AND c.reloptions IS NOT NULL
1706
                ORDER BY nspname, relname";
1707
        }
1708
1709
        /* tmp var to parse the results */
1710
        $_autovacs = $this->selectSet($sql);
1711
1712
        /* result aray to return as RS */
1713
        $autovacs = [];
1714
        while (!$_autovacs->EOF) {
1715
            $_ = [
1716
                'nspname' => $_autovacs->fields['nspname'],
1717
                'relname' => $_autovacs->fields['relname'],
1718
            ];
1719
1720
            foreach (explode(',', $_autovacs->fields['reloptions']) as $var) {
1721
                list($o, $v) = explode('=', $var);
1722
                $_[$o]       = $v;
1723
            }
1724
1725
            $autovacs[] = $_;
1726
1727
            $_autovacs->moveNext();
1728
        }
1729
1730
        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...
1731
    }
1732
1733
    /**
1734
     * Get the fields for uniquely identifying a row in a table.
1735
     *
1736
     * @param string $table The table for which to retrieve the identifier
1737
     *
1738
     * @return array<integer,string>|array|int An array mapping attribute number to attribute name, empty for no identifiers
1739
     */
1740
    public function getRowIdentifier($table)
1741
    {
1742
        $oldtable = $table;
1743
        $c_schema = $this->_schema;
1744
        $this->clean($c_schema);
1745
        $this->clean($table);
1746
1747
        $status = $this->beginTransaction();
1748
        if ($status != 0) {
1749
            return -1;
1750
        }
1751
1752
        // Get the first primary or unique index (sorting primary keys first) that
1753
        // is NOT a partial index.
1754
        $sql = "
1755
            SELECT indrelid, indkey
1756
            FROM pg_catalog.pg_index
1757
            WHERE indisunique AND indrelid=(
1758
                SELECT oid FROM pg_catalog.pg_class
1759
                WHERE relname='{$table}' AND relnamespace=(
1760
                    SELECT oid FROM pg_catalog.pg_namespace
1761
                    WHERE nspname='{$c_schema}'
1762
                )
1763
            ) AND indpred IS NULL AND indexprs IS NULL
1764
            ORDER BY indisprimary DESC LIMIT 1";
1765
        $rs = $this->selectSet($sql);
1766
1767
        // If none, check for an OID column.  Even though OIDs can be duplicated, the edit and delete row
1768
        // functions check that they're only modiying a single row.  Otherwise, return empty array.
1769
        if ($rs->recordCount() == 0) {
1770
            // Check for OID column
1771
            $temp = [];
1772
            if ($this->hasObjectID($table)) {
1773
                $temp = ['oid'];
1774
            }
1775
            $this->endTransaction();
1776
1777
            return $temp;
1778
        } // Otherwise find the names of the keys
1779
1780
        $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

1780
        /** @scrutinizer ignore-call */ 
1781
        $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey']));
Loading history...
1781
        if (!is_array($attnames)) {
1782
            $this->rollbackTransaction();
1783
1784
            return -1;
1785
        }
1786
1787
        $this->endTransaction();
1788
1789
        return $attnames;
1790
    }
1791
1792
    /**
1793
     * Adds a new row to a table.
1794
     *
1795
     * @param string $table  The table in which to insert
1796
     * @param array $fields Array of given field in values
1797
     * @param array $values Array of new values for the row
1798
     * @param array $nulls  An array mapping column => something if it is to be null
1799
     * @param array $format An array of the data type (VALUE or EXPRESSION)
1800
     * @param array $types  An array of field types
1801
     *
1802
     * @return int 0 if operation was successful
1803
     */
1804
    public function insertRow($table, $fields, $values, $nulls, $format, $types)
1805
    {
1806
        if (!is_array($fields) || !is_array($values) || !is_array($nulls)
3 ignored issues
show
introduced by
The condition is_array($fields) is always true.
Loading history...
introduced by
The condition is_array($values) is always true.
Loading history...
introduced by
The condition is_array($nulls) is always true.
Loading history...
1807
            || !is_array($format) || !is_array($types)
2 ignored issues
show
introduced by
The condition is_array($format) is always true.
Loading history...
introduced by
The condition is_array($types) is always true.
Loading history...
1808
            || (count($fields) != count($values))
1809
        ) {
1810
            return -1;
1811
        }
1812
1813
        // Build clause
1814
        if (count($values) > 0) {
1815
            // Escape all field names
1816
            $fields   = array_map(['\PHPPgAdmin\Database\Postgres', 'fieldClean'], $fields);
1817
            $f_schema = $this->_schema;
1818
            $this->fieldClean($table);
1819
            $this->fieldClean($f_schema);
1820
1821
            $sql = '';
1822
            foreach ($values as $i => $value) {
1823
                // Handle NULL values
1824
                if (isset($nulls[$i])) {
1825
                    $sql .= ',NULL';
1826
                } else {
1827
                    $sql .= ',' . $this->formatValue($types[$i], $format[$i], $value);
1828
                }
1829
            }
1830
1831
            $sql = "INSERT INTO \"{$f_schema}\".\"{$table}\" (\"" . implode('","', $fields) . '")
1832
                VALUES (' . substr($sql, 1) . ')';
1833
1834
            return $this->execute($sql);
1835
        }
1836
1837
        return -1;
1838
    }
1839
1840
    /**
1841
     * Formats a value or expression for sql purposes.
1842
     *
1843
     * @param string $type   The type of the field
1844
     * @param mixed $format VALUE or EXPRESSION
1845
     * @param mixed $value  The actual value entered in the field.  Can be NULL
1846
     *
1847
     * @return The suitably quoted and escaped value
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...
1848
     */
1849
    public function formatValue($type, $format, $value)
1850
    {
1851
        switch ($type) {
1852
            case 'bool':
1853
            case 'boolean':
1854
                if ($value == 't') {
1855
                    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...
1856
                }
1857
1858
                if ($value == 'f') {
1859
                    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...
1860
                }
1861
                if ($value == '') {
1862
                    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...
1863
                }
1864
1865
                return $value;
1866
                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...
1867
            default:
1868
                // Checking variable fields is difficult as there might be a size
1869
                // attribute...
1870
                if (strpos($type, 'time') === 0) {
1871
                    // Assume it's one of the time types...
1872
                    if ($value == '') {
1873
                        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...
1874
                    }
1875
1876
                    if (strcasecmp($value, 'CURRENT_TIMESTAMP') == 0
1877
                        || strcasecmp($value, 'CURRENT_TIME') == 0
1878
                        || strcasecmp($value, 'CURRENT_DATE') == 0
1879
                        || strcasecmp($value, 'LOCALTIME') == 0
1880
                        || 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...
1881
                        return $value;
1882
                    }
1883
                    if ($format == 'EXPRESSION') {
1884
                        return $value;
1885
                    }
1886
                    $this->clean($value);
1887
1888
                    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...
1889
                }
1890
                if ($format == 'VALUE') {
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
1896
                return $value;
1897
        }
1898
    }
1899
1900
    // View functions
1901
1902
    /**
1903
     * Updates a row in a table.
1904
     *
1905
     * @param string $table  The table in which to update
1906
     * @param array $vars   An array mapping new values for the row
1907
     * @param array $nulls  An array mapping column => something if it is to be null
1908
     * @param array $format An array of the data type (VALUE or EXPRESSION)
1909
     * @param array $types  An array of field types
1910
     * @param array $keyarr An array mapping column => value to update
1911
     *
1912
     * @return bool|int 0 success
1913
     */
1914
    public function editRow($table, $vars, $nulls, $format, $types, $keyarr)
1915
    {
1916
        if (!is_array($vars) || !is_array($nulls) || !is_array($format) || !is_array($types)) {
4 ignored issues
show
introduced by
The condition is_array($format) is always true.
Loading history...
introduced by
The condition is_array($types) is always true.
Loading history...
introduced by
The condition is_array($nulls) is always true.
Loading history...
introduced by
The condition is_array($vars) is always true.
Loading history...
1917
            return -1;
1918
        }
1919
1920
        $f_schema = $this->_schema;
1921
        $this->fieldClean($f_schema);
1922
        $this->fieldClean($table);
1923
1924
        // Build clause
1925
        if (sizeof($vars) > 0) {
1926
            foreach ($vars as $key => $value) {
1927
                $this->fieldClean($key);
1928
1929
                // Handle NULL values
1930
                if (isset($nulls[$key])) {
1931
                    $tmp = 'NULL';
1932
                } else {
1933
                    $tmp = $this->formatValue($types[$key], $format[$key], $value);
1934
                }
1935
1936
                if (isset($sql)) {
1937
                    $sql .= ", \"{$key}\"={$tmp}";
1938
                } else {
1939
                    $sql = "UPDATE \"{$f_schema}\".\"{$table}\" SET \"{$key}\"={$tmp}";
1940
                }
1941
            }
1942
            $first = true;
1943
            foreach ($keyarr as $k => $v) {
1944
                $this->fieldClean($k);
1945
                $this->clean($v);
1946
                if ($first) {
1947
                    $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...
1948
                    $first = false;
1949
                } else {
1950
                    $sql .= " AND \"{$k}\"='{$v}'";
1951
                }
1952
            }
1953
        }
1954
1955
        // Begin transaction.  We do this so that we can ensure only one row is
1956
        // edited
1957
        $status = $this->beginTransaction();
1958
        if ($status != 0) {
1959
            $this->rollbackTransaction();
1960
1961
            return -1;
1962
        }
1963
1964
        $status = $this->execute($sql);
1965
        if ($status != 0) {
1966
            // update failed
1967
            $this->rollbackTransaction();
1968
1969
            return -1;
1970
        }
1971
1972
        if ($this->conn->Affected_Rows() != 1) {
1973
            // more than one row could be updated
1974
            $this->rollbackTransaction();
1975
1976
            return -2;
1977
        }
1978
1979
        // End transaction
1980
        return $this->endTransaction();
1981
    }
1982
1983
    /**
1984
     * Delete a row from a table.
1985
     *
1986
     * @param string $table  The table from which to delete
1987
     * @param string $key    An array mapping column => value to delete
1988
     * @param bool $schema
1989
     *
1990
     * @return bool|int 0 success
1991
     */
1992
    public function deleteRow($table, $key, $schema = false)
1993
    {
1994
        if (!is_array($key)) {
0 ignored issues
show
introduced by
The condition is_array($key) is always false.
Loading history...
1995
            return -1;
1996
        }
1997
1998
        // Begin transaction.  We do this so that we can ensure only one row is
1999
        // deleted
2000
        $status = $this->beginTransaction();
2001
        if ($status != 0) {
2002
            $this->rollbackTransaction();
2003
2004
            return -1;
2005
        }
2006
2007
        if ($schema === false) {
2008
            $schema = $this->_schema;
2009
        }
2010
2011
        $status = $this->delete($table, $key, $schema);
2012
        if ($status != 0 || $this->conn->Affected_Rows() != 1) {
2013
            $this->rollbackTransaction();
2014
2015
            return -2;
2016
        }
2017
2018
        // End transaction
2019
        return $this->endTransaction();
2020
    }
2021
2022
    /**
2023
     * Returns the SQL for changing the current user.
2024
     *
2025
     * @param string $user The user to change to
2026
     *
2027
     * @return string The SQL
2028
     */
2029
    public function getChangeUserSQL($user)
2030
    {
2031
        $this->clean($user);
2032
2033
        return "SET SESSION AUTHORIZATION '{$user}';";
2034
    }
2035
2036
    abstract public function fieldClean(&$str);
2037
2038
    abstract public function beginTransaction();
2039
2040
    abstract public function rollbackTransaction();
2041
2042
    abstract public function endTransaction();
2043
2044
    abstract public function execute($sql);
2045
2046
    abstract public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null);
2047
2048
    abstract public function selectSet($sql);
2049
2050
    abstract public function clean(&$str);
2051
2052
    abstract public function phpBool($parameter);
2053
2054
    abstract public function hasCreateTableLikeWithConstraints();
2055
2056
    abstract public function hasCreateTableLikeWithIndexes();
2057
2058
    abstract public function hasTablespaces();
2059
2060
    abstract public function delete($table, $conditions, $schema = '');
2061
}
2062