Completed
Pull Request — master (#1175)
by
unknown
01:55
created

PostgresAdapter::getDefaultValueDefinition()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 9
Code Lines 6

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 7
CRAP Score 5

Importance

Changes 0
Metric Value
dl 9
loc 9
ccs 7
cts 7
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 6
nc 6
nop 1
crap 5
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\CustomColumn;
34
use Phinx\Db\Table\Index;
35
use Phinx\Db\Table\ForeignKey;
36
37
class PostgresAdapter extends PdoAdapter implements AdapterInterface
38
{
39
    const INT_SMALL = 65535;
40
41
    /**
42
     * Columns with comments
43
     *
44
     * @var array
45
     */
46
    protected $columnsWithComments = [];
47
48
    /**
49
     * {@inheritdoc}
50 68
     */
51
    public function connect()
52 68
    {
53 68
        if ($this->connection === null) {
54
            if (!class_exists('PDO') || !in_array('pgsql', \PDO::getAvailableDrivers(), true)) {
55
                // @codeCoverageIgnoreStart
56
                throw new \RuntimeException('You need to enable the PDO_Pgsql extension for Phinx to run properly.');
57
                // @codeCoverageIgnoreEnd
58
            }
59 68
60 68
            $db = null;
61
            $options = $this->getOptions();
62
63 68
            // if port is specified use it, otherwise use the PostgreSQL default
64 68 View Code Duplication
            if (isset($options['port'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
218
                // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
219 1
                // but for now just hard-code the adapter quotes
220 1
                $sql .= implode(
221 1
                    ',',
222 1
                    array_map(
223 1
                        function ($v) {
224 1
                            return '"' . $v . '"';
225 1
                        },
226 1
                        $options['primary_key']
227 1
                    )
228 1
                );
229 68
            }
230 68
            $sql .= ')';
231 2
        } else {
232
            $sql = rtrim($sql, ', '); // no primary keys
233
        }
234
235 68
        // set the foreign keys
236 68
        $foreignKeys = $table->getForeignKeys();
237 1
        if (!empty($foreignKeys)) {
238 1
            foreach ($foreignKeys as $foreignKey) {
239 1
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
240 1
            }
241
        }
242 68
243
        $sql .= ');';
244
245 68
        // process column comments
246 6
        if (!empty($this->columnsWithComments)) {
247 6
            foreach ($this->columnsWithComments as $column) {
248 6
                $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
249 6
            }
250
        }
251
252
253 68
        // set the indexes
254 68
        $indexes = $table->getIndexes();
255 5
        if (!empty($indexes)) {
256 5
            foreach ($indexes as $index) {
257 5
                $sql .= $this->getIndexSqlDefinition($index, $table->getName());
258 5
            }
259
        }
260
261 68
        // execute the sql
262
        $this->execute($sql);
263
264 68
        // process table comments
265 1
        if (isset($options['comment'])) {
266 1
            $sql = sprintf(
267 1
                'COMMENT ON TABLE %s IS %s',
268 1
                $this->quoteTableName($table->getName()),
269 1
                $this->getConnection()->quote($options['comment'])
270 1
            );
271 1
            $this->execute($sql);
272 68
        }
273
    }
274
275
    /**
276
     * {@inheritdoc}
277 1
     */
278
    public function renameTable($tableName, $newTableName)
279 1
    {
280 1
        $sql = sprintf(
281 1
            'ALTER TABLE %s RENAME TO %s',
282 1
            $this->quoteTableName($tableName),
283 1
            $this->quoteColumnName($newTableName)
284 1
        );
285 1
        $this->execute($sql);
286
    }
287
288
    /**
289
     * {@inheritdoc}
290 1
     */
291
    public function dropTable($tableName)
292 1
    {
293 1
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
294
    }
295
296
    /**
297
     * {@inheritdoc}
298 1
     */
299
    public function truncateTable($tableName)
300 1
    {
301 1
        $sql = sprintf(
302 1
            'TRUNCATE TABLE %s',
303 1
            $this->quoteTableName($tableName)
304
        );
305 1
306 1
        $this->execute($sql);
307
    }
308
309
    /**
310
     * {@inheritdoc}
311 9
     */
312
    public function getColumns($tableName)
313 9
    {
314 9
        $columns = [];
315
        $sql = sprintf(
316
            "SELECT column_name, data_type, udt_name, is_identity, is_nullable,
317
             column_default, character_maximum_length, numeric_precision, numeric_scale
318 9
             FROM information_schema.columns
319
             WHERE table_name ='%s'",
320 9
            $tableName
321 9
        );
322
        $columnsInfo = $this->fetchAll($sql);
323 9
324 9
        foreach ($columnsInfo as $columnInfo) {
325 9
            $isUserDefined = $columnInfo['data_type'] === 'USER-DEFINED';
326 9
            $column = $isUserDefined ? new CustomColumn() : new Column();
327 9
            $columnType = $isUserDefined ? $columnInfo['udt_name'] : $columnInfo['data_type'];
328 9
            $column->setName($columnInfo['column_name'])
329 9
                   ->setType($this->getPhinxType($columnType, !$isUserDefined))
0 ignored issues
show
Bug introduced by
It seems like $this->getPhinxType($columnType, !$isUserDefined) targeting Phinx\Db\Adapter\PostgresAdapter::getPhinxType() can also be of type array<string,string|inte...ng","limit":"integer"}> or null; 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...
330 9
                   ->setNull($columnInfo['is_nullable'] === 'YES')
331 9
                   ->setDefault($columnInfo['column_default'])
332
                   ->setIdentity($columnInfo['is_identity'] === 'YES')
333 9
                   ->setPrecision($columnInfo['numeric_precision'])
334 1
                   ->setScale($columnInfo['numeric_scale']);
335 1
336
            if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) {
337 9
                $column->setTimezone(true);
338 5
            }
339 5
340 9
            if (isset($columnInfo['character_maximum_length'])) {
341 9
                $column->setLimit($columnInfo['character_maximum_length']);
342 9
            }
343
            $columns[] = $column;
344
        }
345
        return $columns;
346
    }
347
348 24
    /**
349
     * {@inheritdoc}
350 24
     */
351 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...
352
    {
353 24
        $sql = sprintf(
354 24
            "SELECT count(*)
355 24
            FROM information_schema.columns
356
            WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'",
357 24
            $this->getSchemaName(),
358
            $tableName,
359 24
            $columnName
360 24
        );
361
362
        $result = $this->fetchRow($sql);
363
        return  $result['count'] > 0;
364
    }
365
366 18
    /**
367
     * {@inheritdoc}
368 18
     */
369 18 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...
370 18
    {
371 18
        $sql = sprintf(
372 18
            'ALTER TABLE %s ADD %s %s',
373 18
            $this->quoteTableName($table->getName()),
374
            $this->quoteColumnName($column->getName()),
375 18
            $this->getColumnSqlDefinition($column)
376 18
        );
377
378
        $this->execute($sql);
379
    }
380
381 3
    /**
382
     * {@inheritdoc}
383 3
     */
384
    public function renameColumn($tableName, $columnName, $newColumnName)
385
    {
386 3
        $sql = sprintf(
387 3
            "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists
388
             FROM information_schema.columns
389 3
             WHERE table_name ='%s' AND column_name = '%s'",
390 3
            $tableName,
391 3
            $columnName
392 1
        );
393
        $result = $this->fetchRow($sql);
394 2
        if (!(bool) $result['column_exists']) {
395 2
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
396 2
        }
397 2
        $this->execute(
398 2
            sprintf(
399 2
                'ALTER TABLE %s RENAME COLUMN %s TO %s',
400 2
                $this->quoteTableName($tableName),
401 2
                $this->quoteColumnName($columnName),
402 2
                $this->quoteColumnName($newColumnName)
403
            )
404
        );
405
    }
406
407 5
    /**
408
     * {@inheritdoc}
409
     */
410
    public function changeColumn($tableName, $columnName, Column $newColumn)
411 5
    {
412 5
        // TODO - is it possible to merge these 3 queries into less?
413 5
        // change data type
414 5
        $sql = sprintf(
415 5
            'ALTER TABLE %s ALTER COLUMN %s TYPE %s',
416 5
            $this->quoteTableName($tableName),
417
            $this->quoteColumnName($columnName),
418 5
            $this->getColumnSqlDefinition($newColumn)
419 5
        );
420
        //NULL and DEFAULT cannot be set while changing column type
421 5
        $sql = preg_replace('/ NOT NULL/', '', $sql);
422 5
        $sql = preg_replace('/ NULL/', '', $sql);
423
        //If it is set, DEFAULT is the last definition
424 5
        $sql = preg_replace('/DEFAULT .*/', '', $sql);
425 5
        $this->execute($sql);
426 5
        // process null
427 5
        $sql = sprintf(
428 5
            'ALTER TABLE %s ALTER COLUMN %s',
429 5
            $this->quoteTableName($tableName),
430 2
            $this->quoteColumnName($columnName)
431 2
        );
432 4
        if ($newColumn->isNull()) {
433
            $sql .= ' DROP NOT NULL';
434 5
        } else {
435 5
            $sql .= ' SET NOT NULL';
436
        }
437 1
        $this->execute($sql);
438 1
        if (!is_null($newColumn->getDefault())) {
439 1
            //change default
440 1
            $this->execute(
441 1
                sprintf(
442 1
                    'ALTER TABLE %s ALTER COLUMN %s SET %s',
443 1
                    $this->quoteTableName($tableName),
444 1
                    $this->quoteColumnName($columnName),
445 1
                    $this->getDefaultValueDefinition($newColumn->getDefault())
446
                )
447 4
            );
448 4
        } else {
449 4
            //drop default
450 4
            $this->execute(
451 4
                sprintf(
452 4
                    'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
453 4
                    $this->quoteTableName($tableName),
454
                    $this->quoteColumnName($columnName)
455
                )
456 5
            );
457 1
        }
458 1
        // rename column
459 1
        if ($columnName !== $newColumn->getName()) {
460 1
            $this->execute(
461 1
                sprintf(
462 1
                    'ALTER TABLE %s RENAME COLUMN %s TO %s',
463 1
                    $this->quoteTableName($tableName),
464 1
                    $this->quoteColumnName($columnName),
465 1
                    $this->quoteColumnName($newColumn->getName())
466
                )
467
            );
468 5
        }
469 2
470 2
        // change column comment if needed
471 2
        if ($newColumn->getComment()) {
472 5
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
473
            $this->execute($sql);
474
        }
475
    }
476
477 1
    /**
478
     * {@inheritdoc}
479 1
     */
480 1
    public function dropColumn($tableName, $columnName)
481 1
    {
482 1
        $this->execute(
483 1
            sprintf(
484 1
                'ALTER TABLE %s DROP COLUMN %s',
485 1
                $this->quoteTableName($tableName),
486 1
                $this->quoteColumnName($columnName)
487
            )
488
        );
489
    }
490
491
    /**
492
     * Get an array of indexes from a particular table.
493
     *
494 9
     * @param string $tableName Table Name
495
     * @return array
496 9
     */
497 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...
498
    {
499
        $indexes = [];
500
        $sql = "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
        WHERE
509
            t.oid = ix.indrelid
510
            AND i.oid = ix.indexrelid
511
            AND a.attrelid = t.oid
512
            AND a.attnum = ANY(ix.indkey)
513
            AND t.relkind = 'r'
514 9
            AND t.relname = '$tableName'
515 9
        ORDER BY
516 9
            t.relname,
517 9
            i.relname;";
518 9
        $rows = $this->fetchAll($sql);
519 9
        foreach ($rows as $row) {
520 9
            if (!isset($indexes[$row['index_name']])) {
521 9
                $indexes[$row['index_name']] = ['columns' => []];
522 9
            }
523
            $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']);
524
        }
525
        return $indexes;
526
    }
527
528 9
    /**
529
     * {@inheritdoc}
530 9
     */
531 4 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...
532 4
    {
533 9
        if (is_string($columns)) {
534 9
            $columns = [$columns];
535 9
        }
536 9
        $columns = array_map('strtolower', $columns);
537 9
        $indexes = $this->getIndexes($tableName);
538
        foreach ($indexes as $index) {
539 8
            if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) {
540 8
                return true;
541
            }
542
        }
543
        return false;
544
    }
545
546 1
     /**
547
      * {@inheritdoc}
548 1
      */
549 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...
550 1
    {
551 1
        $indexes = $this->getIndexes($tableName);
552
        foreach ($indexes as $name => $index) {
553
            if ($name === $indexName) {
554
                return true;
555
            }
556
        }
557
        return false;
558
    }
559
560 2
    /**
561
     * {@inheritdoc}
562 2
     */
563 2
    public function addIndex(Table $table, Index $index)
564 2
    {
565
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
566
        $this->execute($sql);
567
    }
568
569 1
    /**
570
     * {@inheritdoc}
571 1
     */
572 1
    public function dropIndex($tableName, $columns)
573 1
    {
574
        if (is_string($columns)) {
575 1
            $columns = [$columns]; // str to array
576 1
        }
577
578 1
        $indexes = $this->getIndexes($tableName);
579 1
        $columns = array_map('strtolower', $columns);
580 1
581 1
        foreach ($indexes as $indexName => $index) {
582 1
            $a = array_diff($columns, $index['columns']);
583 1
            if (empty($a)) {
584 1
                $this->execute(
585 1
                    sprintf(
586 1
                        'DROP INDEX IF EXISTS %s',
587
                        $this->quoteColumnName($indexName)
588 1
                    )
589
                );
590
591
                return;
592
            }
593
        }
594
    }
595
596 1
    /**
597
     * {@inheritdoc}
598 1
     */
599 1
    public function dropIndexByName($tableName, $indexName)
600
    {
601 1
        $sql = sprintf(
602 1
            'DROP INDEX IF EXISTS %s',
603 1
            $indexName
604
        );
605
        $this->execute($sql);
606
    }
607
608 3
    /**
609
     * {@inheritdoc}
610 3
     */
611 1 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...
612 1
    {
613 3
        if (is_string($columns)) {
614 3
            $columns = [$columns]; // str to array
615
        }
616
        $foreignKeys = $this->getForeignKeys($tableName);
617
        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...
618
            if (isset($foreignKeys[$constraint])) {
619
                return !empty($foreignKeys[$constraint]);
620 3
            }
621 3
            return false;
622 3
        } else {
623 3
            foreach ($foreignKeys as $key) {
624
                $a = array_diff($columns, $key['columns']);
625 1
                if (empty($a)) {
626 1
                    return true;
627
                }
628
            }
629
            return false;
630
        }
631
    }
632
633
    /**
634
     * Get an array of foreign keys from a particular table.
635
     *
636 3
     * @param string $tableName Table Name
637
     * @return array
638 3
     */
639 3 View Code Duplication
    protected function getForeignKeys($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
640
    {
641
        $foreignKeys = [];
642
        $rows = $this->fetchAll(sprintf(
643
            "SELECT
644
                    tc.constraint_name,
645
                    tc.table_name, kcu.column_name,
646
                    ccu.table_name AS referenced_table_name,
647
                    ccu.column_name AS referenced_column_name
648
                FROM
649
                    information_schema.table_constraints AS tc
650 3
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
651
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
652 3
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
653 3
                ORDER BY kcu.position_in_unique_constraint",
654 3
            $tableName
655 3
        ));
656 3
        foreach ($rows as $row) {
657 3
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
658 3
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
659 3
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
660
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
661
        }
662
        return $foreignKeys;
663
    }
664
665 2
    /**
666
     * {@inheritdoc}
667 2
     */
668 2 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...
669 2
    {
670 2
        $sql = sprintf(
671 2
            'ALTER TABLE %s ADD %s',
672 2
            $this->quoteTableName($table->getName()),
673 2
            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
674
        );
675
        $this->execute($sql);
676
    }
677
678 1
    /**
679
     * {@inheritdoc}
680 1
     */
681 View Code Duplication
    public function dropForeignKey($tableName, $columns, $constraint = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
682
    {
683
        if (is_string($columns)) {
684 1
            $columns = [$columns]; // str to array
685 1
        }
686 1
687 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...
688 1
            $this->execute(
689
                sprintf(
690 1
                    'ALTER TABLE %s DROP CONSTRAINT %s',
691 1
                    $this->quoteTableName($tableName),
692 1
                    $constraint
693 1
                )
694 1
            );
695
        } else {
696
            foreach ($columns as $column) {
697
                $rows = $this->fetchAll(sprintf(
698
                    "SELECT CONSTRAINT_NAME
699
                      FROM information_schema.KEY_COLUMN_USAGE
700
                      WHERE TABLE_SCHEMA = CURRENT_SCHEMA()
701 1
                        AND TABLE_NAME IS NOT NULL
702 1
                        AND TABLE_NAME = '%s'
703
                        AND COLUMN_NAME = '%s'
704 1
                      ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
705
                    $tableName,
706 1
                    $column
707 1
                ));
708 1
709 1
                foreach ($rows as $row) {
710
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
711 1
                }
712
            }
713
        }
714
    }
715
716 68
    /**
717
     * {@inheritdoc}
718
     */
719 68
    public function getSqlType($type, $limit = null)
720 14
    {
721
        switch ($type) {
722 1
            case static::PHINX_TYPE_INTEGER:
723
                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...
724 1
                    return [
725
                        'name' => 'smallint',
726 14
                        'limit' => static::INT_SMALL
727 68
                    ];
728 68
                }
729 68
                return ['name' => $type];
730 68
            case static::PHINX_TYPE_TEXT:
731 68
            case static::PHINX_TYPE_TIME:
732 68
            case static::PHINX_TYPE_DATE:
733 68
            case static::PHINX_TYPE_BOOLEAN:
734 68
            case static::PHINX_TYPE_JSON:
735 68
            case static::PHINX_TYPE_JSONB:
736 68
            case static::PHINX_TYPE_UUID:
737 68
            case static::PHINX_TYPE_CIDR:
738 68
            case static::PHINX_TYPE_INET:
739 2
            case static::PHINX_TYPE_MACADDR:
740 68
                return ['name' => $type];
741 68
            case static::PHINX_TYPE_DECIMAL:
742 68
                return ['name' => $type, 'precision' => 18, 'scale' => 0];
743
            case static::PHINX_TYPE_STRING:
744 68
                return ['name' => 'character varying', 'limit' => 255];
745 68
            case static::PHINX_TYPE_CHAR:
746 68
                return ['name' => 'character', 'limit' => 255];
747 1
            case static::PHINX_TYPE_BIG_INTEGER:
748 68
                return ['name' => 'bigint'];
749 68
            case static::PHINX_TYPE_FLOAT:
750 68
                return ['name' => 'real'];
751 15
            case static::PHINX_TYPE_DATETIME:
752 15
            case static::PHINX_TYPE_TIMESTAMP:
753 1
                return ['name' => 'timestamp'];
754
            case static::PHINX_TYPE_BLOB:
755
            case static::PHINX_TYPE_BINARY:
756
                return ['name' => 'bytea'];
757
            // Geospatial database types
758 14
            // Spatial storage in Postgres is done via the PostGIS extension,
759
            // which enables the use of the "geography" type in combination
760
            // with SRID 4326.
761 14
            case static::PHINX_TYPE_GEOMETRY:
762
                return ['name' => 'geography', 'type' => 'geometry', 'srid' => 4326];
763
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
764 14
            case static::PHINX_TYPE_POINT:
765
                return ['name' => 'geography', 'type' => 'point', 'srid' => 4326];
766
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
767 14
            case static::PHINX_TYPE_LINESTRING:
768
                return ['name' => 'geography', 'type' => 'linestring', 'srid' => 4326];
769
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
770 14
            case static::PHINX_TYPE_POLYGON:
771 14
                return ['name' => 'geography', 'type' => 'polygon', 'srid' => 4326];
772 13
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
773
            default:
774
                if ($this->isArrayType($type)) {
775 1
                    return ['name' => $type];
776 14
                }
777
                // Return array type
778
                throw new \RuntimeException('The type: "' . $type . '" is not supported');
779
        }
780
    }
781
782
    /**
783
     * Returns Phinx type by SQL type
784
     *
785 10
     * @param string $sqlType SQL type
786
     * @param bool $strict If true throw exception when invalid, if false return input
787
     * @returns string Phinx type
788 10
     */
789 10
    public function getPhinxType($sqlType, $strict = true)
790 6
    {
791 10
        switch ($sqlType) {
792 10
            case 'character varying':
793
            case 'varchar':
794 10
                return static::PHINX_TYPE_STRING;
795 2
            case 'character':
796 10
            case 'char':
797
                return static::PHINX_TYPE_CHAR;
798 10
            case 'text':
799
                return static::PHINX_TYPE_TEXT;
800 10
            case 'json':
801
                return static::PHINX_TYPE_JSON;
802 1
            case 'jsonb':
803
                return static::PHINX_TYPE_JSONB;
804 1
            case 'smallint':
805 10
                return [
806 10
                    'name' => 'smallint',
807 10
                    'limit' => static::INT_SMALL
808 9
                ];
809 5
            case 'int':
810 5
            case 'int4':
811 3
            case 'integer':
812 4
                return static::PHINX_TYPE_INTEGER;
813 4
            case 'decimal':
814 2
            case 'numeric':
815 4
                return static::PHINX_TYPE_DECIMAL;
816 4
            case 'bigint':
817 2
            case 'int8':
818 4
                return static::PHINX_TYPE_BIG_INTEGER;
819 1
            case 'real':
820
            case 'float4':
821 4
                return static::PHINX_TYPE_FLOAT;
822 4
            case 'bytea':
823 4
                return static::PHINX_TYPE_BINARY;
824 4
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
825 3
            case 'time':
826 4
            case 'timetz':
827 2
            case 'time with time zone':
828 4
            case 'time without time zone':
829 4
                return static::PHINX_TYPE_TIME;
830 4
            case 'date':
831 4
                return static::PHINX_TYPE_DATE;
832 3
            case 'timestamp':
833 3
            case 'timestamptz':
834 3
            case 'timestamp with time zone':
835 3
            case 'timestamp without time zone':
836 1
                return static::PHINX_TYPE_DATETIME;
837 1
            case 'bool':
838
            case 'boolean':
839
                return static::PHINX_TYPE_BOOLEAN;
840
            case 'uuid':
841
                return static::PHINX_TYPE_UUID;
842
            case 'cidr':
843
                return static::PHINX_TYPE_CIDR;
844
            case 'inet':
845
                return static::PHINX_TYPE_INET;
846
            case 'macaddr':
847
                return static::PHINX_TYPE_MACADDR;
848
            default:
849
                if (!$strict) {
850
                    return $sqlType;
851
                }
852 1
                throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported');
853
        }
854 1
    }
855 1
856 1
    /**
857
     * {@inheritdoc}
858
     */
859
    public function createDatabase($name, $options = [])
860
    {
861 2
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
862
        $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset));
