Passed
Push — master ( f90c9d...ec36d3 )
by Felipe
12:09 queued 06:02
created

TableTrait::saveAutovacuum()   B

Complexity

Conditions 8
Paths 128

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 28
dl 0
loc 50
c 0
b 0
f 0
rs 8.2111
cc 8
nc 128
nop 8

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.51
5
 */
6
7
namespace PHPPgAdmin\Database\Traits;
8
9
/**
10
 * Common trait for tables manipulation.
11
 */
12
trait TableTrait
13
{
14
    use \PHPPgAdmin\Database\Traits\ColumnTrait;
15
    use \PHPPgAdmin\Database\Traits\RowTrait;
16
    use \PHPPgAdmin\Database\Traits\TriggerTrait;
17
18
    /**
19
     * Return all tables in current database excluding schemas 'pg_catalog', 'information_schema' and 'pg_toast'.
20
     *
21
     *
22
     * @return \PHPPgAdmin\ADORecordSet All tables, sorted alphabetically
23
     */
24
    public function getAllTables()
25
    {
26
        $sql = "SELECT
27
                        schemaname AS nspname,
28
                        tablename AS relname,
29
                        tableowner AS relowner
30
                    FROM pg_catalog.pg_tables
31
                    WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
32
                    ORDER BY schemaname, tablename";
33
34
        return $this->selectSet($sql);
35
    }
36
37
    /**
38
     * Return all tables in current database (and schema).
39
     *
40
     * @return \PHPPgAdmin\ADORecordSet All tables, sorted alphabetically
41
     */
42
    public function getTables()
43
    {
44
        $c_schema = $this->_schema;
45
        $this->clean($c_schema);
46
47
        $sql = "
48
                SELECT c.relname,
49
                    pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
50
                    pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
51
                    reltuples::bigint as reltuples,
52
                    pt.spcname as tablespace, ";
53
54
        /*
55
         * Either display_sizes is true for tables and schemas,
56
         * or we must check if said config is an associative array
57
         */
58
        if (isset($this->conf['display_sizes']) &&
59
            (
60
                $this->conf['display_sizes'] === true ||
61
                (
62
                    is_array($this->conf['display_sizes']) &&
63
                    array_key_exists('tables', $this->conf['display_sizes']) &&
64
                    $this->conf['display_sizes']['tables'] === true
65
                )
66
            )
67
        ) {
68
            $sql .= ' pg_size_pretty(pg_total_relation_size(c.oid)) as table_size ';
69
        } else {
70
            $sql .= "   'N/A' as table_size ";
71
        }
72
73
        $sql .= " FROM pg_catalog.pg_class c
74
                LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
75
                LEFT JOIN  pg_catalog.pg_tablespace pt ON  pt.oid=c.reltablespace
76
                WHERE c.relkind = 'r'
77
                AND nspname='{$c_schema}'
78
                ORDER BY c.relname";
79
80
        return $this->selectSet($sql);
81
    }
82
83
    /**
84
     * Finds the names and schemas of parent tables (in order).
85
     *
86
     * @param string $table The table to find the parents for
87
     *
88
     * @return \PHPPgAdmin\ADORecordSet A recordset
89
     */
90
    public function getTableParents($table)
91
    {
92
        $c_schema = $this->_schema;
93
        $this->clean($c_schema);
94
        $this->clean($table);
95
96
        $sql = "
97
            SELECT
98
                pn.nspname, relname
99
            FROM
100
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
101
            WHERE
102
                pc.oid=pi.inhparent
103
                AND pc.relnamespace=pn.oid
104
                AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
105
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
106
            ORDER BY
107
                pi.inhseqno
108
        ";
109
110
        return $this->selectSet($sql);
111
    }
112
113
    /**
114
     * Finds the names and schemas of child tables.
115
     *
116
     * @param string $table The table to find the children for
117
     *
118
     * @return \PHPPgAdmin\ADORecordSet A recordset
119
     */
120
    public function getTableChildren($table)
121
    {
122
        $c_schema = $this->_schema;
123
        $this->clean($c_schema);
124
        $this->clean($table);
125
126
        $sql = "
127
            SELECT
128
                pn.nspname, relname
129
            FROM
130
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
131
            WHERE
132
                pc.oid=pi.inhrelid
133
                AND pc.relnamespace=pn.oid
134
                AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
135
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
136
        ";
137
138
        return $this->selectSet($sql);
139
    }
140
141
    /**
142
     * Returns the SQL definition for the table.
143
     * MUST be run within a transaction.
144
     *
145
     * @param string $table       The table to define
146
     * @param string $cleanprefix set to '-- ' to avoid issuing DROP statement
147
     *
148
     * @return string A string containing the formatted SQL code
149
     */
150
    public function getTableDefPrefix($table, $cleanprefix = '')
151
    {
152
        // Fetch table
153
        $t = $this->getTable($table);
154
        if (!is_object($t) || $t->RecordCount() != 1) {
0 ignored issues
show
introduced by
The condition $t->RecordCount() != 1 is always true.
Loading history...
155
            $this->rollbackTransaction();
156
157
            return null;
158
        }
159
        $this->fieldClean($t->fields['relname']);
160
        $this->fieldClean($t->fields['nspname']);
161
162
        // Fetch attributes
163
        $atts = $this->getTableAttributes($table);
164
        if (!is_object($atts)) {
165
            $this->rollbackTransaction();
166
167
            return null;
168
        }
169
170
        // Fetch constraints
171
        $cons = $this->getConstraints($table);
172
        if (!is_object($cons)) {
173
            $this->rollbackTransaction();
174
175
            return null;
176
        }
177
178
        // Output a reconnect command to create the table as the correct user
179
        $sql = "-- PHPPgAdmin\n".$this->getChangeUserSQL($t->fields['relowner'])."\n\n";
180
181
        $sql = $this->_dumpCreate($t, $sql, $cleanprefix);
182
183
        // Output all table columns
184
        $col_comments_sql = ''; // Accumulate comments on columns
185
        $num              = $atts->RecordCount() + $cons->RecordCount();
186
        $i                = 1;
187
188
        $sql = $this->_dumpSerials($atts, $t, $sql, $col_comments_sql, $i, $num);
189
190
        $consOutput = $this->_dumpConstraints($cons, $table, $sql, $i, $num);
191
192
        if ($consOutput === null) {
193
            return null;
194
        }
195
        $sql = $consOutput;
196
197
        $sql .= ')';
198
199
        // @@@@ DUMP CLUSTERING INFORMATION
200
201
        // Inherits
202
        /**
203
         * XXX: This is currently commented out as handling inheritance isn't this simple.
204
         * You also need to make sure you don't dump inherited columns and defaults, as well
205
         * as inherited NOT NULL and CHECK constraints.  So for the time being, we just do
206
         * not claim to support inheritance.
207
         * $parents = $this->getTableParents($table);
208
         * if ($parents->RecordCount() > 0) {
209
         * $sql .= " INHERITS (";
210
         * while (!$parents->EOF) {
211
         * $this->fieldClean($parents->fields['relname']);
212
         * // Qualify the parent table if it's in another schema
213
         * if ($parents->fields['schemaname'] != $this->_schema) {
214
         * $this->fieldClean($parents->fields['schemaname']);
215
         * $sql .= "\"{$parents->fields['schemaname']}\".";
216
         * }
217
         * $sql .= "\"{$parents->fields['relname']}\"";.
218
         *
219
         * $parents->moveNext();
220
         * if (!$parents->EOF) $sql .= ', ';
221
         * }
222
         * $sql .= ")";
223
         * }
224
         */
225
226
        // Handle WITHOUT OIDS
227
        if ($this->hasObjectID($table)) {
228
            $sql .= ' WITH OIDS';
229
        } else {
230
            $sql .= ' WITHOUT OIDS';
231
        }
232
233
        $sql .= ";\n";
234
235
        $colStorage = $this->_dumpColStats($atts, $t, $sql);
236
237
        if ($colStorage === null) {
238
            return null;
239
        }
240
        $sql = $colStorage;
241
242
        // Comment
243
        if ($t->fields['relcomment'] !== null) {
244
            $this->clean($t->fields['relcomment']);
245
            $sql .= "\n-- Comment\n\n";
246
            $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n";
247
        }
248
249
        // Add comments on columns, if any
250
        if ($col_comments_sql != '') {
251
            $sql .= $col_comments_sql;
252
        }
253
254
        // Privileges
255
        $privs = $this->getPrivileges($table, 'table');
256
        if (!is_array($privs)) {
257
            $this->rollbackTransaction();
258
259
            return null;
260
        }
261
262
        $privsOutput = $this->_dumpPrivileges($privs, $t, $sql);
263
264
        if ($privsOutput === null) {
265
            return null;
266
        }
267
        $sql = $privsOutput;
268
269
        // Add a newline to separate data that follows (if any)
270
        $sql .= "\n";
271
272
        return $sql;
273
    }
274
275
    /**
276
     * Dumps serial-like columns in the table.
277
     *
278
     * @param \PHPPgAdmin\ADORecordSet $atts             table attributes
279
     * @param \PHPPgAdmin\ADORecordSet $tblfields        table fields object
280
     * @param string                   $sql              The sql sentence
281
     *                                                   generated so far
282
     * @param string                   $col_comments_sql Column comments,
283
     *                                                   passed by reference
284
     * @param int                      $i                current counter to
285
     *                                                   know if we should
286
     *                                                   append a comma to the
287
     *                                                   sentence
288
     * @param int                      $num              Table attributes
289
     *                                                   count + table
290
     *                                                   constraints count
291
     *
292
     * @return string original $sql plus appended strings
293
     */
294
    private function _dumpSerials($atts, $tblfields, $sql, &$col_comments_sql, $i, $num)
295
    {
296
        while (!$atts->EOF) {
297
            $this->fieldClean($atts->fields['attname']);
298
            $sql .= "    \"{$atts->fields['attname']}\"";
299
            // Dump SERIAL and BIGSERIAL columns correctly
300
            if ($this->phpBool($atts->fields['attisserial']) &&
301
                ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) {
302
                if ($atts->fields['type'] == 'integer') {
303
                    $sql .= ' SERIAL';
304
                } else {
305
                    $sql .= ' BIGSERIAL';
306
                }
307
            } else {
308
                $sql .= ' '.$this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
309
310
                // Add NOT NULL if necessary
311
                if ($this->phpBool($atts->fields['attnotnull'])) {
312
                    $sql .= ' NOT NULL';
313
                }
314
315
                // Add default if necessary
316
                if ($atts->fields['adsrc'] !== null) {
317
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
318
                }
319
            }
320
321
            // Output comma or not
322
            if ($i < $num) {
323
                $sql .= ",\n";
324
            } else {
325
                $sql .= "\n";
326
            }
327
328
            // Does this column have a comment?
329
            if ($atts->fields['comment'] !== null) {
330
                $this->clean($atts->fields['comment']);
331
                $col_comments_sql .= "COMMENT ON COLUMN \"{$tblfields->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
332
            }
333
334
            $atts->moveNext();
335
            ++$i;
336
        }
337
338
        return $sql;
339
    }
340
341
    /**
342
     * Dumps constraints.
343
     *
344
     * @param \PHPPgAdmin\ADORecordSet $cons  The table constraints
345
     * @param string                   $table The table to define
346
     * @param string                   $sql   The sql sentence generated so
347
     *                                        far
348
     * @param mixed                    $i
349
     * @param int                      $num   Table attributes count + table
350
     *                                        constraints count
351
     *
352
     * @return string original $sql plus appended strings
353
     */
354
    private function _dumpConstraints($cons, $table, $sql, $i, $num)
355
    {
356
        // Output all table constraints
357
        while (!$cons->EOF) {
358
            $this->fieldClean($cons->fields['conname']);
359
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
360
            // Nasty hack to support pre-7.4 PostgreSQL
361
            if ($cons->fields['consrc'] !== null) {
362
                $sql .= $cons->fields['consrc'];
363
            } else {
364
                switch ($cons->fields['contype']) {
365
                    case 'p':
366
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
367
                        $sql .= 'PRIMARY KEY ('.join(',', $keys).')';
368
369
                        break;
370
                    case 'u':
371
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
372
                        $sql .= 'UNIQUE ('.join(',', $keys).')';
373
374
                        break;
375
                    default:
376
                        // Unrecognised constraint
377
                        $this->rollbackTransaction();
378
379
                        return null;
380
                }
381
            }
382
383
            // Output comma or not
384
            if ($i < $num) {
385
                $sql .= ",\n";
386
            } else {
387
                $sql .= "\n";
388
            }
389
390
            $cons->moveNext();
391
            ++$i;
392
        }
393
394
        return $sql;
395
    }
396
397
    /**
398
     * Dumps col statistics.
399
     *
400
     * @param \PHPPgAdmin\ADORecordSet $atts      table attributes
401
     * @param \PHPPgAdmin\ADORecordSet $tblfields table field attributes
402
     * @param string                   $sql       The sql sentence generated so far
403
     *
404
     * @return string original $sql plus appended strings
405
     */
406
    private function _dumpColStats($atts, $tblfields, $sql)
407
    {
408
        // Column storage and statistics
409
        $atts->moveFirst();
410
        $first = true;
411
        while (!$atts->EOF) {
412
            $this->fieldClean($atts->fields['attname']);
413
            // Statistics first
414
            if ($atts->fields['attstattarget'] >= 0) {
415
                if ($first) {
416
                    $sql .= "\n";
417
                    $first = false;
418
                }
419
                $sql .= "ALTER TABLE ONLY \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
420
            }
421
            // Then storage
422
            if ($atts->fields['attstorage'] != $atts->fields['typstorage']) {
423
                switch ($atts->fields['attstorage']) {
424
                    case 'p':
425
                        $storage = 'PLAIN';
426
427
                        break;
428
                    case 'e':
429
                        $storage = 'EXTERNAL';
430
431
                        break;
432
                    case 'm':
433
                        $storage = 'MAIN';
434
435
                        break;
436
                    case 'x':
437
                        $storage = 'EXTENDED';
438
439
                        break;
440
                    default:
441
                        // Unknown storage type
442
                        $this->rollbackTransaction();
443
444
                        return null;
445
                }
446
                $sql .= "ALTER TABLE ONLY \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
447
            }
448
449
            $atts->moveNext();
450
        }
451
452
        return $sql;
453
    }
454
455
    /**
456
     * Dumps privileges.
457
     *
458
     * @param \PHPPgAdmin\ADORecordSet $privs     The table privileges
459
     * @param \PHPPgAdmin\ADORecordSet $tblfields The table fields definition
460
     * @param string                   $sql       The sql sentence generated so far
461
     *
462
     * @return string original $sql plus appended strings
463
     */
464
    private function _dumpPrivileges($privs, $tblfields, $sql)
465
    {
466
        if (sizeof($privs) <= 0) {
0 ignored issues
show
Bug introduced by
$privs of type PHPPgAdmin\ADORecordSet is incompatible with the type Countable|array expected by parameter $var of sizeof(). ( Ignorable by Annotation )

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

466
        if (sizeof(/** @scrutinizer ignore-type */ $privs) <= 0) {
Loading history...
467
            return $sql;
468
        }
469
        $sql .= "\n-- Privileges\n\n";
470
        /*
471
         * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
472
         * wire-in knowledge about the default public privileges for different
473
         * kinds of objects.
474
         */
475
        $sql .= "REVOKE ALL ON TABLE \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" FROM PUBLIC;\n";
476
        foreach ($privs as $v) {
477
            // Get non-GRANT OPTION privs
478
            $nongrant = array_diff($v[2], $v[4]);
479
480
            // Skip empty or owner ACEs
481
            if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $tblfields->fields['relowner'])) {
482
                continue;
483
            }
484
485
            // Change user if necessary
486
            if ($this->hasGrantOption() && $v[3] != $tblfields->fields['relowner']) {
487
                $grantor = $v[3];
488
                $this->clean($grantor);
489
                $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
490
            }
491
492
            // Output privileges with no GRANT OPTION
493
            $sql .= 'GRANT '.join(', ', $nongrant)." ON TABLE \"{$tblfields->fields['relname']}\" TO ";
494
            switch ($v[0]) {
495
                case 'public':
496
                    $sql .= "PUBLIC;\n";
497
498
                    break;
499
                case 'user':
500
                case 'role':
501
                    $this->fieldClean($v[1]);
502
                    $sql .= "\"{$v[1]}\";\n";
503
504
                    break;
505
                case 'group':
506
                    $this->fieldClean($v[1]);
507
                    $sql .= "GROUP \"{$v[1]}\";\n";
508
509
                    break;
510
                default:
511
                    // Unknown privilege type - fail
512
                    $this->rollbackTransaction();
513
514
                    return null;
515
            }
516
517
            // Reset user if necessary
518
            if ($this->hasGrantOption() && $v[3] != $tblfields->fields['relowner']) {
519
                $sql .= "RESET SESSION AUTHORIZATION;\n";
520
            }
521
522
            // Output privileges with GRANT OPTION
523
524
            // Skip empty or owner ACEs
525
            if (!$this->hasGrantOption() || sizeof($v[4]) == 0) {
526
                continue;
527
            }
528
529
            // Change user if necessary
530
            if ($this->hasGrantOption() && $v[3] != $tblfields->fields['relowner']) {
531
                $grantor = $v[3];
532
                $this->clean($grantor);
533
                $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
534
            }
535
536
            $sql .= 'GRANT '.join(', ', $v[4])." ON \"{$tblfields->fields['relname']}\" TO ";
537
            switch ($v[0]) {
538
                case 'public':
539
                    $sql .= 'PUBLIC';
540
541
                    break;
542
                case 'user':
543
                case 'role':
544
                    $this->fieldClean($v[1]);
545
                    $sql .= "\"{$v[1]}\"";
546
547
                    break;
548
                case 'group':
549
                    $this->fieldClean($v[1]);
550
                    $sql .= "GROUP \"{$v[1]}\"";
551
552
                    break;
553
                default:
554
                    // Unknown privilege type - fail
555
                    return null;
556
            }
557
            $sql .= " WITH GRANT OPTION;\n";
558
559
            // Reset user if necessary
560
            if ($this->hasGrantOption() && $v[3] != $tblfields->fields['relowner']) {
561
                $sql .= "RESET SESSION AUTHORIZATION;\n";
562
            }
563
        }
564
565
        return $sql;
566
    }
