Passed
Push — develop ( 563aca...51557c )
by Felipe
04:28
created

TableTrait::createTable()   F

Complexity

Conditions 31
Paths > 20000

Size

Total Lines 160
Code Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 160
rs 2
c 0
b 0
f 0
cc 31
eloc 85
nc 154787
nop 14

How to fix   Long Method    Complexity    Many Parameters   

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:

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.40
5
 */
6
7
namespace PHPPgAdmin\Database;
8
9
/**
10
 * Common trait for tables manipulation.
11
 */
12
trait TableTrait
13
{
14
15
    /**
16
     * Return all tables in current database (and schema).
17
     *
18
     * @param bool|true $all True to fetch all tables, false for just in current schema
19
     *
20
     * @return \PHPPgAdmin\ADORecordSet All tables, sorted alphabetically
21
     */
22
    public function getTables($all = false)
23
    {
24
        $c_schema = $this->_schema;
25
        $this->clean($c_schema);
26
        if ($all) {
27
            // Exclude pg_catalog and information_schema tables
28
            $sql = "SELECT
29
                        schemaname AS nspname,
30
                        tablename AS relname,
31
                        tableowner AS relowner
32
                    FROM pg_catalog.pg_tables
33
                    WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
34
                    ORDER BY schemaname, tablename";
35
        } else {
36
            $sql = "
37
                SELECT c.relname,
38
                    pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
39
                    pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
40
                    reltuples::bigint,
41
                    pt.spcname as tablespace,
42
                    pg_size_pretty(pg_total_relation_size(c.oid)) as table_size
43
                FROM pg_catalog.pg_class c
44
                LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
45
                LEFT JOIN  pg_catalog.pg_tablespace pt ON  pt.oid=c.reltablespace
46
                WHERE c.relkind = 'r'
47
                AND nspname='{$c_schema}'
48
                ORDER BY c.relname";
49
        }
50
51
        return $this->selectSet($sql);
52
    }
53
54
    /**
55
     * Finds the names and schemas of parent tables (in order).
56
     *
57
     * @param string $table The table to find the parents for
58
     *
59
     * @return \PHPPgAdmin\ADORecordSet A recordset
60
     */
61
    public function getTableParents($table)
62
    {
63
        $c_schema = $this->_schema;
64
        $this->clean($c_schema);
65
        $this->clean($table);
66
67
        $sql = "
68
            SELECT
69
                pn.nspname, relname
70
            FROM
71
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
72
            WHERE
73
                pc.oid=pi.inhparent
74
                AND pc.relnamespace=pn.oid
75
                AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
76
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
77
            ORDER BY
78
                pi.inhseqno
79
        ";
80
81
        return $this->selectSet($sql);
82
    }
83
84
    /**
85
     * Finds the names and schemas of child tables.
86
     *
87
     * @param string $table The table to find the children for
88
     *
89
     * @return \PHPPgAdmin\ADORecordSet A recordset
90
     */
91
    public function getTableChildren($table)
92
    {
93
        $c_schema = $this->_schema;
94
        $this->clean($c_schema);
95
        $this->clean($table);
96
97
        $sql = "
98
            SELECT
99
                pn.nspname, relname
100
            FROM
101
                pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
102
            WHERE
103
                pc.oid=pi.inhrelid
104
                AND pc.relnamespace=pn.oid
105
                AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
106
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
107
        ";
108
109
        return $this->selectSet($sql);
110
    }
111
112
    /**
113
     * Returns the SQL definition for the table.
114
     *
115
     * @pre MUST be run within a transaction
116
     *
117
     * @param string    $table The table to define
1 ignored issue
show
Coding Style introduced by
Parameter tags must be defined first in a doc comment
Loading history...
118
     * @param bool|true $clean True to issue drop command, false otherwise
119
     *
120
     * @return string A string containing the formatted SQL code
121
     */
122
    public function getTableDefPrefix($table, $clean = false)
123
    {
124
        // Fetch table
125
        $t = $this->getTable($table);
126
        if (!is_object($t) || $t->recordCount() != 1) {
127
            $this->rollbackTransaction();
128
129
            return null;
130
        }
131
        $this->fieldClean($t->fields['relname']);
132
        $this->fieldClean($t->fields['nspname']);
133
134
        // Fetch attributes
135
        $atts = $this->getTableAttributes($table);
136
        if (!is_object($atts)) {
137
            $this->rollbackTransaction();
138
139
            return null;
140
        }
141
142
        // Fetch constraints
143
        $cons = $this->getConstraints($table);
144
        if (!is_object($cons)) {
145
            $this->rollbackTransaction();
146
147
            return null;
148
        }
149
150
        // Output a reconnect command to create the table as the correct user
151
        $sql = $this->getChangeUserSQL($t->fields['relowner']) . "\n\n";
152
153
        // Set schema search path
154
        $sql .= "SET search_path = \"{$t->fields['nspname']}\", pg_catalog;\n\n";
155
156
        // Begin CREATE TABLE definition
157
        $sql .= "-- Definition\n\n";
158
        // DROP TABLE must be fully qualified in case a table with the same name exists
159
        // in pg_catalog.
160
        if (!$clean) {
161
            $sql .= '-- ';
162
        }
163
164
        $sql .= 'DROP TABLE ';
165
        $sql .= "\"{$t->fields['nspname']}\".\"{$t->fields['relname']}\";\n";
166
        $sql .= "CREATE TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" (\n";
167
168
        // Output all table columns
169
        $col_comments_sql = ''; // Accumulate comments on columns
170
        $num              = $atts->recordCount() + $cons->recordCount();
171
        $i                = 1;
172
        while (!$atts->EOF) {
173
            $this->fieldClean($atts->fields['attname']);
174
            $sql .= "    \"{$atts->fields['attname']}\"";
175
            // Dump SERIAL and BIGSERIAL columns correctly
176
            if ($this->phpBool($atts->fields['attisserial']) &&
177
                ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) {
2 ignored issues
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
178
                if ($atts->fields['type'] == 'integer') {
179
                    $sql .= ' SERIAL';
180
                } else {
181
                    $sql .= ' BIGSERIAL';
182
                }
183
            } else {
184
                $sql .= ' ' . $this->formatType($atts->fields['type'], $atts->fields['atttypmod']);
185
186
                // Add NOT NULL if necessary
187
                if ($this->phpBool($atts->fields['attnotnull'])) {
188
                    $sql .= ' NOT NULL';
189
                }
190
191
                // Add default if necessary
192
                if ($atts->fields['adsrc'] !== null) {
193
                    $sql .= " DEFAULT {$atts->fields['adsrc']}";
194
                }
195
            }
196
197
            // Output comma or not
198
            if ($i < $num) {
199
                $sql .= ",\n";
200
            } else {
201
                $sql .= "\n";
202
            }
203
204
            // Does this column have a comment?
205
            if ($atts->fields['comment'] !== null) {
206
                $this->clean($atts->fields['comment']);
207
                $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\"  IS '{$atts->fields['comment']}';\n";
208
            }
209
210
            $atts->moveNext();
211
            ++$i;
212
        }
213
        // Output all table constraints
214
        while (!$cons->EOF) {
215
            $this->fieldClean($cons->fields['conname']);
216
            $sql .= "    CONSTRAINT \"{$cons->fields['conname']}\" ";
217
            // Nasty hack to support pre-7.4 PostgreSQL
218
            if ($cons->fields['consrc'] !== null) {
219
                $sql .= $cons->fields['consrc'];
220
            } else {
221
                switch ($cons->fields['contype']) {
222
                    case 'p':
223
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
224
                        $sql .= 'PRIMARY KEY (' . join(',', $keys) . ')';
225
226
                        break;
227
                    case 'u':
228
                        $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey']));
229
                        $sql .= 'UNIQUE (' . join(',', $keys) . ')';
230
231
                        break;
232
                    default:
233
                        // Unrecognised constraint
234
                        $this->rollbackTransaction();
235
236
                        return null;
237
                }
238
            }
239
240
            // Output comma or not
241
            if ($i < $num) {
242
                $sql .= ",\n";
243
            } else {
244
                $sql .= "\n";
245
            }
246
247
            $cons->moveNext();
248
            ++$i;
249
        }
250
251
        $sql .= ')';
252
253
        // @@@@ DUMP CLUSTERING INFORMATION
254
255
        // Inherits
256
        /*
257
         * XXX: This is currently commented out as handling inheritance isn't this simple.
258
         * You also need to make sure you don't dump inherited columns and defaults, as well
259
         * as inherited NOT NULL and CHECK constraints.  So for the time being, we just do
260
         * not claim to support inheritance.
261
        $parents = $this->getTableParents($table);
262
        if ($parents->recordCount() > 0) {
263
        $sql .= " INHERITS (";
264
        while (!$parents->EOF) {
265
        $this->fieldClean($parents->fields['relname']);
266
        // Qualify the parent table if it's in another schema
267
        if ($parents->fields['schemaname'] != $this->_schema) {
268
        $this->fieldClean($parents->fields['schemaname']);
269
        $sql .= "\"{$parents->fields['schemaname']}\".";
270
        }
271
        $sql .= "\"{$parents->fields['relname']}\"";
272
273
        $parents->moveNext();
274
        if (!$parents->EOF) $sql .= ', ';
275
        }
276
        $sql .= ")";
277
        }
278
         */
279
280
        // Handle WITHOUT OIDS
281
        if ($this->hasObjectID($table)) {
282
            $sql .= ' WITH OIDS';
283
        } else {
284
            $sql .= ' WITHOUT OIDS';
285
        }
286
287
        $sql .= ";\n";
288
289
        // Column storage and statistics
290
        $atts->moveFirst();
291
        $first = true;
292
        while (!$atts->EOF) {
293
            $this->fieldClean($atts->fields['attname']);
294
            // Statistics first
295
            if ($atts->fields['attstattarget'] >= 0) {
296
                if ($first) {
297
                    $sql .= "\n";
298
                    $first = false;
299
                }
300
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n";
301
            }
302
            // Then storage
303
            if ($atts->fields['attstorage'] != $atts->fields['typstorage']) {
304
                switch ($atts->fields['attstorage']) {
305
                    case 'p':
306
                        $storage = 'PLAIN';
307
308
                        break;
309
                    case 'e':
310
                        $storage = 'EXTERNAL';
311
312
                        break;
313
                    case 'm':
314
                        $storage = 'MAIN';
315
316
                        break;
317
                    case 'x':
318
                        $storage = 'EXTENDED';
319
320
                        break;
321
                    default:
322
                        // Unknown storage type
323
                        $this->rollbackTransaction();
324
325
                        return null;
326
                }
327
                $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n";
328
            }
329
330
            $atts->moveNext();
331
        }
332
333
        // Comment
334
        if ($t->fields['relcomment'] !== null) {
335
            $this->clean($t->fields['relcomment']);
336
            $sql .= "\n-- Comment\n\n";
337
            $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n";
338
        }
339
340
        // Add comments on columns, if any
341
        if ($col_comments_sql != '') {
342
            $sql .= $col_comments_sql;
343
        }
344
345
        // Privileges
346
        $privs = $this->getPrivileges($table, 'table');
347
        if (!is_array($privs)) {
348
            $this->rollbackTransaction();
349
350
            return null;
351
        }
352
353
        if (sizeof($privs) > 0) {
354
            $sql .= "\n-- Privileges\n\n";
355
            /*
356
             * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
357
             * wire-in knowledge about the default public privileges for different
358
             * kinds of objects.
359
             */
360
            $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n";
361
            foreach ($privs as $v) {
362
                // Get non-GRANT OPTION privs
363
                $nongrant = array_diff($v[2], $v[4]);
364
365
                // Skip empty or owner ACEs
366
                if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) {
367
                    continue;
368
                }
369
370
                // Change user if necessary
371
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
372
                    $grantor = $v[3];
373
                    $this->clean($grantor);
374
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
375
                }
376
377
                // Output privileges with no GRANT OPTION
378
                $sql .= 'GRANT ' . join(', ', $nongrant) . " ON TABLE \"{$t->fields['relname']}\" TO ";
379
                switch ($v[0]) {
380
                    case 'public':
381
                        $sql .= "PUBLIC;\n";
382
383
                        break;
384
                    case 'user':
385
                        $this->fieldClean($v[1]);
386
                        $sql .= "\"{$v[1]}\";\n";
387
388
                        break;
389
                    case 'group':
390
                        $this->fieldClean($v[1]);
391
                        $sql .= "GROUP \"{$v[1]}\";\n";
392
393
                        break;
394
                    default:
395
                        // Unknown privilege type - fail
396
                        $this->rollbackTransaction();
397
398
                        return null;
399
                }
400
401
                // Reset user if necessary
402
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
403
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
404
                }
405
406
                // Output privileges with GRANT OPTION
407
408
                // Skip empty or owner ACEs
409
                if (!$this->hasGrantOption() || sizeof($v[4]) == 0) {
410
                    continue;
411
                }
412
413
                // Change user if necessary
414
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
415
                    $grantor = $v[3];
416
                    $this->clean($grantor);
417
                    $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n";
418
                }
419
420
                $sql .= 'GRANT ' . join(', ', $v[4]) . " ON \"{$t->fields['relname']}\" TO ";
421
                switch ($v[0]) {
422
                    case 'public':
423
                        $sql .= 'PUBLIC';
424
425
                        break;
426
                    case 'user':
427
                        $this->fieldClean($v[1]);
428
                        $sql .= "\"{$v[1]}\"";
429
430
                        break;
431
                    case 'group':
432
                        $this->fieldClean($v[1]);
433
                        $sql .= "GROUP \"{$v[1]}\"";
434
435
                        break;
436
                    default:
437
                        // Unknown privilege type - fail
438
                        return null;
439
                }
440
                $sql .= " WITH GRANT OPTION;\n";
441
442
                // Reset user if necessary
443
                if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) {
444
                    $sql .= "RESET SESSION AUTHORIZATION;\n";
445
                }
