Completed
Push — master ( 94018a...6a6ebc )
by AD
13s
created

PostgresAdapter   D

Complexity

Total Complexity 196

Size/Duplication

Total Lines 1120
Duplicated Lines 21.34 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 92.21%

Importance

Changes 0
Metric Value
wmc 196
lcom 1
cbo 5
dl 239
loc 1120
ccs 568
cts 616
cp 0.9221
rs 4.4102
c 0
b 0
f 0

49 Methods

Rating   Name   Duplication   Size   Complexity  
B connect() 5 31 6
A disconnect() 0 4 1
A hasTransactions() 0 4 1
A beginTransaction() 0 4 1
A commitTransaction() 0 4 1
A rollbackTransaction() 0 4 1
A quoteSchemaName() 0 4 1
A quoteTableName() 0 4 1
A quoteColumnName() 0 4 1
A hasTable() 0 15 1
F createTable() 48 102 18
A renameTable() 0 9 1
A dropTable() 0 4 1
B getColumns() 0 33 4
A hasColumn() 14 14 1
A addColumn() 11 11 1
A renameColumn() 0 22 2
B changeColumn() 0 66 5
A dropColumn() 0 10 1
B getIndexes() 30 30 3
A hasIndex() 14 14 4
A hasIndexByName() 10 10 3
A addIndex() 0 5 1
B dropIndex() 0 23 4
A dropIndexByName() 0 8 1
B hasForeignKey() 21 21 6
B getForeignKeys() 25 25 2
A addForeignKey() 9 9 1
B dropForeignKey() 34 34 5
C getSqlType() 0 62 28
C getPhinxType() 0 63 34
A createDatabase() 0 5 2
A hasDatabase() 0 6 1
A dropDatabase() 0 6 1
B getDefaultValueDefinition() 9 9 5
D getColumnSqlDefinition() 0 45 17
A getColumnCommentSqlDefinition() 0 14 2
A getIndexSqlDefinition() 9 20 4
A getForeignKeySqlDefinition() 0 13 4
A createSchemaTable() 0 11 2
A createSchema() 0 5 1
A hasSchema() 0 11 1
A dropSchema() 0 5 1
A dropAllSchemas() 0 6 2
A getAllSchemas() 0 12 2
A getColumnTypes() 0 4 1
A isValidColumnType() 0 5 2
A isArrayType() 0 9 2
A getSchemaName() 0 5 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Phinx\Db\Table;
32
use Phinx\Db\Table\Column;
33
use Phinx\Db\Table\Index;
34
use Phinx\Db\Table\ForeignKey;
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
        return $this->quoteSchemaName($this->getSchemaName()) . '.' . $this->quoteColumnName($tableName);
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144 68
    public function quoteColumnName($columnName)
