Passed
Push — develop ( 2d5edd...a33225 )
by Felipe
05:40
created

TableTrait::getRules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 1
1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.47
5
 */
6
7
namespace PHPPgAdmin\Traits;
8
9
/**
10
 * Common trait for tables manipulation.
11
 */
12
trait TableTrait
13
{
14
    use \PHPPgAdmin\Traits\ColumnTrait;
15
    use \PHPPgAdmin\Traits\RowTrait;
16
    use \PHPPgAdmin\Traits\TriggerTrait;
17
18
    /**
19
     * Return all tables in current database (and schema).
20
     *
21
     * @param bool|true $all True to fetch all tables, false for just in current schema
22
     *
23
     * @return \PHPPgAdmin\ADORecordSet All tables, sorted alphabetically
24
     */
25
    public function getTables($all = false)
26
    {
27
        $c_schema = $this->_schema;
28
        $this->clean($c_schema);
29
        if ($all) {
30
            // Exclude pg_catalog and information_schema tables
31
            $sql = "SELECT
32
                        schemaname AS nspname,
33
                        tablename AS relname,
34
                        tableowner AS relowner
35
                    FROM pg_catalog.pg_tables
36
                    WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
37
                    ORDER BY schemaname, tablename";
38
        } else {
39
            $sql = "
40
                SELECT c.relname,
41
                    pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
42
                    pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
43
                    reltuples::bigint,
44
                    pt.spcname as tablespace,
45
                    pg_size_pretty(pg_total_relation_size(c.oid)) as table_size
46
                FROM pg_catalog.pg_class c
47
                LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
48
                LEFT JOIN  pg_catalog.pg_tablespace pt ON  pt.oid=c.reltablespace
49
                WHERE c.relkind = 'r'
50
                AND nspname='{$c_schema}'
51
                ORDER BY c.relname";
52
        }
53
54
        return $this->selectSet($sql);
55
    }
56
57
    /**
58
     * Finds the names and schemas of parent tables (in order).
59
     *
60
     * @param string $table The table to find the parents for
61
     *
62
     * @return \PHPPgAdmin\ADORecordSet A recordset
63
     */
64
    public function getTableParents($table)
65
    {
66
        $c_schema = $this->_schema;
67
        $this->clean($c_schema);
68
        $this->clean($table);
69
70
        $sql = "
71
            SELECT
72
                pn.nspname, relname
73
            FROM
74
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
75
            WHERE
76
                pc.oid=pi.inhparent
77
                AND pc.relnamespace=pn.oid
78
                AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
79
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
80
            ORDER BY
81
                pi.inhseqno
82
        ";
83
84
        return $this->selectSet($sql);
85
    }
86
87
    /**
88
     * Finds the names and schemas of child tables.
89
     *
90
     * @param string $table The table to find the children for
91
     *
92
     * @return \PHPPgAdmin\ADORecordSet A recordset
93
     */
94
    public function getTableChildren($table)
95
    {
96
        $c_schema = $this->_schema;
97
        $this->clean($c_schema);
98
        $this->clean($table);
99
100
        $sql = "
101
            SELECT
102
                pn.nspname, relname
103
            FROM
104
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
105
            WHERE
106
                pc.oid=pi.inhrelid
107
                AND pc.relnamespace=pn.oid
108
                AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
109
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
110
        ";
111
112
        return $this->selectSet($sql);
113
    }
114
115
    /**
116
     * Returns the SQL definition for the table.
117
     * MUST be run within a transaction.
118
     *
119
     * @param string    $table The table to define
120
     * @param bool|true $clean True to issue drop command, false otherwise
121
     *
122
     * @return string A string containing the formatted SQL code
123
     */
124
    public function getTableDefPrefix($table, $clean = false)
125
    {
126
        // Fetch table
127
        $t = $this->getTable($table);
128
        if (!is_object($t) || $t->RecordCount() != 1) {
129
            $this->rollbackTransaction();
130
131
            return null;
132
        }
133
        $this->fieldClean($t->fields['relname']);
134
        $this->fieldClean($t->fields['nspname']);
135
136
        // Fetch attributes
137
        $atts = $this->getTableAttributes($table);
138
        if (!is_object($atts)) {
139
            $this->rollbackTransaction();
140
141
            return null;
142
        }
143
144
        // Fetch constraints
145
        $cons = $this->getConstraints($table);
146
        if (!is_object($cons)) {
147
            $this->rollbackTransaction();
148
149
            return null;
150
        }
151
152
        // Output a reconnect command to create the table as the correct user
153
        $sql = $this->getChangeUserSQL($t->fields['relowner'])."\n\n";
154
155
        // Set schema search path
156
        $sql .= "SET search_path = \"{$t->fields['nspname']}\", pg_catalog;\n\n";
157
158
        // Begin CREATE TABLE definition
159
        $sql .= "-- Definition\n\n";
160
        // DROP TABLE must be fully qualified in case a table with the same name exists
161
        // in pg_catalog.
162
        if (!$clean) {
163
            $sql .= '-- ';
164
        }
165
166
        $sql .= 'DROP TABLE ';
167
        $sql .= "\"{$t->fields['nspname']}\".\"{$t->fields['relname']}\";\n";
168
        $sql .= "CREATE TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" (\n";
169
170
        // Output all table columns
171
        $col_comments_sql = ''; // Accumulate comments on columns
172
        $num              = $atts->RecordCount() + $cons->RecordCount();
173
        $i                = 1;
174
        while (!$atts->EOF) {
175
            $this->fieldClean($atts->fields['attname']);
176
            $sql .= "    \"{$atts->fields['attname']}\"";
177
            // Dump SERIAL and BIGSERIAL columns correctly
178
            if ($this->phpBool($atts->fields['attisserial']) &&
179
                ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) {
180
                if ($atts->fields['type'] == 'integer') {
181
                    $sql .= ' SERIAL';
182
                } else {
183
                    $sql .= ' BIGSERIAL';
184
                }
185
            } else {
186
                $sql .= ' '.$this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
187
188
                // Add NOT NULL if necessary
189
                if ($this->phpBool($atts->fields['attnotnull'])) {
190
                    $sql .= ' NOT NULL';
191
                }
192
193
                // Add default if necessary
194
                if ($atts->fields['adsrc'] !== null) {
195
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
196
                }
197
            }
198
199
            // Output comma or not
200
            if ($i < $num) {
201
                $sql .= ",\n";
202
            } else {
203
                $sql .= "\n";
204
            }
205
206
            // Does this column have a comment?
207
            if ($atts->fields['comment'] !== null) {
208
                $this->clean($atts->fields['comment']);
209
                $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
210
            }
211
212
            $atts->moveNext();
213
            ++$i;
214
        }
215
        // Output all table constraints
216
        while (!$cons->EOF) {
217
            $this->fieldClean($cons->fields['conname']);
218
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
219
            // Nasty hack to support pre-7.4 PostgreSQL
220
            if ($cons->fields['consrc'] !== null) {
221
                $sql .= $cons->fields['consrc'];
222
            } else {
223
                switch ($cons->fields['contype']) {
224
                    case 'p':
225
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
226
                        $sql .= 'PRIMARY KEY ('.join(',', $keys).')';
227
228
                        break;
229
                    case 'u':
230
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
231
                        $sql .= 'UNIQUE ('.join(',', $keys).')';
232
233
                        break;
234
                    default:
235
                        // Unrecognised constraint
236
                        $this->rollbackTransaction();
237
238
                        return null;
239
                }
240
            }
241
242
            // Output comma or not
243
            if ($i < $num) {
244
                $sql .= ",\n";
245
            } else {
246
                $sql .= "\n";
247
            }
248
249
            $cons->moveNext();
250
            ++$i;
251
        }
252
253
        $sql .= ')';
254
255
        // @@@@ DUMP CLUSTERING INFORMATION
256
257
        // Inherits
258
        /**
259
         * XXX: This is currently commented out as handling inheritance isn't this simple.
260
         * You also need to make sure you don't dump inherited columns and defaults, as well
261
         * as inherited NOT NULL and CHECK constraints.  So for the time being, we just do
262
         * not claim to support inheritance.
263
         * $parents = $this->getTableParents($table);
264
         * if ($parents->RecordCount() > 0) {
265
         * $sql .= " INHERITS (";
266
         * while (!$parents->EOF) {
267
         * $this->fieldClean($parents->fields['relname']);
268
         * // Qualify the parent table if it's in another schema
269
         * if ($parents->fields['schemaname'] != $this->_schema) {
270
         * $this->fieldClean($parents->fields['schemaname']);
271
         * $sql .= "\"{$parents->fields['schemaname']}\".";
272
         * }
273
         * $sql .= "\"{$parents->fields['relname']}\"";.
274
         *
275
         * $parents->moveNext();
276
         * if (!$parents->EOF) $sql .= ', ';
277
         * }
278
         * $sql .= ")";
279
         * }
280
         */
281
282
        // Handle WITHOUT OIDS
283
        if ($this->hasObjectID($table)) {
284
            $sql .= ' WITH OIDS';
285
        } else {
286
            $sql .= ' WITHOUT OIDS';
287
        }
288
289
        $sql .= ";\n";
290
291
        // Column storage and statistics
292
        $atts->moveFirst();
293
        $first = true;
294
        while (!$atts->EOF) {
295
            $this->fieldClean($atts->fields['attname']);
296
            // Statistics first
297
            if ($atts->fields['attstattarget'] >= 0) {
298
                if ($first) {
299
                    $sql .= "\n";
300
                    $first = false;
301
                }
302
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
303
            }
304
            // Then storage
305
            if ($atts->fields['attstorage'] != $atts->fields['typstorage']) {
306
                switch ($atts->fields['attstorage']) {
307
                    case 'p':
308
                        $storage = 'PLAIN';
309
310
                        break;
311
                    case 'e':
312
                        $storage = 'EXTERNAL';
313
314
                        break;
315
                    case 'm':
316
                        $storage = 'MAIN';
317
318
                        break;
319
                    case 'x':
320
                        $storage = 'EXTENDED';
321
322
                        break;
323
                    default:
324
                        // Unknown storage type
325
                        $this->rollbackTransaction();
326
327
                        return null;
328
                }
329
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
330
            }
331
332
            $atts->moveNext();
333
        }
334
335
        // Comment
336
        if ($t->fields['relcomment'] !== null) {
337
            $this->clean($t->fields['relcomment']);
338
            $sql .= "\n-- Comment\n\n";
339
            $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n";
340
        }
341
342
        // Add comments on columns, if any
343
        if ($col_comments_sql != '') {
344
            $sql .= $col_comments_sql;
345
        }
346
347
        // Privileges
348
        $privs = $this->getPrivileges($table, 'table');
349
        if (!is_array($privs)) {
350
            $this->rollbackTransaction();
351
352
            return null;
353
        }
354
355
        if (sizeof($privs) > 0) {
356
            $sql .= "\n-- Privileges\n\n";
357
            /*
358
             * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
359
             * wire-in knowledge about the default public privileges for different
360
             * kinds of objects.
361
             */
362
            $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n";
363
            foreach ($privs as $v) {
364
                // Get non-GRANT OPTION privs
365
                $nongrant = array_diff($v[2], $v[4]);
366
367
                // Skip empty or owner ACEs
368
                if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) {
369
                    continue;
370
                }
371
372
                // Change user if necessary
373
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
374
                    $grantor = $v[3];
375
                    $this->clean($grantor);
376
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
377
                }
378
379
                // Output privileges with no GRANT OPTION
380
                $sql .= 'GRANT '.join(', ', $nongrant)." ON TABLE \"{$t->fields['relname']}\" TO ";
381
                switch ($v[0]) {
382
                    case 'public':
383
                        $sql .= "PUBLIC;\n";
384
385
                        break;
386
                    case 'user':
387
                        $this->fieldClean($v[1]);
388
                        $sql .= "\"{$v[1]}\";\n";
389
390
                        break;
391
                    case 'group':
392
                        $this->fieldClean($v[1]);
393
                        $sql .= "GROUP \"{$v[1]}\";\n";
394
395
                        break;
396
                    default:
397
                        // Unknown privilege type - fail
398
                        $this->rollbackTransaction();
399
400
                        return null;
401
                }
402
403
                // Reset user if necessary
404
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
405
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
406
                }
407
408
                // Output privileges with GRANT OPTION
409
410
                // Skip empty or owner ACEs
411
                if (!$this->hasGrantOption() || sizeof($v[4]) == 0) {
412
                    continue;
413
                }
414
415
                // Change user if necessary
416
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
417
                    $grantor = $v[3];
418
                    $this->clean($grantor);
419
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
420
                }
421
422
                $sql .= 'GRANT '.join(', ', $v[4])." ON \"{$t->fields['relname']}\" TO ";
423
                switch ($v[0]) {
424
                    case 'public':
425
                        $sql .= 'PUBLIC';
426
427
                        break;
428
                    case 'user':
429
                        $this->fieldClean($v[1]);
430
                        $sql .= "\"{$v[1]}\"";
431
432
                        break;
433
                    case 'group':
434
                        $this->fieldClean($v[1]);
435
                        $sql .= "GROUP \"{$v[1]}\"";
436
437
                        break;
438
                    default:
439
                        // Unknown privilege type - fail
440
                        return null;
441
                }
442
                $sql .= " WITH GRANT OPTION;\n";
443
444
                // Reset user if necessary
445
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
446
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
447
                }
