Completed
Pull Request — master (#1039)
by Tom
01:50
created

PostgresAdapter::getColumns()   C

Complexity

Conditions 10
Paths 65

Size

Total Lines 62
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 10

Importance

Changes 0
Metric Value
dl 0
loc 62
ccs 38
cts 38
cp 1
rs 6.4192
c 0
b 0
f 0
cc 10
eloc 39
nc 65
nop 1
crap 10

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * 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
                ), $exception->getCode(), $exception);
77
            }
78 68
79 68
            try {
80 68
                if (isset($options['schema'])) {
81
                    $db->exec('SET search_path TO ' . $options['schema']);
82
                }
83
            } catch (\PDOException $exception) {
84
                throw new \InvalidArgumentException(
85 68
                    sprintf('Schema does not exists: %s', $options['schema']),
86
                    $exception->getCode(),
87 68
                    $exception
88 68
                );
89
            }
90
91
            $this->setConnection($db);
92
        }
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function disconnect()
99
    {
100
        $this->connection = null;
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function hasTransactions()
107
    {
108
        return true;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function beginTransaction()
115
    {
116
        $this->execute('BEGIN');
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function commitTransaction()
123
    {
124
        $this->execute('COMMIT');
125
    }
126
127
    /**
128 68
     * {@inheritdoc}
129
     */
130 68
    public function rollbackTransaction()
131
    {
132
        $this->execute('ROLLBACK');
133
    }
134
135
    /**
136 68
     * Quotes a schema name for use in a query.
137
     *
138 68
     * @param string $schemaName Schema Name
139
     * @return string
140
     */
141
    public function quoteSchemaName($schemaName)
142
    {
143
        return $this->quoteColumnName($schemaName);
144 68
    }
145
146 68
    /**
147
     * {@inheritdoc}
148
     */
149
    public function quoteTableName($tableName)
150
    {
151
        return $this->quoteSchemaName($this->getSchemaName()) . '.' . $this->quoteColumnName($tableName);
152 68
    }
153
154 68
    /**
155 68
     * {@inheritdoc}
156
     */
157
    public function quoteColumnName($columnName)
158
    {
159 68
        return '"' . $columnName . '"';
160 68
    }
161 68
162 68
    /**
163 68
     * {@inheritdoc}
164
     */
165 68
    public function hasTable($tableName)
166
    {
167
        $result = $this->getConnection()->query(
168
            sprintf(
169
                'SELECT *
170
                FROM information_schema.tables
171 68
                WHERE table_schema = %s
172
                AND lower(table_name) = lower(%s)',
173 68
                $this->getConnection()->quote($this->getSchemaName()),
174
                $this->getConnection()->quote($tableName)
175
            )
176 68
        );
177 68
178 48
        return $result->rowCount() === 1;
179 48
    }
180 48
181 48
    /**
182
     * {@inheritdoc}
183 48
     */
184 48
    public function createTable(Table $table)
185 68
    {
186
        $options = $table->getOptions();
187 2
188 2
         // Add the default primary key
189 2
        $columns = $table->getPendingColumns();
190 2 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...
191
            $column = new Column();
192 2
            $column->setName('id')
193 2
                   ->setType('integer')
194 2
                   ->setIdentity(true);
195
196
            array_unshift($columns, $column);
197 68
            $options['primary_key'] = 'id';
198 68
        } elseif (isset($options['id']) && is_string($options['id'])) {
199
            // Handle id => "field_name" to support AUTO_INCREMENT
200 68
            $column = new Column();
201 68
            $column->setName($options['id'])
202 68
                   ->setType('integer')
203
                   ->setIdentity(true);
204
205 68
            array_unshift($columns, $column);
206 6
            $options['primary_key'] = $options['id'];
207 6
        }
208 68
209
        // TODO - process table options like collation etc
210
        $sql = 'CREATE TABLE ';
211 68
        $sql .= $this->quoteTableName($table->getName()) . ' (';
212 68
213 68
        $this->columnsWithComments = [];
214 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...
215 68
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
216 68
217
            // set column comments, if needed
218
            if ($column->getComment()) {
219 1
                $this->columnsWithComments[] = $column;
220 1
            }
221 1
        }
222 1
223 1
         // set the primary key(s)
224 1 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...
225 1
            $sql = rtrim($sql);
226 1
            $sql .= sprintf(' CONSTRAINT %s_pkey PRIMARY KEY (', $table->getName());
227 1
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
228 1
                $sql .= $this->quoteColumnName($options['primary_key']);
229 68
            } 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...
230 68
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
231 2
            }
232
            $sql .= ')';
233
        } else {
234
            $sql = rtrim($sql, ', '); // no primary keys
235 68
        }