446
            }
447
        }
448
449
        // Add a newline to separate data that follows (if any)
450
        $sql .= "\n";
451
452
        return $sql;
453
    }
454
455
    /**
456
     * Returns table information.
457
     *
458
     * @param string $table The name of the table
459
     *
460
     * @return \PHPPgAdmin\ADORecordSet A recordset
461
     */
462
    public function getTable($table)
463
    {
464
        $c_schema = $this->_schema;
465
        $this->clean($c_schema);
466
        $this->clean($table);
467
468
        $sql = "
469
            SELECT
470
              c.relname, n.nspname, u.usename AS relowner,
471
              pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
472
              (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
473
            FROM pg_catalog.pg_class c
474
                 LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
475
                 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
476
            WHERE c.relkind = 'r'
477
                  AND n.nspname = '{$c_schema}'
478
                  AND n.oid = c.relnamespace
479
                  AND c.relname = '{$table}'";
480
481
        return $this->selectSet($sql);
482
    }
483
484
    /**
485
     * Retrieve the attribute definition of a table.
486
     *
487
     * @param string $table The name of the table
488
     * @param string $field (optional) The name of a field to return
489
     *
490
     * @return All attributes in order
491
     */
492
    public function getTableAttributes($table, $field = '')
493
    {
494
        $c_schema = $this->_schema;
495
        $this->clean($c_schema);
496
        $this->clean($table);
497
        $this->clean($field);
498
499
        if ($field == '') {
500
            // This query is made much more complex by the addition of the 'attisserial' field.
501
            // The subquery to get that field checks to see if there is an internally dependent
502
            // sequence on the field.
503
            $sql = "
504
                SELECT
505
                    a.attname, a.attnum,
506
                    pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
507
                    a.atttypmod,
508
                    a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
509
                    a.attstattarget, a.attstorage, t.typstorage,
510
                    (
511
                        SELECT 1 FROM pg_catalog.pg_depend pd, pg_catalog.pg_class pc
512
                        WHERE pd.objid=pc.oid
513
                        AND pd.classid=pc.tableoid
514
                        AND pd.refclassid=pc.tableoid
515
                        AND pd.refobjid=a.attrelid
516
                        AND pd.refobjsubid=a.attnum
517
                        AND pd.deptype='i'
518
                        AND pc.relkind='S'
519
                    ) IS NOT NULL AS attisserial,
520
                    pg_catalog.col_description(a.attrelid, a.attnum) AS comment
521
                FROM
522
                    pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
523
                    ON a.attrelid=adef.adrelid
524
                    AND a.attnum=adef.adnum
525
                    LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
526
                WHERE
527
                    a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
528
                        AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
529
                        nspname = '{$c_schema}'))
530
                    AND a.attnum > 0 AND NOT a.attisdropped
531
                ORDER BY a.attnum";
532
        } else {
533
            $sql = "
534
                SELECT
535
                    a.attname, a.attnum,
536
                    pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
537
                    pg_catalog.format_type(a.atttypid, NULL) as base_type,
538
                    a.atttypmod,
539
                    a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
540
                    a.attstattarget, a.attstorage, t.typstorage,
541
                    pg_catalog.col_description(a.attrelid, a.attnum) AS comment
542
                FROM
543
                    pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
544
                    ON a.attrelid=adef.adrelid
545
                    AND a.attnum=adef.adnum
546
                    LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
547
                WHERE
548
                    a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
549
                        AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
550
                        nspname = '{$c_schema}'))