145
    {
146 68
        return '"'. $columnName . '"';
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152 68
    public function hasTable($tableName)
153
    {
154 68
        $result = $this->getConnection()->query(
155 68
            sprintf(
156
                'SELECT *
157
                FROM information_schema.tables
158
                WHERE table_schema = %s
159 68
                AND lower(table_name) = lower(%s)',
160 68
                $this->getConnection()->quote($this->getSchemaName()),
161 68
                $this->getConnection()->quote($tableName)
162 68
            )
163 68
        );
164
165 68
        return $result->rowCount() === 1;
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 68
    public function createTable(Table $table)
172
    {
173 68
        $options = $table->getOptions();
174
175
         // Add the default primary key
176 68
        $columns = $table->getPendingColumns();
177 68 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...
178 48
            $column = new Column();
179 48
            $column->setName('id')
180 48
                   ->setType('integer')
181 48
                   ->setIdentity(true);
182
183 48
            array_unshift($columns, $column);
184 48
            $options['primary_key'] = 'id';
185 68
        } elseif (isset($options['id']) && is_string($options['id'])) {
186
            // Handle id => "field_name" to support AUTO_INCREMENT
187 2
            $column = new Column();
188 2
            $column->setName($options['id'])
189 2
                   ->setType('integer')
190 2
                   ->setIdentity(true);
191
192 2
            array_unshift($columns, $column);
193 2
            $options['primary_key'] = $options['id'];
194 2
        }
195
196
        // TODO - process table options like collation etc
197 68
        $sql = 'CREATE TABLE ';
198 68
        $sql .= $this->quoteTableName($table->getName()) . ' (';
199
200 68
        $this->columnsWithComments = [];
201 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...
202 68
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
203
204
            // set column comments, if needed
205 68
            if ($column->getComment()) {
206 6
                $this->columnsWithComments[] = $column;
207 6
            }
208 68
        }
209
210
         // set the primary key(s)
211 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...
212 68
            $sql = rtrim($sql);
213 68
            $sql .= sprintf(' CONSTRAINT %s_pkey PRIMARY KEY (', $table->getName());
214 68
            if (is_string($options['primary_key'])) {       // handle primary_key => 'id'
215 68
                $sql .= $this->quoteColumnName($options['primary_key']);
216 68
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
217
                // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
218
                // but for now just hard-code the adapter quotes
219 1
                $sql .= implode(
220 1
                    ',',
221 1
                    array_map(
222 1
                        function ($v) {
223 1
                            return '"' . $v . '"';
224 1
                        },
225 1
                        $options['primary_key']
226 1
                    )
227 1
                );
228 1
            }
229 68
            $sql .= ')';
230 68
        } else {
231 2
            $sql = substr(rtrim($sql), 0, -1);              // no primary keys
232
        }
233
234
        // set the foreign keys
235 68
        $foreignKeys = $table->getForeignKeys();
236 68
        if (!empty($foreignKeys)) {
237 1
            foreach ($foreignKeys as $foreignKey) {
238 1
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
239 1
            }
240 1
        }
241
242 68
        $sql .= ');';
243
244
        // process column comments
245 68
        if (!empty($this->columnsWithComments)) {
246 6
            foreach ($this->columnsWithComments as $column) {
247 6
                $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
248 6
            }
249 6
        }
250
251
252
        // set the indexes
253 68
        $indexes = $table->getIndexes();
254 68
        if (!empty($indexes)) {
255 5
            foreach ($indexes as $index) {
256 5
                $sql .= $this->getIndexSqlDefinition($index, $table->getName());
257 5
            }
258 5
        }
259
260
        // execute the sql
261 68
        $this->execute($sql);
262
263
        // process table comments
264 68
        if (isset($options['comment'])) {
265 1
            $sql = sprintf(
266 1
                'COMMENT ON TABLE %s IS %s',
267 1
                $this->quoteTableName($table->getName()),
268 1
                $this->getConnection()->quote($options['comment'])
269 1
            );
270 1
            $this->execute($sql);
271 1
        }
272 68
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277 1
    public function renameTable($tableName, $newTableName)