236 68
237 1
        // set the foreign keys
238 1
        $foreignKeys = $table->getForeignKeys();
239 1
        if (!empty($foreignKeys)) {
240 1
            foreach ($foreignKeys as $foreignKey) {
241
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
242 68
            }
243
        }
244
245 68
        $sql .= ');';
246 6
247 6
        // process column comments
248 6
        if (!empty($this->columnsWithComments)) {
249 6
            foreach ($this->columnsWithComments as $column) {
250
                $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
251
            }
252
        }
253 68
254 68
        // set the indexes
255 5
        $indexes = $table->getIndexes();
256 5
        if (!empty($indexes)) {
257 5
            foreach ($indexes as $index) {
258 5
                $sql .= $this->getIndexSqlDefinition($index, $table->getName());
259
            }
260
        }
261 68
262
        // execute the sql
263
        $this->execute($sql);
264 68
265 1
        // process table comments
266 1
        if (isset($options['comment'])) {
267 1
            $sql = sprintf(
268 1
                'COMMENT ON TABLE %s IS %s',
269 1
                $this->quoteTableName($table->getName()),
270 1
                $this->getConnection()->quote($options['comment'])
271 1
            );
272 68
            $this->execute($sql);
273
        }
274
    }
275
276
    /**
277 1
     * {@inheritdoc}
278
     */
279 1
    public function renameTable($tableName, $newTableName)
280 1
    {
281 1
        $sql = sprintf(
282 1
            'ALTER TABLE %s RENAME TO %s',
283 1
            $this->quoteTableName($tableName),
284 1
            $this->quoteColumnName($newTableName)
285 1
        );
286
        $this->execute($sql);
287
    }
288
289
    /**
290 1
     * {@inheritdoc}
291
     */
292 1
    public function dropTable($tableName)
293 1
    {
294
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
295
    }
296
297
    /**
298 1
     * {@inheritdoc}
299
     */
300 1
    public function truncateTable($tableName)
301 1
    {
302 1
        $sql = sprintf(
303 1
            'TRUNCATE TABLE %s',
304
            $this->quoteTableName($tableName)
305 1
        );
306 1
307
        $this->execute($sql);
308
    }
309
310
    /**
311 9
     * {@inheritdoc}
312
     */
313 9
    public function getColumns($tableName)
314 9
    {
315
        $columns = [];
316
        $sql = sprintf(
317
            "SELECT column_name, data_type, udt_name, is_identity, is_nullable,
318 9
             column_default, character_maximum_length, numeric_precision, numeric_scale,
319
             datetime_precision
320 9
             FROM information_schema.columns
321 9
             WHERE table_name ='%s'",
322
            $tableName
323 9
        );
324 9
        $columnsInfo = $this->fetchAll($sql);
325 9
326 9
        foreach ($columnsInfo as $columnInfo) {
327 9
            $isUserDefined = $columnInfo['data_type'] === 'USER-DEFINED';
328 9
            if ($isUserDefined) {
329 9
                $columnType = Literal::from($columnInfo['udt_name']);
330 9
            } else {
331 9
                $columnType = $this->getPhinxType($columnInfo['data_type']);
332
            }
333 9
            // If the default value begins with a ' or looks like a function mark it as literal
334 1
            if (isset($columnInfo['column_default'][0]) && $columnInfo['column_default'][0] === "'") {
335 1
                if (preg_match('/^\'(.*)\'::[^:]+$/', $columnInfo['column_default'], $match)) {
336
                    // '' and \' are replaced with a single '
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...
337 9
                    $columnDefault = preg_replace('/[\'\\\\]\'/', "'", $match[1]);
338 5
                } else {
339 5
                    $columnDefault = Literal::from($columnInfo['column_default']);
340 9
                }
341 9
            } elseif (preg_match('/^\D[a-z_\d]*\(.*\)$/', $columnInfo['column_default'])) {
342 9
                $columnDefault = Literal::from($columnInfo['column_default']);
343
            } else {
344
                $columnDefault = $columnInfo['column_default'];
345
            }
346
347
            $column = new Column();
348 24
            $column->setName($columnInfo['column_name'])
349
                   ->setType($columnType)
350 24
                   ->setNull($columnInfo['is_nullable'] === 'YES')
351
                   ->setDefault($columnDefault)
352
                   ->setIdentity($columnInfo['is_identity'] === 'YES')
353 24
                   ->setScale($columnInfo['numeric_scale']);
354 24
355 24
            if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) {
356
                $column->setTimezone(true);
357 24
            }
358
359 24
            if (isset($columnInfo['character_maximum_length'])) {
360 24
                $column->setLimit($columnInfo['character_maximum_length']);
361
            }
362
363
            $phinxType = $this->getPhinxType($columnInfo['data_type']);
364
            if (in_array($phinxType, [static::PHINX_TYPE_TIME, static::PHINX_TYPE_DATETIME])) {
365
                $column->setPrecision($columnInfo['datetime_precision']);
366 18
            } else {
367
                $column->setPrecision($columnInfo['numeric_precision']);
368 18
            }
369 18
370 18
            $columns[] = $column;
371 18
        }
