Completed
Pull Request — master (#1193)
by Dmitriy
02:06
created

PostgresAdapter::renameColumn()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 17
cts 17
cp 1
rs 8.9713
c 0
b 0
f 0
cc 2
eloc 16
nc 2
nop 3
crap 2
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
36
class PostgresAdapter extends PdoAdapter implements AdapterInterface
37
{
38
    const INT_SMALL = 65535;
39
40
    /**
41
     * Columns with comments
42
     *
43
     * @var array
44
     */
45
    protected $columnsWithComments = [];
46
47
    /**
48
     * {@inheritdoc}
49
     */
50 68
    public function connect()
51
    {
52 68
        if ($this->connection === null) {
53 68
            if (!class_exists('PDO') || !in_array('pgsql', \PDO::getAvailableDrivers(), true)) {
54
                // @codeCoverageIgnoreStart
55
                throw new \RuntimeException('You need to enable the PDO_Pgsql extension for Phinx to run properly.');
56
                // @codeCoverageIgnoreEnd
57
            }
58
59 68
            $db = null;
60 68
            $options = $this->getOptions();
61
62
            // if port is specified use it, otherwise use the PostgreSQL default
63 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...
64 68
                $dsn = 'pgsql:host=' . $options['host'] . ';port=' . $options['port'] . ';dbname=' . $options['name'];
65 68
            } else {
66 1
                $dsn = 'pgsql:host=' . $options['host'] . ';dbname=' . $options['name'];
67
            }
68
69
            try {
70 68
                $db = new \PDO($dsn, $options['user'], $options['pass'], [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
71 68
            } catch (\PDOException $exception) {
72 1
                throw new \InvalidArgumentException(sprintf(
73 1
                    'There was a problem connecting to the database: %s',
74 1
                    $exception->getMessage()
75 1
                ));
76
            }
77
78 68
            $this->setConnection($db);
79 68
        }
80 68
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 68
    public function disconnect()
86
    {
87 68
        $this->connection = null;
88 68
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function hasTransactions()
94
    {
95
        return true;
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function beginTransaction()
102
    {
103
        $this->execute('BEGIN');
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function commitTransaction()
110
    {
111
        $this->execute('COMMIT');
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function rollbackTransaction()
118
    {
119
        $this->execute('ROLLBACK');
120
    }
121
122
    /**
123
     * Quotes a schema name for use in a query.
124
     *
125
     * @param string $schemaName Schema Name
126
     * @return string
127
     */
128 68
    public function quoteSchemaName($schemaName)
129
    {
130 68
        return $this->quoteColumnName($schemaName);
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 68
    public function quoteTableName($tableName)
137
    {
138 68
        $parts = $this->getSchemaName($tableName);
139
140
        return $this->quoteSchemaName($parts['schema']) . '.' . $this->quoteColumnName($parts['table']);
141
    }
142
143
    /**
144 68
     * {@inheritdoc}
145
     */
146 68
    public function quoteColumnName($columnName)
147
    {
148
        return '"' . $columnName . '"';
149
    }
150
151
    /**
152 68
     * {@inheritdoc}
153
     */
154 68
    public function hasTable($tableName)
155 68
    {
156
        $parts = $this->getSchemaName($tableName);
157
        $result = $this->getConnection()->query(
158
            sprintf(
159 68
                'SELECT *
160 68
                FROM information_schema.tables
161 68
                WHERE table_schema = %s
162 68
                AND table_name = %s',
163 68
                $this->getConnection()->quote($parts['schema']),
164
                $this->getConnection()->quote($parts['table'])
165 68
            )
166
        );
167
168
        return $result->rowCount() === 1;
169
    }
170
171 68
    /**
172
     * {@inheritdoc}
173 68
     */
174
    public function createTable(Table $table)
175
    {
176 68
        $options = $table->getOptions();
177 68
        $parts = $this->getSchemaName($table->getName());
178 48
179 48
         // Add the default primary key
180 48
        $columns = $table->getPendingColumns();
181 48 View Code Duplication
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
182
            $column = new Column();
183 48
            $column->setName('id')
184 48
                   ->setType('integer')
185 68
                   ->setIdentity(true);
186
187 2
            array_unshift($columns, $column);
188 2
            $options['primary_key'] = 'id';
189 2
        } elseif (isset($options['id']) && is_string($options['id'])) {
190 2
            // Handle id => "field_name" to support AUTO_INCREMENT
191
            $column = new Column();
192 2
            $column->setName($options['id'])
193 2
                   ->setType('integer')
194 2
                   ->setIdentity(true);
195
196
            array_unshift($columns, $column);
197 68
            $options['primary_key'] = $options['id'];
198 68
        }
199
200 68
        // TODO - process table options like collation etc
201 68
        $sql = 'CREATE TABLE ';
202 68
        $sql .= $this->quoteTableName($table->getName()) . ' (';
203
204
        $this->columnsWithComments = [];
205 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...
206 6
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
207 6
208 68
            // set column comments, if needed
209
            if ($column->getComment()) {
210
                $this->columnsWithComments[] = $column;
211 68
            }
212 68
        }
213 68
214 68
         // set the primary key(s)
215 68
        if (isset($options['primary_key'])) {
216 68
            $sql = rtrim($sql);
217
            $sql .= sprintf(' CONSTRAINT %s PRIMARY KEY (', $this->quoteColumnName($parts['table'] . '_pkey'));
218 View Code Duplication
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
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...
219 1
                $sql .= $this->quoteColumnName($options['primary_key']);
220 1
            } 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...
221 1
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
222 1
            }
223 1
            $sql .= ')';
224 1
        } else {
225 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
226 1
        }
227 1
228 1
        // set the foreign keys
229 68
        $foreignKeys = $table->getForeignKeys();
230 68
        if (!empty($foreignKeys)) {
231 2
            foreach ($foreignKeys as $foreignKey) {
232
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
233
            }
234
        }
235 68
236 68
        $sql .= ');';
237 1
238 1
        // process column comments
239 1
        if (!empty($this->columnsWithComments)) {
240 1
            foreach ($this->columnsWithComments as $column) {
241
                $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
242 68
            }
243
        }
244
245 68
        // set the indexes
246 6
        $indexes = $table->getIndexes();
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
    public function renameTable($tableName, $newTableName)
271 1
    {
272 68
        $sql = sprintf(
273
            'ALTER TABLE %s RENAME TO %s',
274
            $this->quoteTableName($tableName),
275
            $this->quoteColumnName($newTableName)
276
        );
277 1
        $this->execute($sql);
278
    }
279 1
280 1
    /**
281 1
     * {@inheritdoc}
282 1
     */
283 1
    public function dropTable($tableName)
284 1
    {
285 1
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
286
    }
287
288
    /**
289
     * {@inheritdoc}
290 1
     */
291
    public function truncateTable($tableName)
292 1
    {
293 1
        $sql = sprintf(
294
            'TRUNCATE TABLE %s',
295
            $this->quoteTableName($tableName)
296
        );
297
298 1
        $this->execute($sql);
299
    }
300 1
301 1
    /**
302 1
     * {@inheritdoc}
303 1
     */
304
    public function getColumns($tableName)
305 1
    {
306 1
        $parts = $this->getSchemaName($tableName);
307
        $columns = [];
308
        $sql = sprintf(
309
            "SELECT column_name, data_type, is_identity, is_nullable,
310
             column_default, character_maximum_length, numeric_precision, numeric_scale
311 9
             FROM information_schema.columns
312
             WHERE table_schema = %s AND table_name = %s",
313 9
            $this->getConnection()->quote($parts['schema']),
314 9
            $this->getConnection()->quote($parts['table'])
315
        );
316
        $columnsInfo = $this->fetchAll($sql);
317
318 9
        foreach ($columnsInfo as $columnInfo) {
319
            $column = new Column();
320 9
            $column->setName($columnInfo['column_name'])
321 9
                   ->setType($this->getPhinxType($columnInfo['data_type']))
0 ignored issues
show
Bug introduced by
It seems like $this->getPhinxType($columnInfo['data_type']) targeting Phinx\Db\Adapter\PostgresAdapter::getPhinxType() can also be of type array<string,string|inte...ng","limit":"integer"}>; however, Phinx\Db\Table\Column::setType() 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...
322
                   ->setNull($columnInfo['is_nullable'] === 'YES')
323 9
                   ->setDefault($columnInfo['column_default'])
324 9
                   ->setIdentity($columnInfo['is_identity'] === 'YES')
325 9
                   ->setPrecision($columnInfo['numeric_precision'])
326 9
                   ->setScale($columnInfo['numeric_scale']);
327 9
328 9
            if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) {
329 9
                $column->setTimezone(true);
330 9
            }
331 9
332
            if (isset($columnInfo['character_maximum_length'])) {
333 9
                $column->setLimit($columnInfo['character_maximum_length']);
334 1
            }
335 1
            $columns[] = $column;
336
        }
337 9
338 5
        return $columns;
339 5
    }
340 9
341 9
    /**
342 9
     * {@inheritdoc}
343
     */
344
    public function hasColumn($tableName, $columnName)
345
    {
346
        $parts = $this->getSchemaName($tableName);
347
        $sql = sprintf(
348 24
            "SELECT count(*)
349
            FROM information_schema.columns
350 24
            WHERE table_schema = %s AND table_name = %s AND column_name = %s",
351
            $this->getConnection()->quote($parts['schema']),
352
            $this->getConnection()->quote($parts['table']),
353 24
            $this->getConnection()->quote($columnName)
354 24
        );
355 24
356
        $result = $this->fetchRow($sql);
357 24
358
        return $result['count'] > 0;
359 24
    }
360 24
361
    /**
362
     * {@inheritdoc}
363
     */
364 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...
365
    {
366 18
        $sql = sprintf(
367
            'ALTER TABLE %s ADD %s %s',
368 18
            $this->quoteTableName($table->getName()),
369 18
            $this->quoteColumnName($column->getName()),
370 18
            $this->getColumnSqlDefinition($column)
371 18
        );
372 18
373 18
        $this->execute($sql);
374
    }
375 18
376 18
    /**
377
     * {@inheritdoc}
378
     */
379
    public function renameColumn($tableName, $columnName, $newColumnName)
380
    {
381 3
        $parts = $this->getSchemaName($tableName);
382
        $sql = sprintf(
383 3
            "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists
384
             FROM information_schema.columns
385
             WHERE table_schema = %s AND table_name = %s AND column_name = %s",
386 3
            $this->getConnection()->quote($parts['schema']),
387 3
            $this->getConnection()->quote($parts['table']),
388
            $this->getConnection()->quote($columnName)
389 3
        );
390 3
        $result = $this->fetchRow($sql);
391 3
        if (!(bool)$result['column_exists']) {
392 1
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
393
        }
394 2
        $this->execute(
395 2
            sprintf(
396 2
                'ALTER TABLE %s RENAME COLUMN %s TO %s',
397 2
                $this->quoteTableName($tableName),
398 2
                $this->quoteColumnName($columnName),
399 2
                $this->quoteColumnName($newColumnName)
400 2
            )
401 2
        );
402 2
    }
403
404
    /**
405
     * {@inheritdoc}
406
     */
407 5
    public function changeColumn($tableName, $columnName, Column $newColumn)
408
    {
409
        // TODO - is it possible to merge these 3 queries into less?
410
        // change data type
411 5
        $sql = sprintf(
412 5
            'ALTER TABLE %s ALTER COLUMN %s TYPE %s',
413 5
            $this->quoteTableName($tableName),
414 5
            $this->quoteColumnName($columnName),
415 5
            $this->getColumnSqlDefinition($newColumn)
416 5
        );
417
        //NULL and DEFAULT cannot be set while changing column type
418 5
        $sql = preg_replace('/ NOT NULL/', '', $sql);
419 5
        $sql = preg_replace('/ NULL/', '', $sql);
420
        //If it is set, DEFAULT is the last definition
421 5
        $sql = preg_replace('/DEFAULT .*/', '', $sql);
422 5
        $this->execute($sql);
423
        // process null
424 5
        $sql = sprintf(
425 5
            'ALTER TABLE %s ALTER COLUMN %s',
426 5
            $this->quoteTableName($tableName),
427 5
            $this->quoteColumnName($columnName)
428 5
        );
429 5
        if ($newColumn->isNull()) {
430 2
            $sql .= ' DROP NOT NULL';
431 2
        } else {
432 4
            $sql .= ' SET NOT NULL';
433
        }
434 5
        $this->execute($sql);
435 5
        if (!is_null($newColumn->getDefault())) {
436
            //change default
437 1
            $this->execute(
438 1
                sprintf(
439 1
                    'ALTER TABLE %s ALTER COLUMN %s SET %s',
440 1
                    $this->quoteTableName($tableName),
441 1
                    $this->quoteColumnName($columnName),
442 1
                    $this->getDefaultValueDefinition($newColumn->getDefault())
443 1
                )
444 1
            );
445 1
        } else {
446
            //drop default
447 4
            $this->execute(
448 4
                sprintf(
449 4
                    'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
450 4
                    $this->quoteTableName($tableName),
451 4
                    $this->quoteColumnName($columnName)
452 4
                )
453 4
            );
454
        }
455
        // rename column
456 5
        if ($columnName !== $newColumn->getName()) {
457 1
            $this->execute(
458 1
                sprintf(
459 1
                    'ALTER TABLE %s RENAME COLUMN %s TO %s',
460 1
                    $this->quoteTableName($tableName),
461 1
                    $this->quoteColumnName($columnName),
462 1
                    $this->quoteColumnName($newColumn->getName())
463 1
                )
464 1
            );
465 1
        }
466
467
        // change column comment if needed
468 5
        if ($newColumn->getComment()) {
469 2
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
470 2
            $this->execute($sql);
471 2
        }
472 5
    }
473
474
    /**
475
     * {@inheritdoc}
476
     */
477 1
    public function dropColumn($tableName, $columnName)
478
    {
479 1
        $this->execute(
480 1
            sprintf(
481 1
                'ALTER TABLE %s DROP COLUMN %s',
482 1
                $this->quoteTableName($tableName),
483 1
                $this->quoteColumnName($columnName)
484 1
            )
485 1
        );
486 1
    }
487
488
    /**
489
     * Get an array of indexes from a particular table.
490
     *
491
     * @param string $tableName Table Name
492
     * @return array
493
     */
494 9
    protected function getIndexes($tableName)
495
    {
496 9
        $parts = $this->getSchemaName($tableName);
497
498
        $indexes = [];
499
        $sql = sprintf(
500
            "SELECT
501
                i.relname AS index_name,
502
                a.attname AS column_name
503
            FROM
504
                pg_class t,
505
                pg_class i,
506
                pg_index ix,
507
                pg_attribute a,
508
                pg_namespace nsp
509
            WHERE
510
                t.oid = ix.indrelid
511
                AND i.oid = ix.indexrelid
512
                AND a.attrelid = t.oid
513
                AND a.attnum = ANY(ix.indkey)
514 9
                AND t.relnamespace = nsp.oid
515 9
                AND nsp.nspname = %s
516 9
                AND t.relkind = 'r'
517 9
                AND t.relname = %s
518 9
            ORDER BY
519 9
                t.relname,
520 9
                i.relname",
521 9
            $this->getConnection()->quote($parts['schema']),
522 9
            $this->getConnection()->quote($parts['table'])
523
        );
524
        $rows = $this->fetchAll($sql);
525 View Code Duplication
        foreach ($rows as $row) {
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...
526
            if (!isset($indexes[$row['index_name']])) {
527
                $indexes[$row['index_name']] = ['columns' => []];
528 9
            }
529
            $indexes[$row['index_name']]['columns'][] = $row['column_name'];
530 9
        }
531 4
532 4
        return $indexes;
533 9
    }
534 9
535 9
    /**
536 9
     * {@inheritdoc}
537 9
     */
538 View Code Duplication
    public function hasIndex($tableName, $columns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

Loading history...
622 3
    {
623 3
        if (is_string($columns)) {
624
            $columns = [$columns]; // str to array
625 1
        }
626 1
        $foreignKeys = $this->getForeignKeys($tableName);
627
        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...
628
            if (isset($foreignKeys[$constraint])) {
629
                return !empty($foreignKeys[$constraint]);
630
            }
631
632
            return false;
633
        } else {
634
            foreach ($foreignKeys as $key) {
635
                $a = array_diff($columns, $key['columns']);
636 3
                if (empty($a)) {
637
                    return true;
638 3
                }
639 3
            }
640
641
            return false;
642
        }
643
    }
644
645
    /**
646
     * Get an array of foreign keys from a particular table.
647
     *
648
     * @param string $tableName Table Name
649
     * @return array
650 3
     */
651
    protected function getForeignKeys($tableName)
652 3
    {
653 3
        $parts = $this->getSchemaName($tableName);
654 3
        $foreignKeys = [];
655 3
        $rows = $this->fetchAll(sprintf(
656 3
            "SELECT
657 3
                    tc.constraint_name,
658 3
                    tc.table_name, kcu.column_name,
659 3
                    ccu.table_name AS referenced_table_name,
660
                    ccu.column_name AS referenced_column_name
661
                FROM
662
                    information_schema.table_constraints AS tc
663
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
664
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
665 2
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_schema = %s AND tc.table_name = %s
666
                ORDER BY kcu.position_in_unique_constraint",
667 2
            $this->getConnection()->quote($parts['schema']),
668 2
            $this->getConnection()->quote($parts['table'])
669 2
        ));
670 2
        foreach ($rows as $row) {
671 2
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
672 2
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
673 2
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
674
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
675
        }
676
677
        return $foreignKeys;
678 1
    }
679
680 1
    /**
681
     * {@inheritdoc}
682
     */
683 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...
684 1
    {
685 1
        $sql = sprintf(
686 1
            'ALTER TABLE %s ADD %s',
687 1
            $this->quoteTableName($table->getName()),
688 1
            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
689
        );
690 1
        $this->execute($sql);
691 1
    }
692 1
693 1
    /**
694 1
     * {@inheritdoc}
695
     */
696
    public function dropForeignKey($tableName, $columns, $constraint = null)
697
    {
698
        if (is_string($columns)) {
699
            $columns = [$columns]; // str to array
700
        }
701 1
702 1
        $parts = $this->getSchemaName($tableName);
703
704 1
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

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

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
742 68
                    return [
743
                        'name' => 'smallint',
744 68
                        'limit' => static::INT_SMALL
745 68
                    ];
746 68
                }
747 1
748 68
                return ['name' => $type];
749 68
            case static::PHINX_TYPE_TEXT:
750 68
            case static::PHINX_TYPE_TIME:
751 15
            case static::PHINX_TYPE_DATE:
752 15
            case static::PHINX_TYPE_BOOLEAN:
753 1
            case static::PHINX_TYPE_JSON:
754
            case static::PHINX_TYPE_JSONB:
755
            case static::PHINX_TYPE_UUID:
756
            case static::PHINX_TYPE_CIDR:
757
            case static::PHINX_TYPE_INET:
758 14
            case static::PHINX_TYPE_MACADDR:
759
                return ['name' => $type];
760
            case static::PHINX_TYPE_DECIMAL:
761 14
                return ['name' => $type, 'precision' => 18, 'scale' => 0];
762
            case static::PHINX_TYPE_STRING:
763
                return ['name' => 'character varying', 'limit' => 255];
764 14
            case static::PHINX_TYPE_CHAR:
765
                return ['name' => 'character', 'limit' => 255];
766
            case static::PHINX_TYPE_BIG_INTEGER:
767 14
                return ['name' => 'bigint'];
768
            case static::PHINX_TYPE_FLOAT:
769
                return ['name' => 'real'];
770 14
            case static::PHINX_TYPE_DATETIME:
771 14
            case static::PHINX_TYPE_TIMESTAMP:
772 13
                return ['name' => 'timestamp'];
773
            case static::PHINX_TYPE_BLOB:
774
            case static::PHINX_TYPE_BINARY:
775 1
                return ['name' => 'bytea'];
776 14
            // Geospatial database types
777
            // Spatial storage in Postgres is done via the PostGIS extension,
778
            // which enables the use of the "geography" type in combination
779
            // with SRID 4326.
780
            case static::PHINX_TYPE_GEOMETRY:
781
                return ['name' => 'geography', 'type' => 'geometry', 'srid' => 4326];
782
            case static::PHINX_TYPE_POINT:
783
                return ['name' => 'geography', 'type' => 'point', 'srid' => 4326];
784
            case static::PHINX_TYPE_LINESTRING:
785 10
                return ['name' => 'geography', 'type' => 'linestring', 'srid' => 4326];
786
            case static::PHINX_TYPE_POLYGON:
787
                return ['name' => 'geography', 'type' => 'polygon', 'srid' => 4326];
788 10
            default:
789 10
                if ($this->isArrayType($type)) {
790 6
                    return ['name' => $type];
791 10
                }
792 10
                // Return array type
793
                throw new \RuntimeException('The type: "' . $type . '" is not supported');
794 10
        }
795 2
    }
796 10
797
    /**
798 10
     * Returns Phinx type by SQL type
799
     *
800 10
     * @param string $sqlType SQL type
801
     * @returns string Phinx type
802 1
     */
803
    public function getPhinxType($sqlType)
804 1
    {
805 10
        switch ($sqlType) {
806 10
            case 'character varying':
807 10
            case 'varchar':
808 9
                return static::PHINX_TYPE_STRING;
809 5
            case 'character':
810 5
            case 'char':
811 3
                return static::PHINX_TYPE_CHAR;
812 4
            case 'text':
813 4
                return static::PHINX_TYPE_TEXT;
814 2
            case 'json':
815 4
                return static::PHINX_TYPE_JSON;
816 4
            case 'jsonb':
817 2
                return static::PHINX_TYPE_JSONB;
818 4
            case 'smallint':
819 1
                return [
820
                    'name' => 'smallint',
821 4
                    'limit' => static::INT_SMALL
822 4
                ];
823 4
            case 'int':
824 4
            case 'int4':
825 3
            case 'integer':
826 4
                return static::PHINX_TYPE_INTEGER;
827 2
            case 'decimal':
828 4
            case 'numeric':
829 4
                return static::PHINX_TYPE_DECIMAL;
830 4
            case 'bigint':
831 4
            case 'int8':
832 3
                return static::PHINX_TYPE_BIG_INTEGER;
833 3
            case 'real':
834 3
            case 'float4':
835 3
                return static::PHINX_TYPE_FLOAT;
836 1
            case 'bytea':
837 1
                return static::PHINX_TYPE_BINARY;
838
            case 'time':
839
            case 'timetz':
840
            case 'time with time zone':
841
            case 'time without time zone':
842
                return static::PHINX_TYPE_TIME;
843
            case 'date':
844
                return static::PHINX_TYPE_DATE;
845
            case 'timestamp':
846
            case 'timestamptz':
847
            case 'timestamp with time zone':
848
            case 'timestamp without time zone':
849
                return static::PHINX_TYPE_DATETIME;
850
            case 'bool':
851
            case 'boolean':
852 1
                return static::PHINX_TYPE_BOOLEAN;
853
            case 'uuid':
854 1
                return static::PHINX_TYPE_UUID;
855 1
            case 'cidr':
856 1
                return static::PHINX_TYPE_CIDR;
857
            case 'inet':
858
                return static::PHINX_TYPE_INET;
859
            case 'macaddr':
860
                return static::PHINX_TYPE_MACADDR;
861 2
            default:
862
                throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported');
863 2
        }
864 2
    }
865 2
866
    /**
867
     * {@inheritdoc}
868
     */
869
    public function createDatabase($name, $options = [])
870
    {
871 1
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
872
        $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset));
873 1
    }
874 1
875 1
    /**
876 1
     * {@inheritdoc}
877
     */
878
    public function hasDatabase($name)
879
    {
880
        $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $name);
881
        $result = $this->fetchRow($sql);
882
883
        return $result['count'] > 0;
884 68
    }