567
568
    /**
569
     * Dumps a create.
570
     *
571
     * @param \PHPPgAdmin\ADORecordSet $tblfields   table fields object
572
     * @param string                   $sql         The sql sentence generated so far
573
     * @param string                   $cleanprefix set to '-- ' to avoid issuing DROP statement
574
     * @param mixed                    $fields
575
     *
576
     * @return string original $sql plus appended strings
577
     */
578
    private function _dumpCreate($tblfields, $sql, $cleanprefix)
579
    {
580
        // Set schema search path
581
        $sql .= "SET search_path = \"{$tblfields->fields['nspname']}\", pg_catalog;\n\n";
582
583
        // Begin CREATE TABLE definition
584
        $sql .= "-- Definition\n\n";
585
        // DROP TABLE must be fully qualified in case a table with the same name exists
586
        $sql .= $cleanprefix.'DROP TABLE ';
587
        $sql .= "\"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\";\n";
588
        $sql .= "CREATE TABLE \"{$tblfields->fields['nspname']}\".\"{$tblfields->fields['relname']}\" (\n";
589
590
        return $sql;
591
    }
592
593
    /**
594
     * Returns table information.
595
     *
596
     * @param string $table The name of the table
597
     *
598
     * @return \PHPPgAdmin\ADORecordSet A recordset
599
     */