448
            }
449
        }
450
451
        // Add a newline to separate data that follows (if any)
452
        $sql .= "\n";
453
454
        return $sql;
455
    }
456
457
    /**
458
     * Returns table information.
459
     *
460
     * @param string $table The name of the table
461
     *
462
     * @return \PHPPgAdmin\ADORecordSet A recordset
463
     */
464
    public function getTable($table)
465
    {
466
        $c_schema = $this->_schema;
467
        $this->clean($c_schema);
468
        $this->clean($table);
469
470
        $sql = "
471
            SELECT
472
              c.relname, n.nspname, u.usename AS relowner,
473
              pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
474
              (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
475
            FROM pg_catalog.pg_class c
476
                 LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
477
                 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
478
            WHERE c.relkind = 'r'
479
                  AND n.nspname = '{$c_schema}'
480
                  AND n.oid = c.relnamespace
481
                  AND c.relname = '{$table}'";
482
483
        return $this->selectSet($sql);
484
    }
485
486
    /**
487
     * Retrieve the attribute definition of a table.
488
     *
489
     * @param string $table The name of the table
490
     * @param string $field (optional) The name of a field to return
491
     *
492
     * @return \PHPPgAdmin\ADORecordSet All attributes in order
493
     */
494
    public function getTableAttributes($table, $field = '')
495
    {
496
        $c_schema = $this->_schema;
497
        $this->clean($c_schema);
498
        $this->clean($table);
499
        $this->clean($field);
500
501
        if ($field == '') {
502
            // This query is made much more complex by the addition of the 'attisserial' field.
503
            // The subquery to get that field checks to see if there is an internally dependent
504
            // sequence on the field.
505
            $sql = "
506
                SELECT
507
                    a.attname, a.attnum,
508
                    pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
509
                    a.atttypmod,
510
                    a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
511
                    a.attstattarget, a.attstorage, t.typstorage,
512
                    (
513
                        SELECT 1 FROM pg_catalog.pg_depend pd, pg_catalog.pg_class pc
514
                        WHERE pd.objid=pc.oid
515
                        AND pd.classid=pc.tableoid
516
                        AND pd.refclassid=pc.tableoid
517
                        AND pd.refobjid=a.attrelid
518
                        AND pd.refobjsubid=a.attnum
519
                        AND pd.deptype='i'
520
                        AND pc.relkind='S'
521
                    ) IS NOT NULL AS attisserial,
522
                    pg_catalog.col_description(a.attrelid, a.attnum) AS comment
523
                FROM
524
                    pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
525
                    ON a.attrelid=adef.adrelid
526
                    AND a.attnum=adef.adnum
527
                    LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
528
                WHERE
529
                    a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
530
                        AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
531
                        nspname = '{$c_schema}'))
532
                    AND a.attnum > 0 AND NOT a.attisdropped
533
                ORDER BY a.attnum";
534
        } else {
535
            $sql = "
536
                SELECT
537
                    a.attname, a.attnum,
538
                    pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
539
                    pg_catalog.format_type(a.atttypid, NULL) as base_type,
540
                    a.atttypmod,
541
                    a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
542
                    a.attstattarget, a.attstorage, t.typstorage,
543
                    pg_catalog.col_description(a.attrelid, a.attnum) AS comment
544
                FROM
545
                    pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
546
                    ON a.attrelid=adef.adrelid
547
                    AND a.attnum=adef.adnum
548
                    LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
549
                WHERE
550
                    a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
551
                        AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
552
                        nspname = '{$c_schema}'))
