Passed
Pull Request — develop (#212)
by Felipe
05:01
created

TableTrait::_getTableAttributesAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 41
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 41
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.49
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
        if (isset($conf['display_sizes']) && $conf['display_sizes'] === true) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $conf seems to never exist and therefore isset should always be false.
Loading history...
55
            $sql .= ' pg_size_pretty(pg_total_relation_size(c.oid)) as table_size ';
56
        } else {
57
            $sql .= "   'N/A' as table_size ";
58
        }
59
60
        $sql .= " FROM pg_catalog.pg_class c
61
                LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
62
                LEFT JOIN  pg_catalog.pg_tablespace pt ON  pt.oid=c.reltablespace
63
                WHERE c.relkind = 'r'
64
                AND nspname='{$c_schema}'
65
                ORDER BY c.relname";
66
67
        return $this->selectSet($sql);
68
    }
69
70
    /**
71
     * Finds the names and schemas of parent tables (in order).
72
     *
73
     * @param string $table The table to find the parents for
74
     *
75
     * @return \PHPPgAdmin\ADORecordSet A recordset
76
     */
77
    public function getTableParents($table)
78
    {
79
        $c_schema = $this->_schema;
80
        $this->clean($c_schema);
81
        $this->clean($table);
82
83
        $sql = "
84
            SELECT
85
                pn.nspname, relname
86
            FROM
87
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
88
            WHERE
89
                pc.oid=pi.inhparent
90
                AND pc.relnamespace=pn.oid
91
                AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
92
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
93
            ORDER BY
94
                pi.inhseqno
95
        ";
96
97
        return $this->selectSet($sql);
98
    }
99
100
    /**
101
     * Finds the names and schemas of child tables.
102
     *
103
     * @param string $table The table to find the children for
104
     *
105
     * @return \PHPPgAdmin\ADORecordSet A recordset
106
     */
107
    public function getTableChildren($table)
108
    {
109
        $c_schema = $this->_schema;
110
        $this->clean($c_schema);
111
        $this->clean($table);
112
113
        $sql = "
114
            SELECT
115
                pn.nspname, relname
116
            FROM
117
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
118
            WHERE
119
                pc.oid=pi.inhrelid
120
                AND pc.relnamespace=pn.oid
121
                AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
122
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
123
        ";
124
125
        return $this->selectSet($sql);
126
    }
127
128
    /**
129
     * Returns the SQL definition for the table.
130
     * MUST be run within a transaction.
131
     *
132
     * @param string $table       The table to define
133
     * @param string $cleanprefix set to '-- ' to avoid issuing DROP statement
134
     *
135
     * @return string A string containing the formatted SQL code
136
     */
137
    public function getTableDefPrefix($table, $cleanprefix = '')