600
    public function getTable($table)
601
    {
602
        $c_schema = $this->_schema;
603
        $this->clean($c_schema);
604
        $this->clean($table);
605
606
        $sql = '
607
            SELECT
608
              c.relname, n.nspname, ';
609
610
        $sql .= ($this->hasRoles() ? ' coalesce(u.usename,r.rolname) ' : ' u.usename')." AS relowner,
611
              pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
612
              pt.spcname  AS tablespace
613
            FROM pg_catalog.pg_class c
614
                LEFT JOIN pg_catalog.pg_tablespace pt ON pt.oid=c.reltablespace
615
                 LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
616
                 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace ";
617
618
        $sql .= ($this->hasRoles() ? ' LEFT JOIN pg_catalog.pg_roles r ON c.relowner = r.oid ' : '').
619
            " WHERE c.relkind = 'r'
620
                  AND n.nspname = '{$c_schema}'
621
                  AND n.oid = c.relnamespace
622
                  AND c.relname = '{$table}'";
623
624
        return $this->selectSet($sql);
625
    }
626
627
    /**
628
     * Retrieve all attributes definition of a table.
629
     *
630
     * @param string $table    The name of the table
631
     * @param string $c_schema The name of the schema
632
     *
633
     * @return \PHPPgAdmin\ADORecordSet All attributes in order
634
     */
