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

TableTrait::_dumpSerials()   B

Complexity

Conditions 10
Paths 25

Size

Total Lines 45
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 45
rs 7.6666
c 0
b 0
f 0
cc 10
nc 25
nop 4

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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