Completed
Pull Request — master (#1175)
by David Joseph
01:44
created

PostgresAdapter   D

Complexity

Total Complexity 198

Size/Duplication

Total Lines 1131
Duplicated Lines 20.34 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 92.21%

Importance

Changes 0
Metric Value
wmc 198
lcom 1
cbo 6
dl 230
loc 1131
ccs 568
cts 616
cp 0.9221
rs 4.4102
c 0
b 0
f 0

48 Methods

Rating   Name   Duplication   Size   Complexity  
B connect() 5 31 6
A disconnect() 0 4 1
A hasTransactions() 0 4 1
A beginTransaction() 0 4 1
A commitTransaction() 0 4 1
A rollbackTransaction() 0 4 1
A quoteSchemaName() 0 4 1
A quoteTableName() 0 4 1
A quoteColumnName() 0 4 1
A hasTable() 0 15 1
A getColumnCommentSqlDefinition() 0 14 2
A getIndexSqlDefinition() 9 21 4
A getForeignKeySqlDefinition() 15 15 4
A createSchemaTable() 0 11 2
A createSchema() 0 5 1
A hasSchema() 0 12 1
A dropSchema() 0 5 1
A dropAllSchemas() 0 6 2
A getAllSchemas() 0 13 2
A getColumnTypes() 0 4 1
A isValidColumnType() 0 5 2
A isArrayType() 0 10 2
A getSchemaName() 0 6 2
F createTable() 38 91 18
A renameTable() 0 9 1
A dropTable() 0 4 1
C getColumns() 0 47 7
A hasColumn() 14 15 1
A addColumn() 0 15 2
A renameColumn() 0 22 2
B changeColumn() 0 66 5
A dropColumn() 0 10 1
B getIndexes() 30 31 3
A hasIndex() 14 15 4
A hasIndexByName() 10 11 3
A addIndex() 0 5 1
B dropIndex() 0 23 4
A dropIndexByName() 0 8 1
B hasForeignKey() 21 23 6
B getForeignKeys() 25 26 2
A addForeignKey() 9 9 1
B dropForeignKey() 34 34 5
C getSqlType() 0 61 29
C getPhinxType() 0 64 35
A createDatabase() 0 5 2
A hasDatabase() 0 7 1
A dropDatabase() 0 6 1
C getColumnSqlDefinition() 6 50 18

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PostgresAdapter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PostgresAdapter, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Phinx\Db\Table;
32
use Phinx\Db\Table\Column;
33
use Phinx\Db\Table\ForeignKey;
34
use Phinx\Db\Table\Index;
35
use Phinx\Util\Literal;
36
37
class PostgresAdapter extends PdoAdapter implements AdapterInterface
38
{
39
    const INT_SMALL = 65535;
40
41
    /**
42
     * Columns with comments
43
     *
44
     * @var array
45
     */
46
    protected $columnsWithComments = [];
47
48
    /**
49
     * {@inheritdoc}
50 68
     */
51
    public function connect()
52 68
    {
53 68
        if ($this->connection === null) {
54
            if (!class_exists('PDO') || !in_array('pgsql', \PDO::getAvailableDrivers(), true)) {
55
                // @codeCoverageIgnoreStart
56
                throw new \RuntimeException('You need to enable the PDO_Pgsql extension for Phinx to run properly.');
57
                // @codeCoverageIgnoreEnd
58
            }
59 68
60 68
            $db = null;
61
            $options = $this->getOptions();
62
63 68
            // if port is specified use it, otherwise use the PostgreSQL default
64 68 View Code Duplication
            if (isset($options['port'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
65 68
                $dsn = 'pgsql:host=' . $options['host'] . ';port=' . $options['port'] . ';dbname=' . $options['name'];
66 1
            } else {
67
                $dsn = 'pgsql:host=' . $options['host'] . ';dbname=' . $options['name'];
68
            }
69
70 68
            try {
71 68
                $db = new \PDO($dsn, $options['user'], $options['pass'], [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
72 1
            } catch (\PDOException $exception) {
73 1
                throw new \InvalidArgumentException(sprintf(
74 1
                    'There was a problem connecting to the database: %s',
75 1
                    $exception->getMessage()
76
                ));
77
            }
78 68
79 68
            $this->setConnection($db);
80 68
        }
81
    }
82
83
    /**
84
     * {@inheritdoc}
85 68
     */
86
    public function disconnect()
87 68
    {
88 68
        $this->connection = null;
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function hasTransactions()
95
    {
96
        return true;
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function beginTransaction()
103
    {
104
        $this->execute('BEGIN');
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function commitTransaction()
111
    {
112
        $this->execute('COMMIT');
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function rollbackTransaction()
119
    {
120
        $this->execute('ROLLBACK');
121
    }
122
123
    /**
124
     * Quotes a schema name for use in a query.
125
     *
126
     * @param string $schemaName Schema Name
127
     * @return string
128 68
     */
129
    public function quoteSchemaName($schemaName)
130 68
    {
131
        return $this->quoteColumnName($schemaName);
132
    }
133
134
    /**
135
     * {@inheritdoc}
136 68
     */
137
    public function quoteTableName($tableName)
138 68
    {
139
        return $this->quoteSchemaName($this->getSchemaName()) . '.' . $this->quoteColumnName($tableName);
140
    }
141
142
    /**
143
     * {@inheritdoc}
144 68
     */
145
    public function quoteColumnName($columnName)
146 68
    {
147
        return '"' . $columnName . '"';
148
    }
149
150
    /**
151
     * {@inheritdoc}
152 68
     */
153
    public function hasTable($tableName)
154 68
    {
155 68
        $result = $this->getConnection()->query(
156
            sprintf(
157
                'SELECT *
158
                FROM information_schema.tables
159 68
                WHERE table_schema = %s
160 68
                AND lower(table_name) = lower(%s)',
161 68
                $this->getConnection()->quote($this->getSchemaName()),
162 68
                $this->getConnection()->quote($tableName)
163 68
            )
164
        );
165 68
166
        return $result->rowCount() === 1;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171 68
     */
172
    public function createTable(Table $table)
173 68
    {
174
        $options = $table->getOptions();
175
176 68
         // Add the default primary key
177 68
        $columns = $table->getPendingColumns();
178 48 View Code Duplication
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179 48
            $column = new Column();
180 48
            $column->setName('id')
181 48
                   ->setType('integer')
182
                   ->setIdentity(true);
183 48
184 48
            array_unshift($columns, $column);
185 68
            $options['primary_key'] = 'id';
186
        } elseif (isset($options['id']) && is_string($options['id'])) {
187 2
            // Handle id => "field_name" to support AUTO_INCREMENT
188 2
            $column = new Column();
189 2
            $column->setName($options['id'])
190 2
                   ->setType('integer')
191
                   ->setIdentity(true);
192 2
193 2
            array_unshift($columns, $column);
194 2
            $options['primary_key'] = $options['id'];
195
        }
196
197 68
        // TODO - process table options like collation etc
198 68
        $sql = 'CREATE TABLE ';
199
        $sql .= $this->quoteTableName($table->getName()) . ' (';
200 68
201 68
        $this->columnsWithComments = [];
202 68 View Code Duplication
        foreach ($columns as $column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
204
205 68
            // set column comments, if needed
206 6
            if ($column->getComment()) {
207 6
                $this->columnsWithComments[] = $column;
208 68
            }
209
        }
210
211 68
         // set the primary key(s)
212 68 View Code Duplication
        if (isset($options['primary_key'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
213 68
            $sql = rtrim($sql);
214 68
            $sql .= sprintf(' CONSTRAINT %s_pkey PRIMARY KEY (', $table->getName());
215 68
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
216 68
                $sql .= $this->quoteColumnName($options['primary_key']);
217
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
218
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
219 1
            }
220 1
            $sql .= ')';
221 1
        } else {
222 1
            $sql = rtrim($sql, ', '); // no primary keys
223 1
        }
224 1
225 1
        // set the foreign keys
226 1
        $foreignKeys = $table->getForeignKeys();
227 1
        if (!empty($foreignKeys)) {
228 1
            foreach ($foreignKeys as $foreignKey) {
229 68
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
230 68
            }
231 2
        }
232
233
        $sql .= ');';
234
235 68
        // process column comments
236 68
        if (!empty($this->columnsWithComments)) {
237 1
            foreach ($this->columnsWithComments as $column) {
238 1
                $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
239 1
            }
240 1
        }
241
242 68
        // set the indexes
243
        $indexes = $table->getIndexes();
244
        if (!empty($indexes)) {
245 68
            foreach ($indexes as $index) {
246 6
                $sql .= $this->getIndexSqlDefinition($index, $table->getName());
247 6
            }
248 6
        }
249 6
250
        // execute the sql
251
        $this->execute($sql);
252
253 68
        // process table comments
254 68
        if (isset($options['comment'])) {
255 5
            $sql = sprintf(
256 5
                'COMMENT ON TABLE %s IS %s',
257 5
                $this->quoteTableName($table->getName()),
258 5
                $this->getConnection()->quote($options['comment'])
259
            );
260
            $this->execute($sql);
261 68
        }
262
    }
263
264 68
    /**
265 1
     * {@inheritdoc}
266 1
     */
267 1
    public function renameTable($tableName, $newTableName)
268 1
    {
269 1
        $sql = sprintf(
270 1
            'ALTER TABLE %s RENAME TO %s',
271 1
            $this->quoteTableName($tableName),
272 68
            $this->quoteColumnName($newTableName)
273
        );
274
        $this->execute($sql);
275
    }
276
277 1
    /**
278
     * {@inheritdoc}
279 1
     */
280 1
    public function dropTable($tableName)
281 1
    {
282 1
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
283 1
    }
284 1
285 1
    /**
286
     * {@inheritdoc}
287
     */
288
    public function truncateTable($tableName)
289
    {
290 1
        $sql = sprintf(
291
            'TRUNCATE TABLE %s',
292 1
            $this->quoteTableName($tableName)
293 1
        );
294
295
        $this->execute($sql);
296
    }
297
298 1
    /**
299
     * {@inheritdoc}
300 1
     */
301 1
    public function getColumns($tableName)
302 1
    {
303 1
        $columns = [];
304
        $sql = sprintf(
305 1
            "SELECT column_name, data_type, udt_name, is_identity, is_nullable,
306 1
             column_default, character_maximum_length, numeric_precision, numeric_scale
307
             FROM information_schema.columns
308
             WHERE table_name ='%s'",
309
            $tableName
310
        );
311 9
        $columnsInfo = $this->fetchAll($sql);
312
313 9
        foreach ($columnsInfo as $columnInfo) {
314 9
            $isUserDefined = $columnInfo['data_type'] === 'USER-DEFINED';
315
            if ($isUserDefined) {
316
                $columnType = Literal::from($columnInfo['udt_name']);
317
            } else {
318 9
                $columnType = $this->getPhinxType($columnInfo['data_type']);
319
            }
320 9
            // If the default value begins with a ' or looks like a function mark it as literal
321 9
            if (strpos($columnInfo['column_default'], "'") === 0 || preg_match('/^\D[a-z_\d]*\(.*\)$/', $columnInfo['column_default'])) {
322
                $columnDefault = Literal::from($columnInfo['column_default']);
323 9
            } else {
324 9
                $columnDefault = $columnInfo['column_default'];
325 9
            }
326 9
327 9
            $column = new Column();
328 9
            $column->setName($columnInfo['column_name'])
329 9
                   ->setType($columnType)
330 9
                   ->setNull($columnInfo['is_nullable'] === 'YES')
331 9
                   ->setDefault($columnDefault)
332
                   ->setIdentity($columnInfo['is_identity'] === 'YES')
333 9
                   ->setPrecision($columnInfo['numeric_precision'])
334 1
                   ->setScale($columnInfo['numeric_scale']);
335 1
336
            if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) {
337 9
                $column->setTimezone(true);
338 5
            }
339 5
340 9
            if (isset($columnInfo['character_maximum_length'])) {
341 9
                $column->setLimit($columnInfo['character_maximum_length']);
342 9
            }
343
            $columns[] = $column;
344
        }
345
346
        return $columns;
347
    }
348 24
349
    /**
350 24
     * {@inheritdoc}
351
     */
352 View Code Duplication
    public function hasColumn($tableName, $columnName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
353 24
    {
354 24
        $sql = sprintf(
355 24
            "SELECT count(*)
356
            FROM information_schema.columns
357 24
            WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'",
358
            $this->getSchemaName(),
359 24
            $tableName,
360 24
            $columnName
361
        );
362
363
        $result = $this->fetchRow($sql);
364
365
        return $result['count'] > 0;
366 18
    }
367
368 18
    /**
369 18
     * {@inheritdoc}
370 18
     */
371 18
    public function addColumn(Table $table, Column $column)
372 18
    {
373 18
        $sql = sprintf(
374
            'ALTER TABLE %s ADD %s %s;',
375 18
            $this->quoteTableName($table->getName()),
376 18
            $this->quoteColumnName($column->getName()),
377
            $this->getColumnSqlDefinition($column)
378
        );
379
380
        if ($column->getComment()) {
381 3
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
382
        }
383 3
384
        $this->execute($sql);
385
    }
386 3
387 3
    /**
388
     * {@inheritdoc}
389 3
     */
390 3
    public function renameColumn($tableName, $columnName, $newColumnName)
391 3
    {
392 1
        $sql = sprintf(
393
            "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists
394 2
             FROM information_schema.columns
395 2
             WHERE table_name ='%s' AND column_name = '%s'",
396 2
            $tableName,
397 2
            $columnName
398 2
        );
399 2
        $result = $this->fetchRow($sql);
400 2
        if (!(bool)$result['column_exists']) {
401 2
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
402 2
        }
403
        $this->execute(
404
            sprintf(
405
                'ALTER TABLE %s RENAME COLUMN %s TO %s',
406
                $this->quoteTableName($tableName),
407 5
                $this->quoteColumnName($columnName),
408
                $this->quoteColumnName($newColumnName)
409
            )
410
        );
411 5
    }
412 5
413 5
    /**
414 5
     * {@inheritdoc}
415 5
     */
416 5
    public function changeColumn($tableName, $columnName, Column $newColumn)
417
    {
418 5
        // TODO - is it possible to merge these 3 queries into less?
419 5
        // change data type
420
        $sql = sprintf(
421 5
            'ALTER TABLE %s ALTER COLUMN %s TYPE %s',
422 5
            $this->quoteTableName($tableName),
423
            $this->quoteColumnName($columnName),
424 5
            $this->getColumnSqlDefinition($newColumn)
425 5
        );
426 5
        //NULL and DEFAULT cannot be set while changing column type
427 5
        $sql = preg_replace('/ NOT NULL/', '', $sql);
428 5
        $sql = preg_replace('/ NULL/', '', $sql);
429 5
        //If it is set, DEFAULT is the last definition
430 2
        $sql = preg_replace('/DEFAULT .*/', '', $sql);
431 2
        $this->execute($sql);
432 4
        // process null
433
        $sql = sprintf(
434 5
            'ALTER TABLE %s ALTER COLUMN %s',
435 5
            $this->quoteTableName($tableName),
436
            $this->quoteColumnName($columnName)
437 1
        );
438 1
        if ($newColumn->isNull()) {
439 1
            $sql .= ' DROP NOT NULL';
440 1
        } else {
441 1
            $sql .= ' SET NOT NULL';
442 1
        }
443 1
        $this->execute($sql);
444 1
        if (!is_null($newColumn->getDefault())) {
445 1
            //change default
446
            $this->execute(
447 4
                sprintf(
448 4
                    'ALTER TABLE %s ALTER COLUMN %s SET %s',
449 4
                    $this->quoteTableName($tableName),
450 4
                    $this->quoteColumnName($columnName),
451 4
                    $this->getDefaultValueDefinition($newColumn->getDefault())
452 4
                )
453 4
            );
454
        } else {
455
            //drop default
456 5
            $this->execute(
457 1
                sprintf(
458 1
                    'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
459 1
                    $this->quoteTableName($tableName),
460 1
                    $this->quoteColumnName($columnName)
461 1
                )
462 1
            );
463 1
        }
464 1
        // rename column
465 1
        if ($columnName !== $newColumn->getName()) {
466
            $this->execute(
467
                sprintf(
468 5
                    'ALTER TABLE %s RENAME COLUMN %s TO %s',
469 2
                    $this->quoteTableName($tableName),
470 2
                    $this->quoteColumnName($columnName),
471 2
                    $this->quoteColumnName($newColumn->getName())
472 5
                )
473
            );
474
        }
475
476
        // change column comment if needed
477 1
        if ($newColumn->getComment()) {
478
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
479 1
            $this->execute($sql);
480 1
        }
481 1
    }
482 1
483 1
    /**
484 1
     * {@inheritdoc}
485 1
     */
486 1
    public function dropColumn($tableName, $columnName)
487
    {
488
        $this->execute(
489
            sprintf(
490
                'ALTER TABLE %s DROP COLUMN %s',
491
                $this->quoteTableName($tableName),
492
                $this->quoteColumnName($columnName)
493
            )
494 9
        );
495
    }
496 9
497
    /**
498
     * Get an array of indexes from a particular table.
499
     *
500
     * @param string $tableName Table Name
501
     * @return array
502
     */
503 View Code Duplication
    protected function getIndexes($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
504
    {
505
        $indexes = [];
506
        $sql = "SELECT
507
            i.relname AS index_name,
508
            a.attname AS column_name
509
        FROM
510
            pg_class t,
511
            pg_class i,
512
            pg_index ix,
513
            pg_attribute a
514 9
        WHERE
515 9
            t.oid = ix.indrelid
516 9
            AND i.oid = ix.indexrelid
517 9
            AND a.attrelid = t.oid
518 9
            AND a.attnum = ANY(ix.indkey)
519 9
            AND t.relkind = 'r'
520 9
            AND t.relname = '$tableName'
521 9
        ORDER BY
522 9
            t.relname,
523
            i.relname;";
524
        $rows = $this->fetchAll($sql);
525
        foreach ($rows as $row) {
526
            if (!isset($indexes[$row['index_name']])) {
527
                $indexes[$row['index_name']] = ['columns' => []];
528 9
            }
529
            $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']);
530 9
        }
531 4
532 4
        return $indexes;
533 9
    }
534 9
535 9
    /**
536 9
     * {@inheritdoc}
537 9
     */
538 View Code Duplication
    public function hasIndex($tableName, $columns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
539 8
    {
540 8
        if (is_string($columns)) {
541
            $columns = [$columns];
542
        }
543
        $columns = array_map('strtolower', $columns);
544
        $indexes = $this->getIndexes($tableName);
545
        foreach ($indexes as $index) {
546 1
            if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) {
547
                return true;
548 1
            }
549 1
        }
550 1
551 1
        return false;
552
    }
553
554
    /**
555
     * {@inheritdoc}
556
     */
557 View Code Duplication
    public function hasIndexByName($tableName, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
558
    {
559
        $indexes = $this->getIndexes($tableName);
560 2
        foreach ($indexes as $name => $index) {
561
            if ($name === $indexName) {
562 2
                return true;
563 2
            }
564 2
        }
565
566
        return false;
567
    }
568
569 1
    /**
570
     * {@inheritdoc}
571 1
     */
572 1
    public function addIndex(Table $table, Index $index)
573 1
    {
574
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
575 1
        $this->execute($sql);
576 1
    }
577
578 1
    /**
579 1
     * {@inheritdoc}
580 1
     */
581 1
    public function dropIndex($tableName, $columns)
582 1
    {
583 1
        if (is_string($columns)) {
584 1
            $columns = [$columns]; // str to array
585 1
        }
586 1
587
        $indexes = $this->getIndexes($tableName);
588 1
        $columns = array_map('strtolower', $columns);
589
590
        foreach ($indexes as $indexName => $index) {
591
            $a = array_diff($columns, $index['columns']);
592
            if (empty($a)) {
593
                $this->execute(
594
                    sprintf(
595
                        'DROP INDEX IF EXISTS %s',
596 1
                        $this->quoteColumnName($indexName)
597
                    )
598 1
                );
599 1
600
                return;
601 1
            }
602 1
        }
603 1
    }
604
605
    /**
606
     * {@inheritdoc}
607
     */
608 3
    public function dropIndexByName($tableName, $indexName)
609
    {
610 3
        $sql = sprintf(
611 1
            'DROP INDEX IF EXISTS %s',
612 1
            $indexName
613 3
        );
614 3
        $this->execute($sql);
615
    }
616
617
    /**
618
     * {@inheritdoc}
619
     */
620 3 View Code Duplication
    public function hasForeignKey($tableName, $columns, $constraint = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
621 3
    {
622 3
        if (is_string($columns)) {
623 3
            $columns = [$columns]; // str to array
624
        }
625 1
        $foreignKeys = $this->getForeignKeys($tableName);
626 1
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
627
            if (isset($foreignKeys[$constraint])) {
628
                return !empty($foreignKeys[$constraint]);
629
            }
630
631
            return false;
632
        } else {
633
            foreach ($foreignKeys as $key) {
634
                $a = array_diff($columns, $key['columns']);
635
                if (empty($a)) {
636 3
                    return true;
637
                }
638 3
            }
639 3
640
            return false;
641
        }
642
    }
643
644
    /**
645
     * Get an array of foreign keys from a particular table.
646
     *
647
     * @param string $tableName Table Name
648
     * @return array
649
     */
650 3 View Code Duplication
    protected function getForeignKeys($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
651
    {
652 3
        $foreignKeys = [];
653 3
        $rows = $this->fetchAll(sprintf(
654 3
            "SELECT
655 3
                    tc.constraint_name,
656 3
                    tc.table_name, kcu.column_name,
657 3
                    ccu.table_name AS referenced_table_name,
658 3
                    ccu.column_name AS referenced_column_name
659 3
                FROM
660
                    information_schema.table_constraints AS tc
661
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
662
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
663
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
664
                ORDER BY kcu.position_in_unique_constraint",
665 2
            $tableName
666
        ));
667 2
        foreach ($rows as $row) {
668 2
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
669 2
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
670 2
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
671 2
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
672 2
        }
673 2
674
        return $foreignKeys;
675
    }
676
677
    /**
678 1
     * {@inheritdoc}
679
     */
680 1 View Code Duplication
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
681
    {
682
        $sql = sprintf(
683
            'ALTER TABLE %s ADD %s',
684 1
            $this->quoteTableName($table->getName()),
685 1
            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
686 1
        );
687 1
        $this->execute($sql);
688 1
    }
689
690 1
    /**
691 1
     * {@inheritdoc}
692 1
     */
693 1 View Code Duplication
    public function dropForeignKey($tableName, $columns, $constraint = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
694 1
    {
695
        if (is_string($columns)) {
696
            $columns = [$columns]; // str to array
697
        }
698
699
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
700
            $this->execute(
701 1
                sprintf(
702 1
                    'ALTER TABLE %s DROP CONSTRAINT %s',
703
                    $this->quoteTableName($tableName),
704 1
                    $constraint
705
                )
706 1
            );
707 1
        } else {
708 1
            foreach ($columns as $column) {
709 1
                $rows = $this->fetchAll(sprintf(
710
                    "SELECT CONSTRAINT_NAME
711 1
                      FROM information_schema.KEY_COLUMN_USAGE
712
                      WHERE TABLE_SCHEMA = CURRENT_SCHEMA()
713
                        AND TABLE_NAME IS NOT NULL
714
                        AND TABLE_NAME = '%s'
715
                        AND COLUMN_NAME = '%s'
716 68
                      ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
717
                    $tableName,
718
                    $column
719 68
                ));
720 14
721
                foreach ($rows as $row) {
722 1
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
723
                }
724 1
            }
725
        }
726 14
    }
727 68
728 68
    /**
729 68
     * {@inheritdoc}
730 68
     */
731 68
    public function getSqlType($type, $limit = null)
732 68
    {
733 68
        switch ($type) {
734 68
            case static::PHINX_TYPE_INTEGER:
735 68
                if ($limit && $limit == static::INT_SMALL) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
736 68
                    return [
737 68
                        'name' => 'smallint',
738 68
                        'limit' => static::INT_SMALL
739 2
                    ];
740 68
                }
741 68
742 68
                return ['name' => $type];
743
            case static::PHINX_TYPE_TEXT:
744 68
            case static::PHINX_TYPE_TIME:
745 68
            case static::PHINX_TYPE_DATE:
746 68
            case static::PHINX_TYPE_BOOLEAN:
747 1
            case static::PHINX_TYPE_JSON:
748 68
            case static::PHINX_TYPE_JSONB:
749 68
            case static::PHINX_TYPE_UUID:
750 68
            case static::PHINX_TYPE_CIDR:
751 15
            case static::PHINX_TYPE_INET:
752 15
            case static::PHINX_TYPE_MACADDR:
753 1
                return ['name' => $type];
754
            case static::PHINX_TYPE_DECIMAL:
755
                return ['name' => $type, 'precision' => 18, 'scale' => 0];
756
            case static::PHINX_TYPE_STRING:
757
                return ['name' => 'character varying', 'limit' => 255];
758 14
            case static::PHINX_TYPE_CHAR:
759
                return ['name' => 'character', 'limit' => 255];
760
            case static::PHINX_TYPE_BIG_INTEGER:
761 14
                return ['name' => 'bigint'];
762
            case static::PHINX_TYPE_FLOAT:
763
                return ['name' => 'real'];
764 14
            case static::PHINX_TYPE_DATETIME:
765
            case static::PHINX_TYPE_TIMESTAMP:
766
                return ['name' => 'timestamp'];
767 14
            case static::PHINX_TYPE_BLOB:
768
            case static::PHINX_TYPE_BINARY:
769
                return ['name' => 'bytea'];
770 14
            case static::PHINX_TYPE_INTERVAL:
771 14
                return ['name' => 'interval'];
772 13
            // Geospatial database types
773
            // Spatial storage in Postgres is done via the PostGIS extension,
774
            // which enables the use of the "geography" type in combination
775 1
            // with SRID 4326.
776 14
            case static::PHINX_TYPE_GEOMETRY:
777
                return ['name' => 'geography', 'type' => 'geometry', 'srid' => 4326];
778
            case static::PHINX_TYPE_POINT:
779
                return ['name' => 'geography', 'type' => 'point', 'srid' => 4326];
780
            case static::PHINX_TYPE_LINESTRING:
781
                return ['name' => 'geography', 'type' => 'linestring', 'srid' => 4326];
782
            case static::PHINX_TYPE_POLYGON:
783
                return ['name' => 'geography', 'type' => 'polygon', 'srid' => 4326];
784
            default:
785 10
                if ($this->isArrayType($type)) {
786
                    return ['name' => $type];
787
                }
788 10
                // Return array type
789 10
                throw new \RuntimeException('The type: "' . $type . '" is not supported');
790 6
        }
791 10
    }
792 10
793
    /**
794 10
     * Returns Phinx type by SQL type
795 2
     *
796 10
     * @param string $sqlType SQL type
797
     * @returns string Phinx type
798 10
     */
799
    public function getPhinxType($sqlType)
800 10
    {
801
        switch ($sqlType) {
802 1
            case 'character varying':
803
            case 'varchar':
804 1
                return static::PHINX_TYPE_STRING;
805 10
            case 'character':
806 10
            case 'char':
807 10
                return static::PHINX_TYPE_CHAR;
808 9
            case 'text':
809 5
                return static::PHINX_TYPE_TEXT;
810 5
            case 'json':
811 3
                return static::PHINX_TYPE_JSON;
812 4
            case 'jsonb':
813 4
                return static::PHINX_TYPE_JSONB;
814 2
            case 'smallint':
815 4
                return [
816 4
                    'name' => 'smallint',
817 2
                    'limit' => static::INT_SMALL
818 4
                ];
819 1
            case 'int':
820
            case 'int4':
821 4
            case 'integer':
822 4
                return static::PHINX_TYPE_INTEGER;
823 4
            case 'decimal':
824 4
            case 'numeric':
825 3
                return static::PHINX_TYPE_DECIMAL;
826 4
            case 'bigint':
827 2
            case 'int8':
828 4
                return static::PHINX_TYPE_BIG_INTEGER;
829 4
            case 'real':
830 4
            case 'float4':
831 4
                return static::PHINX_TYPE_FLOAT;
832 3
            case 'bytea':
833 3
                return static::PHINX_TYPE_BINARY;
834 3
            case 'interval':
835 3
                return static::PHINX_TYPE_INTERVAL;
836 1
            case 'time':
837 1
            case 'timetz':
838
            case 'time with time zone':
839
            case 'time without time zone':
840
                return static::PHINX_TYPE_TIME;
841
            case 'date':
842
                return static::PHINX_TYPE_DATE;
843
            case 'timestamp':
844
            case 'timestamptz':
845
            case 'timestamp with time zone':
846
            case 'timestamp without time zone':
847
                return static::PHINX_TYPE_DATETIME;
848
            case 'bool':
849
            case 'boolean':
850
                return static::PHINX_TYPE_BOOLEAN;
851
            case 'uuid':
852 1
                return static::PHINX_TYPE_UUID;
853
            case 'cidr':
854 1
                return static::PHINX_TYPE_CIDR;
855 1
            case 'inet':
856 1
                return static::PHINX_TYPE_INET;
857
            case 'macaddr':
858
                return static::PHINX_TYPE_MACADDR;
859
            default:
860
                throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported');
861 2
        }
862
    }
863 2
864 2
    /**
865 2
     * {@inheritdoc}
866
     */
867
    public function createDatabase($name, $options = [])
868
    {
869
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
870
        $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset));
871 1
    }
872
873 1
    /**
874 1
     * {@inheritdoc}
875 1
     */
876 1
    public function hasDatabase($databaseName)
877
    {
878
        $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName);
879
        $result = $this->fetchRow($sql);
880
881
        return $result['count'] > 0;
882
    }
883
884 68
    /**
885
     * {@inheritdoc}
886 68
     */
887 4
    public function dropDatabase($name)
888 68
    {
889 68
        $this->disconnect();
890 68
        $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name));
891 68
        $this->connect();
892
    }
893
894
    /**
895
     * Gets the PostgreSQL Column Definition for a Column object.
896
     *
897
     * @param \Phinx\Db\Table\Column $column Column
898
     * @return string
899
     */
900 68
    protected function getColumnSqlDefinition(Column $column)
901
    {
902 68
        $buffer = [];
903 68
        if ($column->isIdentity()) {
904 50
            $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL';
905 50
        } else {
906 68 View Code Duplication
            if ($column->getType() instanceof Literal) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
907 68
                $buffer[] = (string)$column->getType();
908
            } else {
909 68
                $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
910 1
                $buffer[] = strtoupper($sqlType['name']);
911 1
            }
912 1
            // integers cant have limits in postgres
913 1
            if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) {
914 1
                $buffer[] = sprintf(
915 68
                    '(%s, %s)',
916
                    $column->getPrecision() ?: $sqlType['precision'],
0 ignored issues
show
Bug introduced by
The variable $sqlType does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
917
                    $column->getScale() ?: $sqlType['scale']
918
                );
919
            } elseif (in_array($sqlType['name'], ['geography'])) {
920
                // geography type must be written with geometry type and srid, like this: geography(POLYGON,4326)
921
                $buffer[] = sprintf(
922 68
                    '(%s,%s)',
923 68
                    strtoupper($sqlType['type']),
924 68
                    $sqlType['srid']
925 68
                );
926 68
            } elseif (!in_array($sqlType['name'], ['integer', 'smallint', 'bigint'])) {
927
                if ($column->getLimit() || isset($sqlType['limit'])) {
928
                    $buffer[] = sprintf('(%s)', $column->getLimit() ?: $sqlType['limit']);
929 68
                }
930 68
            }
931 68
932 68
            $timeTypes = [
933 1
                'time',
934 1
                'timestamp',
935
            ];
936
            if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) {
937 68
                $buffer[] = strtoupper('with time zone');
938
            }
939 68
        }
940 68
941 68
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
942
        $buffer = implode(' ', $buffer);
943 68
944
        if (!is_null($column->getDefault())) {
945
            $buffer .= $this->getDefaultValueDefinition($column->getDefault());
946
        }
947
948
        return $buffer;
949
    }
950
951
    /**
952
     * Gets the PostgreSQL Column Comment Defininition for a column object.
953 6
     *
954
     * @param \Phinx\Db\Table\Column $column Column
955
     * @param string $tableName Table name
956 6
     * @return string
957 6
     */
958 6
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
959
    {
960 6
        // passing 'null' is to remove column comment
961 6
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0)
962 6
                 ? $this->getConnection()->quote($column->getComment())
963 6
                 : 'NULL';
964
965 6
        return sprintf(
966
            'COMMENT ON COLUMN %s.%s IS %s;',
967
            $this->quoteSchemaName($tableName),
968
            $this->quoteColumnName($column->getName()),
969
            $comment
970
        );
971
    }
972
973
    /**
974
     * Gets the PostgreSQL Index Definition for an Index object.
975 7
     *
976
     * @param \Phinx\Db\Table\Index  $index Index
977 7
     * @param string $tableName Table name
978 3
     * @return string
979 3
     */
980 5
    protected function getIndexSqlDefinition(Index $index, $tableName)
981 5
    {
982 View Code Duplication
        if (is_string($index->getName())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
983
            $indexName = $index->getName();
984 5
        } else {
985
            $columnNames = $index->getColumns();
986 7
            if (is_string($columnNames)) {
987 7
                $columnNames = [$columnNames];
988 7
            }
989 7
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
990 7
        }
991 7
        $def = sprintf(
992 7
            "CREATE %s INDEX %s ON %s (%s);",
993 7
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
994
            $indexName,
995
            $this->quoteTableName($tableName),
996
            implode(',', array_map([$this, 'quoteColumnName'], $index->getColumns()))
997
        );
998
999
        return $def;
1000
    }
1001
1002
    /**
1003 3
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1004
     *
1005 3
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1006 3
     * @param string     $tableName  Table name
1007 3
     * @return string
1008 3
     */
1009 View Code Duplication
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1010
    {
1011 3
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
1012
1013
        $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1014 3
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1015
        if ($foreignKey->getOnDelete()) {
1016
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1017
        }
1018
        if ($foreignKey->getOnUpdate()) {
1019
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1020 68
        }
1021
1022
        return $def;
1023 68
    }
1024 67
1025 67
    /**
1026
     * {@inheritdoc}
1027 68
     */
1028
    public function createSchemaTable()
1029 68
    {
1030 68
        // Create the public/custom schema if it doesn't already exist
1031
        if ($this->hasSchema($this->getSchemaName()) === false) {
1032
            $this->createSchema($this->getSchemaName());
1033
        }
1034
1035
        $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName()));
1036
1037
        parent::createSchemaTable();
1038 68
    }
1039
1040 68
    /**
1041 68
     * Creates the specified schema.
1042 68
     *
1043
     * @param  string $schemaName Schema Name
1044
     * @return void
1045
     */
1046
    public function createSchema($schemaName = 'public')
1047
    {
1048
        $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name"
1049
        $this->execute($sql);
1050 68
    }
1051
1052 68
    /**
1053
     * Checks to see if a schema exists.
1054
     *
1055 68
     * @param string $schemaName Schema Name
1056
     * @return bool
1057 68
     */
1058 68
    public function hasSchema($schemaName)
1059 68
    {
1060
        $sql = sprintf(
1061
            "SELECT count(*)
1062
             FROM pg_namespace
1063
             WHERE nspname = '%s'",
1064
            $schemaName
1065
        );
1066
        $result = $this->fetchRow($sql);
1067
1068 68
        return $result['count'] > 0;
1069
    }
1070 68
1071 68
    /**
1072 68
     * Drops the specified schema table.
1073
     *
1074
     * @param string $schemaName Schema name
1075
     * @return void
1076
     */
1077
    public function dropSchema($schemaName)
1078
    {
1079 68
        $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName));
1080
        $this->execute($sql);
1081 68
    }