635
    private function _getTableAttributesAll($table, $c_schema)
636
    {
637
        $sql = "
638
            SELECT
639
                a.attname,
640
                a.attnum,
641
                pg_catalog.format_type(a.atttypid, a.atttypmod) AS TYPE,
642
                a.atttypmod,
643
                a.attnotnull,
644
                a.atthasdef,
645
                pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, TRUE) AS adsrc,
646
                a.attstattarget,
647
                a.attstorage,
648
                t.typstorage,
649
                CASE
650
                WHEN pc.oid IS NULL THEN FALSE
651
                ELSE TRUE
652
                END AS attisserial,
653
                pg_catalog.col_description(a.attrelid, a.attnum) AS COMMENT
654
655
            FROM pg_catalog.pg_tables tbl
656
            JOIN pg_catalog.pg_class tbl_class ON tbl.tablename=tbl_class.relname
657
            JOIN  pg_catalog.pg_attribute a ON tbl_class.oid = a.attrelid
658
            JOIN pg_catalog.pg_namespace    ON pg_namespace.oid = tbl_class.relnamespace
659
                                            AND pg_namespace.nspname=tbl.schemaname
660
            LEFT JOIN pg_catalog.pg_attrdef adef    ON a.attrelid=adef.adrelid
661
                                                    AND a.attnum=adef.adnum
662
            LEFT JOIN pg_catalog.pg_type t  ON a.atttypid=t.oid
663
            LEFT JOIN  pg_catalog.pg_depend pd  ON pd.refobjid=a.attrelid
664
                                                AND pd.refobjsubid=a.attnum
665
                                                AND pd.deptype='i'
666
            LEFT JOIN pg_catalog.pg_class pc ON pd.objid=pc.oid
667
                                            AND pd.classid=pc.tableoid
668
                                            AND pd.refclassid=pc.tableoid
669
                                            AND pc.relkind='S'
670
            WHERE tbl.tablename='{$table}'
671
            AND tbl.schemaname='{$c_schema}'
672
            AND a.attnum > 0 AND NOT a.attisdropped
673
            ORDER BY a.attnum";
674
675
        return $this->selectSet($sql);
676
    }
677
678
    /**
679
     * Retrieve single attribute definition of a table.
680
     *
681
     * @param string $table    The name of the table
682
     * @param string $c_schema The schema of the table
683
     * @param string $field    (optional) The name of a field to return
684
     *
685
     * @return \PHPPgAdmin\ADORecordSet All attributes in order
686
     */
687
    private function _getTableAttribute($table, $c_schema, $field)
688
    {
689
        $sql = "
690
                SELECT
691
                    a.attname, a.attnum,
692
                    pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
693
                    pg_catalog.format_type(a.atttypid, NULL) as base_type,
694
                    a.atttypmod,
695
                    a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
696
                    a.attstattarget, a.attstorage, t.typstorage,
697
                    pg_catalog.col_description(a.attrelid, a.attnum) AS comment
698
                FROM
699
                    pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
700
                    ON a.attrelid=adef.adrelid
701
                    AND a.attnum=adef.adnum
702
                    LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
703
                WHERE
704
                    a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
705
                        AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
706
                        nspname = '{$c_schema}'))
707
                    AND a.attname = '{$field}'";
708
709
        return $this->selectSet($sql);
710
    }
711
712
    /**
713
     * Retrieve the attribute definition of a table.
714
     *
715
     * @param string $table The name of the table
716
     * @param string $field (optional) The name of a field to return
717
     *
718
     * @return \PHPPgAdmin\ADORecordSet All attributes in order
719
     */
720
    public function getTableAttributes($table, $field = '')
721
    {
722
        $c_schema = $this->_schema;
723
        $this->clean($c_schema);
724
        $this->clean($table);
725
726
        if ($field == '') {
727
            // This query is made much more complex by the addition of the 'attisserial' field.
728
            // The subquery to get that field checks to see if there is an internally dependent
729
            // sequence on the field.
730
            return $this->_getTableAttributesAll($table, $c_schema);
731
        }
732
        $this->clean($field);
733
734
        return $this->_getTableAttribute($table, $c_schema, $field);
735
    }
736
737
    /**
738
     * Returns a list of all constraints on a table.
739
     *
740
     * @param string $table The table to find rules for
741
     *
742
     * @return \PHPPgAdmin\ADORecordSet A recordset
743
     */
744
    public function getConstraints($table)
745
    {
746
        $c_schema = $this->_schema;
747
        $this->clean($c_schema);
748
        $this->clean($table);
749
750
        // This SQL is greatly complicated by the need to retrieve
751
        // index clustering information for primary and unique constraints
752
        $sql = "SELECT
753
                pc.conname,
754
                pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc,
755
                pc.contype,
756
                CASE WHEN pc.contype='u' OR pc.contype='p' THEN (
757
                    SELECT
758
                        indisclustered
759
                    FROM
760
                        pg_catalog.pg_depend pd,
761
                        pg_catalog.pg_class pl,
762
                        pg_catalog.pg_index pi
763
                    WHERE
764
                        pd.refclassid=pc.tableoid
765
                        AND pd.refobjid=pc.oid
766
                        AND pd.objid=pl.oid
767
                        AND pl.oid=pi.indexrelid
768
                ) ELSE
769
                    NULL
770
                END AS indisclustered
771
            FROM
772
                pg_catalog.pg_constraint pc
773
            WHERE
774
                pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
775
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
776
                    WHERE nspname='{$c_schema}'))
777
            ORDER BY
778
                1
779
        ";
780
781
        return $this->selectSet($sql);
782
    }
783
784
    /**
785
     * Checks to see whether or not a table has a unique id column.
786
     *
787
     * @param string $table The table name
788
     *
789
     * @return true if it has a unique id, false otherwise
790
     */
791
    public function hasObjectID($table)
792
    {
793
        $c_schema = $this->_schema;
794
        $this->clean($c_schema);
795
        $this->clean($table);
796
797
        $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}'
798
            AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')";
799
800
        $rs = $this->selectSet($sql);
801
        if ($rs->RecordCount() != 1) {
802
            return null;
803
        }
804
805
        $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']);
806
807
        return $rs->fields['relhasoids'];
808
    }
809
810
    /**
811
     * Returns extra table definition information that is most usefully
812
     * dumped after the table contents for speed and efficiency reasons.
813
     *
814
     * @param string $table The table to define
815
     *
816
     * @return string A string containing the formatted SQL code
817
     */
818
    public function getTableDefSuffix($table)