278
    {
279 1
        $sql = sprintf(
280 1
            'ALTER TABLE %s RENAME TO %s',
281 1
            $this->quoteTableName($tableName),
282 1
            $this->quoteColumnName($newTableName)
283 1
        );
284 1
        $this->execute($sql);
285 1
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290 1
    public function dropTable($tableName)
291
    {
292 1
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
293 1
    }
294
295
    /**
296
     * {@inheritdoc}
297
     */
298 1
    public function truncateTable($tableName)
299
    {
300 1
        $sql = sprintf(
301 1
            'TRUNCATE TABLE %s',
302 1
            $this->quoteTableName($tableName)
303 1
        );
304
305 1
        $this->execute($sql);
306 1
    }
307
308
    /**
309
     * {@inheritdoc}
310
     */
311 9
    public function getColumns($tableName)
312
    {
313 9
        $columns = [];
314 9
        $sql = sprintf(
315
            "SELECT column_name, data_type, is_identity, is_nullable,
316
             column_default, character_maximum_length, numeric_precision, numeric_scale
317
             FROM information_schema.columns
318 9
             WHERE table_name ='%s'",
319
            $tableName
320 9
        );
321 9
        $columnsInfo = $this->fetchAll($sql);
322
323 9
        foreach ($columnsInfo as $columnInfo) {
324 9
            $column = new Column();
325 9
            $column->setName($columnInfo['column_name'])
326 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"}> 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...
327 9
                   ->setNull($columnInfo['is_nullable'] === 'YES')
328 9
                   ->setDefault($columnInfo['column_default'])
329 9
                   ->setIdentity($columnInfo['is_identity'] === 'YES')
330 9
                   ->setPrecision($columnInfo['numeric_precision'])
331 9
                   ->setScale($columnInfo['numeric_scale']);
332
333 9
            if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) {
334 1
                $column->setTimezone(true);
335 1
            }
336
337 9
            if (isset($columnInfo['character_maximum_length'])) {
338 5
                $column->setLimit($columnInfo['character_maximum_length']);
339 5
            }
340 9
            $columns[] = $column;
341 9
        }
342 9
        return $columns;
343
    }
344
345
    /**
346
     * {@inheritdoc}
347
     */