1082 68
1083 68
    /**
1084 68
     * Drops all schemas.
1085
     *
1086
     * @return void
1087
     */
1088
    public function dropAllSchemas()
1089
    {
1090
        foreach ($this->getAllSchemas() as $schema) {
1091 68
            $this->dropSchema($schema);
1092
        }
1093
    }
1094
1095 68
    /**
1096 68
     * Returns schemas.
1097 68
     *
1098 68
     * @return array
1099 68
     */
1100 68
    public function getAllSchemas()
1101 68
    {
1102
        $sql = "SELECT schema_name
1103
                FROM information_schema.schemata
1104
                WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'";
1105
        $items = $this->fetchAll($sql);
1106
        $schemaNames = [];
1107 73
        foreach ($items as $item) {
1108
            $schemaNames[] = $item['schema_name'];
1109 73
        }
1110
1111
        return $schemaNames;
1112
    }
1113
1114
    /**
1115 73
     * {@inheritdoc}
1116
     */
1117
    public function getColumnTypes()
1118 73
    {
1119
        return array_merge(parent::getColumnTypes(), ['json', 'jsonb', 'cidr', 'inet', 'macaddr', 'interval']);
1120
    }
1121
1122
    /**
1123
     * {@inheritdoc}
1124
     */
1125
    public function isValidColumnType(Column $column)