553
                    AND a.attname = '{$field}'";
554
        }
555
556
        return $this->selectSet($sql);
557
    }
558
559
    /**
560
     * Returns a list of all constraints on a table.
561
     *
562
     * @param string $table The table to find rules for
563
     *
564
     * @return \PHPPgAdmin\ADORecordSet A recordset
565
     */
566
    public function getConstraints($table)
567
    {
568
        $c_schema = $this->_schema;
569
        $this->clean($c_schema);
570
        $this->clean($table);
571
572
        // This SQL is greatly complicated by the need to retrieve
573
        // index clustering information for primary and unique constraints
574
        $sql = "SELECT
575
                pc.conname,
576
                pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc,
577
                pc.contype,
578
                CASE WHEN pc.contype='u' OR pc.contype='p' THEN (
579
                    SELECT
580
                        indisclustered
581
                    FROM
582
                        pg_catalog.pg_depend pd,
583
                        pg_catalog.pg_class pl,
584
                        pg_catalog.pg_index pi
585
                    WHERE
586
                        pd.refclassid=pc.tableoid
587
                        AND pd.refobjid=pc.oid
588
                        AND pd.objid=pl.oid
589
                        AND pl.oid=pi.indexrelid
590
                ) ELSE
591
                    NULL
592
                END AS indisclustered
593
            FROM
594
                pg_catalog.pg_constraint pc
595
            WHERE
596
                pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
597
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
598
                    WHERE nspname='{$c_schema}'))