348 24 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...
349
    {
350 24
        $sql = sprintf(
351
            "SELECT count(*)
352
            FROM information_schema.columns
353 24
            WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'",
354 24
            $this->getSchemaName(),
355 24
            $tableName,
356
            $columnName
357 24
        );
358
359 24
        $result = $this->fetchRow($sql);
360 24
        return  $result['count'] > 0;
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     */
366 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...
367
    {
368 18
        $sql = sprintf(
369 18
            'ALTER TABLE %s ADD %s %s',
370 18
            $this->quoteTableName($table->getName()),
371 18
            $this->quoteColumnName($column->getName()),
372 18
            $this->getColumnSqlDefinition($column)
373 18
        );
374
375 18
        $this->execute($sql);
376 18
    }
377
378
    /**
379
     * {@inheritdoc}
380
     */
381 3
    public function renameColumn($tableName, $columnName, $newColumnName)
382
    {
383 3
        $sql = sprintf(
384
            "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists
385
             FROM information_schema.columns
386 3
             WHERE table_name ='%s' AND column_name = '%s'",
387 3
            $tableName,
388
            $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 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...
495
    {
496 9
        $indexes = [];
497
        $sql = "SELECT
498
            i.relname AS index_name,
499
            a.attname AS column_name
500
        FROM
501
            pg_class t,
502
            pg_class i,
503
            pg_index ix,
504
            pg_attribute a
505
        WHERE
506
            t.oid = ix.indrelid
507
            AND i.oid = ix.indexrelid
508
            AND a.attrelid = t.oid
509
            AND a.attnum = ANY(ix.indkey)
510
            AND t.relkind = 'r'
511
            AND t.relname = '$tableName'
512
        ORDER BY
513
            t.relname,
514 9
            i.relname;";
515 9
        $rows = $this->fetchAll($sql);
516 9
        foreach ($rows as $row) {
517 9
            if (!isset($indexes[$row['index_name']])) {
518 9
                $indexes[$row['index_name']] = ['columns' => []];
519 9
            }
520 9
            $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']);
521 9
        }
522 9
        return $indexes;
523
    }
524
525
    /**
526
     * {@inheritdoc}
527
     */
528 9 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...
529
    {
530 9
        if (is_string($columns)) {
531 4
            $columns = [$columns];
532 4
        }
533 9
        $columns = array_map('strtolower', $columns);
534 9
        $indexes = $this->getIndexes($tableName);
535 9
        foreach ($indexes as $index) {
536 9
            if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) {
537 9
                return true;
538
            }
539 8
        }
540 8
        return false;
541
    }
542
543
     /**
544
      * {@inheritdoc}
545
      */
546 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...
547
    {
548 1
        $indexes = $this->getIndexes($tableName);
549 1
        foreach ($indexes as $name => $index) {
550 1
            if ($name === $indexName) {
551 1
                return true;
552
            }
553
        }
554
        return false;
555
    }
556
557
    /**
558
     * {@inheritdoc}
559
     */
560 2
    public function addIndex(Table $table, Index $index)
561
    {
562 2
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
563 2
        $this->execute($sql);
564 2
    }
565
566
    /**
567
     * {@inheritdoc}
568
     */
569 1
    public function dropIndex($tableName, $columns)
570
    {
571 1
        if (is_string($columns)) {
572 1
            $columns = [$columns]; // str to array
573 1
        }
574
575 1
        $indexes = $this->getIndexes($tableName);
576 1
        $columns = array_map('strtolower', $columns);
577
578 1
        foreach ($indexes as $indexName => $index) {
579 1
            $a = array_diff($columns, $index['columns']);
580 1
            if (empty($a)) {
581 1
                $this->execute(
582 1
                    sprintf(
583 1
                        'DROP INDEX IF EXISTS %s',
584 1
                        $this->quoteColumnName($indexName)
585 1
                    )
586 1
                );
587
588 1
                return;
589
            }
590
        }
591
    }
592
593
    /**
594
     * {@inheritdoc}
595
     */
596 1
    public function dropIndexByName($tableName, $indexName)
597
    {
598 1
        $sql = sprintf(
599 1
            'DROP INDEX IF EXISTS %s',
600
            $indexName
601 1
        );
602 1
        $this->execute($sql);
603 1
    }
604
605
    /**
606
     * {@inheritdoc}
607
     */
608 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...
609
    {
610 3
        if (is_string($columns)) {
611 1
            $columns = [$columns]; // str to array
612 1
        }
613 3
        $foreignKeys = $this->getForeignKeys($tableName);
614 3
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
615
            if (isset($foreignKeys[$constraint])) {
616
                return !empty($foreignKeys[$constraint]);
617
            }
618
            return false;
619
        } else {
620 3
            foreach ($foreignKeys as $key) {
621 3
                $a = array_diff($columns, $key['columns']);
622 3
                if (empty($a)) {
623 3
                    return true;
624
                }
625 1
            }
626 1
            return false;
627
        }
628
    }
629
630
    /**
631
     * Get an array of foreign keys from a particular table.
632
     *
633
     * @param string $tableName Table Name
634
     * @return array
635
     */
636 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...
637
    {
638 3
        $foreignKeys = [];
639 3
        $rows = $this->fetchAll(sprintf(
640
            "SELECT
641
                    tc.constraint_name,
642
                    tc.table_name, kcu.column_name,
643
                    ccu.table_name AS referenced_table_name,
644
                    ccu.column_name AS referenced_column_name
645
                FROM
646
                    information_schema.table_constraints AS tc
647
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
648
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
649
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
650 3
                ORDER BY kcu.position_in_unique_constraint",
651
            $tableName
652 3
        ));
653 3
        foreach ($rows as $row) {
654 3
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
655 3
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
656 3
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
657 3
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
658 3
        }
659 3
        return $foreignKeys;
660
    }
661
662
    /**
663
     * {@inheritdoc}
664
     */
665 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...
666
    {
667 2
        $sql = sprintf(
668 2
            'ALTER TABLE %s ADD %s',
669 2
            $this->quoteTableName($table->getName()),
670 2
            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
671 2
        );
672 2
        $this->execute($sql);
673 2
    }
674
675
    /**
676
     * {@inheritdoc}
677
     */
678 1 View Code Duplication
    public function dropForeignKey($tableName, $columns, $constraint = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
679
    {
680 1
        if (is_string($columns)) {
681
            $columns = [$columns]; // str to array
682
        }
683
684 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...
685 1
            $this->execute(
686 1
                sprintf(
687 1
                    'ALTER TABLE %s DROP CONSTRAINT %s',
688 1
                    $this->quoteTableName($tableName),
689
                    $constraint
690 1
                )
691 1
            );
692 1
        } else {
693 1
            foreach ($columns as $column) {
694 1
                $rows = $this->fetchAll(sprintf(
695
                    "SELECT CONSTRAINT_NAME
696
                      FROM information_schema.KEY_COLUMN_USAGE
697
                      WHERE TABLE_SCHEMA = CURRENT_SCHEMA()
698
                        AND TABLE_NAME IS NOT NULL
699
                        AND TABLE_NAME = '%s'
700
                        AND COLUMN_NAME = '%s'
701 1
                      ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
702 1
                    $tableName,
703
                    $column
704 1
                ));
705
706 1
                foreach ($rows as $row) {
707 1
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
708 1
                }
709 1
            }
710
        }
711 1
    }
712
713
    /**
714
     * {@inheritdoc}
715
     */
716 68
    public function getSqlType($type, $limit = null)
717
    {
718
        switch ($type) {
719 68
            case static::PHINX_TYPE_INTEGER:
720 14
                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...
721
                    return [
722 1
                        'name' => 'smallint',
723
                        'limit' => static::INT_SMALL
724 1
                    ];
725
                }
726 14
                return ['name' => $type];
727 68
            case static::PHINX_TYPE_TEXT:
728 68
            case static::PHINX_TYPE_TIME:
729 68
            case static::PHINX_TYPE_DATE:
730 68
            case static::PHINX_TYPE_BOOLEAN:
731 68
            case static::PHINX_TYPE_JSON:
732 68
            case static::PHINX_TYPE_JSONB:
733 68
            case static::PHINX_TYPE_UUID:
734 68
            case static::PHINX_TYPE_CIDR:
735 68
            case static::PHINX_TYPE_INET:
736 68
            case static::PHINX_TYPE_MACADDR:
737 68
                return ['name' => $type];
738 68
            case static::PHINX_TYPE_DECIMAL:
739 2
                return ['name' => $type, 'precision' => 18, 'scale' => 0];
740 68
            case static::PHINX_TYPE_STRING:
741 68
                return ['name' => 'character varying', 'limit' => 255];
742 68
            case static::PHINX_TYPE_CHAR:
743
                return ['name' => 'character', 'limit' => 255];
744 68
            case static::PHINX_TYPE_BIG_INTEGER:
745 68
                return ['name' => 'bigint'];
746 68
            case static::PHINX_TYPE_FLOAT:
747 1
                return ['name' => 'real'];
748 68
            case static::PHINX_TYPE_DATETIME:
749 68
            case static::PHINX_TYPE_TIMESTAMP:
750 68
                return ['name' => 'timestamp'];
751 15
            case static::PHINX_TYPE_BLOB:
752 15
            case static::PHINX_TYPE_BINARY:
753 1
                return ['name' => 'bytea'];
754
            // Geospatial database types
755
            // Spatial storage in Postgres is done via the PostGIS extension,
756
            // which enables the use of the "geography" type in combination
757
            // with SRID 4326.
758 14
            case static::PHINX_TYPE_GEOMETRY:
759
                return ['name' => 'geography', 'type' => 'geometry', 'srid' => 4326];
760
                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...
761 14
            case static::PHINX_TYPE_POINT:
762
                return ['name' => 'geography', 'type' => 'point', '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_LINESTRING:
765
                return ['name' => 'geography', 'type' => 'linestring', '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_POLYGON:
768
                return ['name' => 'geography', 'type' => 'polygon', '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
            default:
771 14
                if ($this->isArrayType($type)) {
772 13
                    return ['name' => $type];
773
                }
774
                // Return array type
775 1
                throw new \RuntimeException('The type: "' . $type . '" is not supported');
776 14
        }
777
    }
778
779
    /**
780
     * Returns Phinx type by SQL type
781
     *
782
     * @param string $sqlType SQL type
783
     * @returns string Phinx type
784
     */
785 10
    public function getPhinxType($sqlType)
786
    {
787
        switch ($sqlType) {
788 10
            case 'character varying':
789 10
            case 'varchar':
790 6
                return static::PHINX_TYPE_STRING;
791 10
            case 'character':
792 10
            case 'char':
793
                return static::PHINX_TYPE_CHAR;
794 10
            case 'text':
795 2
                return static::PHINX_TYPE_TEXT;
796 10
            case 'json':
797
                return static::PHINX_TYPE_JSON;
798 10
            case 'jsonb':
799
                return static::PHINX_TYPE_JSONB;
800 10
            case 'smallint':
801
                return [
802 1
                    'name' => 'smallint',
803
                    'limit' => static::INT_SMALL
804 1
                ];
805 10
            case 'int':
806 10
            case 'int4':
807 10
            case 'integer':
808 9
                return static::PHINX_TYPE_INTEGER;
809 5
            case 'decimal':
810 5
            case 'numeric':
811 3
                return static::PHINX_TYPE_DECIMAL;
812 4
            case 'bigint':
813 4
            case 'int8':
814 2
                return static::PHINX_TYPE_BIG_INTEGER;
815 4
            case 'real':
816 4
            case 'float4':
817 2
                return static::PHINX_TYPE_FLOAT;
818 4
            case 'bytea':
819 1
                return static::PHINX_TYPE_BINARY;
820
                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...
821 4
            case 'time':
822 4
            case 'timetz':
823 4
            case 'time with time zone':
824 4
            case 'time without time zone':
825 3
                return static::PHINX_TYPE_TIME;
826 4
            case 'date':
827 2
                return static::PHINX_TYPE_DATE;
828 4
            case 'timestamp':
829 4
            case 'timestamptz':
830 4
            case 'timestamp with time zone':
831 4
            case 'timestamp without time zone':
832 3
                return static::PHINX_TYPE_DATETIME;
833 3
            case 'bool':
834 3
            case 'boolean':
835 3
                return static::PHINX_TYPE_BOOLEAN;
836 1
            case 'uuid':
837 1
                return static::PHINX_TYPE_UUID;
838
            case 'cidr':
839
                return static::PHINX_TYPE_CIDR;
840
            case 'inet':
841
                return static::PHINX_TYPE_INET;
842
            case 'macaddr':
843
                return static::PHINX_TYPE_MACADDR;
844
            default:
845
                throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported');
846
        }
847
    }
848
849
    /**
850
     * {@inheritdoc}
851
     */
852 1
    public function createDatabase($name, $options = [])
853
    {
854 1
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
855 1
        $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset));
856 1
    }
857
858
    /**
859
     * {@inheritdoc}
860
     */
861 2
    public function hasDatabase($databaseName)
862
    {
863 2
        $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName);
864 2
        $result = $this->fetchRow($sql);
865 2
        return  $result['count'] > 0;
866
    }