863 2
    }
864 2
865 2
    /**
866
     * {@inheritdoc}
867
     */
868
    public function hasDatabase($databaseName)
869
    {
870
        $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName);
871 1
        $result = $this->fetchRow($sql);
872
        return  $result['count'] > 0;
873 1
    }
874 1
875 1
    /**
876 1
     * {@inheritdoc}
877
     */
878
    public function dropDatabase($name)
879
    {
880
        $this->disconnect();
881
        $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name));
882
        $this->connect();
883
    }
884 68
885
    /**
886 68
     * Gets the PostgreSQL Column Definition for a Column object.
887 4
     *
888 68
     * @param Column $column Column
889 68
     * @return string
890 68
     */
891 68
    protected function getColumnSqlDefinition(Column $column)
892
    {
893
        $buffer = [];
894
        if ($column instanceof CustomColumn) {
895
            $buffer[] = $column->getType();
896
        } else if ($column->isIdentity()) {
897
            $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL';
898
        } else {
899
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
900 68
            $buffer[] = strtoupper($sqlType['name']);
901
            // integers cant have limits in postgres
902 68
            if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) {
903 68
                $buffer[] = sprintf(
904 50
                    '(%s, %s)',
905 50
                    $column->getPrecision() ? $column->getPrecision() : $sqlType['precision'],
906 68
                    $column->getScale() ? $column->getScale() : $sqlType['scale']
907 68
                );
908
            } elseif (in_array($sqlType['name'], ['geography'])) {
909 68
                // geography type must be written with geometry type and srid, like this: geography(POLYGON,4326)
910 1
                $buffer[] = sprintf(
911 1
                    '(%s,%s)',
912 1
                    strtoupper($sqlType['type']),
913 1
                    $sqlType['srid']
914 1
                );
915 68
            } elseif (!in_array($sqlType['name'], ['integer', 'smallint', 'bigint'])) {
916
                if ($column->getLimit() || isset($sqlType['limit'])) {
917
                    $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
918
                }
919
            }
920
921
            $timeTypes = [
922 68
                'time',
923 68
                'timestamp',
924 68
            ];
925 68
            if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) {
926 68
                $buffer[] = strtoupper('with time zone');
927
            }