885
886 68
    /**
887 4
     * {@inheritdoc}
888 68
     */
889 68
    public function dropDatabase($name)
890 68
    {
891 68
        $this->disconnect();
892
        $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name));
893
        $this->connect();
894
    }
895
896
    /**
897
     * Get the defintion for a `DEFAULT` statement.
898
     *
899
     * @param  mixed $default
900 68
     * @return string
901
     */
902 68 View Code Duplication
    protected function getDefaultValueDefinition($default)
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...
903 68
    {
904 50
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
905 50
            $default = $this->getConnection()->quote($default);
906 68
        } elseif (is_bool($default)) {
907 68
            $default = $this->castToBool($default);
908
        }
909 68
910 1
        return isset($default) ? 'DEFAULT ' . $default : '';
911 1
    }
912 1
913 1
    /**
914 1
     * Gets the PostgreSQL Column Definition for a Column object.
915 68
     *
916
     * @param \Phinx\Db\Table\Column $column Column
917
     * @return string
918
     */
919
    protected function getColumnSqlDefinition(Column $column)
920
    {
921
        $buffer = [];
922 68
        if ($column->isIdentity()) {
923 68
            $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL';
924 68
        } else {
925 68
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
926 68
            $buffer[] = strtoupper($sqlType['name']);
927
            // integers cant have limits in postgres
928
            if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) {
929 68
                $buffer[] = sprintf(
930 68
                    '(%s, %s)',
931 68
                    $column->getPrecision() ?: $sqlType['precision'],
932 68
                    $column->getScale() ?: $sqlType['scale']
933 1
                );
934 1
            } elseif (in_array($sqlType['name'], ['geography'])) {
935
                // geography type must be written with geometry type and srid, like this: geography(POLYGON,4326)
936
                $buffer[] = sprintf(
937 68
                    '(%s,%s)',
938
                    strtoupper($sqlType['type']),
939 68
                    $sqlType['srid']
940 68
                );
941 68
            } elseif (!in_array($sqlType['name'], ['integer', 'smallint', 'bigint'])) {
942
                if ($column->getLimit() || isset($sqlType['limit'])) {
943 68
                    $buffer[] = sprintf('(%s)', $column->getLimit() ?: $sqlType['limit']);
944
                }
945
            }
946
947
            $timeTypes = [
948
                'time',
949
                'timestamp',
950
            ];
951
            if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) {
952
                $buffer[] = strtoupper('with time zone');
953 6
            }