372 18
373 18
        return $columns;
374
    }
375 18
376 18
    /**
377
     * {@inheritdoc}
378
     */
379 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...
380
    {
381 3
        $sql = sprintf(
382
            "SELECT count(*)
383 3
            FROM information_schema.columns
384
            WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'",
385
            $this->getSchemaName(),
386 3
            $tableName,
387 3
            $columnName
388
        );
389 3
390 3
        $result = $this->fetchRow($sql);
391 3
392 1
        return $result['count'] > 0;
393
    }
394 2
395 2
    /**
396 2
     * {@inheritdoc}
397 2
     */
398 2 View Code Duplication
    public function addColumn(Table $table, Column $column)
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...
399 2
    {
400 2
        $sql = sprintf(
401 2
            'ALTER TABLE %s ADD %s %s;',
402 2
            $this->quoteTableName($table->getName()),
403
            $this->quoteColumnName($column->getName()),
404
            $this->getColumnSqlDefinition($column)
405
        );
406
407 5
        if ($column->getComment()) {
408
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
409
        }
410
411 5
        $this->execute($sql);
412 5
    }
413 5
414 5
    /**
415 5
     * {@inheritdoc}
416 5
     */
417
    public function renameColumn($tableName, $columnName, $newColumnName)
418 5
    {
419 5
        $sql = sprintf(
420
            "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists
421 5
             FROM information_schema.columns
422 5
             WHERE table_name ='%s' AND column_name = '%s'",
423
            $tableName,
424 5
            $columnName
425 5
        );
426 5
        $result = $this->fetchRow($sql);
427 5
        if (!(bool)$result['column_exists']) {
428 5
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
429 5
        }
430 2
        $this->execute(
431 2
            sprintf(
432 4
                'ALTER TABLE %s RENAME COLUMN %s TO %s',
433
                $this->quoteTableName($tableName),
434 5
                $this->quoteColumnName($columnName),
435 5
                $this->quoteColumnName($newColumnName)
436
            )
437 1
        );
438 1
    }
439 1
440 1
    /**
441 1
     * {@inheritdoc}
442 1
     */
443 1
    public function changeColumn($tableName, $columnName, Column $newColumn)
444 1
    {
445 1
        // TODO - is it possible to merge these 3 queries into less?
446
        // change data type
447 4
        $sql = sprintf(
448 4
            'ALTER TABLE %s ALTER COLUMN %s TYPE %s',
449 4
            $this->quoteTableName($tableName),
450 4
            $this->quoteColumnName($columnName),
451 4
            $this->getColumnSqlDefinition($newColumn)
452 4
        );
453 4
        //NULL and DEFAULT cannot be set while changing column type
454
        $sql = preg_replace('/ NOT NULL/', '', $sql);
455
        $sql = preg_replace('/ NULL/', '', $sql);
456 5
        //If it is set, DEFAULT is the last definition
457 1
        $sql = preg_replace('/DEFAULT .*/', '', $sql);
458 1
        $this->execute($sql);
459 1
        // process null
460 1
        $sql = sprintf(
461 1
            'ALTER TABLE %s ALTER COLUMN %s',
462 1
            $this->quoteTableName($tableName),
463 1
            $this->quoteColumnName($columnName)
464 1
        );
465 1
        if ($newColumn->isNull()) {
466
            $sql .= ' DROP NOT NULL';
467
        } else {
468 5
            $sql .= ' SET NOT NULL';
469 2
        }
470 2
        $this->execute($sql);
471 2
        if (!is_null($newColumn->getDefault())) {
472 5
            //change default
473
            $this->execute(
474
                sprintf(
475
                    'ALTER TABLE %s ALTER COLUMN %s SET %s',
476
                    $this->quoteTableName($tableName),
477 1
                    $this->quoteColumnName($columnName),
478
                    $this->getDefaultValueDefinition($newColumn->getDefault(), $newColumn->getType())
0 ignored issues
show
Bug introduced by
It seems like $newColumn->getType() targeting Phinx\Db\Table\Column::getType() can also be of type object<Phinx\Util\Literal>; however, Phinx\Db\Adapter\Postgre...efaultValueDefinition() 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...
479 1
                )
480 1
            );
481 1
        } else {
482 1
            //drop default
483 1
            $this->execute(
484 1
                sprintf(
485 1
                    'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
486 1
                    $this->quoteTableName($tableName),
487
                    $this->quoteColumnName($columnName)
488
                )
489
            );
490
        }
491
        // rename column
492
        if ($columnName !== $newColumn->getName()) {
493
            $this->execute(
494 9
                sprintf(
495
                    'ALTER TABLE %s RENAME COLUMN %s TO %s',
496 9
                    $this->quoteTableName($tableName),
497
                    $this->quoteColumnName($columnName),
498
                    $this->quoteColumnName($newColumn->getName())
499
                )
500
            );
501
        }
502
503
        // change column comment if needed
504
        if ($newColumn->getComment()) {
505
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
506
            $this->execute($sql);
507
        }
508
    }