928
        }
929 68
930 68
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
931 68
        $buffer = implode(' ', $buffer);
932 68
933 1
        if (!is_null($column->getDefault())) {
934 1
            $buffer .= $this->getDefaultValueDefinition($column->getDefault());
935
        }
936
937 68
        return $buffer;
938
    }
939 68
940 68
    /**
941 68
     * Gets the PostgreSQL Column Comment Defininition for a column object.
942
     *
943 68
     * @param Column $column Column
944
     * @param string $tableName Table name
945
     * @return string
946
     */
947
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
948
    {
949
        // passing 'null' is to remove column comment
950
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0)
951
                 ? $this->getConnection()->quote($column->getComment())
952
                 : 'NULL';
953 6
954
        return sprintf(
955
            'COMMENT ON COLUMN %s.%s IS %s;',
956 6
            $this->quoteSchemaName($tableName),
957 6
            $this->quoteColumnName($column->getName()),
958 6
            $comment
959
        );
960 6
    }
961 6
962 6
    /**
963 6
     * Gets the PostgreSQL Index Definition for an Index object.
964
     *
965 6
     * @param Index  $index Index
966
     * @param string $tableName Table name
967
     * @return string
968
     */
969
    protected function getIndexSqlDefinition(Index $index, $tableName)
970
    {
971 View Code Duplication
        if (is_string($index->getName())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
972
            $indexName = $index->getName();
973
        } else {
974
            $columnNames = $index->getColumns();
975 7
            if (is_string($columnNames)) {
976
                $columnNames = [$columnNames];
977 7
            }
978 3
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
979 3
        }
980 5
        $def = sprintf(
981 5
            "CREATE %s INDEX %s ON %s (%s);",
982
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
983
            $indexName,
984 5
            $this->quoteTableName($tableName),
985
            implode(',', array_map([$this, 'quoteColumnName'], $index->getColumns()))
986 7
        );
987 7
        return $def;
988 7
    }