551
                    AND a.attname = '{$field}'";
552
        }
553
554
        return $this->selectSet($sql);
555
    }
556
557
    /**
558
     * Returns a list of all constraints on a table.
559
     *
560
     * @param string $table The table to find rules for
561
     *
562
     * @return \PHPPgAdmin\ADORecordSet A recordset
563
     */
564
    public function getConstraints($table)
565
    {
566
        $c_schema = $this->_schema;
567
        $this->clean($c_schema);
568
        $this->clean($table);
569
570
        // This SQL is greatly complicated by the need to retrieve
571
        // index clustering information for primary and unique constraints
572
        $sql = "SELECT
573
                pc.conname,
574
                pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc,
575
                pc.contype,
576
                CASE WHEN pc.contype='u' OR pc.contype='p' THEN (
577
                    SELECT
578
                        indisclustered
579
                    FROM
580
                        pg_catalog.pg_depend pd,
581
                        pg_catalog.pg_class pl,
582
                        pg_catalog.pg_index pi
583
                    WHERE
584
                        pd.refclassid=pc.tableoid
585
                        AND pd.refobjid=pc.oid
586
                        AND pd.objid=pl.oid
587
                        AND pl.oid=pi.indexrelid
588
                ) ELSE
589
                    NULL
590
                END AS indisclustered
591
            FROM
592
                pg_catalog.pg_constraint pc
593
            WHERE
594
                pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
595
                    AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
596
                    WHERE nspname='{$c_schema}'))
597
            ORDER BY
598
                1
599
        ";
600
601
        return $this->selectSet($sql);
602
    }
603
604
    /**
605
     * Checks to see whether or not a table has a unique id column.
606
     *
607
     * @param string $table The table name
608
     *
609
     * @return true if it has a unique id, false otherwise
610
     */
611
    public function hasObjectID($table)
612
    {
613
        $c_schema = $this->_schema;
614
        $this->clean($c_schema);
615
        $this->clean($table);
616
617
        $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}'
618
            AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')";
619
620
        $rs = $this->selectSet($sql);
621
        if ($rs->recordCount() != 1) {
622
            return null;
623
        }
624
625
        $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']);
626
627
        return $rs->fields['relhasoids'];
628
    }
629
630
    /**
631
     * Returns extra table definition information that is most usefully
632
     * dumped after the table contents for speed and efficiency reasons.
633
     *
634
     * @param string $table The table to define
635
     *
636
     * @return string A string containing the formatted SQL code
637
     */
638
    public function getTableDefSuffix($table)
639
    {
640
        $sql = '';
641
642
        // Indexes
643
        $indexes = $this->getIndexes($table);
644
        if (!is_object($indexes)) {
645
            $this->rollbackTransaction();
646
647
            return null;
648
        }
649
650
        if ($indexes->recordCount() > 0) {
651
            $sql .= "\n-- Indexes\n\n";
652
            while (!$indexes->EOF) {
653
                $sql .= $indexes->fields['inddef'] . ";\n";
654
655
                $indexes->moveNext();
656
            }
657
        }
658
659
        // Triggers
660
        $triggers = $this->getTriggers($table);
661
        if (!is_object($triggers)) {
662
            $this->rollbackTransaction();
663
664
            return null;
665
        }
666
667
        if ($triggers->recordCount() > 0) {
668
            $sql .= "\n-- Triggers\n\n";
669
            while (!$triggers->EOF) {
670
                $sql .= $triggers->fields['tgdef'];
671
                $sql .= ";\n";
672
673
                $triggers->moveNext();
674
            }
675
        }
676
677
        // Rules
678
        $rules = $this->getRules($table);
679
        if (!is_object($rules)) {
680
            $this->rollbackTransaction();
681
682
            return null;
683
        }
684
685
        if ($rules->recordCount() > 0) {
686
            $sql .= "\n-- Rules\n\n";
687
            while (!$rules->EOF) {
688
                $sql .= $rules->fields['definition'] . "\n";
689
690
                $rules->moveNext();
691
            }
692
        }
693
694
        return $sql;
695
    }
696
697
    /**
698
     * Grabs a list of indexes for a table.
699
     *
700
     * @param string                         $table  The name of a table whose indexes to retrieve
701
     * @param bool|\PHPPgAdmin\Database\Only $unique Only get unique/pk indexes
702
     *
703
     * @return \PHPPgAdmin\ADORecordSet A recordset
704
     */
705
    public function getIndexes($table = '', $unique = false)
706
    {
707
        $this->clean($table);
708
709
        $sql = "
710
            SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
711
                pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef
712
            FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
713
            WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid)
714
                AND c.oid = i.indrelid AND i.indexrelid = c2.oid
715
        ";
716
        if ($unique) {
717
            $sql .= ' AND i.indisunique ';
718
        }
719
720
        $sql .= ' ORDER BY c2.relname';
721
722
        return $this->selectSet($sql);
723
    }
724
725
    /**
726
     * Grabs a list of triggers on a table.
727
     *
728
     * @param string $table The name of a table whose triggers to retrieve
729
     *
730
     * @return \PHPPgAdmin\ADORecordSet A recordset
731
     */
732
    public function getTriggers($table = '')
733
    {
734
        $c_schema = $this->_schema;
735
        $this->clean($c_schema);
736
        $this->clean($table);
737
738
        $sql = "SELECT
739
                t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef,
740
                CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid,
741
                p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
742
                ns.nspname AS pronamespace
743
            FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns
744
            WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
745
                AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
746
                AND ( tgconstraint = 0 OR NOT EXISTS
747
                        (SELECT 1 FROM pg_catalog.pg_depend d    JOIN pg_catalog.pg_constraint c
748
                            ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
749
                        WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))
750
                AND p.oid=t.tgfoid
751
                AND p.pronamespace = ns.oid";
752
753
        return $this->selectSet($sql);
754
    }
755
756
    /**
757
     * Returns a list of all rules on a table OR view.
758
     *
759
     * @param string $table The table to find rules for
760
     *
761
     * @return \PHPPgAdmin\ADORecordSet A recordset
762
     */
763
    public function getRules($table)
764
    {
765
        $c_schema = $this->_schema;
766
        $this->clean($c_schema);
767
        $this->clean($table);
768
769
        $sql = "
770
            SELECT *
771
            FROM pg_catalog.pg_rules
772
            WHERE
773
                schemaname='{$c_schema}' AND tablename='{$table}'
774
            ORDER BY rulename
775
        ";
776
777
        return $this->selectSet($sql);
778
    }
779
780
    /**
781
     * Creates a new table in the database.
782
     *
783
     * @param string $name        The name of the table
784
     * @param int $fields      The number of fields
785
     * @param array $field       An array of field names
786
     * @param array $type        An array of field types
787
     * @param array $array       An array of '' or '[]' for each type if it's an array or not
788
     * @param array $length      An array of field lengths
789
     * @param array $notnull     An array of not null
790
     * @param array $default     An array of default values
791
     * @param bool $withoutoids True if WITHOUT OIDS, false otherwise
792
     * @param array $colcomment  An array of comments
793
     * @param string $tblcomment the comment for the table
794
     * @param string $tablespace  The tablespace name ('' means none/default)
795
     * @param array $uniquekey   An Array indicating the fields that are unique (those indexes that are set)
796
     * @param array $primarykey  An Array indicating the field used for the primarykey (those indexes that are set)
797
     *
798
     * @return bool|int 0 success
799
     *
800
     */