599
            ORDER BY
600
                1
601
        ";
602
603
        return $this->selectSet($sql);
604
    }
605
606
    /**
607
     * Checks to see whether or not a table has a unique id column.
608
     *
609
     * @param string $table The table name
610
     *
611
     * @return true if it has a unique id, false otherwise
612
     */
613
    public function hasObjectID($table)
614
    {
615
        $c_schema = $this->_schema;
616
        $this->clean($c_schema);
617
        $this->clean($table);
618
619
        $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}'
620
            AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')";
621
622
        $rs = $this->selectSet($sql);
623
        if ($rs->RecordCount() != 1) {
624
            return null;
625
        }
626
627
        $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']);
628
629
        return $rs->fields['relhasoids'];
630
    }
631
632
    /**
633
     * Returns extra table definition information that is most usefully
634
     * dumped after the table contents for speed and efficiency reasons.
635
     *
636
     * @param string $table The table to define
637
     *
638
     * @return string A string containing the formatted SQL code
639
     */
640
    public function getTableDefSuffix($table)
641
    {
642
        $sql = '';
643
644
        // Indexes
645
        $indexes = $this->getIndexes($table);
646
        if (!is_object($indexes)) {
647
            $this->rollbackTransaction();
648
649
            return null;
650
        }
651
652
        if ($indexes->RecordCount() > 0) {
653
            $sql .= "\n-- Indexes\n\n";
654
            while (!$indexes->EOF) {
655
                $sql .= $indexes->fields['inddef'].";\n";
656
657
                $indexes->moveNext();
658
            }
659
        }
660
661
        // Triggers
662
        $triggers = $this->getTriggers($table);
663
        if (!is_object($triggers)) {
664
            $this->rollbackTransaction();
665
666
            return null;
667
        }
668
669
        if ($triggers->RecordCount() > 0) {
670
            $sql .= "\n-- Triggers\n\n";
671
            while (!$triggers->EOF) {
672
                $sql .= $triggers->fields['tgdef'];
673
                $sql .= ";\n";
674
675
                $triggers->moveNext();
676
            }
677
        }
678
679
        // Rules
680
        $rules = $this->getRules($table);
681
        if (!is_object($rules)) {
682
            $this->rollbackTransaction();
683
684
            return null;
685
        }
686
687
        if ($rules->RecordCount() > 0) {
688
            $sql .= "\n-- Rules\n\n";
689
            while (!$rules->EOF) {
690
                $sql .= $rules->fields['definition']."\n";
691
692
                $rules->moveNext();
693
            }
694
        }
695
696
        return $sql;
697
    }
698
699
    /**
700
     * Grabs a list of indexes for a table.
701
     *
702
     * @param string $table  The name of a table whose indexes to retrieve
703
     * @param bool   $unique Only get unique/pk indexes
704
     *
705
     * @return \PHPPgAdmin\ADORecordSet A recordset
706
     */
707
    public function getIndexes($table = '', $unique = false)
708
    {
709
        $this->clean($table);
710
711
        $sql = "
712
            SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
713
                pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef
714
            FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
715
            WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid)
716
                AND c.oid = i.indrelid AND i.indexrelid = c2.oid
717
        ";
718
        if ($unique) {
719
            $sql .= ' AND i.indisunique ';
720
        }
721
722
        $sql .= ' ORDER BY c2.relname';
723
724
        return $this->selectSet($sql);
725
    }
726
727
    /**
728
     * Grabs a list of triggers on a table.
729
     *
730
     * @param string $table The name of a table whose triggers to retrieve
731
     *
732
     * @return \PHPPgAdmin\ADORecordSet A recordset
733
     */
734
    public function getTriggers($table = '')
735
    {
736
        $c_schema = $this->_schema;
737
        $this->clean($c_schema);
738
        $this->clean($table);
739
740
        $sql = "SELECT
741
                t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef,
742
                CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid,
743
                p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
744
                ns.nspname AS pronamespace
745
            FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns
746
            WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
747
                AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
748
                AND ( tgconstraint = 0 OR NOT EXISTS
749
                        (SELECT 1 FROM pg_catalog.pg_depend d    JOIN pg_catalog.pg_constraint c
750
                            ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
751
                        WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))
752
                AND p.oid=t.tgfoid
753
                AND p.pronamespace = ns.oid";
754
755
        return $this->selectSet($sql);
756
    }
757
758
    /**
759
     * Returns a list of all rules on a table OR view.
760
     *
761
     * @param string $table The table to find rules for
762
     *
763
     * @return \PHPPgAdmin\ADORecordSet A recordset
764
     */
765
    public function getRules($table)
766
    {
767
        $c_schema = $this->_schema;
768
        $this->clean($c_schema);
769
        $this->clean($table);
770
771
        $sql = "
772
            SELECT *
773
            FROM pg_catalog.pg_rules
774
            WHERE
775
                schemaname='{$c_schema}' AND tablename='{$table}'
776
            ORDER BY rulename
777
        ";
778
779
        return $this->selectSet($sql);
780
    }