867
868
    /**
869
     * {@inheritdoc}
870
     */
871 1
    public function dropDatabase($name)
872
    {
873 1
        $this->disconnect();
874 1
        $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name));
875 1
        $this->connect();
876 1
    }
877
878
    /**
879
     * Get the defintion for a `DEFAULT` statement.
880
     *
881
     * @param  mixed $default
882
     * @return string
883
     */
884 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...
885
    {
886 68
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
887 4
            $default = $this->getConnection()->quote($default);
888 68
        } elseif (is_bool($default)) {
889 68
            $default = $this->castToBool($default);
890 68
        }
891 68
        return isset($default) ? 'DEFAULT ' . $default : '';
892
    }
893
894
    /**
895
     * Gets the PostgreSQL Column Definition for a Column object.
896
     *
897
     * @param Column $column Column
898
     * @return string
899
     */
900 68
    protected function getColumnSqlDefinition(Column $column)
901
    {
902 68
        $buffer = [];
903 68
        if ($column->isIdentity()) {
904 50
            $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL';
905 50
        } else {
906 68
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
907 68
            $buffer[] = strtoupper($sqlType['name']);
908
            // integers cant have limits in postgres
909 68
            if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) {
910 1
                $buffer[] = sprintf(
911 1
                    '(%s, %s)',
912 1
                    $column->getPrecision() ? $column->getPrecision() : $sqlType['precision'],
913 1
                    $column->getScale() ? $column->getScale() : $sqlType['scale']
914 1
                );
915 68
            } elseif (in_array($sqlType['name'], ['geography'])) {
916
                // geography type must be written with geometry type and srid, like this: geography(POLYGON,4326)
917
                $buffer[] = sprintf(
918
                    '(%s,%s)',
919
                    strtoupper($sqlType['type']),
920
                    $sqlType['srid']
921
                );
922 68
            } elseif (!in_array($sqlType['name'], ['integer', 'smallint', 'bigint'])) {
923 68
                if ($column->getLimit() || isset($sqlType['limit'])) {
924 68
                    $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
925 68
                }
926 68
            }
927
928
            $timeTypes = [
929 68
                'time',
930 68
                'timestamp',
931 68
            ];
932 68
            if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) {
933 1
                $buffer[] = strtoupper('with time zone');
934 1
            }