509
510
    /**
511
     * {@inheritdoc}
512
     */
513
    public function dropColumn($tableName, $columnName)
514 9
    {
515 9
        $this->execute(
516 9
            sprintf(
517 9
                'ALTER TABLE %s DROP COLUMN %s',
518 9
                $this->quoteTableName($tableName),
519 9
                $this->quoteColumnName($columnName)
520 9
            )
521 9
        );
522 9
    }
523
524
    /**
525
     * Get an array of indexes from a particular table.
526
     *
527
     * @param string $tableName Table Name
528 9
     * @return array
529
     */
530 9 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...
531 4
    {
532 4
        $indexes = [];
533 9
        $sql = "SELECT
534 9
            i.relname AS index_name,
535 9
            a.attname AS column_name
536 9
        FROM
537 9
            pg_class t,
538
            pg_class i,
539 8
            pg_index ix,
540 8
            pg_attribute a
541
        WHERE
542
            t.oid = ix.indrelid
543
            AND i.oid = ix.indexrelid
544
            AND a.attrelid = t.oid
545
            AND a.attnum = ANY(ix.indkey)
546 1
            AND t.relkind = 'r'
547
            AND t.relname = '$tableName'
548 1
        ORDER BY
549 1
            t.relname,
550 1
            i.relname;";
551 1
        $rows = $this->fetchAll($sql);
552
        foreach ($rows as $row) {
553
            if (!isset($indexes[$row['index_name']])) {
554
                $indexes[$row['index_name']] = ['columns' => []];
555
            }
556
            $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']);
557
        }
558
559
        return $indexes;
560 2
    }
561
562 2
    /**
563 2
     * {@inheritdoc}
564 2
     */
565 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...
566
    {
567
        if (is_string($columns)) {
568
            $columns = [$columns];
569 1
        }
570
        $columns = array_map('strtolower', $columns);
571 1
        $indexes = $this->getIndexes($tableName);
572 1
        foreach ($indexes as $index) {
573 1
            if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) {
574
                return true;
575 1
            }
576 1
        }
577
578 1
        return false;
579 1
    }
580 1
581 1
    /**
582 1
     * {@inheritdoc}
583 1
     */
584 1 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...
585 1
    {
586 1
        $indexes = $this->getIndexes($tableName);
587
        foreach ($indexes as $name => $index) {
588 1
            if ($name === $indexName) {
589
                return true;
590
            }
591
        }
592
593
        return false;
594
    }
595
596 1
    /**
597
     * {@inheritdoc}
598 1
     */
599 1
    public function addIndex(Table $table, Index $index)
600
    {
601 1
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
602 1
        $this->execute($sql);
603 1
    }
604
605
    /**
606
     * {@inheritdoc}
607
     */
608 3
    public function dropIndex($tableName, $columns)
609
    {
610 3
        if (is_string($columns)) {
611 1
            $columns = [$columns]; // str to array
612 1
        }
613 3
614 3
        $indexes = $this->getIndexes($tableName);
615
        $columns = array_map('strtolower', $columns);
616
617
        foreach ($indexes as $indexName => $index) {
618
            $a = array_diff($columns, $index['columns']);
619
            if (empty($a)) {
620 3
                $this->execute(
621 3
                    sprintf(
622 3
                        'DROP INDEX IF EXISTS %s',
623 3
                        $this->quoteColumnName($indexName)
624
                    )
625 1
                );
626 1
627
                return;
628
            }
629
        }
630
    }
631
632
    /**
633
     * {@inheritdoc}
634
     */
635
    public function dropIndexByName($tableName, $indexName)
636 3
    {
637
        $sql = sprintf(
638 3
            'DROP INDEX IF EXISTS %s',
639 3
            $indexName
640
        );
641
        $this->execute($sql);
642
    }
643
644
    /**
645
     * {@inheritdoc}
646
     */