954
        }
955
956 6
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
957 6
958 6
        if (!is_null($column->getDefault())) {
959
            $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
960 6
        }
961 6
962 6
        return implode(' ', $buffer);
963 6
    }
964
965 6
    /**
966
     * Gets the PostgreSQL Column Comment Definition for a column object.
967
     *
968
     * @param \Phinx\Db\Table\Column $column Column
969
     * @param string $tableName Table name
970
     * @return string
971
     */
972
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
973
    {
974
        // passing 'null' is to remove column comment
975 7
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0)
976
                 ? $this->getConnection()->quote($column->getComment())
977 7
                 : 'NULL';
978 3
979 3
        return sprintf(
980 5
            'COMMENT ON COLUMN %s.%s IS %s;',
981 5
            $this->quoteTableName($tableName),
982
            $this->quoteColumnName($column->getName()),
983
            $comment
984 5
        );
985
    }
986 7
987 7
    /**
988 7
     * Gets the PostgreSQL Index Definition for an Index object.
989 7
     *
990 7
     * @param \Phinx\Db\Table\Index  $index Index
991 7
     * @param string $tableName Table name
992 7
     * @return string
993 7
     */
994
    protected function getIndexSqlDefinition(Index $index, $tableName)
995
    {
996
        $parts = $this->getSchemaName($tableName);
997
998
        if (is_string($index->getName())) {
999
            $indexName = $index->getName();
1000 View Code Duplication
        } else {
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...
1001
            $columnNames = $index->getColumns();
1002
            if (is_string($columnNames)) {
1003 3
                $columnNames = [$columnNames];
1004
            }
1005 3
            $indexName = sprintf('%s_%s', $parts['table'], implode('_', $columnNames));
1006 3
        }
1007 3
        $def = sprintf(
1008 3
            "CREATE %s INDEX %s ON %s (%s);",
1009
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
1010
            $this->quoteColumnName($indexName),
1011 3
            $this->quoteTableName($tableName),
1012
            implode(',', array_map([$this, 'quoteColumnName'], $index->getColumns()))
1013
        );
1014 3
1015
        return $def;
1016
    }
