Passed
Push — develop ( 7eecac...7414a0 )
by Felipe
03:57
created

TableTrait::saveAutovacuum()   B

Complexity

Conditions 8
Paths 128

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * PHPPgAdmin v6.0.0-beta.50
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
        $sql = $this->_dumpCreate($t, $sql, $cleanprefix);
169
170
        // Output all table columns
171
        $col_comments_sql = ''; // Accumulate comments on columns
172
        $num              = $atts->RecordCount() + $cons->RecordCount();
173
        $i                = 1;
0 ignored issues
show
Unused Code introduced by
The assignment to $i is dead and can be removed.
Loading history...
174
175
        $sql = $this->_dumpSerials($atts, $sql, $col_comments_sql, $num);
176
177
        $consOutput = $this->_dumpConstraints($cons, $sql, $num);
178
179
        if ($consOutput === null) {
180
            return null;
181
        }
182
        $sql = $consOutput;
183
184
        $sql .= ')';
185
186
        // @@@@ DUMP CLUSTERING INFORMATION
187
188
        // Inherits
189
        /**
190
         * XXX: This is currently commented out as handling inheritance isn't this simple.
191
         * You also need to make sure you don't dump inherited columns and defaults, as well
192
         * as inherited NOT NULL and CHECK constraints.  So for the time being, we just do
193
         * not claim to support inheritance.
194
         * $parents = $this->getTableParents($table);
195
         * if ($parents->RecordCount() > 0) {
196
         * $sql .= " INHERITS (";
197
         * while (!$parents->EOF) {
198
         * $this->fieldClean($parents->fields['relname']);
199
         * // Qualify the parent table if it's in another schema
200
         * if ($parents->fields['schemaname'] != $this->_schema) {
201
         * $this->fieldClean($parents->fields['schemaname']);
202
         * $sql .= "\"{$parents->fields['schemaname']}\".";
203
         * }
204
         * $sql .= "\"{$parents->fields['relname']}\"";.
205
         *
206
         * $parents->moveNext();
207
         * if (!$parents->EOF) $sql .= ', ';
208
         * }
209
         * $sql .= ")";
210
         * }
211
         */
212
213
        // Handle WITHOUT OIDS
214
        if ($this->hasObjectID($table)) {
215
            $sql .= ' WITH OIDS';
216
        } else {
217
            $sql .= ' WITHOUT OIDS';
218
        }
219
220
        $sql .= ";\n";
221
222
        $colStorage = $this->_dumpColStats($atts, $sql);
223
224
        if ($colStorage === null) {
225
            return null;
226
        }
227
        $sql = $colStorage;
228
229
        // Comment
230
        if ($t->fields['relcomment'] !== null) {
231
            $this->clean($t->fields['relcomment']);
232
            $sql .= "\n-- Comment\n\n";
233
            $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n";
234
        }
235
236
        // Add comments on columns, if any
237
        if ($col_comments_sql != '') {
238
            $sql .= $col_comments_sql;
239
        }
240
241
        // Privileges
242
        $privs = $this->getPrivileges($table, 'table');
243
        if (!is_array($privs)) {
244
            $this->rollbackTransaction();
245
246
            return null;
247
        }
248
249
        $privsOutput = $this->_dumpPrivileges($privs, $sql);
250
251
        if ($privsOutput === null) {
252
            return null;
253
        }
254
        $sql .= $privsOutput;
255
256
        // Add a newline to separate data that follows (if any)
257
        $sql .= "\n";
258
259
        return $sql;
260
    }
261
262
    /**
263
     * Dumps serial-like columns in the table.
264
     *
265
     * @param \PHPPgAdmin\ADORecordSet $atts             table attributes
266
     * @param string                   $sql              The sql sentence generated so far
267
     * @param string                   $col_comments_sql Column comments, passed by reference
268
     * @param int                      $num              Table attributes count + table constraints count
269
     *
270
     * @return string original $sql plus appended strings
271
     */
272
    private function _dumpSerials($atts, $sql, &$col_comments_sql, $num)