0 ignored issues
show
Coding Style introduced by
Additional blank lines found at end of doc comment
Loading history...
801
    public function createTable(
802
        $name,
803
        $fields,
804
        $field,
805
        $type,
806
        $array,
807
        $length,
808
        $notnull,
809
        $default,
810
        $withoutoids,
811
        $colcomment,
812
        $tblcomment,
813
        $tablespace,
814
        $uniquekey,
815
        $primarykey
816
    ) {
817
        $f_schema = $this->_schema;
818
        $this->fieldClean($f_schema);
819
        $this->fieldClean($name);
820
821
        $status = $this->beginTransaction();
822
        if ($status != 0) {
823
            return -1;
824
        }
825
826
        $found       = false;
827
        $first       = true;
828
        $comment_sql = ''; //Accumulate comments for the columns
829
        $sql         = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (";
830
        for ($i = 0; $i < $fields; ++$i) {
831
            $this->fieldClean($field[$i]);
832
            $this->clean($type[$i]);
833
            $this->clean($length[$i]);
834
            $this->clean($colcomment[$i]);
835
836
            // Skip blank columns - for user convenience
837
            if ($field[$i] == '' || $type[$i] == '') {
838
                continue;
839
            }
840
841
            // If not the first column, add a comma
842
            if (!$first) {
843
                $sql .= ', ';
844
            } else {
845
                $first = false;
846
            }
847
848
            switch ($type[$i]) {
849
                // Have to account for weird placing of length for with/without
850
                // time zone types
851
                case 'timestamp with time zone':
852
                case 'timestamp without time zone':
853
                    $qual = substr($type[$i], 9);
854
                    $sql .= "\"{$field[$i]}\" timestamp";
855
                    if ($length[$i] != '') {
856
                        $sql .= "({$length[$i]})";
857
                    }
858
859
                    $sql .= $qual;
860
861
                    break;
862
                case 'time with time zone':
863
                case 'time without time zone':
864
                    $qual = substr($type[$i], 4);
865
                    $sql .= "\"{$field[$i]}\" time";
866
                    if ($length[$i] != '') {
867
                        $sql .= "({$length[$i]})";
868
                    }
869
870
                    $sql .= $qual;
871
872
                    break;
873
                default:
874
                    $sql .= "\"{$field[$i]}\" {$type[$i]}";
875
                    if ($length[$i] != '') {
876
                        $sql .= "({$length[$i]})";
877
                    }
878
            }
879
            // Add array qualifier if necessary
880
            if ($array[$i] == '[]') {
881
                $sql .= '[]';
882
            }
883
884
            // Add other qualifiers
885
            if (!isset($primarykey[$i])) {
886
                if (isset($uniquekey[$i])) {
887
                    $sql .= ' UNIQUE';
888
                }
889
890
                if (isset($notnull[$i])) {
891
                    $sql .= ' NOT NULL';
892
                }
893
            }
894
            if ($default[$i] != '') {
895
                $sql .= " DEFAULT {$default[$i]}";
896
            }
897
898
            if ($colcomment[$i] != '') {
899
                $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n";
900
            }
901
902
            $found = true;
903
        }
904
905
        if (!$found) {
906
            return -1;
907
        }
908
909
        // PRIMARY KEY
910
        $primarykeycolumns = [];
911
        for ($i = 0; $i < $fields; ++$i) {
912
            if (isset($primarykey[$i])) {
913
                $primarykeycolumns[] = "\"{$field[$i]}\"";
914
            }
915
        }
916
        if (count($primarykeycolumns) > 0) {
917
            $sql .= ', PRIMARY KEY (' . implode(', ', $primarykeycolumns) . ')';
918
        }
919
920
        $sql .= ')';
921
922
        // WITHOUT OIDS
923
        if ($withoutoids) {
924
            $sql .= ' WITHOUT OIDS';
925
        } else {
926
            $sql .= ' WITH OIDS';
927
        }
928
929
        // Tablespace
930
        if ($this->hasTablespaces() && $tablespace != '') {
931
            $this->fieldClean($tablespace);
932
            $sql .= " TABLESPACE \"{$tablespace}\"";
933
        }
934
935
        $status = $this->execute($sql);
936
        if ($status) {
937
            $this->rollbackTransaction();
938
939
            return -1;
940
        }
941
942
        if ($tblcomment != '') {
943
            $status = $this->setComment('TABLE', '', $name, $tblcomment, true);
944
            if ($status) {
945
                $this->rollbackTransaction();
946
947
                return -1;
948
            }
949
        }
950
951
        if ($comment_sql != '') {
952
            $status = $this->execute($comment_sql);
953
            if ($status) {
954
                $this->rollbackTransaction();
955
956
                return -1;
957
            }
958
        }
959
960
        return $this->endTransaction();
961
    }
962
963
    /**
964
     * Creates a new table in the database copying attribs and other properties from another table.
965
     *
966
     * @param string $name        The name of the table
967
     * @param array $like        an array giving the schema ans the name of the table from which attribs are copying
968
     *                            from: array(
969
     *                            'table' => table name,
970
     *                            'schema' => the schema name,
971
     *                            )
972
     * @param bool   $defaults    if true, copy the defaults values as well
973
     * @param bool   $constraints if true, copy the constraints as well (CHECK on table & attr)
974
     * @param bool   $idx
975
     * @param string $tablespace  The tablespace name ('' means none/default)
976
     *
977
     * @return bool|int
978
     */
979
    public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '')
980
    {
981
        $f_schema = $this->_schema;
982
        $this->fieldClean($f_schema);
983
        $this->fieldClean($name);
984
        $this->fieldClean($like['schema']);
985
        $this->fieldClean($like['table']);
986
        $like = "\"{$like['schema']}\".\"{$like['table']}\"";
987
988
        $status = $this->beginTransaction();
989
        if ($status != 0) {
990
            return -1;
991
        }
992
993
        $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}";
994
995
        if ($defaults) {
996
            $sql .= ' INCLUDING DEFAULTS';
997
        }
998
999
        if ($this->hasCreateTableLikeWithConstraints() && $constraints) {
1000
            $sql .= ' INCLUDING CONSTRAINTS';
1001
        }
1002
1003
        if ($this->hasCreateTableLikeWithIndexes() && $idx) {
1004
            $sql .= ' INCLUDING INDEXES';
1005
        }
1006
1007
        $sql .= ')';
1008
1009
        if ($this->hasTablespaces() && $tablespace != '') {
1010
            $this->fieldClean($tablespace);
1011
            $sql .= " TABLESPACE \"{$tablespace}\"";
1012
        }
1013
1014
        $status = $this->execute($sql);
1015
        if ($status) {
1016
            $this->rollbackTransaction();
1017
1018
            return -1;
1019
        }
1020
1021
        return $this->endTransaction();
1022
    }
1023
1024
    /**
1025
     * Alter table properties.
1026
     *
1027
     * @param string $table      The name of the table
1028
     * @param string $name       The new name for the table
1029
     * @param string $owner      The new owner for the table
1030
     * @param string $schema     The new schema for the table
1031
     * @param string $comment    The comment on the table
1032
     * @param string $tablespace The new tablespace for the table ('' means leave as is)
1033
     *
1034
     * @return bool|int 0 success
1035
     */
1036
    public function alterTable($table, $name, $owner, $schema, $comment, $tablespace)
1037
    {
1038
        $data = $this->getTable($table);
1039
1040
        if ($data->recordCount() != 1) {
1041
            return -2;
1042
        }
1043
1044
        $status = $this->beginTransaction();
1045
        if ($status != 0) {
1046
            $this->rollbackTransaction();
1047
1048
            return -1;
1049
        }
1050
1051
        $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace);
1052
1053
        if ($status != 0) {
1054
            $this->rollbackTransaction();
1055
1056
            return $status;
1057
        }
1058
1059
        return $this->endTransaction();
1060
    }
1061
1062
    /**
1063
     * Protected method which alter a table
1064
     * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION.
1065
     *
1066
     * @param \PHPPgAdmin\ADORecordSet $tblrs      The table recordSet returned by getTable()
1067
     * @param string $name       The new name for the table
1068
     * @param string $owner      The new owner for the table
1069
     * @param string $schema     The new schema for the table
1070
     * @param string $comment    The comment on the table
1071
     * @param string $tablespace The new tablespace for the table ('' means leave as is)
1072
     *
1073
     * @return int 0 success
1074
     */