1017
1018
    /**
1019
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1020 68
     *
1021
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1022
     * @param string     $tableName  Table name
1023 68
     * @return string
1024 67
     */
1025 67
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
1026
    {
1027 68
        $parts = $this->getSchemaName($tableName);
1028
1029 68
        $constraintName = $foreignKey->getConstraint() ?: ($parts['table'] . '_' . implode('_', $foreignKey->getColumns()) . '_fkey');
1030 68
        $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName)
0 ignored issues
show
Bug introduced by
It seems like $constraintName defined by $foreignKey->getConstrai...getColumns()) . '_fkey' on line 1029 can also be of type boolean; however, Phinx\Db\Adapter\Postgre...pter::quoteColumnName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1031
            . ' FOREIGN KEY ("'
1032
            . implode('", "', $foreignKey->getColumns())
1033
            . '")'
1034
            . " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\""
1035
            . implode('", "', $foreignKey->getReferencedColumns())
1036
            . '")';
1037
        if ($foreignKey->getOnDelete()) {
1038 68
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1039
        }
1040 68
        if ($foreignKey->getOnUpdate()) {
1041 68
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1042 68
        }
1043
1044
        return $def;
1045
    }
1046
1047
    /**
1048
     * {@inheritdoc}
1049
     */
1050 68
    public function createSchemaTable()