935
        }
936
937 68
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
938
939 68
        if (!is_null($column->getDefault())) {
940 68
            $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
941 68
        }
942
943 68
        return implode(' ', $buffer);
944
    }
945
946
    /**
947
     * Gets the PostgreSQL Column Comment Defininition for a column object.
948
     *
949
     * @param Column $column Column
950
     * @param string $tableName Table name
951
     * @return string
952
     */
953 6
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
954
    {
955
        // passing 'null' is to remove column comment
956 6
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0)
957 6
                 ? $this->getConnection()->quote($column->getComment())
958 6
                 : 'NULL';
959
960 6
        return sprintf(
961 6
            'COMMENT ON COLUMN %s.%s IS %s;',
962 6
            $this->quoteSchemaName($tableName),
963 6
            $this->quoteColumnName($column->getName()),
964
            $comment
965 6
        );
966
    }
967
968
    /**
969
     * Gets the PostgreSQL Index Definition for an Index object.
970
     *
971
     * @param Index  $index Index
972
     * @param string $tableName Table name
973
     * @return string
974
     */
975 7
    protected function getIndexSqlDefinition(Index $index, $tableName)
976
    {
977 7 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...
978 3
            $indexName = $index->getName();
979 3
        } else {
980 5
            $columnNames = $index->getColumns();
981 5
            if (is_string($columnNames)) {
982
                $columnNames = [$columnNames];
983
            }
984 5
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
985
        }