138
    {
139
        // Fetch table
140
        $t = $this->getTable($table);
141
        if (!is_object($t) || $t->RecordCount() != 1) {
0 ignored issues
show
introduced by
The condition $t->RecordCount() != 1 is always true.
Loading history...
142
            $this->rollbackTransaction();
143
144
            return null;
145
        }
146
        $this->fieldClean($t->fields['relname']);
147
        $this->fieldClean($t->fields['nspname']);
148
149
        // Fetch attributes
150
        $atts = $this->getTableAttributes($table);
151
        if (!is_object($atts)) {
152
            $this->rollbackTransaction();
153
154
            return null;
155
        }
156
157
        // Fetch constraints
158
        $cons = $this->getConstraints($table);
159
        if (!is_object($cons)) {
160
            $this->rollbackTransaction();
161
162
            return null;
163
        }
164
165
        // Output a reconnect command to create the table as the correct user
166
        $sql = $this->getChangeUserSQL($t->fields['relowner'])."\n\n";
167
168
        // Set schema search path
169
        $sql .= "SET search_path = \"{$t->fields['nspname']}\", pg_catalog;\n\n";
170
171
        // Begin CREATE TABLE definition
172
        $sql .= "-- Definition\n\n";
173
        // DROP TABLE must be fully qualified in case a table with the same name exists
174
        $sql .= $cleanprefix.'DROP TABLE ';
175
        $sql .= "\"{$t->fields['nspname']}\".\"{$t->fields['relname']}\";\n";
176
        $sql .= "CREATE TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" (\n";
177
178
        // Output all table columns
179
        $col_comments_sql = ''; // Accumulate comments on columns
180
        $num              = $atts->RecordCount() + $cons->RecordCount();
181
        $i                = 1;
182
        while (!$atts->EOF) {
183
            $this->fieldClean($atts->fields['attname']);
184
            $sql .= "    \"{$atts->fields['attname']}\"";
185
            // Dump SERIAL and BIGSERIAL columns correctly
186
            if ($this->phpBool($atts->fields['attisserial']) &&
187
                ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) {
188
                if ($atts->fields['type'] == 'integer') {
189
                    $sql .= ' SERIAL';
190
                } else {
191
                    $sql .= ' BIGSERIAL';
192
                }
193
            } else {
194
                $sql .= ' '.$this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
195
196
                // Add NOT NULL if necessary
197
                if ($this->phpBool($atts->fields['attnotnull'])) {
198
                    $sql .= ' NOT NULL';
199
                }
200
201
                // Add default if necessary
202
                if ($atts->fields['adsrc'] !== null) {
203
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
204
                }
205
            }
206
207
            // Output comma or not
208
            if ($i < $num) {
209
                $sql .= ",\n";
210
            } else {
211
                $sql .= "\n";
212
            }
213
214
            // Does this column have a comment?
215
            if ($atts->fields['comment'] !== null) {
216
                $this->clean($atts->fields['comment']);
217
                $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
218
            }
219
220
            $atts->moveNext();
221
            ++$i;
222
        }
223
        // Output all table constraints
224
        while (!$cons->EOF) {
225
            $this->fieldClean($cons->fields['conname']);
226
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
227
            // Nasty hack to support pre-7.4 PostgreSQL
228
            if ($cons->fields['consrc'] !== null) {
229
                $sql .= $cons->fields['consrc'];
230
            } else {
231
                switch ($cons->fields['contype']) {
232
                    case 'p':
233
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
234
                        $sql .= 'PRIMARY KEY ('.join(',', $keys).')';
235
236
                        break;
237
                    case 'u':
238
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
239
                        $sql .= 'UNIQUE ('.join(',', $keys).')';
240
241
                        break;
242
                    default:
243
                        // Unrecognised constraint
244
                        $this->rollbackTransaction();
245
246
                        return null;
247
                }
248
            }
249
250
            // Output comma or not
251
            if ($i < $num) {
252
                $sql .= ",\n";
253
            } else {
254
                $sql .= "\n";
255
            }
256
257
            $cons->moveNext();
258
            ++$i;
259
        }
260
261
        $sql .= ')';
262
263
        // @@@@ DUMP CLUSTERING INFORMATION
264
265
        // Inherits
266
        /**
267
         * XXX: This is currently commented out as handling inheritance isn't this simple.
268
         * You also need to make sure you don't dump inherited columns and defaults, as well
269
         * as inherited NOT NULL and CHECK constraints.  So for the time being, we just do
270
         * not claim to support inheritance.
271
         * $parents = $this->getTableParents($table);
272
         * if ($parents->RecordCount() > 0) {
273
         * $sql .= " INHERITS (";
274
         * while (!$parents->EOF) {
275
         * $this->fieldClean($parents->fields['relname']);
276
         * // Qualify the parent table if it's in another schema
277
         * if ($parents->fields['schemaname'] != $this->_schema) {
278
         * $this->fieldClean($parents->fields['schemaname']);
279
         * $sql .= "\"{$parents->fields['schemaname']}\".";
280
         * }
281
         * $sql .= "\"{$parents->fields['relname']}\"";.
282
         *
283
         * $parents->moveNext();
284
         * if (!$parents->EOF) $sql .= ', ';
285
         * }
286
         * $sql .= ")";
287
         * }
288
         */
289
290
        // Handle WITHOUT OIDS
291
        if ($this->hasObjectID($table)) {
292
            $sql .= ' WITH OIDS';
293
        } else {
294
            $sql .= ' WITHOUT OIDS';
295
        }
296
297
        $sql .= ";\n";
298
299
        // Column storage and statistics
300
        $atts->moveFirst();
301
        $first = true;
302
        while (!$atts->EOF) {
303
            $this->fieldClean($atts->fields['attname']);
304
            // Statistics first
305
            if ($atts->fields['attstattarget'] >= 0) {
306
                if ($first) {
307
                    $sql .= "\n";
308
                    $first = false;
309
                }
310
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
311
            }
312
            // Then storage
313
            if ($atts->fields['attstorage'] != $atts->fields['typstorage']) {
314
                switch ($atts->fields['attstorage']) {
315
                    case 'p':
316
                        $storage = 'PLAIN';
317
318
                        break;
319
                    case 'e':
320
                        $storage = 'EXTERNAL';
321
322
                        break;
323
                    case 'm':
324
                        $storage = 'MAIN';
325
326
                        break;
327
                    case 'x':
328
                        $storage = 'EXTENDED';
329
330
                        break;
331
                    default:
332
                        // Unknown storage type
333
                        $this->rollbackTransaction();
334
335
                        return null;
336
                }
337
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
338
            }
339
340
            $atts->moveNext();
341
        }
342
343
        // Comment
344
        if ($t->fields['relcomment'] !== null) {
345
            $this->clean($t->fields['relcomment']);
346
            $sql .= "\n-- Comment\n\n";
347
            $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n";
348
        }
349
350
        // Add comments on columns, if any
351
        if ($col_comments_sql != '') {
352
            $sql .= $col_comments_sql;
353
        }
354
355
        // Privileges
356
        $privs = $this->getPrivileges($table, 'table');
357
        if (!is_array($privs)) {
358
            $this->rollbackTransaction();
359
360
            return null;
361
        }
362
363
        if (sizeof($privs) > 0) {
364
            $sql .= "\n-- Privileges\n\n";
365
            /*
366
             * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
367
             * wire-in knowledge about the default public privileges for different
368
             * kinds of objects.
369
             */
370
            $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n";
371
            foreach ($privs as $v) {
372
                // Get non-GRANT OPTION privs
373
                $nongrant = array_diff($v[2], $v[4]);
374
375
                // Skip empty or owner ACEs
376
                if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) {
377
                    continue;
378
                }
379
380
                // Change user if necessary
381
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
382
                    $grantor = $v[3];
383
                    $this->clean($grantor);
384
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
385
                }
386
387
                // Output privileges with no GRANT OPTION
388
                $sql .= 'GRANT '.join(', ', $nongrant)." ON TABLE \"{$t->fields['relname']}\" TO ";
389
                switch ($v[0]) {
390
                    case 'public':
391
                        $sql .= "PUBLIC;\n";
392
393
                        break;
394
                    case 'user':
395
                    case 'role':
396
                        $this->fieldClean($v[1]);
397
                        $sql .= "\"{$v[1]}\";\n";
398
399
                        break;
400
                    case 'group':
401
                        $this->fieldClean($v[1]);
402
                        $sql .= "GROUP \"{$v[1]}\";\n";
403
404
                        break;
405
                    default:
406
                        // Unknown privilege type - fail
407
                        $this->rollbackTransaction();
408
409
                        return null;
410
                }
411
412
                // Reset user if necessary
413
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
414
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
415
                }
416
417
                // Output privileges with GRANT OPTION
418
419
                // Skip empty or owner ACEs
420
                if (!$this->hasGrantOption() || sizeof($v[4]) == 0) {
421
                    continue;
422
                }
423
424
                // Change user if necessary
425
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
426
                    $grantor = $v[3];
427
                    $this->clean($grantor);
428
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
429
                }