1051
    {
1052 68
        // Create the public/custom schema if it doesn't already exist
1053
        if ($this->hasSchema($this->getGlobalSchemaName()) === false) {
1054
            $this->createSchema($this->getGlobalSchemaName());
1055 68
        }
1056
1057 68
        $this->fetchAll(sprintf('SET search_path TO %s', $this->getGlobalSchemaName()));
1058 68
1059 68
        parent::createSchemaTable();
1060
    }
1061
1062
    /**
1063
     * Creates the specified schema.
1064
     *
1065
     * @param  string $schemaName Schema Name
1066
     * @return void
1067
     */
1068 68
    public function createSchema($schemaName = 'public')
1069
    {
1070 68
        $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name"
1071 68
        $this->execute($sql);
1072 68
    }
1073
1074
    /**
1075
     * Checks to see if a schema exists.
1076
     *
1077
     * @param string $schemaName Schema Name
1078
     * @return bool
1079 68
     */
1080
    public function hasSchema($schemaName)
1081 68
    {
1082 68
        $sql = sprintf(
1083 68
            "SELECT count(*)
1084 68
             FROM pg_namespace
1085
             WHERE nspname = %s",
1086
            $this->getConnection()->quote($schemaName)
1087
        );
1088
        $result = $this->fetchRow($sql);
1089
1090
        return $result['count'] > 0;
1091 68
    }