986 7
        $def = sprintf(
987 7
            "CREATE %s INDEX %s ON %s (%s);",
988 7
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
989 7
            $indexName,
990 7
            $this->quoteTableName($tableName),
991 7
            implode(',', array_map([$this, 'quoteColumnName'], $index->getColumns()))
992 7
        );
993 7
        return $def;
994
    }
995
996
    /**
997
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
998
     *
999
     * @param ForeignKey $foreignKey
1000
     * @param string     $tableName  Table name
1001
     * @return string
1002
     */
1003 3
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
1004
    {
1005 3
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
1006 3
        $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1007 3
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1008 3
        if ($foreignKey->getOnDelete()) {
1009
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1010
        }
1011 3
        if ($foreignKey->getOnUpdate()) {
1012
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1013
        }
1014 3
        return $def;
1015
    }
1016
1017
    /**
1018
     * {@inheritdoc}
1019
     */
1020 68
    public function createSchemaTable()
1021
    {
1022
        // Create the public/custom schema if it doesn't already exist
1023 68
        if ($this->hasSchema($this->getSchemaName()) === false) {
1024 67
            $this->createSchema($this->getSchemaName());
1025 67
        }
1026
1027 68
        $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName()));
1028
1029 68
        parent::createSchemaTable();
1030 68
    }