647 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...
648
    {
649
        if (is_string($columns)) {
650 3
            $columns = [$columns]; // str to array
651
        }
652 3
        $foreignKeys = $this->getForeignKeys($tableName);
653 3
        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...
654 3
            if (isset($foreignKeys[$constraint])) {
655 3
                return !empty($foreignKeys[$constraint]);
656 3
            }
657 3
658 3
            return false;
659 3
        } else {
660
            foreach ($foreignKeys as $key) {
661
                $a = array_diff($columns, $key['columns']);
662
                if (empty($a)) {
663
                    return true;
664
                }
665 2
            }
666
667 2
            return false;
668 2
        }
669 2
    }
670 2
671 2
    /**
672 2
     * Get an array of foreign keys from a particular table.
673 2
     *
674
     * @param string $tableName Table Name
675
     * @return array
676
     */
677 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...
678 1
    {
679
        $foreignKeys = [];
680 1
        $rows = $this->fetchAll(sprintf(
681
            "SELECT
682
                    tc.constraint_name,
683
                    tc.table_name, kcu.column_name,
684 1
                    ccu.table_name AS referenced_table_name,
685 1
                    ccu.column_name AS referenced_column_name
686 1
                FROM
687 1
                    information_schema.table_constraints AS tc
688 1
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
689
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
690 1
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
691 1
                ORDER BY kcu.position_in_unique_constraint",
692 1
            $tableName
693 1
        ));
694 1
        foreach ($rows as $row) {
695
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
696
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
697
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
698
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
699
        }
700
701 1
        return $foreignKeys;
702 1
    }
703
704 1
    /**
705
     * {@inheritdoc}
706 1
     */
707 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...
708 1
    {
709 1
        $sql = sprintf(
710
            'ALTER TABLE %s ADD %s',
711 1
            $this->quoteTableName($table->getName()),
712
            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
713
        );
714
        $this->execute($sql);
715
    }
716 68
717
    /**
718
     * {@inheritdoc}
719 68
     */
720 14 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...
721
    {
722 1
        if (is_string($columns)) {
723
            $columns = [$columns]; // str to array
724 1
        }
725
726 14
        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...
727 68
            $this->execute(
728 68
                sprintf(
729 68
                    'ALTER TABLE %s DROP CONSTRAINT %s',
730 68
                    $this->quoteTableName($tableName),
731 68
                    $constraint
732 68
                )
733 68
            );
734 68
        } else {
735 68
            foreach ($columns as $column) {
736 68
                $rows = $this->fetchAll(sprintf(
737 68
                    "SELECT CONSTRAINT_NAME
738 68
                      FROM information_schema.KEY_COLUMN_USAGE
739 2
                      WHERE TABLE_SCHEMA = CURRENT_SCHEMA()
740 68
                        AND TABLE_NAME IS NOT NULL
741 68
                        AND TABLE_NAME = '%s'
742 68
                        AND COLUMN_NAME = '%s'
743
                      ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
744 68
                    $tableName,
745 68
                    $column
746 68
                ));
747 1
748 68
                foreach ($rows as $row) {
749 68
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
750 68
                }
751 15
            }
752 15
        }
753 1
    }
754
755
    /**
756
     * {@inheritdoc}
757
     */
758 14
    public function getSqlType($type, $limit = null)
759
    {
760
        switch ($type) {
761 14
            case static::PHINX_TYPE_TEXT:
762
            case static::PHINX_TYPE_TIME:
763
            case static::PHINX_TYPE_DATE:
764 14
            case static::PHINX_TYPE_BOOLEAN:
765
            case static::PHINX_TYPE_JSON:
766
            case static::PHINX_TYPE_JSONB:
767 14
            case static::PHINX_TYPE_UUID:
768
            case static::PHINX_TYPE_CIDR:
769
            case static::PHINX_TYPE_INET:
770 14
            case static::PHINX_TYPE_MACADDR:
771 14
            case static::PHINX_TYPE_TIMESTAMP:
772 13
                return ['name' => $type];
773
            case static::PHINX_TYPE_INTEGER:
774
                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...
775 1
                    return [
776 14
                        'name' => 'smallint',
777
                        'limit' => static::INT_SMALL
778
                    ];
779
                }
780
781
                return ['name' => $type];
782
            case static::PHINX_TYPE_DECIMAL:
783
                return ['name' => $type, 'precision' => 18, 'scale' => 0];
784
            case static::PHINX_TYPE_STRING:
785 10
                return ['name' => 'character varying', 'limit' => 255];
786
            case static::PHINX_TYPE_CHAR:
787
                return ['name' => 'character', 'limit' => 255];
788 10
            case static::PHINX_TYPE_BIG_INTEGER:
789 10
                return ['name' => 'bigint'];
790 6
            case static::PHINX_TYPE_FLOAT:
791 10
                return ['name' => 'real'];
792 10
            case static::PHINX_TYPE_DATETIME:
793
                return ['name' => 'timestamp'];
794 10
            case static::PHINX_TYPE_BLOB:
795 2
            case static::PHINX_TYPE_BINARY:
796 10
                return ['name' => 'bytea'];
797
            case static::PHINX_TYPE_INTERVAL:
798 10
                return ['name' => 'interval'];
799
            // Geospatial database types
800 10
            // Spatial storage in Postgres is done via the PostGIS extension,
801
            // which enables the use of the "geography" type in combination
802 1
            // with SRID 4326.
803
            case static::PHINX_TYPE_GEOMETRY:
804 1
                return ['name' => 'geography', 'type' => 'geometry', 'srid' => 4326];
805 10
            case static::PHINX_TYPE_POINT:
806 10
                return ['name' => 'geography', 'type' => 'point', 'srid' => 4326];
807 10
            case static::PHINX_TYPE_LINESTRING:
808 9
                return ['name' => 'geography', 'type' => 'linestring', 'srid' => 4326];
809 5
            case static::PHINX_TYPE_POLYGON:
810 5
                return ['name' => 'geography', 'type' => 'polygon', 'srid' => 4326];
811 3
            default:
812 4
                if ($this->isArrayType($type)) {
813 4
                    return ['name' => $type];
814 2
                }
815 4
                // Return array type
816 4
                throw new \RuntimeException('The type: "' . $type . '" is not supported');
817 2
        }