1092
1093
    /**
1094
     * Drops the specified schema table.
1095 68
     *
1096 68
     * @param string $schemaName Schema name
1097 68
     * @return void
1098 68
     */
1099 68
    public function dropSchema($schemaName)
1100 68
    {
1101 68
        $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName));
1102
        $this->execute($sql);
1103
    }
1104
1105
    /**
1106
     * Drops all schemas.
1107 73
     *
1108
     * @return void
1109 73
     */
1110
    public function dropAllSchemas()
1111
    {
1112
        foreach ($this->getAllSchemas() as $schema) {
1113
            $this->dropSchema($schema);
1114
        }
1115 73
    }
1116
1117
    /**
1118 73
     * Returns schemas.
1119
     *
1120
     * @return array
1121
     */
1122
    public function getAllSchemas()
1123
    {
1124
        $sql = "SELECT schema_name
1125
                FROM information_schema.schemata
1126
                WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'";
1127 14
        $items = $this->fetchAll($sql);
1128
        $schemaNames = [];
1129 14
        foreach ($items as $item) {
1130 1
            $schemaNames[] = $item['schema_name'];
1131
        }
1132
1133 13
        return $schemaNames;
1134 13
    }
1135
1136
    /**
1137
     * {@inheritdoc}
1138
     */