1126
    {
1127 14
        // If not a standard column type, maybe it is array type?
1128
        return (parent::isValidColumnType($column) || $this->isArrayType($column->getType()));
0 ignored issues
show
Bug introduced by
It seems like $column->getType() targeting Phinx\Db\Table\Column::getType() can also be of type object<Phinx\Util\Literal>; however, Phinx\Db\Adapter\PostgresAdapter::isArrayType() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1129 14
    }
1130 1
1131
    /**
1132
     * Check if the given column is an array of a valid type.
1133 13
     *
1134 13
     * @param  string $columnType
1135
     * @return bool
1136
     */
1137
    protected function isArrayType($columnType)
1138
    {
1139
        if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) {
1140
            return false;
1141
        }
1142 68
1143
        $baseType = $matches[1];
1144 68
1145 68
        return in_array($baseType, $this->getColumnTypes());
1146
    }
1147
1148
    /**
1149
     * Gets the schema name.
1150
     *
1151 68
     * @return string
1152
     */
1153 68
    private function getSchemaName()
1154
    {
1155
        $options = $this->getOptions();
1156
1157
        return empty($options['schema']) ? 'public' : $options['schema'];
1158
    }
1159
1160
    /**
1161
     * {@inheritdoc}
1162
     */
1163
    public function castToBool($value)
1164
    {
1165
        return (bool)$value ? 'TRUE' : 'FALSE';
1166
    }
1167
}
1168