989 7
990 7
    /**
991 7
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
992 7
     *
993 7
     * @param ForeignKey $foreignKey
994
     * @param string     $tableName  Table name
995
     * @return string
996
     */
997 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...
998
    {
999
        $constraintName = $foreignKey->getConstraint()
1000
            ? $foreignKey->getConstraint()
1001
            : $tableName . '_' . implode('_', $foreignKey->getColumns());
1002
1003 3
        $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1004
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1005 3
        if ($foreignKey->getOnDelete()) {
1006 3
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1007 3
        }
1008 3
        if ($foreignKey->getOnUpdate()) {
1009
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1010
        }
1011 3
        return $def;
1012
    }
1013
1014 3
    /**
1015
     * {@inheritdoc}
1016
     */
1017
    public function createSchemaTable()
1018
    {
1019
        // Create the public/custom schema if it doesn't already exist
1020 68
        if ($this->hasSchema($this->getSchemaName()) === false) {
1021
            $this->createSchema($this->getSchemaName());
1022
        }
1023 68
1024 67
        $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName()));
1025 67
1026
        parent::createSchemaTable();
1027 68
    }
1028
1029 68
    /**
1030 68
     * Creates the specified schema.
1031
     *
1032
     * @param  string $schemaName Schema Name
1033
     * @return void
1034
     */