1075
    protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace)
1 ignored issue
show
Coding Style introduced by
Protected method name "TableTrait::_alterTable" must not be prefixed with an underscore
Loading history...
1076
    {
1077
        $this->fieldArrayClean($tblrs->fields);
1078
1079
        // Comment
1080
        $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment);
1081
        if ($status != 0) {
1082
            return -4;
1083
        }
1084
1085
        // Owner
1086
        $this->fieldClean($owner);
1087
        $status = $this->alterTableOwner($tblrs, $owner);
1088
        if ($status != 0) {
1089
            return -5;
1090
        }
1091
1092
        // Tablespace
1093
        $this->fieldClean($tablespace);
1094
        $status = $this->alterTableTablespace($tblrs, $tablespace);
1095
        if ($status != 0) {
1096
            return -6;
1097
        }
1098
1099
        // Rename
1100
        $this->fieldClean($name);
1101
        $status = $this->alterTableName($tblrs, $name);
1102
        if ($status != 0) {
1103
            return -3;
1104
        }
1105
1106
        // Schema
1107
        $this->fieldClean($schema);
1108
        $status = $this->alterTableSchema($tblrs, $schema);
1109
        if ($status != 0) {
1110
            return -7;
1111
        }
1112
1113
        return 0;
1114
    }
1115
1116
    /**
1117
     * Alter a table's owner
1118
     * /!\ this function is called from _alterTable which take care of escaping fields.
1119
     *
1120
     * @param \PHPPgAdmin\ADORecordSet     $tblrs The table RecordSet returned by getTable()
1121
     * @param string|null $owner
1122
     *
1123
     * @return int 0 if operation was successful
1124
     *
1125
     */
0 ignored issues
show
Coding Style introduced by
Additional blank lines found at end of doc comment
Loading history...
1126
    public function alterTableOwner($tblrs, $owner = null)
1127
    {
1128
        /* vars cleaned in _alterTable */
1129
        if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) {
1130
            $f_schema = $this->_schema;
1131
            $this->fieldClean($f_schema);
1132
            // If owner has been changed, then do the alteration.  We are
1133
            // careful to avoid this generally as changing owner is a
1134
            // superuser only function.
1135
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\"";
1136
1137
            return $this->execute($sql);
1138
        }
1139
1140
        return 0;
1141
    }
1142
1143
    /**
1144
     * Alter a table's tablespace
1145
     * /!\ this function is called from _alterTable which take care of escaping fields.
1146
     *
1147
     * @param \PHPPgAdmin\ADORecordSet     $tblrs      The table RecordSet returned by getTable()
1148
     * @param string|null $tablespace
1149
     *
1150
     * @return int 0 if operation was successful
1151
     *
1152
     */
0 ignored issues
show
Coding Style introduced by
Additional blank lines found at end of doc comment
Loading history...
1153
    public function alterTableTablespace($tblrs, $tablespace = null)
1154
    {
1155
        /* vars cleaned in _alterTable */
1156
        if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) {
1157
            $f_schema = $this->_schema;
1158
            $this->fieldClean($f_schema);
1159
1160
            // If tablespace has been changed, then do the alteration.  We
1161
            // don't want to do this unnecessarily.
1162
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\"";
1163
1164
            return $this->execute($sql);
1165
        }
1166
1167
        return 0;
1168
    }
1169
1170
    /**
1171
     * Alter a table's name
1172
     * /!\ this function is called from _alterTable which take care of escaping fields.
1173
     *
1174
     * @param \PHPPgAdmin\ADORecordSet $tblrs The table RecordSet returned by getTable()
1175
     * @param string $name  The new table's name
1176
     *
1177
     * @return int 0 if operation was successful
1178
     */
1179
    public function alterTableName($tblrs, $name = null)
1180
    {
1181
        /* vars cleaned in _alterTable */
1182
        // Rename (only if name has changed)
1183
        if (!empty($name) && ($name != $tblrs->fields['relname'])) {
1184
            $f_schema = $this->_schema;
1185
            $this->fieldClean($f_schema);
1186
1187
            $sql    = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" RENAME TO \"{$name}\"";
1188
            $status = $this->execute($sql);
1189
            if ($status == 0) {
1190
                $tblrs->fields['relname'] = $name;
1191
            } else {
1192
                return $status;
1193
            }
1194
        }
1195
1196
        return 0;
1197
    }
1198
1199
    // Row functions
1200
1201
    /**
1202
     * Alter a table's schema
1203
     * /!\ this function is called from _alterTable which take care of escaping fields.
1204
     *
1205
     * @param \PHPPgAdmin\ADORecordSet $tblrs  The table RecordSet returned by getTable()
1206
     * @param string|null $schema
1207
     *
1208
     * @return int 0 if operation was successful
1209
     *
1210
     */
0 ignored issues
show
Coding Style introduced by
Additional blank lines found at end of doc comment
Loading history...
1211
    public function alterTableSchema($tblrs, $schema = null)
1212
    {
1213
        /* vars cleaned in _alterTable */
1214
        if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) {
1215
            $f_schema = $this->_schema;
1216
            $this->fieldClean($f_schema);
1217
            // If tablespace has been changed, then do the alteration.  We
1218
            // don't want to do this unnecessarily.
1219
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET SCHEMA \"{$schema}\"";
1220
1221
            return $this->execute($sql);
1222
        }
1223
1224
        return 0;
1225
    }
1226
1227
    /**
1228
     * Empties a table in the database.
1229
     *
1230
     * @param string $table The table to be emptied
1231
     * @param boolean $cascade True to cascade truncate, false to restrict
1232
     *
1233
     * @return array<integer,mixed|string> 0 if operation was successful
1234
     */
1235
    public function emptyTable($table, $cascade)
1236
    {
1237
        $f_schema = $this->_schema;
1238
        $this->fieldClean($f_schema);
1239
        $this->fieldClean($table);
1240
1241
        $sql = "TRUNCATE TABLE \"{$f_schema}\".\"{$table}\" ";
1242
        if ($cascade) {
1243
            $sql = $sql . ' CASCADE';
1244
        }
1245
1246
        $status = $this->execute($sql);
1247
1248
        return [$status, $sql];
1249
    }
1250
1251
    /**
1252
     * Removes a table from the database.
1253
     *
1254
     * @param string $table   The table to drop
1255
     * @param boolean $cascade True to cascade drop, false to restrict
1256
     *
1257
     * @return int 0 if operation was successful
1258
     */
1259
    public function dropTable($table, $cascade)
1260
    {
1261
        $f_schema = $this->_schema;
1262
        $this->fieldClean($f_schema);
1263
        $this->fieldClean($table);
1264
1265
        $sql = "DROP TABLE \"{$f_schema}\".\"{$table}\"";
1266
        if ($cascade) {
1267
            $sql .= ' CASCADE';
1268
        }
1269
1270
        return $this->execute($sql);
1271
    }
1272
1273
    /**
1274
     * Add a new column to a table.
1275
     *
1276
     * @param string $table   The table to add to
1277
     * @param string $column  The name of the new column
1278
     * @param string $type    The type of the column
1279
     * @param boolean $array   True if array type, false otherwise
1280
     * @param int $length  The optional size of the column (ie. 30 for varchar(30))
1281
     * @param bool $notnull True if NOT NULL, false otherwise
1282
     * @param mixed $default The default for the column.  '' for none.
1283
     * @param string $comment comment for the column
1284
     *
1285
     * @return bool|int 0 success
1286
     */
1287
    public function addColumn($table, $column, $type, $array, $length, $notnull, $default, $comment)
1288
    {
1289
        $f_schema = $this->_schema;
1290
        $this->fieldClean($f_schema);
1291
        $this->fieldClean($table);
1292
        $this->fieldClean($column);
1293
        $this->clean($type);
1294
        $this->clean($length);
1295
1296
        if ($length == '') {
1297
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}";
1298
        } else {
1299
            switch ($type) {
1300
                // Have to account for weird placing of length for with/without
1301
                // time zone types
1302
                case 'timestamp with time zone':
1303
                case 'timestamp without time zone':
1304
                    $qual = substr($type, 9);
1305
                    $sql  = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" timestamp({$length}){$qual}";
1306
1307
                    break;
1308
                case 'time with time zone':
1309
                case 'time without time zone':
1310
                    $qual = substr($type, 4);
1311
                    $sql  = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" time({$length}){$qual}";
1312
1313
                    break;
1314
                default:
1315
                    $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}({$length})";
1316
            }