430
431
                $sql .= 'GRANT '.join(', ', $v[4])." ON \"{$t->fields['relname']}\" TO ";
432
                switch ($v[0]) {
433
                    case 'public':
434
                        $sql .= 'PUBLIC';
435
436
                        break;
437
                    case 'user':
438
                    case 'role':
439
                        $this->fieldClean($v[1]);
440
                        $sql .= "\"{$v[1]}\"";
441
442
                        break;
443
                    case 'group':
444
                        $this->fieldClean($v[1]);
445
                        $sql .= "GROUP \"{$v[1]}\"";
446
447
                        break;
448
                    default:
449
                        // Unknown privilege type - fail
450
                        return null;
451
                }
452
                $sql .= " WITH GRANT OPTION;\n";
453
454
                // Reset user if necessary
455
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
456
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
457
                }
458
            }
459
        }
460
461
        // Add a newline to separate data that follows (if any)
462
        $sql .= "\n";
463
464
        return $sql;
465
    }
466
467
    /**
468
     * Returns table information.
469
     *
470
     * @param string $table The name of the table
471
     *
472
     * @return \PHPPgAdmin\ADORecordSet A recordset
473
     */
474
    public function getTable($table)
475
    {
476
        $c_schema = $this->_schema;
477
        $this->clean($c_schema);
478
        $this->clean($table);
479
480
        $sql = '
481
            SELECT
482
              c.relname, n.nspname, ';
483
484
        $sql .= ($this->hasRoles() ? ' coalesce(u.usename,r.rolname) ' : ' u.usename')." AS relowner,
0 ignored issues
show
Bug introduced by
It seems like hasRoles() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

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