1035
    public function createSchema($schemaName = 'public')
1036
    {
1037
        $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name"
1038 68
        $this->execute($sql);
1039
    }
1040 68
1041 68
    /**
1042 68
     * Checks to see if a schema exists.
1043
     *
1044
     * @param string $schemaName Schema Name
1045
     * @return boolean
1046
     */
1047
    public function hasSchema($schemaName)
1048
    {
1049
        $sql = sprintf(
1050 68
            "SELECT count(*)
1051
             FROM pg_namespace
1052 68
             WHERE nspname = '%s'",
1053
            $schemaName
1054
        );
1055 68
        $result = $this->fetchRow($sql);
1056
        return $result['count'] > 0;
1057 68
    }
1058 68
1059 68
    /**
1060
     * Drops the specified schema table.
1061
     *
1062
     * @param string $schemaName Schema name
1063
     * @return void
1064
     */
1065
    public function dropSchema($schemaName)
1066
    {
1067
        $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName));
1068 68
        $this->execute($sql);
1069
    }
1070 68
1071 68
    /**
1072 68
     * Drops all schemas.
1073
     *
1074
     * @return void
1075
     */
1076
    public function dropAllSchemas()
1077
    {
1078
        foreach ($this->getAllSchemas() as $schema) {
1079 68
            $this->dropSchema($schema);
1080
        }
1081 68
    }