1317
        }
1318
1319
        // Add array qualifier, if requested
1320
        if ($array) {
1321
            $sql .= '[]';
1322
        }
1323
1324
        // If we have advanced column adding, add the extra qualifiers
1325
        if ($this->hasCreateFieldWithConstraints()) {
1326
            // NOT NULL clause
1327
            if ($notnull) {
1328
                $sql .= ' NOT NULL';
1329
            }
1330
1331
            // DEFAULT clause
1332
            if ($default != '') {
1333
                $sql .= ' DEFAULT ' . $default;
1334
            }
1335
        }
1336
1337
        $status = $this->beginTransaction();
1338
        if ($status != 0) {
1339
            return -1;
1340
        }
1341
1342
        $status = $this->execute($sql);
1343
        if ($status != 0) {
1344
            $this->rollbackTransaction();
1345
1346
            return -1;
1347
        }
1348
1349
        $status = $this->setComment('COLUMN', $column, $table, $comment);
1350
        if ($status != 0) {
1351
            $this->rollbackTransaction();
1352
1353
            return -1;
1354
        }
1355
1356
        return $this->endTransaction();
1357
    }
1358
1359
    /**
1360
     * Alters a column in a table.
1361
     *
1362
     * @param string $table      The table in which the column resides
1363
     * @param string $column     The column to alter
1364
     * @param string $name       The new name for the column
1365
     * @param boolean $notnull    (boolean) True if not null, false otherwise
1366
     * @param boolean $oldnotnull (boolean) True if column is already not null, false otherwise
1367
     * @param mixed $default    The new default for the column
1368
     * @param mixed $olddefault The old default for the column
1369
     * @param string $type       The new type for the column
1370
     * @param integer $length     The optional size of the column (ie. 30 for varchar(30))
1371
     * @param boolean $array      True if array type, false otherwise
1372
     * @param string $oldtype    The old type for the column
1373
     * @param string $comment    Comment for the column
1374
     *
1375
     * @return array 0 success
1376
     */
1377
    public function alterColumn(
1378
        $table,
1379
        $column,
1380
        $name,
1381
        $notnull,
1382
        $oldnotnull,
1383
        $default,
1384
        $olddefault,
1385
        $type,
1386
        $length,
1387
        $array,
1388
        $oldtype,
1389
        $comment
1390
    ) {
1391
        // Begin transaction
1392
        $status = $this->beginTransaction();
1393
        $sql    = '';
1394
        if ($status != 0) {
1395
            $this->rollbackTransaction();
1396
1397
            return [-6, $sql];
1398
        }
1399
1400
        // Rename the column, if it has been changed
1401
        if ($column != $name) {
1402
            $status = $this->renameColumn($table, $column, $name);
1403
            if ($status != 0) {
1404
                $this->rollbackTransaction();
1405
1406
                return [-4, $sql];
1407
            }
1408
        }
1409
1410
        $f_schema = $this->_schema;
1411
        $this->fieldClean($f_schema);
1412
        $this->fieldClean($name);
1413
        $this->fieldClean($table);
1414
        $this->fieldClean($column);
1415
1416
        $toAlter = [];
1417
        // Create the command for changing nullability
1418
        if ($notnull != $oldnotnull) {
1419
            $toAlter[] = "ALTER COLUMN \"{$name}\" " . ($notnull ? 'SET' : 'DROP') . ' NOT NULL';
1420
        }
1421
1422
        // Add default, if it has changed
1423
        if ($default != $olddefault) {
1424
            if ($default == '') {
1425
                $toAlter[] = "ALTER COLUMN \"{$name}\" DROP DEFAULT";
1426
            } else {
1427
                $toAlter[] = "ALTER COLUMN \"{$name}\" SET DEFAULT {$default}";
1428
            }
1429
        }
1430
1431
        // Add type, if it has changed
1432
        if ($length == '') {
1433
            $ftype = $type;
1434
        } else {
1435
            switch ($type) {
1436
                // Have to account for weird placing of length for with/without
1437
                // time zone types
1438
                case 'timestamp with time zone':
1439
                case 'timestamp without time zone':
1440
                    $qual  = substr($type, 9);
1441
                    $ftype = "timestamp({$length}){$qual}";
1442
1443
                    break;
1444
                case 'time with time zone':
1445
                case 'time without time zone':
1446
                    $qual  = substr($type, 4);
1447
                    $ftype = "time({$length}){$qual}";
1448
1449
                    break;
1450
                default:
1451
                    $ftype = "{$type}({$length})";
1452
            }
1453
        }
1454
1455
        // Add array qualifier, if requested
1456
        if ($array) {
1457
            $ftype .= '[]';
1458
        }
1459
1460
        if ($ftype != $oldtype) {
1461
            $toAlter[] = "ALTER COLUMN \"{$name}\" TYPE {$ftype}";
1462
        }
1463
1464
        // Attempt to process the batch alteration, if anything has been changed
1465
        if (!empty($toAlter)) {
1466
            // Initialise an empty SQL string
1467
            $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" "
1468
            . implode(',', $toAlter);
1469
1470
            $status = $this->execute($sql);
1471
            if ($status != 0) {
1472
                $this->rollbackTransaction();
1473
1474
                return [-1, $sql];
1475
            }
1476
        }
1477
1478
        // Update the comment on the column
1479
        $status = $this->setComment('COLUMN', $name, $table, $comment);
1480
        if ($status != 0) {
1481
            $this->rollbackTransaction();
1482
1483
            return [-5, $sql];
1484
        }
1485
1486
        return [$this->endTransaction(), $sql];
1487
    }
1488
1489
    /**
1490
     * Renames a column in a table.
1491
     *
1492
     * @param string $table   The table containing the column to be renamed
1493
     * @param string $column  The column to be renamed
1494
     * @param string $newName The new name for the column
1495
     *
1496
     * @return int 0 if operation was successful
1497
     */
1498
    public function renameColumn($table, $column, $newName)
1499
    {
1500
        $f_schema = $this->_schema;
1501
        $this->fieldClean($f_schema);
1502
        $this->fieldClean($table);
1503
        $this->fieldClean($column);
1504
        $this->fieldClean($newName);
1505
1506
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" RENAME COLUMN \"{$column}\" TO \"{$newName}\"";
1507
1508
        return $this->execute($sql);
1509
    }
1510
1511
    /**
1512
     * Sets default value of a column.
1513
     *
1514
     * @param string $table   The table from which to drop
1515
     * @param string $column  The column name to set
1516
     * @param mixed $default The new default value
1517
     *
1518
     * @return int 0 if operation was successful
1519
     */
1520
    public function setColumnDefault($table, $column, $default)
1521
    {
1522
        $f_schema = $this->_schema;
1523
        $this->fieldClean($f_schema);
1524
        $this->fieldClean($table);
1525
        $this->fieldClean($column);
1526
1527
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" SET DEFAULT {$default}";
1528
1529
        return $this->execute($sql);
1530
    }
1531
1532
    /**
1533
     * Sets whether or not a column can contain NULLs.
1534
     *
1535
     * @param string $table  The table that contains the column
1536
     * @param string $column The column to alter
1537
     * @param bool $state  True to set null, false to set not null
1538
     *
1539
     * @return int 0 if operation was successful
1540
     */
1541
    public function setColumnNull($table, $column, $state)
1542
    {
1543
        $f_schema = $this->_schema;
1544
        $this->fieldClean($f_schema);
1545
        $this->fieldClean($table);
1546
        $this->fieldClean($column);
1547
1548
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . ($state ? 'DROP' : 'SET') . ' NOT NULL';
1549
1550
        return $this->execute($sql);
1551
    }
1552
1553
    /**
1554
     * Drops a column from a table.
1555
     *
1556
     * @param string $table   The table from which to drop a column
1557
     * @param string $column  The column to be dropped
1558
     * @param bool $cascade True to cascade drop, false to restrict
1559
     *
1560
     * @return int 0 if operation was successful
1561
     */