1139
    public function getColumnTypes()
1140
    {
1141
        return array_merge(parent::getColumnTypes(), ['json', 'jsonb', 'cidr', 'inet', 'macaddr']);
1142 68
    }
1143
1144 68
    /**
1145 68
     * {@inheritdoc}
1146
     */
1147
    public function isValidColumnType(Column $column)
1148
    {
1149
        // If not a standard column type, maybe it is array type?
1150
        return (parent::isValidColumnType($column) || $this->isArrayType($column->getType()));
1151 68
    }
1152
1153 68
    /**
1154
     * Check if the given column is an array of a valid type.
1155
     *
1156
     * @param  string $columnType
1157
     * @return bool
1158
     */
1159
    protected function isArrayType($columnType)
1160
    {
1161
        if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) {
1162
            return false;
1163
        }
1164
1165
        $baseType = $matches[1];
1166
1167
        return in_array($baseType, $this->getColumnTypes());
1168
    }
1169
1170
    /**
1171
     * @param string $tableName
1172
     * @return array
1173
     */
1174
    private function getSchemaName($tableName)
1175
    {
1176
        $schema = $this->getGlobalSchemaName();
1177
        $table = $tableName;
1178
        if (false !== strpos($tableName, '.')) {
1179
            list($schema, $table) = explode('.', $tableName);
1180
        }
1181
1182
        return [
1183
            'schema' => $schema,
1184
            'table' => $table,
1185
        ];
1186
    }
1187
1188
    /**
1189
     * Gets the schema name.
1190
     *
1191
     * @return string
1192
     */
1193
    private function getGlobalSchemaName()
1194
    {
1195
        $options = $this->getOptions();
1196
1197
        return empty($options['schema']) ? 'public' : $options['schema'];
1198
    }
1199
1200
    /**
1201
     * {@inheritdoc}
1202
     */
1203
    public function castToBool($value)
1204
    {
1205
        return (bool)$value ? 'TRUE' : 'FALSE';
1206
    }
1207
}
1208