781
782
    /**
783
     * Creates a new table in the database.
784
     *
785
     * @param string $name        The name of the table
786
     * @param int    $fields      The number of fields
787
     * @param array  $field       An array of field names
788
     * @param array  $type        An array of field types
789
     * @param array  $array       An array of '' or '[]' for each type if it's an array or not
790
     * @param array  $length      An array of field lengths
791
     * @param array  $notnull     An array of not null
792
     * @param array  $default     An array of default values
793
     * @param bool   $withoutoids True if WITHOUT OIDS, false otherwise
794
     * @param array  $colcomment  An array of comments
795
     * @param string $tblcomment  the comment for the table
796
     * @param string $tablespace  The tablespace name ('' means none/default)
797
     * @param array  $uniquekey   An Array indicating the fields that are unique (those indexes that are set)
798
     * @param array  $primarykey  An Array indicating the field used for the primarykey (those indexes that are set)
799
     *
800
     * @return bool|int 0 success
801
     */
802
    public function createTable(
803
        $name,
804
        $fields,
805
        $field,
806
        $type,
807
        $array,
808
        $length,
809
        $notnull,
810
        $default,
811
        $withoutoids,
812
        $colcomment,
813
        $tblcomment,
814
        $tablespace,
815
        $uniquekey,
816
        $primarykey
817
    ) {
818
        $f_schema = $this->_schema;
819
        $this->fieldClean($f_schema);
820
        $this->fieldClean($name);
821
822
        $status = $this->beginTransaction();
823
        if ($status != 0) {
824
            return -1;
825
        }
826
827
        $found       = false;
828
        $first       = true;
829
        $comment_sql = ''; //Accumulate comments for the columns
830
        $sql         = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (";
831
        for ($i = 0; $i < $fields; ++$i) {
832
            $this->fieldClean($field[$i]);
833
            $this->clean($type[$i]);
834
            $this->clean($length[$i]);
835
            $this->clean($colcomment[$i]);
836
837
            // Skip blank columns - for user convenience
838
            if ($field[$i] == '' || $type[$i] == '') {
839
                continue;
840
            }
841
842
            // If not the first column, add a comma
843
            if (!$first) {
844
                $sql .= ', ';
845
            } else {
846
                $first = false;
847
            }
848
849
            switch ($type[$i]) {
850
                // Have to account for weird placing of length for with/without
851
                // time zone types
852
                case 'timestamp with time zone':
853
                case 'timestamp without time zone':
854
                    $qual = substr($type[$i], 9);
855
                    $sql .= "\"{$field[$i]}\" timestamp";
856
                    if ($length[$i] != '') {
857
                        $sql .= "({$length[$i]})";
858
                    }
859
860
                    $sql .= $qual;
861
862
                    break;
863
                case 'time with time zone':
864
                case 'time without time zone':
865
                    $qual = substr($type[$i], 4);
866
                    $sql .= "\"{$field[$i]}\" time";
867
                    if ($length[$i] != '') {
868
                        $sql .= "({$length[$i]})";
869
                    }
870
871
                    $sql .= $qual;
872
873
                    break;
874
                default:
875
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
876
                    if ($length[$i] != '') {
877
                        $sql .= "({$length[$i]})";
878
                    }
879
            }
880
            // Add array qualifier if necessary
881
            if ($array[$i] == '[]') {
882
                $sql .= '[]';
883
            }
884
885
            // Add other qualifiers
886
            if (!isset($primarykey[$i])) {
887
                if (isset($uniquekey[$i])) {
888
                    $sql .= ' UNIQUE';
889
                }
890
891
                if (isset($notnull[$i])) {
892
                    $sql .= ' NOT NULL';
893
                }
894
            }
895
            if ($default[$i] != '') {
896
                $sql .= " DEFAULT {$default[$i]}";
897
            }
898
899
            if ($colcomment[$i] != '') {
900
                $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
901
            }
902
903
            $found = true;
904
        }
905
906
        if (!$found) {
0 ignored issues
show
introduced by
The condition $found is always false.
Loading history...
907
            return -1;
908
        }
909
910
        // PRIMARY KEY
911
        $primarykeycolumns = [];
912
        for ($i = 0; $i < $fields; ++$i) {
913
            if (isset($primarykey[$i])) {
914
                $primarykeycolumns[] = "\"{$field[$i]}\"";
915
            }
916
        }
917
        if (count($primarykeycolumns) > 0) {
918
            $sql .= ', PRIMARY KEY ('.implode(', ', $primarykeycolumns).')';
919
        }
920
921
        $sql .= ')';
922
923
        // WITHOUT OIDS
924
        if ($withoutoids) {
925
            $sql .= ' WITHOUT OIDS';
926
        } else {
927
            $sql .= ' WITH OIDS';
928
        }
929
930
        // Tablespace
931
        if ($this->hasTablespaces() && $tablespace != '') {
932
            $this->fieldClean($tablespace);
933
            $sql .= " TABLESPACE \"{$tablespace}\"";
934
        }
935
936
        $status = $this->execute($sql);
937
        if ($status) {
938
            $this->rollbackTransaction();
939
940
            return -1;
941
        }
942
943
        if ($tblcomment != '') {
944
            $status = $this->setComment('TABLE', '', $name, $tblcomment, true);
945
            if ($status) {
946
                $this->rollbackTransaction();
947
948
                return -1;
949
            }
950
        }
951
952
        if ($comment_sql != '') {
953
            $status = $this->execute($comment_sql);
954
            if ($status) {
955
                $this->rollbackTransaction();
956
957
                return -1;
958
            }
959
        }
960
961
        return $this->endTransaction();
962
    }
963
964
    /**
965
     * Creates a new table in the database copying attribs and other properties from another table.
966
     *
967
     * @param string $name        The name of the table
968
     * @param array  $like        an array giving the schema ans the name of the table from which attribs are copying
969
     *                            from: array(
970
     *                            'table' => table name,
971
     *                            'schema' => the schema name,
972
     *                            )
973
     * @param bool   $defaults    if true, copy the defaults values as well
974
     * @param bool   $constraints if true, copy the constraints as well (CHECK on table & attr)
975
     * @param bool   $idx
976
     * @param string $tablespace  The tablespace name ('' means none/default)
977
     *
978
     * @return bool|int
979
     */
980
    public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '')