1562
    public function dropColumn($table, $column, $cascade)
1563
    {
1564
        $f_schema = $this->_schema;
1565
        $this->fieldClean($f_schema);
1566
        $this->fieldClean($table);
1567
        $this->fieldClean($column);
1568
1569
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DROP COLUMN \"{$column}\"";
1570
        if ($cascade) {
1571
            $sql .= ' CASCADE';
1572
        }
1573
1574
        return $this->execute($sql);
1575
    }
1576
1577
    /**
1578
     * Drops default value of a column.
1579
     *
1580
     * @param string $table  The table from which to drop
1581
     * @param string $column The column name to drop default
1582
     *
1583
     * @return int 0 if operation was successful
1584
     */
1585
    public function dropColumnDefault($table, $column)
1586
    {
1587
        $f_schema = $this->_schema;
1588
        $this->fieldClean($f_schema);
1589
        $this->fieldClean($table);
1590
        $this->fieldClean($column);
1591
1592
        $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT";
1593
1594
        return $this->execute($sql);
1595
    }
1596
1597
    /**
1598
     * Sets up the data object for a dump.  eg. Starts the appropriate
1599
     * transaction, sets variables, etc.
1600
     *
1601
     * @return int 0 success
1602
     */
1603
    public function beginDump()
1604
    {
1605
        // Begin serializable transaction (to dump consistent data)
1606
        $status = $this->beginTransaction();
1607
        if ($status != 0) {
1608
            return -1;
1609
        }
1610
1611
        // Set serializable
1612
        $sql    = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE';
1613
        $status = $this->execute($sql);
1614
        if ($status != 0) {
1615
            $this->rollbackTransaction();
1616
1617
            return -1;
1618
        }
1619
1620
        // Set datestyle to ISO
1621
        $sql    = 'SET DATESTYLE = ISO';
1622
        $status = $this->execute($sql);
1623
        if ($status != 0) {
1624
            $this->rollbackTransaction();
1625
1626
            return -1;
1627
        }
1628
1629
        // Set extra_float_digits to 2
1630
        $sql    = 'SET extra_float_digits TO 2';
1631
        $status = $this->execute($sql);
1632
        if ($status != 0) {
1633
            $this->rollbackTransaction();
1634
1635
            return -1;
1636
        }
1637
1638
        return 0;
1639
    }
1640
1641
    /**
1642
     * Ends the data object for a dump.
1643
     *
1644
     * @return bool 0 success
1645
     */
1646
    public function endDump()
1647
    {
1648
        return $this->endTransaction();
1649
    }
1650
1651
    /**
1652
     * Returns a recordset of all columns in a relation.  Used for data export.
1653
     *
1654
     * @@ Note: Really needs to use a cursor
1655
     *
1656
     * @param string $relation The name of a relation
1 ignored issue
show
Coding Style introduced by
Parameter tags must be defined first in a doc comment
Loading history...
1657
     * @param bool $oids true to dump also the oids
1658
     *
1659
     * @return \PHPPgAdmin\ADORecordSet A recordset on success
1660
     */
1661
    public function dumpRelation($relation, $oids)
1662
    {
1663
        $this->fieldClean($relation);
1664
1665
        // Actually retrieve the rows
1666
        if ($oids) {
1667
            $oid_str = $this->id . ', ';
1668
        } else {
1669
            $oid_str = '';
1670
        }
1671
1672
        return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\"");
1673
    }
1674
1675
    /**
1676
     * Returns all available autovacuum per table information.
1677
     *
1678
     * @param string $table if given, return autovacuum info for the given table or return all informations for all table
1679
     *
1680
     * @return \PHPPgAdmin\ADORecordSet A recordset
1681
     */
1682
    public function getTableAutovacuum($table = '')
1683
    {
1684
        $sql = '';
1685
1686
        if ($table !== '') {
1687
            $this->clean($table);
1688
            $c_schema = $this->_schema;
1689
            $this->clean($c_schema);
1690
1691
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1692
                FROM pg_class c
1693
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1694
                WHERE c.relkind = 'r'::\"char\"
1695
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1696
                    AND c.reloptions IS NOT NULL
1697
                    AND c.relname = '{$table}' AND n.nspname = '{$c_schema}'
1698
                ORDER BY nspname, relname";
1699
        } else {
1700
            $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions
1701
                FROM pg_class c
1702
                    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
1703
                WHERE c.relkind = 'r'::\"char\"
1704
                    AND n.nspname NOT IN ('pg_catalog','information_schema')
1705
                    AND c.reloptions IS NOT NULL
1706
                ORDER BY nspname, relname";
1707
        }
1708
1709
        /* tmp var to parse the results */
1710
        $_autovacs = $this->selectSet($sql);
1711
1712
        /* result aray to return as RS */
1713
        $autovacs = [];
1714
        while (!$_autovacs->EOF) {
1715
            $_ = [
1716
                'nspname' => $_autovacs->fields['nspname'],
1717
                'relname' => $_autovacs->fields['relname'],
1718
            ];
1719
1720
            foreach (explode(',', $_autovacs->fields['reloptions']) as $var) {
1721
                list($o, $v) = explode('=', $var);
1722
                $_[$o]       = $v;
1723
            }
1724
1725
            $autovacs[] = $_;
1726
1727
            $_autovacs->moveNext();
1728
        }
1729
1730
        return new \PHPPgAdmin\ArrayRecordSet($autovacs);
1731
    }
1732
1733
    /**
1734
     * Get the fields for uniquely identifying a row in a table.
1735
     *
1736
     * @param string $table The table for which to retrieve the identifier
1737
     *
1738
     * @return array<integer,string>|array|int An array mapping attribute number to attribute name, empty for no identifiers
1739
     */
1740
    public function getRowIdentifier($table)
1741
    {
1742
        $oldtable = $table;
1743
        $c_schema = $this->_schema;
1744
        $this->clean($c_schema);
1745
        $this->clean($table);
1746
1747
        $status = $this->beginTransaction();
1748
        if ($status != 0) {
1749
            return -1;
1750
        }
1751
1752
        // Get the first primary or unique index (sorting primary keys first) that
1753
        // is NOT a partial index.
1754
        $sql = "
1755
            SELECT indrelid, indkey
1756
            FROM pg_catalog.pg_index
1757
            WHERE indisunique AND indrelid=(
1758
                SELECT oid FROM pg_catalog.pg_class
1759
                WHERE relname='{$table}' AND relnamespace=(
1760
                    SELECT oid FROM pg_catalog.pg_namespace
1761
                    WHERE nspname='{$c_schema}'
1762
                )
1763
            ) AND indpred IS NULL AND indexprs IS NULL
1764
            ORDER BY indisprimary DESC LIMIT 1";
1765
        $rs = $this->selectSet($sql);
1766
1767
        // If none, check for an OID column.  Even though OIDs can be duplicated, the edit and delete row
1768
        // functions check that they're only modiying a single row.  Otherwise, return empty array.
1769
        if ($rs->recordCount() == 0) {
1770
            // Check for OID column
1771
            $temp = [];
1772
            if ($this->hasObjectID($table)) {
1773
                $temp = ['oid'];
1774
            }
1775
            $this->endTransaction();
1776
1777
            return $temp;
1778
        } // Otherwise find the names of the keys
1779
1780
        $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey']));
1781
        if (!is_array($attnames)) {
1782
            $this->rollbackTransaction();
1783
1784
            return -1;
1785
        }
1786
1787
        $this->endTransaction();
1788
1789
        return $attnames;
1790
    }
1791
1792
    /**
1793
     * Adds a new row to a table.
1794
     *
1795
     * @param string $table  The table in which to insert
1796
     * @param array $fields Array of given field in values
1797
     * @param array $values Array of new values for the row
1798
     * @param array $nulls  An array mapping column => something if it is to be null
1799
     * @param array $format An array of the data type (VALUE or EXPRESSION)
1800
     * @param array $types  An array of field types
1801
     *
1802
     * @return int 0 if operation was successful
1803
     */
1804
    public function insertRow($table, $fields, $values, $nulls, $format, $types)