819
    {
820
        $sql = '';
821
822
        // Indexes
823
        $indexes = $this->getIndexes($table);
824
        if (!is_object($indexes)) {
825
            $this->rollbackTransaction();
826
827
            return null;
828
        }
829
830
        if ($indexes->RecordCount() > 0) {
831
            $sql .= "\n-- Indexes\n\n";
832
            while (!$indexes->EOF) {
833
                $sql .= $indexes->fields['inddef'].";\n";
834
835
                $indexes->moveNext();
836
            }
837
        }
838
839
        // Triggers
840
        $triggers = $this->getTriggers($table);
841
        if (!is_object($triggers)) {
842
            $this->rollbackTransaction();
843
844
            return null;
845
        }
846
847
        if ($triggers->RecordCount() > 0) {
848
            $sql .= "\n-- Triggers\n\n";
849
            while (!$triggers->EOF) {
850
                $sql .= $triggers->fields['tgdef'];
851
                $sql .= ";\n";
852
853
                $triggers->moveNext();
854
            }
855
        }
856
857
        // Rules
858
        $rules = $this->getRules($table);
859
        if (!is_object($rules)) {
860
            $this->rollbackTransaction();
861
862
            return null;
863
        }
864
865
        if ($rules->RecordCount() > 0) {
866
            $sql .= "\n-- Rules\n\n";
867
            while (!$rules->EOF) {
868
                $sql .= $rules->fields['definition']."\n";
869
870
                $rules->moveNext();
871
            }
872
        }
873
874
        return $sql;
875
    }
876
877
    /**
878
     * Grabs a list of indexes for a table.
879
     *
880
     * @param string $table  The name of a table whose indexes to retrieve
881
     * @param bool   $unique Only get unique/pk indexes
882
     *
883
     * @return \PHPPgAdmin\ADORecordSet A recordset
884
     */
885
    public function getIndexes($table = '', $unique = false)
886
    {
887
        $this->clean($table);
888
889
        $sql = "
890
            SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
891
                pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef
892
            FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
893
            WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid)
894
                AND c.oid = i.indrelid AND i.indexrelid = c2.oid
895
        ";
896
        if ($unique) {
897
            $sql .= ' AND i.indisunique ';
898
        }
899
900
        $sql .= ' ORDER BY c2.relname';
901
902
        return $this->selectSet($sql);
903
    }
904
905
    /**
906
     * Grabs a list of triggers on a table.
907
     *
908
     * @param string $table The name of a table whose triggers to retrieve
909
     *
910
     * @return \PHPPgAdmin\ADORecordSet A recordset
911
     */
912
    public function getTriggers($table = '')
913
    {
914
        $c_schema = $this->_schema;
915
        $this->clean($c_schema);
916
        $this->clean($table);
917
918
        $sql = "SELECT
919
                t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef,
920
                CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid,
921
                p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
922
                ns.nspname AS pronamespace
923
            FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns
924
            WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
925
                AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
926
                AND ( tgconstraint = 0 OR NOT EXISTS
927
                        (SELECT 1 FROM pg_catalog.pg_depend d    JOIN pg_catalog.pg_constraint c
928
                            ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
929
                        WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))
930
                AND p.oid=t.tgfoid
931
                AND p.pronamespace = ns.oid";
932
933
        return $this->selectSet($sql);
934
    }
935
936
    /**
937
     * Returns a list of all rules on a table OR view.
938
     *
939
     * @param string $table The table to find rules for
940
     *
941
     * @return \PHPPgAdmin\ADORecordSet A recordset
942
     */
943
    public function getRules($table)
944
    {
945
        $c_schema = $this->_schema;
946
        $this->clean($c_schema);
947
        $this->clean($table);
948
949
        $sql = "
950
            SELECT *
951
            FROM pg_catalog.pg_rules
952
            WHERE
953
                schemaname='{$c_schema}' AND tablename='{$table}'
954
            ORDER BY rulename
955
        ";
956
957
        return $this->selectSet($sql);
958
    }
959
960
    /**
961
     * Creates a new table in the database.
962
     *
963
     * @param string $name        The name of the table
964
     * @param int    $fields      The number of fields
965
     * @param array  $field       An array of field names
966
     * @param array  $type        An array of field types
967
     * @param array  $array       An array of '' or '[]' for each type if it's an array or not
968
     * @param array  $length      An array of field lengths
969
     * @param array  $notnull     An array of not null
970
     * @param array  $default     An array of default values
971
     * @param bool   $withoutoids True if WITHOUT OIDS, false otherwise
972
     * @param array  $colcomment  An array of comments
973
     * @param string $tblcomment  the comment for the table
974
     * @param string $tablespace  The tablespace name ('' means none/default)
975
     * @param array  $uniquekey   An Array indicating the fields that are unique (those indexes that are set)
976
     * @param array  $primarykey  An Array indicating the field used for the primarykey (those indexes that are set)
977
     *
978
     * @return bool|int 0 success
979
     */