981
    {
982
        $f_schema = $this->_schema;
983
        $this->fieldClean($f_schema);
984
        $this->fieldClean($name);
985
        $this->fieldClean($like['schema']);
986
        $this->fieldClean($like['table']);
987
        $like = "\"{$like['schema']}\".\"{$like['table']}\"";
988
989
        $status = $this->beginTransaction();
990
        if ($status != 0) {
991
            return -1;
992
        }
993
994
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}";
995
996
        if ($defaults) {
997
            $sql .= ' INCLUDING DEFAULTS';
998
        }
999
1000
        if ($this->hasCreateTableLikeWithConstraints() && $constraints) {
1001
            $sql .= ' INCLUDING CONSTRAINTS';
1002
        }
1003
1004
        if ($this->hasCreateTableLikeWithIndexes() && $idx) {
1005
            $sql .= ' INCLUDING INDEXES';
1006
        }
1007
1008
        $sql .= ')';
1009
1010
        if ($this->hasTablespaces() && $tablespace != '') {
1011
            $this->fieldClean($tablespace);
1012
            $sql .= " TABLESPACE \"{$tablespace}\"";
1013
        }
1014
1015
        $status = $this->execute($sql);
1016
        if ($status) {
1017
            $this->rollbackTransaction();
1018
1019
            return -1;
1020
        }
1021
1022
        return $this->endTransaction();
1023
    }
1024
1025
    /**
1026
     * Alter table properties.
1027
     *
1028
     * @param string $table      The name of the table
1029
     * @param string $name       The new name for the table
1030
     * @param string $owner      The new owner for the table
1031
     * @param string $schema     The new schema for the table
1032
     * @param string $comment    The comment on the table
1033
     * @param string $tablespace The new tablespace for the table ('' means leave as is)
1034
     *
1035
     * @return bool|int 0 success
1036
     */
1037
    public function alterTable($table, $name, $owner, $schema, $comment, $tablespace)
1038
    {
1039
        $data = $this->getTable($table);
1040
1041
        if ($data->RecordCount() != 1) {
1042
            return -2;
1043
        }
1044
1045
        $status = $this->beginTransaction();
1046
        if ($status != 0) {
1047
            $this->rollbackTransaction();
1048
1049
            return -1;
1050
        }
1051
1052
        $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace);
1053
1054
        if ($status != 0) {
1055
            $this->rollbackTransaction();
1056
1057
            return $status;
1058
        }
1059
1060
        return $this->endTransaction();
1061
    }
1062
1063
    /**
1064
     * Protected method which alter a table
1065
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION.
1066
     *
1067
     * @param \PHPPgAdmin\ADORecordSet $tblrs      The table recordSet returned by getTable()
1068
     * @param string                   $name       The new name for the table
1069
     * @param string                   $owner      The new owner for the table
1070
     * @param string                   $schema     The new schema for the table
1071
     * @param string                   $comment    The comment on the table
1072
     * @param string                   $tablespace The new tablespace for the table ('' means leave as is)
1073
     *
1074
     * @return int 0 success
1075
     */
1076
    protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace)
1077
    {
1078
        $this->fieldArrayClean($tblrs->fields);
1079
1080
        // Comment
1081
        $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment);
1082
        if ($status != 0) {
1083
            return -4;
1084
        }
1085
1086
        // Owner
1087
        $this->fieldClean($owner);
1088
        $status = $this->alterTableOwner($tblrs, $owner);
1089
        if ($status != 0) {
1090
            return -5;
1091
        }
1092
1093
        // Tablespace
1094
        $this->fieldClean($tablespace);
1095
        $status = $this->alterTableTablespace($tblrs, $tablespace);
1096
        if ($status != 0) {
1097
            return -6;
1098
        }
1099
1100
        // Rename
1101
        $this->fieldClean($name);
1102
        $status = $this->alterTableName($tblrs, $name);
1103
        if ($status != 0) {
1104
            return -3;
1105
        }
1106
1107
        // Schema
1108
        $this->fieldClean($schema);
1109
        $status = $this->alterTableSchema($tblrs, $schema);
1110
        if ($status != 0) {
1111
            return -7;
1112
        }
1113
1114
        return 0;
1115
    }
1116
1117
    /**
1118
     * Alter a table's owner
1119
     * /!\ this function is called from _alterTable which take care of escaping fields.
1120
     *
1121
     * @param \PHPPgAdmin\ADORecordSet $tblrs The table RecordSet returned by getTable()
1122
     * @param null|string              $owner
1123
     *
1124
     * @return int 0 if operation was successful
1125
     */
1126
    public function alterTableOwner($tblrs, $owner = null)
1127
    {
1128
        /* vars cleaned in _alterTable */
1129
        if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) {
1130
            $f_schema = $this->_schema;
1131
            $this->fieldClean($f_schema);
1132
            // If owner has been changed, then do the alteration.  We are
1133
            // careful to avoid this generally as changing owner is a
1134
            // superuser only function.
1135
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\"";
1136
1137
            return $this->execute($sql);
1138
        }
1139
1140
        return 0;
1141
    }
1142
1143
    /**
1144
     * Alter a table's tablespace
1145
     * /!\ this function is called from _alterTable which take care of escaping fields.
1146
     *
1147
     * @param \PHPPgAdmin\ADORecordSet $tblrs      The table RecordSet returned by getTable()
1148
     * @param null|string              $tablespace
1149
     *
1150
     * @return int 0 if operation was successful
1151
     */
1152
    public function alterTableTablespace($tblrs, $tablespace = null)
1153
    {
1154
        /* vars cleaned in _alterTable */
1155
        if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) {
1156
            $f_schema = $this->_schema;
1157
            $this->fieldClean($f_schema);
1158
1159
            // If tablespace has been changed, then do the alteration.  We
1160
            // don't want to do this unnecessarily.
1161
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\"";
1162
1163
            return $this->execute($sql);
1164
        }
1165
1166
        return 0;