1031
1032
    /**
1033
     * Creates the specified schema.
1034
     *
1035
     * @param  string $schemaName Schema Name
1036
     * @return void
1037
     */
1038 68
    public function createSchema($schemaName = 'public')
1039
    {
1040 68
        $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name"
1041 68
        $this->execute($sql);
1042 68
    }
1043
1044
    /**
1045
     * Checks to see if a schema exists.
1046
     *
1047
     * @param string $schemaName Schema Name
1048
     * @return boolean
1049
     */
1050 68
    public function hasSchema($schemaName)
1051
    {
1052 68
        $sql = sprintf(
1053
            "SELECT count(*)
1054
             FROM pg_namespace
1055 68
             WHERE nspname = '%s'",
1056
            $schemaName
1057 68
        );
1058 68
        $result = $this->fetchRow($sql);
1059 68
        return $result['count'] > 0;
1060
    }
1061
1062
    /**
1063
     * Drops the specified schema table.
1064
     *
1065
     * @param string $schemaName Schema name
1066
     * @return void
1067
     */
1068 68
    public function dropSchema($schemaName)
1069
    {
1070 68
        $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName));
1071 68
        $this->execute($sql);
1072 68
    }
1073
1074
    /**
1075
     * Drops all schemas.
1076
     *
1077
     * @return void
1078
     */
1079 68
    public function dropAllSchemas()
1080
    {
1081 68
        foreach ($this->getAllSchemas() as $schema) {
1082 68
            $this->dropSchema($schema);
1083 68
        }
1084 68
    }
1085
1086
    /**
1087
     * Returns schemas.
1088
     *
1089
     * @return array
1090
     */
1091 68
    public function getAllSchemas()
1092
    {
1093
        $sql = "SELECT schema_name
1094
                FROM information_schema.schemata
1095 68
                WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'";
1096 68
        $items = $this->fetchAll($sql);
1097 68
        $schemaNames = [];
1098 68
        foreach ($items as $item) {
1099 68
            $schemaNames[] = $item['schema_name'];
1100 68
        }
1101 68
        return $schemaNames;
1102
    }
1103
1104
    /**
1105
     * {@inheritdoc}
1106
     */
1107 73
    public function getColumnTypes()
1108
    {
1109 73
        return array_merge(parent::getColumnTypes(), ['json', 'jsonb', 'cidr', 'inet', 'macaddr']);
1110
    }
1111
1112
    /**
1113
     * {@inheritdoc}
1114
     */
1115 73
    public function isValidColumnType(Column $column)
1116
    {
1117
        // If not a standard column type, maybe it is array type?
1118 73
        return (parent::isValidColumnType($column) || $this->isArrayType($column->getType()));
1119
    }
1120
1121
    /**
1122
     * Check if the given column is an array of a valid type.
1123
     *
1124
     * @param  string $columnType
1125
     * @return bool
1126
     */
1127 14
    protected function isArrayType($columnType)
1128
    {
1129 14
        if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) {
1130 1
            return false;
1131
        }
1132
1133 13
        $baseType = $matches[1];
1134 13
        return in_array($baseType, $this->getColumnTypes());
1135
    }
1136
1137
    /**
1138
     * Gets the schema name.
1139
     *
1140
     * @return string
1141
     */
1142 68
    private function getSchemaName()
1143
    {
1144 68
        $options = $this->getOptions();
1145 68
        return empty($options['schema']) ? 'public' : $options['schema'];
1146
    }
1147
1148
    /**
1149
     * {@inheritdoc}
1150
     */
1151 68
    public function castToBool($value)
1152
    {
1153 68
        return (bool) $value ? 'TRUE' : 'FALSE';
1154
    }
1155
}
1156