980
    public function createTable(
981
        $name,
982
        $fields,
983
        $field,
984
        $type,
985
        $array,
986
        $length,
987
        $notnull,
988
        $default,
989
        $withoutoids,
990
        $colcomment,
991
        $tblcomment,
992
        $tablespace,
993
        $uniquekey,
994
        $primarykey
995
    ) {
996
        $f_schema = $this->_schema;
997
        $this->fieldClean($f_schema);
998
        $this->fieldClean($name);
999
1000
        $status = $this->beginTransaction();
1001
        if ($status != 0) {
1002
            return -1;
1003
        }
1004
1005
        $found       = false;
1006
        $first       = true;
1007
        $comment_sql = ''; //Accumulate comments for the columns
1008
        $sql         = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (";
1009
        for ($i = 0; $i < $fields; ++$i) {
1010
            $this->fieldClean($field[$i]);
1011
            $this->clean($type[$i]);
1012
            $this->clean($length[$i]);
1013
            $this->clean($colcomment[$i]);
1014
1015
            // Skip blank columns - for user convenience
1016
            if ($field[$i] == '' || $type[$i] == '') {
1017
                continue;
1018
            }
1019
1020
            // If not the first column, add a comma
1021
            if (!$first) {
1022
                $sql .= ', ';
1023
            } else {
1024
                $first = false;
1025
            }
1026
1027
            switch ($type[$i]) {
1028
                // Have to account for weird placing of length for with/without
1029
                // time zone types
1030
                case 'timestamp with time zone':
1031
                case 'timestamp without time zone':
1032
                    $qual = substr($type[$i], 9);
1033
                    $sql .= "\"{$field[$i]}\" timestamp";
1034
                    if ($length[$i] != '') {
1035
                        $sql .= "({$length[$i]})";
1036
                    }
1037
1038
                    $sql .= $qual;
1039
1040
                    break;
1041
                case 'time with time zone':
1042
                case 'time without time zone':
1043
                    $qual = substr($type[$i], 4);
1044
                    $sql .= "\"{$field[$i]}\" time";
1045
                    if ($length[$i] != '') {
1046
                        $sql .= "({$length[$i]})";
1047
                    }
1048
1049
                    $sql .= $qual;
1050
1051
                    break;
1052
                default:
1053
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
1054
                    if ($length[$i] != '') {
1055
                        $sql .= "({$length[$i]})";
1056
                    }
1057
            }
1058
            // Add array qualifier if necessary
1059
            if ($array[$i] == '[]') {
1060
                $sql .= '[]';
1061
            }
1062
1063
            // Add other qualifiers
1064
            if (!isset($primarykey[$i])) {
1065
                if (isset($uniquekey[$i])) {
1066
                    $sql .= ' UNIQUE';
1067
                }
1068
1069
                if (isset($notnull[$i])) {
1070
                    $sql .= ' NOT NULL';
1071
                }
1072
            }
1073
            if ($default[$i] != '') {
1074
                $sql .= " DEFAULT {$default[$i]}";
1075
            }
1076
1077
            if ($colcomment[$i] != '') {
1078
                $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
1079
            }
1080
1081
            $found = true;
1082
        }
1083
1084
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
1085
            return -1;
1086
        }
1087
1088
        // PRIMARY KEY
1089
        $primarykeycolumns = [];
1090
        for ($i = 0; $i < $fields; ++$i) {
1091
            if (isset($primarykey[$i])) {
1092
                $primarykeycolumns[] = "\"{$field[$i]}\"";
1093
            }
1094
        }
1095
        if (count($primarykeycolumns) > 0) {
1096
            $sql .= ', PRIMARY KEY ('.implode(', ', $primarykeycolumns).')';
1097
        }
1098
1099
        $sql .= ')';
1100
1101
        // WITHOUT OIDS
1102
        if ($withoutoids) {
1103
            $sql .= ' WITHOUT OIDS';
1104
        } else {
1105
            $sql .= ' WITH OIDS';
1106
        }
1107
1108
        // Tablespace
1109
        if ($this->hasTablespaces() && $tablespace != '') {
1110
            $this->fieldClean($tablespace);
1111
            $sql .= " TABLESPACE \"{$tablespace}\"";
1112
        }
1113
1114
        $status = $this->execute($sql);
1115
        if ($status) {
1116
            $this->rollbackTransaction();
1117
1118
            return -1;
1119
        }
1120
1121
        if ($tblcomment != '') {
1122
            $status = $this->setComment('TABLE', '', $name, $tblcomment, true);
1123
            if ($status) {
1124
                $this->rollbackTransaction();
1125
1126
                return -1;
1127
            }
1128
        }
1129
1130
        if ($comment_sql != '') {
1131
            $status = $this->execute($comment_sql);
1132
            if ($status) {
1133
                $this->rollbackTransaction();
1134
1135
                return -1;
1136
            }
1137
        }
1138
1139
        return $this->endTransaction();
1140
    }
1141
1142
    /**
1143
     * Creates a new table in the database copying attribs and other properties from another table.
1144
     *
1145
     * @param string $name        The name of the table
1146
     * @param array  $like        an array giving the schema ans the name of the table from which attribs are copying
1147
     *                            from: array(
1148
     *                            'table' => table name,
1149
     *                            'schema' => the schema name,
1150
     *                            )
1151
     * @param bool   $defaults    if true, copy the defaults values as well
1152
     * @param bool   $constraints if true, copy the constraints as well (CHECK on table & attr)
1153
     * @param bool   $idx
1154
     * @param string $tablespace  The tablespace name ('' means none/default)
1155
     *
1156
     * @return bool|int
1157
     */
1158
    public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '')
1159
    {
1160
        $f_schema = $this->_schema;
1161
        $this->fieldClean($f_schema);
1162
        $this->fieldClean($name);
1163
        $this->fieldClean($like['schema']);
1164
        $this->fieldClean($like['table']);
1165
        $like = "\"{$like['schema']}\".\"{$like['table']}\"";
1166
1167
        $status = $this->beginTransaction();
1168
        if ($status != 0) {
1169
            return -1;
1170
        }
1171
1172
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}";
1173
1174
        if ($defaults) {
1175
            $sql .= ' INCLUDING DEFAULTS';
1176
        }
1177
1178
        if ($this->hasCreateTableLikeWithConstraints() && $constraints) {
1179
            $sql .= ' INCLUDING CONSTRAINTS';
1180
        }
1181
1182
        if ($this->hasCreateTableLikeWithIndexes() && $idx) {
1183
            $sql .= ' INCLUDING INDEXES';
1184
        }
1185
1186
        $sql .= ')';
1187
1188
        if ($this->hasTablespaces() && $tablespace != '') {
1189
            $this->fieldClean($tablespace);
1190
            $sql .= " TABLESPACE \"{$tablespace}\"";
1191
        }
1192
1193
        $status = $this->execute($sql);
1194
        if ($status) {
1195
            $this->rollbackTransaction();
1196
1197
            return -1;
1198
        }
1199
1200
        return $this->endTransaction();
1201
    }
1202
1203
    /**
1204
     * Alter table properties.
1205
     *
1206
     * @param string $table      The name of the table
1207
     * @param string $name       The new name for the table
1208
     * @param string $owner      The new owner for the table
1209
     * @param string $schema     The new schema for the table
1210
     * @param string $comment    The comment on the table
1211
     * @param string $tablespace The new tablespace for the table ('' means leave as is)
1212
     *
1213
     * @return bool|int 0 success
1214
     */
1215
    public function alterTable($table, $name, $owner, $schema, $comment, $tablespace)
1216
    {
1217
        $data = $this->getTable($table);
1218
1219
        if ($data->RecordCount() != 1) {
1220
            return -2;
1221
        }
1222
1223
        $status = $this->beginTransaction();
1224
        if ($status != 0) {
1225
            $this->rollbackTransaction();
1226
1227
            return -1;
1228
        }
1229
1230
        $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace);
1231
1232
        if ($status != 0) {
1233
            $this->rollbackTransaction();
1234
1235
            return $status;
1236
        }
1237
1238
        return $this->endTransaction();
1239
    }
1240
1241
    /**
1242
     * Protected method which alter a table
1243
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION.
1244
     *
1245
     * @param \PHPPgAdmin\ADORecordSet $tblrs      The table recordSet returned by getTable()
1246
     * @param string                   $name       The new name for the table
1247
     * @param string                   $owner      The new owner for the table
1248
     * @param string                   $schema     The new schema for the table
1249
     * @param string                   $comment    The comment on the table
1250
     * @param string                   $tablespace The new tablespace for the table ('' means leave as is)
1251
     *
1252
     * @return int 0 success
1253
     */
1254
    protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace)
1255
    {
1256
        $this->fieldArrayClean($tblrs->fields);
1257
1258
        // Comment
1259
        $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment);
1260
        if ($status != 0) {
1261
            return -4;
1262
        }
1263
1264
        // Owner
1265
        $this->fieldClean($owner);
1266
        $status = $this->alterTableOwner($tblrs, $owner);
1267
        if ($status != 0) {
1268
            return -5;
1269
        }
1270
1271
        // Tablespace
1272
        $this->fieldClean($tablespace);
1273
        $status = $this->alterTableTablespace($tblrs, $tablespace);
1274
        if ($status != 0) {
1275
            return -6;
1276
        }
1277
1278
        // Rename
1279
        $this->fieldClean($name);
