Completed
Push — master ( 152a5f...57e856 )
by José
11s
created

PostgresAdapter::getChangeColumnInstructions()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 63
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 63
rs 8.6498
c 0
b 0
f 0
ccs 36
cts 36
cp 1
cc 5
eloc 36
nc 16
nop 3
crap 5

How to fix   Long Method   

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