1082 68
1083 68
    /**
1084 68
     * Returns schemas.
1085
     *
1086
     * @return array
1087
     */
1088
    public function getAllSchemas()
1089
    {
1090
        $sql = "SELECT schema_name
1091 68
                FROM information_schema.schemata
1092
                WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'";
1093
        $items = $this->fetchAll($sql);
1094
        $schemaNames = [];
1095 68
        foreach ($items as $item) {
1096 68
            $schemaNames[] = $item['schema_name'];
1097 68
        }
1098 68
        return $schemaNames;
1099 68
    }
1100 68
1101 68
    /**
1102
     * {@inheritdoc}
1103
     */
1104
    public function getColumnTypes()
1105
    {
1106
        return array_merge(parent::getColumnTypes(), ['json', 'jsonb', 'cidr', 'inet', 'macaddr']);
1107 73
    }
1108
1109 73
    /**
1110
     * {@inheritdoc}
1111
     */
1112
    public function isValidColumnType(Column $column)
1113
    {
1114
        // If not a standard column type, maybe it is array type?
1115 73
        return (parent::isValidColumnType($column) || $this->isArrayType($column->getType()));
1116
    }
1117
1118 73
    /**
1119
     * Check if the given column is an array of a valid type.
1120
     *
1121
     * @param  string $columnType
1122
     * @return bool
1123
     */
1124
    protected function isArrayType($columnType)
1125
    {
1126
        if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) {
1127 14
            return false;
1128
        }
1129 14
1130 1
        $baseType = $matches[1];
1131
        return in_array($baseType, $this->getColumnTypes());
1132
    }
1133 13
1134 13
    /**
1135
     * Gets the schema name.
1136
     *
1137
     * @return string
1138
     */
1139
    private function getSchemaName()
1140
    {
1141
        $options = $this->getOptions();
1142 68
        return empty($options['schema']) ? 'public' : $options['schema'];
1143
    }
1144 68
1145 68
    /**
1146
     * {@inheritdoc}
1147
     */
1148
    public function castToBool($value)
1149
    {
1150
        return (bool) $value ? 'TRUE' : 'FALSE';
1151 68
    }
1152
}
1153