273
    {
274
        while (!$atts->EOF) {
275
            $this->fieldClean($atts->fields['attname']);
276
            $sql .= "    \"{$atts->fields['attname']}\"";
277
            // Dump SERIAL and BIGSERIAL columns correctly
278
            if ($this->phpBool($atts->fields['attisserial']) &&
279
                ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) {
280
                if ($atts->fields['type'] == 'integer') {
281
                    $sql .= ' SERIAL';
282
                } else {
283
                    $sql .= ' BIGSERIAL';
284
                }
285
            } else {
286
                $sql .= ' '.$this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
0 ignored issues
show
Bug introduced by
It seems like formatType() 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

286
                $sql .= ' '.$this->/** @scrutinizer ignore-call */ formatType($atts->fields['type'], $atts->fields['atttypmod']);
Loading history...
287
288
                // Add NOT NULL if necessary
289
                if ($this->phpBool($atts->fields['attnotnull'])) {
290
                    $sql .= ' NOT NULL';
291
                }
292
293
                // Add default if necessary
294
                if ($atts->fields['adsrc'] !== null) {
295
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
296
                }
297
            }
298
299
            // Output comma or not
300
            if ($i < $num) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i seems to be never defined.
Loading history...
301
                $sql .= ",\n";
302
            } else {
303
                $sql .= "\n";
304
            }
305
306
            // Does this column have a comment?
307
            if ($atts->fields['comment'] !== null) {
308
                $this->clean($atts->fields['comment']);
309
                $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $t seems to be never defined.
Loading history...
310
            }
311
312
            $atts->moveNext();
313
            ++$i;
314
        }
315
316
        return $sql;
317
    }
318
319
    /**
320
     * Dumps constraints.
321
     *
322
     * @param \PHPPgAdmin\ADORecordSet $cons The table constraints
323
     * @param string                   $sql  The sql sentence generated so far
324
     * @param int                      $num  Table attributes count + table constraints count
325
     *
326
     * @return string original $sql plus appended strings
327
     */
328
    private function _dumpConstraints($cons, $sql, $num)
329
    {
330
        // Output all table constraints
331
        while (!$cons->EOF) {
332
            $this->fieldClean($cons->fields['conname']);
333
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
334
            // Nasty hack to support pre-7.4 PostgreSQL
335
            if ($cons->fields['consrc'] !== null) {
336
                $sql .= $cons->fields['consrc'];
337
            } else {
338
                switch ($cons->fields['contype']) {
339
                    case 'p':
340
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $table seems to be never defined.
Loading history...
341
                        $sql .= 'PRIMARY KEY ('.join(',', $keys).')';
342
343
                        break;
344
                    case 'u':
345
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
346
                        $sql .= 'UNIQUE ('.join(',', $keys).')';
347
348
                        break;
349
                    default:
350
                        // Unrecognised constraint
351
                        $this->rollbackTransaction();
352
353
                        return null;
354
                }
355
            }
356
357
            // Output comma or not
358
            if ($i < $num) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i seems to be never defined.
Loading history...
359
                $sql .= ",\n";
360
            } else {
361
                $sql .= "\n";
362
            }
363
364
            $cons->moveNext();
365
            ++$i;
366
        }
367
368
        return $sql;
369
    }
370
371
    /**
372
     * Dumps col statistics.
373
     *
374
     * @param \PHPPgAdmin\ADORecordSet $atts table attributes
375
     * @param string                   $sql  The sql sentence generated so far
376
     *
377
     * @return string original $sql plus appended strings
378
     */
379
    private function _dumpColStats($atts, $sql)
380
    {
381
        // Column storage and statistics
382
        $atts->moveFirst();
383
        $first = true;
384
        while (!$atts->EOF) {
385
            $this->fieldClean($atts->fields['attname']);
386
            // Statistics first
387
            if ($atts->fields['attstattarget'] >= 0) {
388
                if ($first) {
389
                    $sql .= "\n";
390
                    $first = false;
391
                }
392
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $t seems to be never defined.
Loading history...
393
            }
394
            // Then storage
395
            if ($atts->fields['attstorage'] != $atts->fields['typstorage']) {
396
                switch ($atts->fields['attstorage']) {
397
                    case 'p':
398
                        $storage = 'PLAIN';
399
400
                        break;
401
                    case 'e':
402
                        $storage = 'EXTERNAL';
403
404
                        break;
405
                    case 'm':
406
                        $storage = 'MAIN';
407
408
                        break;
409
                    case 'x':
410
                        $storage = 'EXTENDED';
411
412
                        break;
413
                    default:
414
                        // Unknown storage type
415
                        $this->rollbackTransaction();
416
417
                        return null;
418
                }
419
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
420
            }
421
422
            $atts->moveNext();
423
        }
424
425
        return $sql;
426
    }