1805
    {
1806
        if (!is_array($fields) || !is_array($values) || !is_array($nulls)
1807
            || !is_array($format) || !is_array($types)
1808
            || (count($fields) != count($values))
1809
        ) {
1810
            return -1;
1811
        }
1812
1813
        // Build clause
1814
        if (count($values) > 0) {
1815
            // Escape all field names
1816
            $fields   = array_map(['\PHPPgAdmin\Database\Postgres', 'fieldClean'], $fields);
1817
            $f_schema = $this->_schema;
1818
            $this->fieldClean($table);
1819
            $this->fieldClean($f_schema);
1820
1821
            $sql = '';
1822
            foreach ($values as $i => $value) {
1823
                // Handle NULL values
1824
                if (isset($nulls[$i])) {
1825
                    $sql .= ',NULL';
1826
                } else {
1827
                    $sql .= ',' . $this->formatValue($types[$i], $format[$i], $value);
1828
                }
1829
            }
1830
1831
            $sql = "INSERT INTO \"{$f_schema}\".\"{$table}\" (\"" . implode('","', $fields) . '")
1832
                VALUES (' . substr($sql, 1) . ')';
1833
1834
            return $this->execute($sql);
1835
        }
1836
1837
        return -1;
1838
    }
1839
1840
    /**
1841
     * Formats a value or expression for sql purposes.
1842
     *
1843
     * @param string $type   The type of the field
1844
     * @param mixed $format VALUE or EXPRESSION
1845
     * @param mixed $value  The actual value entered in the field.  Can be NULL
1846
     *
1847
     * @return The suitably quoted and escaped value
1848
     */
1849
    public function formatValue($type, $format, $value)
1850
    {
1851
        switch ($type) {
1852
            case 'bool':
1853
            case 'boolean':
1854
                if ($value == 't') {
1855
                    return 'TRUE';
1856
                }
1857
1858
                if ($value == 'f') {
1859
                    return 'FALSE';
1860
                }
1861
                if ($value == '') {
1862
                    return 'NULL';
1863
                }
1864
1865
                return $value;
1866
                break;
1867
            default:
1868
                // Checking variable fields is difficult as there might be a size
1869
                // attribute...
1870
                if (strpos($type, 'time') === 0) {
1871
                    // Assume it's one of the time types...
1872
                    if ($value == '') {
1873
                        return "''";
1874
                    }
1875
1876
                    if (strcasecmp($value, 'CURRENT_TIMESTAMP') == 0
1877
                        || strcasecmp($value, 'CURRENT_TIME') == 0
1878
                        || strcasecmp($value, 'CURRENT_DATE') == 0
1879
                        || strcasecmp($value, 'LOCALTIME') == 0
1880
                        || strcasecmp($value, 'LOCALTIMESTAMP') == 0) {
1 ignored issue
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
1881
                        return $value;
1882
                    }
1883
                    if ($format == 'EXPRESSION') {
1884
                        return $value;
1885
                    }
1886
                    $this->clean($value);
1887
1888
                    return "'{$value}'";
1889
                }
1890
                if ($format == 'VALUE') {
1891
                    $this->clean($value);
1892
1893
                    return "'{$value}'";
1894
                }
1895
1896
                return $value;
1897
        }
1898
    }
1899
1900
    // View functions
1901
1902
    /**
1903
     * Updates a row in a table.
1904
     *
1905
     * @param string $table  The table in which to update
1906
     * @param array $vars   An array mapping new values for the row
1907
     * @param array $nulls  An array mapping column => something if it is to be null
1908
     * @param array $format An array of the data type (VALUE or EXPRESSION)
1909
     * @param array $types  An array of field types
1910
     * @param array $keyarr An array mapping column => value to update
1911
     *
1912
     * @return bool|int 0 success
1913
     */
1914
    public function editRow($table, $vars, $nulls, $format, $types, $keyarr)
1915
    {
1916
        if (!is_array($vars) || !is_array($nulls) || !is_array($format) || !is_array($types)) {
1917
            return -1;
1918
        }
1919
1920
        $f_schema = $this->_schema;
1921
        $this->fieldClean($f_schema);
1922
        $this->fieldClean($table);
1923
1924
        // Build clause
1925
        if (sizeof($vars) > 0) {
1926
            foreach ($vars as $key => $value) {
1927
                $this->fieldClean($key);
1928
1929
                // Handle NULL values
1930
                if (isset($nulls[$key])) {
1931
                    $tmp = 'NULL';
1932
                } else {
1933
                    $tmp = $this->formatValue($types[$key], $format[$key], $value);
1934
                }
1935
1936
                if (isset($sql)) {
1937
                    $sql .= ", \"{$key}\"={$tmp}";
1938
                } else {
1939
                    $sql = "UPDATE \"{$f_schema}\".\"{$table}\" SET \"{$key}\"={$tmp}";
1940
                }
1941
            }
1942
            $first = true;
1943
            foreach ($keyarr as $k => $v) {
1944
                $this->fieldClean($k);
1945
                $this->clean($v);
1946
                if ($first) {
1947
                    $sql .= " WHERE \"{$k}\"='{$v}'";
1948
                    $first = false;
1949
                } else {
1950
                    $sql .= " AND \"{$k}\"='{$v}'";
1951
                }
1952
            }
1953
        }
1954
1955
        // Begin transaction.  We do this so that we can ensure only one row is
1956
        // edited
1957
        $status = $this->beginTransaction();
1958
        if ($status != 0) {
1959
            $this->rollbackTransaction();
1960
1961
            return -1;
1962
        }
1963
1964
        $status = $this->execute($sql);
1965
        if ($status != 0) {
1966
            // update failed
1967
            $this->rollbackTransaction();
1968
1969
            return -1;
1970
        }
1971
1972
        if ($this->conn->Affected_Rows() != 1) {
1973
            // more than one row could be updated
1974
            $this->rollbackTransaction();
1975
1976
            return -2;
1977
        }
1978
1979
        // End transaction
1980
        return $this->endTransaction();
1981
    }
1982
1983
    /**
1984
     * Delete a row from a table.
1985
     *
1986
     * @param string $table  The table from which to delete
1987
     * @param string $key    An array mapping column => value to delete
1988
     * @param bool $schema
1989
     *
1990
     * @return bool|int 0 success
1991
     */
1992
    public function deleteRow($table, $key, $schema = false)
1993
    {
1994
        if (!is_array($key)) {
1995
            return -1;
1996
        }
1997
1998
        // Begin transaction.  We do this so that we can ensure only one row is
1999
        // deleted
2000
        $status = $this->beginTransaction();
2001
        if ($status != 0) {
2002
            $this->rollbackTransaction();
2003
2004
            return -1;
2005
        }
2006
2007
        if ($schema === false) {
2008
            $schema = $this->_schema;
2009
        }
2010
2011
        $status = $this->delete($table, $key, $schema);
2012
        if ($status != 0 || $this->conn->Affected_Rows() != 1) {
2013
            $this->rollbackTransaction();
2014
2015
            return -2;
2016
        }
2017
2018
        // End transaction
2019
        return $this->endTransaction();
2020
    }
2021
2022
    /**
2023
     * Returns the SQL for changing the current user.
2024
     *
2025
     * @param string $user The user to change to
2026
     *
2027
     * @return string The SQL
2028
     */
2029
    public function getChangeUserSQL($user)
2030
    {
2031
        $this->clean($user);
2032
2033
        return "SET SESSION AUTHORIZATION '{$user}';";
2034
    }
2035
2036
    abstract public function fieldClean(&$str);
2037
2038
    abstract public function beginTransaction();
2039
2040
    abstract public function rollbackTransaction();
2041
2042
    abstract public function endTransaction();
2043
2044
    abstract public function execute($sql);
2045
2046
    abstract public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null);
2047
2048
    abstract public function selectSet($sql);
2049
2050
    abstract public function clean(&$str);
2051
2052
    abstract public function phpBool($parameter);
2053
2054
    abstract public function hasCreateTableLikeWithConstraints();
2055
2056
    abstract public function hasCreateTableLikeWithIndexes();
2057
2058
    abstract public function hasTablespaces();
2059
2060
    abstract ublic function delete($table, $conditions, $schema = '');
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST on line 2060 at column 13
Loading history...
introduced by
Possible parse error: non-abstract method defined as abstract
Loading history...
2061
}
2062