818 4
    }
819 1
820
    /**
821 4
     * Returns Phinx type by SQL type
822 4
     *
823 4
     * @param string $sqlType SQL type
824 4
     * @returns string Phinx type
825 3
     */
826 4
    public function getPhinxType($sqlType)
827 2
    {
828 4
        switch ($sqlType) {
829 4
            case 'character varying':
830 4
            case 'varchar':
831 4
                return static::PHINX_TYPE_STRING;
832 3
            case 'character':
833 3
            case 'char':
834 3
                return static::PHINX_TYPE_CHAR;
835 3
            case 'text':
836 1
                return static::PHINX_TYPE_TEXT;
837 1
            case 'json':
838
                return static::PHINX_TYPE_JSON;
839
            case 'jsonb':
840
                return static::PHINX_TYPE_JSONB;
841
            case 'smallint':
842
                return [
843
                    'name' => 'smallint',
844
                    'limit' => static::INT_SMALL
845
                ];
846
            case 'int':
847
            case 'int4':
848
            case 'integer':
849
                return static::PHINX_TYPE_INTEGER;
850
            case 'decimal':
851
            case 'numeric':
852 1
                return static::PHINX_TYPE_DECIMAL;
853
            case 'bigint':
854 1
            case 'int8':
855 1
                return static::PHINX_TYPE_BIG_INTEGER;
856 1
            case 'real':
857
            case 'float4':
858
                return static::PHINX_TYPE_FLOAT;
859
            case 'bytea':
860
                return static::PHINX_TYPE_BINARY;
861 2
            case 'interval':
862
                return static::PHINX_TYPE_INTERVAL;
863 2
            case 'time':
864 2
            case 'timetz':
865 2
            case 'time with time zone':
866
            case 'time without time zone':
867
                return static::PHINX_TYPE_TIME;
868
            case 'date':
869
                return static::PHINX_TYPE_DATE;
870
            case 'timestamp':
871 1
            case 'timestamptz':
872
            case 'timestamp with time zone':
873 1
            case 'timestamp without time zone':
874 1
                return static::PHINX_TYPE_DATETIME;
875 1
            case 'bool':
876 1
            case 'boolean':
877
                return static::PHINX_TYPE_BOOLEAN;
878
            case 'uuid':
879
                return static::PHINX_TYPE_UUID;
880
            case 'cidr':
881
                return static::PHINX_TYPE_CIDR;
882
            case 'inet':
883
                return static::PHINX_TYPE_INET;
884 68
            case 'macaddr':
885
                return static::PHINX_TYPE_MACADDR;
886 68
            default:
887 4
                throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported');
888 68
        }
889 68
    }
890 68
891 68
    /**
892
     * {@inheritdoc}
893
     */
894
    public function createDatabase($name, $options = [])
895
    {
896
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
897
        $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset));
898
    }
899
900 68
    /**
901
     * {@inheritdoc}
902 68
     */
903 68
    public function hasDatabase($databaseName)
904 50
    {
905 50
        $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName);
906 68
        $result = $this->fetchRow($sql);
907 68
908
        return $result['count'] > 0;
909 68
    }
910 1
911 1
    /**
912 1
     * {@inheritdoc}
913 1
     */
914 1
    public function dropDatabase($name)