427
428
    /**
429
     * Dumps privileges.
430
     *
431
     * @param \PHPPgAdmin\ADORecordSet $privs The table privileges
432
     * @param string                   $sql   The sql sentence generated so far
433
     *
434
     * @return string original $sql plus appended strings
435
     */
436
    private function _dumpPrivileges($privs, $sql)
437
    {
438
        if (sizeof($privs) <= 0) {
439
            return $sql;
440
        }
441
        $sql .= "\n-- Privileges\n\n";
442
        /*
443
         * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
444
         * wire-in knowledge about the default public privileges for different
445
         * kinds of objects.
446
         */
447
        $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $t seems to be never defined.
Loading history...
448
        foreach ($privs as $v) {
449
            // Get non-GRANT OPTION privs
450
            $nongrant = array_diff($v[2], $v[4]);
451
452
            // Skip empty or owner ACEs
453
            if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) {
454
                continue;
455
            }
456
457
            // Change user if necessary
458
            if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
0 ignored issues
show
Bug introduced by
It seems like hasGrantOption() 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

458
            if ($this->/** @scrutinizer ignore-call */ hasGrantOption() && $v[3] != $t->fields['relowner']) {
Loading history...
459
                $grantor = $v[3];
460
                $this->clean($grantor);
461
                $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
462
            }
463
464
            // Output privileges with no GRANT OPTION
465
            $sql .= 'GRANT '.join(', ', $nongrant)." ON TABLE \"{$t->fields['relname']}\" TO ";
466
            switch ($v[0]) {
467
                case 'public':
468
                    $sql .= "PUBLIC;\n";
469
470
                    break;
471
                case 'user':
472
                case 'role':
473
                    $this->fieldClean($v[1]);
474
                    $sql .= "\"{$v[1]}\";\n";
475
476
                    break;
477
                case 'group':
478
                    $this->fieldClean($v[1]);
479
                    $sql .= "GROUP \"{$v[1]}\";\n";
480
481
                    break;
482
                default:
483
                    // Unknown privilege type - fail
484
                    $this->rollbackTransaction();
485
486
                    return null;
487
            }
488
489
            // Reset user if necessary
490
            if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
491
                $sql .= "RESET SESSION AUTHORIZATION;\n";
492
            }
493
494
            // Output privileges with GRANT OPTION
495
496
            // Skip empty or owner ACEs
497
            if (!$this->hasGrantOption() || sizeof($v[4]) == 0) {
498
                continue;
499
            }
500
501
            // Change user if necessary
502
            if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
503
                $grantor = $v[3];
504
                $this->clean($grantor);
505
                $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
506
            }
507
508
            $sql .= 'GRANT '.join(', ', $v[4])." ON \"{$t->fields['relname']}\" TO ";
509
            switch ($v[0]) {
510
                case 'public':
511
                    $sql .= 'PUBLIC';
512
513
                    break;
514
                case 'user':
515
                case 'role':
516
                    $this->fieldClean($v[1]);
517
                    $sql .= "\"{$v[1]}\"";
518
519
                    break;
520
                case 'group':
521
                    $this->fieldClean($v[1]);
522
                    $sql .= "GROUP \"{$v[1]}\"";
523
524
                    break;
525
                default:
526
                    // Unknown privilege type - fail
527
                    return null;
528
            }
529
            $sql .= " WITH GRANT OPTION;\n";
530
531
            // Reset user if necessary
532
            if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
533
                $sql .= "RESET SESSION AUTHORIZATION;\n";
534
            }
535
        }
536
537
        return $sql;
538
    }
539
540
    /**
541
     * Dumps a create.
542
     *
543
     * @param \PHPPgAdmin\ADORecordSet $tblfields   table fields object
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter $tblfields does not match actual variable name $fields
Loading history...
544
     * @param string                   $sql         The sql sentence generated so far
545
     * @param string                   $cleanprefix set to '-- ' to avoid issuing DROP statement
546
     * @param mixed                    $fields
547
     *
548
     * @return string original $sql plus appended strings
549
     */
550
    private function _dumpCreate($fields, $sql, $cleanprefix)
0 ignored issues
show
Unused Code introduced by
The parameter $fields is not used and could be removed. ( Ignorable by Annotation )

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

550
    private function _dumpCreate(/** @scrutinizer ignore-unused */ $fields, $sql, $cleanprefix)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

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