1167
    }
1168
1169
    /**
1170
     * Alter a table's name
1171
     * /!\ this function is called from _alterTable which take care of escaping fields.
1172
     *
1173
     * @param \PHPPgAdmin\ADORecordSet $tblrs The table RecordSet returned by getTable()
1174
     * @param string                   $name  The new table's name
1175
     *
1176
     * @return int 0 if operation was successful
1177
     */
1178
    public function alterTableName($tblrs, $name = null)
1179
    {
1180
        /* vars cleaned in _alterTable */
1181
        // Rename (only if name has changed)
1182
        if (!empty($name) && ($name != $tblrs->fields['relname'])) {
1183
            $f_schema = $this->_schema;
1184
            $this->fieldClean($f_schema);
1185
1186
            $sql    = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" RENAME TO \"{$name}\"";
1187
            $status = $this->execute($sql);
1188
            if ($status == 0) {
1189
                $tblrs->fields['relname'] = $name;
1190
            } else {
1191
                return $status;
1192
            }
1193
        }
1194
1195
        return 0;
1196
    }
1197
1198
    // Row functions
1199
1200
    /**
1201
     * Alter a table's schema
1202
     * /!\ this function is called from _alterTable which take care of escaping fields.
1203
     *
1204
     * @param \PHPPgAdmin\ADORecordSet $tblrs  The table RecordSet returned by getTable()
1205
     * @param null|string              $schema
1206
     *
1207
     * @return int 0 if operation was successful
1208
     */
1209
    public function alterTableSchema($tblrs, $schema = null)
1210
    {
1211
        /* vars cleaned in _alterTable */
1212
        if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) {
1213
            $f_schema = $this->_schema;
1214
            $this->fieldClean($f_schema);
1215
            // If tablespace has been changed, then do the alteration.  We
1216
            // don't want to do this unnecessarily.
1217
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET SCHEMA \"{$schema}\"";
1218
1219
            return $this->execute($sql);
1220
        }
1221
1222
        return 0;
1223
    }
1224
1225
    /**
1226
     * Empties a table in the database.
1227
     *
1228
     * @param string $table   The table to be emptied
1229
     * @param bool   $cascade True to cascade truncate, false to restrict
1230
     *
1231
     * @return array<integer,mixed|string> 0 if operation was successful
1232
     */
1233
    public function emptyTable($table, $cascade)
1234
    {
1235
        $f_schema = $this->_schema;
1236
        $this->fieldClean($f_schema);
1237
        $this->fieldClean($table);
1238
1239
        $sql = "TRUNCATE TABLE \"{$f_schema}\".\"{$table}\" ";
1240
        if ($cascade) {
1241
            $sql = $sql.' CASCADE';
1242
        }
1243
1244
        $status = $this->execute($sql);
1245
1246
        return [$status, $sql];
1247
    }
1248
1249
    /**
1250
     * Removes a table from the database.
1251
     *
1252
     * @param string $table   The table to drop
1253
     * @param bool   $cascade True to cascade drop, false to restrict
1254
     *
1255
     * @return int 0 if operation was successful
1256
     */
1257
    public function dropTable($table, $cascade)
1258
    {
1259
        $f_schema = $this->_schema;
1260
        $this->fieldClean($f_schema);
1261
        $this->fieldClean($table);
1262
1263
        $sql = "DROP TABLE \"{$f_schema}\".\"{$table}\"";
1264
        if ($cascade) {
1265
            $sql .= ' CASCADE';
1266
        }
1267
1268
        return $this->execute($sql);
1269
    }
1270
1271
    /**
1272
     * Sets up the data object for a dump.  eg. Starts the appropriate
1273
     * transaction, sets variables, etc.
1274
     *
1275
     * @return int 0 success
1276
     */
1277
    public function beginDump()
1278
    {
1279
        // Begin serializable transaction (to dump consistent data)
1280
        $status = $this->beginTransaction();
1281
        if ($status != 0) {
1282
            return -1;
1283
        }
1284
1285
        // Set serializable
1286
        $sql    = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE';
1287
        $status = $this->execute($sql);
1288
        if ($status != 0) {
1289
            $this->rollbackTransaction();
1290
1291
            return -1;
1292
        }
1293
1294
        // Set datestyle to ISO
1295
        $sql    = 'SET DATESTYLE = ISO';
1296
        $status = $this->execute($sql);
1297
        if ($status != 0) {
1298
            $this->rollbackTransaction();
1299
1300
            return -1;
1301
        }
1302
1303
        // Set extra_float_digits to 2
1304
        $sql    = 'SET extra_float_digits TO 2';
1305
        $status = $this->execute($sql);
1306
        if ($status != 0) {
1307
            $this->rollbackTransaction();
1308
1309
            return -1;
1310
        }
1311
1312
        return 0;
1313
    }
1314
1315
    /**
1316
     * Ends the data object for a dump.
1317
     *
1318
     * @return bool 0 success
1319
     */
1320
    public function endDump()
1321
    {
1322
        return $this->endTransaction();
1323
    }
1324
1325
    /**
1326
     * Returns a recordset of all columns in a relation.  Used for data export.
1327
     *
1328
     * @@ Note: Really needs to use a cursor
1329
     *
1330
     * @param string $relation The name of a relation
1331
     * @param bool   $oids     true to dump also the oids
1332
     *
1333
     * @return \PHPPgAdmin\ADORecordSet A recordset on success
1334
     */
1335
    public function dumpRelation($relation, $oids)
1336
    {
1337
        $this->fieldClean($relation);
1338
1339
        // Actually retrieve the rows
1340
        if ($oids) {
1341
            $oid_str = $this->id.', ';
1342
        } else {
1343
            $oid_str = '';
1344
        }
1345
1346
        return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\"");
1347
    }
1348
1349
    /**
1350
     * Returns all available autovacuum per table information.
1351
     *
1352
     * @param string $table if given, return autovacuum info for the given table or return all informations for all table
1353
     *
1354
     * @return \PHPPgAdmin\ArrayRecordSet A recordset
1355
     */