915 68
    {
916
        $this->disconnect();
917
        $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name));
918
        $this->connect();
919
    }
920
921
    /**
922 68
     * Get the defintion for a `DEFAULT` statement.
923 68
     *
924 68
     * @param mixed $default default value
925 68
     * @param string $columnType column type added
926 68
     * @return string
927
     */
928
    protected function getDefaultValueDefinition($default, $columnType)
929 68
    {
930 68
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
931 68
            $default = $this->getConnection()->quote($default);
932 68
        } elseif (is_bool($default)) {
933 1
            $default = $this->castToBool($default);
934 1
        } elseif ($columnType === static::PHINX_TYPE_BOOLEAN) {
935
            $default = $this->castToBool((bool)$default);
936
        }
937 68
938
        return isset($default) ? 'DEFAULT ' . $default : '';
939 68
    }
940 68
941 68
    /**
942
     * Gets the PostgreSQL Column Definition for a Column object.
943 68
     *
944
     * @param \Phinx\Db\Table\Column $column Column
945
     * @return string
946
     */
947
    protected function getColumnSqlDefinition(Column $column)
948
    {
949
        $buffer = [];
950
        if ($column->isIdentity()) {
951
            $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL';
952
        } elseif ($column->getType() instanceof Literal) {
953 6
            $buffer[] = (string)$column->getType();
954
        } else {
955
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
956 6
            $buffer[] = strtoupper($sqlType['name']);
957 6
958 6
            // integers cant have limits in postgres
959
            if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) {
960 6
                $buffer[] = sprintf(
961 6
                    '(%s, %s)',
962 6
                    $column->getPrecision() ?: $sqlType['precision'],
963 6
                    $column->getScale() ?: $sqlType['scale']
964
                );
965 6
            } elseif (in_array($sqlType['name'], ['geography'])) {
966
                // geography type must be written with geometry type and srid, like this: geography(POLYGON,4326)
967
                $buffer[] = sprintf(
968
                    '(%s,%s)',
969
                    strtoupper($sqlType['type']),
970
                    $sqlType['srid']
971
                );
972
            } elseif (!in_array($sqlType['name'], ['integer', 'smallint', 'bigint', 'boolean'])) {
973
                if ($column->getLimit() || isset($sqlType['limit'])) {
974
                    $buffer[] = sprintf('(%s)', $column->getLimit() ?: $sqlType['limit']);
975 7
                }
976
            }
977 7
978 3
            $timeTypes = [
979 3
                'time',
980 5
                'timestamp',
981 5
            ];
982
983
            if (in_array($sqlType['name'], $timeTypes) && is_numeric($column->getPrecision())) {
984 5
                $buffer[] = sprintf('(%s)', $column->getPrecision());
985
            }
986 7
987 7
            if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) {
988 7
                $buffer[] = strtoupper('with time zone');
989 7
            }
990 7
        }
991 7
992 7
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
993 7
994
        if (!is_null($column->getDefault())) {
995
            $buffer[] = $this->getDefaultValueDefinition($column->getDefault(), $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\Postgre...efaultValueDefinition() 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...
996
        }
997
998
        return implode(' ', $buffer);
999
    }
1000
1001
    /**
1002
     * Gets the PostgreSQL Column Comment Defininition for a column object.
1003 3
     *
1004
     * @param \Phinx\Db\Table\Column $column Column
1005 3
     * @param string $tableName Table name
1006 3
     * @return string
1007 3
     */
1008 3
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
1009
    {
1010
        // passing 'null' is to remove column comment
1011 3
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0)
1012
                 ? $this->getConnection()->quote($column->getComment())
1013
                 : 'NULL';
1014 3
1015
        return sprintf(
1016
            'COMMENT ON COLUMN %s.%s IS %s;',
1017
            $this->quoteSchemaName($tableName),
1018
            $this->quoteColumnName($column->getName()),
1019
            $comment
1020 68
        );
1021
    }
1022
1023 68
    /**
1024 67
     * Gets the PostgreSQL Index Definition for an Index object.
1025 67
     *
1026
     * @param \Phinx\Db\Table\Index  $index Index
1027 68
     * @param string $tableName Table name
1028
     * @return string
1029 68
     */
1030 68
    protected function getIndexSqlDefinition(Index $index, $tableName)
1031
    {
1032 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...
1033
            $indexName = $index->getName();
1034
        } else {
1035
            $columnNames = $index->getColumns();
1036
            if (is_string($columnNames)) {
1037
                $columnNames = [$columnNames];
1038 68
            }
1039
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
1040 68
        }
