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

TableTrait::alterTableOwner()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 15
c 0
b 0
f 0
rs 10
cc 3
nc 2
nop 2
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