1356
    public function getTableAutovacuum($table = '')
1357
    {
1358
        $sql = '';
1359
1360
        if ($table !== '') {
1361
            $this->clean($table);
1362
            $c_schema = $this->_schema;
1363
            $this->clean($c_schema);
1364
1365
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1366
                FROM pg_class c
1367
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1368
                WHERE c.relkind = 'r'::\"char\"
1369
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1370
                    AND c.reloptions IS NOT NULL
1371
                    AND c.relname = '{$table}' AND n.nspname = '{$c_schema}'
1372
                ORDER BY nspname, relname";
1373
        } else {
1374
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1375
                FROM pg_class c
1376
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1377
                WHERE c.relkind = 'r'::\"char\"
1378
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1379
                    AND c.reloptions IS NOT NULL
1380
                ORDER BY nspname, relname";
1381
        }
1382
1383
        /* tmp var to parse the results */
1384
        $_autovacs = $this->selectSet($sql);
1385
1386
        /* result aray to return as RS */
1387
        $autovacs = [];
1388
        while (!$_autovacs->EOF) {
1389
            $_ = [
1390
                'nspname' => $_autovacs->fields['nspname'],
1391
                'relname' => $_autovacs->fields['relname'],
1392
            ];
1393
1394
            foreach (explode(',', $_autovacs->fields['reloptions']) as $var) {
1395
                list($o, $v) = explode('=', $var);
1396
                $_[$o]       = $v;
1397
            }
1398
1399
            $autovacs[] = $_;
1400
1401
            $_autovacs->moveNext();
1402
        }
1403
1404
        return new \PHPPgAdmin\ArrayRecordSet($autovacs);
1405
    }
1406
1407
    /**
1408
     * Returns the SQL for changing the current user.
1409
     *
1410
     * @param string $user The user to change to
1411
     *
1412
     * @return string The SQL
1413
     */
1414
    public function getChangeUserSQL($user)
1415
    {
1416
        $this->clean($user);
1417
1418
        return "SET SESSION AUTHORIZATION '{$user}';";
1419
    }
1420
1421
    /**
1422
     * Returns all available autovacuum per table information.
1423
     *
1424
     * @param string $table          table name
1425
     * @param bool   $vacenabled     true if vacuum is enabled
1426
     * @param int    $vacthreshold   vacuum threshold
1427
     * @param int    $vacscalefactor vacuum scalefactor
1428
     * @param int    $anathresold    analyze threshold
1429
     * @param int    $anascalefactor analyze scale factor
1430
     * @param int    $vaccostdelay   vacuum cost delay
1431
     * @param int    $vaccostlimit   vacuum cost limit
1432
     *
1433
     * @return bool 0 if successful
1434
     */
1435
    public function saveAutovacuum(
1436
        $table,
1437
        $vacenabled,
1438
        $vacthreshold,
1439
        $vacscalefactor,
1440
        $anathresold,
1441
        $anascalefactor,
1442
        $vaccostdelay,
1443
        $vaccostlimit
1444
    ) {
1445
        $f_schema = $this->_schema;
1446
        $this->fieldClean($f_schema);
1447
        $this->fieldClean($table);
1448
1449
        $params = [];
1450
1451
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET (";
1452
1453
        if (!empty($vacenabled)) {
1454
            $this->clean($vacenabled);
1455
            $params[] = "autovacuum_enabled='{$vacenabled}'";
1456
        }
1457
        if (!empty($vacthreshold)) {
1458
            $this->clean($vacthreshold);
1459
            $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'";
1460
        }
1461
        if (!empty($vacscalefactor)) {
1462
            $this->clean($vacscalefactor);
1463
            $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'";
1464
        }
1465
        if (!empty($anathresold)) {
1466
            $this->clean($anathresold);
1467
            $params[] = "autovacuum_analyze_threshold='{$anathresold}'";
1468
        }
1469
        if (!empty($anascalefactor)) {
1470
            $this->clean($anascalefactor);
1471
            $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'";
1472
        }
1473
        if (!empty($vaccostdelay)) {
1474
            $this->clean($vaccostdelay);
1475
            $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'";
1476
        }
1477
        if (!empty($vaccostlimit)) {
1478
            $this->clean($vaccostlimit);
1479
            $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'";
1480
        }
1481
1482
        $sql = $sql.implode(',', $params).');';
1483
1484
        return $this->execute($sql);
1485
    }
1486
1487
    // Type conversion routines
1488
1489
    /**
1490
     * Drops autovacuum config for a table.
1491
     *
1492
     * @param string $table The table
1493
     *
1494
     * @return bool 0 if successful
1495
     */
1496
    public function dropAutovacuum($table)
1497
    {
1498
        $f_schema = $this->_schema;
1499
        $this->fieldClean($f_schema);
1500
        $this->fieldClean($table);
1501
1502
        return $this->execute(
1503
            "
1504
            ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold,
1505
                autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor,
1506
                autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit
1507
            );"
1508
        );
1509
    }
1510
1511
    abstract public function fieldClean(&$str);
1512
1513
    abstract public function beginTransaction();
1514
1515
    abstract public function rollbackTransaction();
1516
1517
    abstract public function endTransaction();
1518
1519
    abstract public function execute($sql);
1520
1521
    abstract public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null);
1522
1523
    abstract public function selectSet($sql);
1524
1525
    abstract public function clean(&$str);
1526
1527
    abstract public function phpBool($parameter);
1528
1529
    abstract public function hasCreateTableLikeWithConstraints();
1530
1531
    abstract public function hasCreateTableLikeWithIndexes();
1532
1533
    abstract public function hasTablespaces();
1534
1535
    abstract public function delete($table, $conditions, $schema = '');
1536
1537
    abstract public function fieldArrayClean(&$arr);
1538
1539
    abstract public function hasCreateFieldWithConstraints();
1540
1541
    abstract public function getAttributeNames($table, $atts);
1542
}
1543