1041 68
        $def = sprintf(
1042 68
            "CREATE %s INDEX %s ON %s (%s);",
1043
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
1044
            $indexName,
1045
            $this->quoteTableName($tableName),
1046
            implode(',', array_map([$this, 'quoteColumnName'], $index->getColumns()))
1047
        );
1048
1049
        return $def;
1050 68
    }
1051
1052 68
    /**
1053
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1054
     *
1055 68
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1056
     * @param string     $tableName  Table name
1057 68
     * @return string
1058 68
     */
1059 68 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...
1060
    {
1061
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
1062
1063
        $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1064
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1065
        if ($foreignKey->getOnDelete()) {
1066
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1067
        }
1068 68
        if ($foreignKey->getOnUpdate()) {
1069
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1070 68
        }
1071 68
1072 68
        return $def;
1073
    }
1074
1075
    /**
1076
     * {@inheritdoc}
1077
     */
1078
    public function createSchemaTable()
1079 68
    {
1080
        // Create the public/custom schema if it doesn't already exist
1081 68
        if ($this->hasSchema($this->getSchemaName()) === false) {
1082 68
            $this->createSchema($this->getSchemaName());
1083 68
        }
1084 68
1085
        $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName()));
1086
1087
        parent::createSchemaTable();
1088
    }
1089
1090
    /**
1091 68
     * Creates the specified schema.
1092
     *
1093
     * @param  string $schemaName Schema Name
1094
     * @return void
1095 68
     */
1096 68
    public function createSchema($schemaName = 'public')
1097 68
    {
1098 68
        $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name"
1099 68
        $this->execute($sql);
1100 68
    }
1101 68
1102
    /**
1103
     * Checks to see if a schema exists.
1104
     *
1105
     * @param string $schemaName Schema Name
1106
     * @return bool
1107 73
     */
1108
    public function hasSchema($schemaName)
1109 73
    {
1110
        $sql = sprintf(
1111
            "SELECT count(*)
1112
             FROM pg_namespace
1113
             WHERE nspname = '%s'",
1114
            $schemaName
1115 73
        );
1116
        $result = $this->fetchRow($sql);
1117
1118 73
        return $result['count'] > 0;
1119
    }
1120
1121
    /**
1122
     * Drops the specified schema table.
1123
     *
1124
     * @param string $schemaName Schema name
1125
     * @return void
1126
     */
1127 14
    public function dropSchema($schemaName)
1128
    {
1129 14
        $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName));
1130 1
        $this->execute($sql);
1131
    }
1132
1133 13
    /**
1134 13
     * Drops all schemas.
1135
     *
1136
     * @return void
1137
     */
1138
    public function dropAllSchemas()
1139
    {
1140
        foreach ($this->getAllSchemas() as $schema) {
1141
            $this->dropSchema($schema);
1142 68
        }
1143
    }
1144 68
1145 68
    /**
1146
     * Returns schemas.
1147
     *
1148
     * @return array
1149
     */
1150
    public function getAllSchemas()
1151 68
    {
1152
        $sql = "SELECT schema_name
1153 68
                FROM information_schema.schemata
1154
                WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'";
1155
        $items = $this->fetchAll($sql);
1156
        $schemaNames = [];
1157
        foreach ($items as $item) {
1158
            $schemaNames[] = $item['schema_name'];
1159
        }
1160
1161
        return $schemaNames;
1162
    }
1163
1164
    /**
1165
     * {@inheritdoc}
1166
     */
1167
    public function getColumnTypes()
1168
    {
1169
        return array_merge(parent::getColumnTypes(), ['json', 'jsonb', 'cidr', 'inet', 'macaddr', 'interval']);
1170
    }
1171
1172
    /**
1173
     * {@inheritdoc}
1174
     */
1175
    public function isValidColumnType(Column $column)
1176
    {
1177
        // If not a standard column type, maybe it is array type?
1178
        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...
1179
    }
1180
1181
    /**
1182
     * Check if the given column is an array of a valid type.
1183
     *
1184
     * @param  string $columnType
1185
     * @return bool
1186
     */
1187
    protected function isArrayType($columnType)
1188
    {
1189
        if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) {
1190
            return false;
1191
        }
1192
1193
        $baseType = $matches[1];
1194
1195
        return in_array($baseType, $this->getColumnTypes());
1196
    }
1197
1198
    /**
1199
     * Gets the schema name.
1200
     *
1201
     * @return string
1202
     */
1203
    private function getSchemaName()
1204
    {
1205
        $options = $this->getOptions();
1206
1207
        return empty($options['schema']) ? 'public' : $options['schema'];
1208
    }
1209
1210
    /**
1211
     * {@inheritdoc}
1212
     */
1213
    public function castToBool($value)
1214
    {
1215
        return (bool)$value ? 'TRUE' : 'FALSE';
1216
    }
1217
}
1218