1280
        $status = $this->alterTableName($tblrs, $name);
1281
        if ($status != 0) {
1282
            return -3;
1283
        }
1284
1285
        // Schema
1286
        $this->fieldClean($schema);
1287
        $status = $this->alterTableSchema($tblrs, $schema);
1288
        if ($status != 0) {
1289
            return -7;
1290
        }
1291
1292
        return 0;
1293
    }
1294
1295
    /**
1296
     * Alter a table's owner
1297
     * /!\ this function is called from _alterTable which take care of escaping fields.
1298
     *
1299
     * @param \PHPPgAdmin\ADORecordSet $tblrs The table RecordSet returned by getTable()
1300
     * @param null|string              $owner
1301
     *
1302
     * @return int 0 if operation was successful
1303
     */
1304
    public function alterTableOwner($tblrs, $owner = null)
1305
    {
1306
        /* vars cleaned in _alterTable */
1307
        if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) {
1308
            $f_schema = $this->_schema;
1309
            $this->fieldClean($f_schema);
1310
            // If owner has been changed, then do the alteration.  We are
1311
            // careful to avoid this generally as changing owner is a
1312
            // superuser only function.
1313
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\"";
1314
1315
            return $this->execute($sql);
1316
        }
1317
1318
        return 0;
1319
    }
1320
1321
    /**
1322
     * Alter a table's tablespace
1323
     * /!\ this function is called from _alterTable which take care of escaping fields.
1324
     *
1325
     * @param \PHPPgAdmin\ADORecordSet $tblrs      The table RecordSet returned by getTable()
1326
     * @param null|string              $tablespace
1327
     *
1328
     * @return int 0 if operation was successful
1329
     */
1330
    public function alterTableTablespace($tblrs, $tablespace = null)
1331
    {
1332
        /* vars cleaned in _alterTable */
1333
        if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) {
1334
            $f_schema = $this->_schema;
1335
            $this->fieldClean($f_schema);
1336
1337
            // If tablespace has been changed, then do the alteration.  We
1338
            // don't want to do this unnecessarily.
1339
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\"";
1340
1341
            return $this->execute($sql);
1342
        }
1343
1344
        return 0;
1345
    }
1346
1347
    /**
1348
     * Alter a table's name
1349
     * /!\ this function is called from _alterTable which take care of escaping fields.
1350
     *
1351
     * @param \PHPPgAdmin\ADORecordSet $tblrs The table RecordSet returned by getTable()
1352
     * @param string                   $name  The new table's name
1353
     *
1354
     * @return int 0 if operation was successful
1355
     */
1356
    public function alterTableName($tblrs, $name = null)
1357
    {
1358
        /* vars cleaned in _alterTable */
1359
        // Rename (only if name has changed)
1360
        if (!empty($name) && ($name != $tblrs->fields['relname'])) {
1361
            $f_schema = $this->_schema;
1362
            $this->fieldClean($f_schema);
1363
1364
            $sql    = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" RENAME TO \"{$name}\"";
1365
            $status = $this->execute($sql);
1366
            if ($status == 0) {
1367
                $tblrs->fields['relname'] = $name;
1368
            } else {
1369
                return $status;
1370
            }
1371
        }
1372
1373
        return 0;
1374
    }
1375
1376
    // Row functions
1377
1378
    /**
1379
     * Alter a table's schema
1380
     * /!\ this function is called from _alterTable which take care of escaping fields.
1381
     *
1382
     * @param \PHPPgAdmin\ADORecordSet $tblrs  The table RecordSet returned by getTable()
1383
     * @param null|string              $schema
1384
     *
1385
     * @return int 0 if operation was successful
1386
     */
1387
    public function alterTableSchema($tblrs, $schema = null)
1388
    {
1389
        /* vars cleaned in _alterTable */
1390
        if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) {
1391
            $f_schema = $this->_schema;
1392
            $this->fieldClean($f_schema);
1393
            // If tablespace has been changed, then do the alteration.  We
1394
            // don't want to do this unnecessarily.
1395
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET SCHEMA \"{$schema}\"";
1396
1397
            return $this->execute($sql);
1398
        }
1399
1400
        return 0;
1401
    }
1402
1403
    /**
1404
     * Empties a table in the database.
1405
     *
1406
     * @param string $table   The table to be emptied
1407
     * @param bool   $cascade True to cascade truncate, false to restrict
1408
     *
1409
     * @return array<integer,mixed|string> 0 if operation was successful
1410
     */
1411
    public function emptyTable($table, $cascade)
1412
    {
1413
        $f_schema = $this->_schema;
1414
        $this->fieldClean($f_schema);
1415
        $this->fieldClean($table);
1416
1417
        $sql = "TRUNCATE TABLE \"{$f_schema}\".\"{$table}\" ";
1418
        if ($cascade) {
1419
            $sql = $sql.' CASCADE';
1420
        }
1421
1422
        $status = $this->execute($sql);
1423
1424
        return [$status, $sql];
1425
    }
1426
1427
    /**
1428
     * Removes a table from the database.
1429
     *
1430
     * @param string $table   The table to drop
1431
     * @param bool   $cascade True to cascade drop, false to restrict
1432
     *
1433
     * @return int 0 if operation was successful
1434
     */
1435
    public function dropTable($table, $cascade)
1436
    {
1437
        $f_schema = $this->_schema;
1438
        $this->fieldClean($f_schema);
1439
        $this->fieldClean($table);
1440
1441
        $sql = "DROP TABLE \"{$f_schema}\".\"{$table}\"";
1442
        if ($cascade) {
1443
            $sql .= ' CASCADE';
1444
        }
1445
1446
        return $this->execute($sql);
1447
    }
1448
1449
    /**
1450
     * Sets up the data object for a dump.  eg. Starts the appropriate
1451
     * transaction, sets variables, etc.
1452
     *
1453
     * @return int 0 success
1454
     */
1455
    public function beginDump()
1456
    {
1457
        // Begin serializable transaction (to dump consistent data)
1458
        $status = $this->beginTransaction();
1459
        if ($status != 0) {
1460
            return -1;
1461
        }
1462
1463
        // Set serializable
1464
        $sql    = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE';
1465
        $status = $this->execute($sql);
1466
        if ($status != 0) {
1467
            $this->rollbackTransaction();
1468
1469
            return -1;
1470
        }
1471
1472
        // Set datestyle to ISO
1473
        $sql    = 'SET DATESTYLE = ISO';
1474
        $status = $this->execute($sql);
1475
        if ($status != 0) {
1476
            $this->rollbackTransaction();
1477
1478
            return -1;
1479
        }
1480
1481
        // Set extra_float_digits to 2
1482
        $sql    = 'SET extra_float_digits TO 2';
1483
        $status = $this->execute($sql);
1484
        if ($status != 0) {
1485
            $this->rollbackTransaction();
1486
1487
            return -1;
1488
        }
1489
1490
        return 0;
1491
    }
1492
1493
    /**
1494
     * Ends the data object for a dump.
1495
     *
1496
     * @return bool 0 success
1497
     */
1498
    public function endDump()
1499
    {
1500
        return $this->endTransaction();
1501
    }
1502
1503
    /**
1504
     * Returns a recordset of all columns in a relation.  Used for data export.
1505
     *
1506
     * @@ Note: Really needs to use a cursor
1507
     *
1508
     * @param string $relation The name of a relation
1509
     * @param bool   $oids     true to dump also the oids
1510
     *
1511
     * @return \PHPPgAdmin\ADORecordSet A recordset on success
1512
     */
1513
    public function dumpRelation($relation, $oids)
1514
    {
1515
        $this->fieldClean($relation);
1516
1517
        // Actually retrieve the rows
1518
        if ($oids) {
1519
            $oid_str = $this->id.', ';
1520
        } else {
1521
            $oid_str = '';
1522
        }
1523
1524
        return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\"");
1525
    }
1526
1527
    /**
1528
     * Returns all available autovacuum per table information.
1529
     *
1530
     * @param string $table if given, return autovacuum info for the given table or return all informations for all table
1531
     *
1532
     * @return \PHPPgAdmin\ArrayRecordSet A recordset
1533
     */
1534
    public function getTableAutovacuum($table = '')
1535
    {
1536
        $sql = '';
1537
1538
        if ($table !== '') {
1539
            $this->clean($table);
1540
            $c_schema = $this->_schema;
1541
            $this->clean($c_schema);
1542
1543
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1544
                FROM pg_class c
1545
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1546
                WHERE c.relkind = 'r'::\"char\"
1547
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1548
                    AND c.reloptions IS NOT NULL
1549
                    AND c.relname = '{$table}' AND n.nspname = '{$c_schema}'
1550
                ORDER BY nspname, relname";
1551
        } else {
1552
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1553
                FROM pg_class c
1554
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1555
                WHERE c.relkind = 'r'::\"char\"
1556
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1557
                    AND c.reloptions IS NOT NULL
1558
                ORDER BY nspname, relname";
1559
        }
1560
1561
        /* tmp var to parse the results */
1562
        $_autovacs = $this->selectSet($sql);
1563
1564
        /* result aray to return as RS */
1565
        $autovacs = [];
1566
        while (!$_autovacs->EOF) {
1567
            $_ = [
1568
                'nspname' => $_autovacs->fields['nspname'],
1569
                'relname' => $_autovacs->fields['relname'],
1570
            ];
1571
1572
            foreach (explode(',', $_autovacs->fields['reloptions']) as $var) {
1573
                list($o, $v) = explode('=', $var);
1574
                $_[$o]       = $v;
1575
            }
1576
1577
            $autovacs[] = $_;
1578
1579
            $_autovacs->moveNext();
1580
        }
1581
1582
        return new \PHPPgAdmin\ArrayRecordSet($autovacs);
1583
    }
1584
1585
    /**
1586
     * Returns the SQL for changing the current user.
1587
     *
1588
     * @param string $user The user to change to
1589
     *
1590
     * @return string The SQL
1591
     */
1592
    public function getChangeUserSQL($user)
1593
    {
1594
        $this->clean($user);
1595
1596
        return "SET SESSION AUTHORIZATION '{$user}';";
1597
    }
1598
1599
    /**
1600
     * Returns all available autovacuum per table information.
1601
     *
1602
     * @param string $table          table name
1603
     * @param bool   $vacenabled     true if vacuum is enabled
1604
     * @param int    $vacthreshold   vacuum threshold
1605
     * @param int    $vacscalefactor vacuum scalefactor
1606
     * @param int    $anathresold    analyze threshold
1607
     * @param int    $anascalefactor analyze scale factor
1608
     * @param int    $vaccostdelay   vacuum cost delay
1609
     * @param int    $vaccostlimit   vacuum cost limit
1610
     *
1611
     * @return bool 0 if successful
1612
     */
1613
    public function saveAutovacuum(
1614
        $table,
1615
        $vacenabled,
1616
        $vacthreshold,
1617
        $vacscalefactor,
1618
        $anathresold,
1619
        $anascalefactor,
1620
        $vaccostdelay,
1621
        $vaccostlimit
1622
    ) {
1623
        $f_schema = $this->_schema;
1624
        $this->fieldClean($f_schema);
1625
        $this->fieldClean($table);
1626
1627
        $params = [];
1628
1629
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
1630
1631
        if (!empty($vacenabled)) {
1632
            $this->clean($vacenabled);
1633
            $params[] = "autovacuum_enabled='{$vacenabled}'";
1634
        }
1635
        if (!empty($vacthreshold)) {
1636
            $this->clean($vacthreshold);
1637
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
1638
        }
1639
        if (!empty($vacscalefactor)) {
1640
            $this->clean($vacscalefactor);
1641
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
1642
        }
1643
        if (!empty($anathresold)) {
1644
            $this->clean($anathresold);
1645
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
1646
        }
1647
        if (!empty($anascalefactor)) {
1648
            $this->clean($anascalefactor);
1649
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
1650
        }
1651
        if (!empty($vaccostdelay)) {
1652
            $this->clean($vaccostdelay);
1653
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
1654
        }
1655
        if (!empty($vaccostlimit)) {
1656
            $this->clean($vaccostlimit);
1657
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
1658
        }
1659
1660
        $sql = $sql.implode(',', $params).');';
1661
1662
        return $this->execute($sql);
1663
    }
1664
1665
    // Type conversion routines
1666
1667
    /**
1668
     * Drops autovacuum config for a table.
1669
     *
1670
     * @param string $table The table
1671
     *
1672
     * @return bool 0 if successful
1673
     */
1674
    public function dropAutovacuum($table)
1675
    {
1676
        $f_schema = $this->_schema;
1677
        $this->fieldClean($f_schema);
1678
        $this->fieldClean($table);
1679
1680
        return $this->execute(
1681
            "
1682
            ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
1683
                autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
1684
                autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
1685
            );"
1686
        );
1687
    }
1688
1689
    abstract public function formatType($typname, $typmod);
1690
1691
    abstract public function hasGrantOption();
1692
1693
    abstract public function hasRoles();
1694
1695
    abstract public function fieldClean(&$str);
1696
1697
    abstract public function beginTransaction();
1698
1699
    abstract public function rollbackTransaction();
1700
1701
    abstract public function endTransaction();
1702
1703
    abstract public function execute($sql);
1704
1705
    abstract public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null);
1706
1707
    abstract public function selectSet($sql);
1708
1709
    abstract public function clean(&$str);
1710
1711
    abstract public function phpBool($parameter);
1712
1713
    abstract public function hasCreateTableLikeWithConstraints();
1714
1715
    abstract public function hasCreateTableLikeWithIndexes();
1716
1717
    abstract public function hasTablespaces();
1718
1719
    abstract public function delete($table, $conditions, $schema = '');
1720
1721
    abstract public function fieldArrayClean(&$arr);
1722
1723
    abstract public function hasCreateFieldWithConstraints();
1724
1725
    abstract public function getAttributeNames($table, $atts);
1726
}
1727