Passed
Push — 2.x ( a18c78...0f4623 )
by Aleksei
41:02 queued 21:04
created

PostgresHandler::assertValid()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 3
nop 1
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 20
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Driver\Postgres;
13
14
use Cycle\Database\Driver\Handler;
15
use Cycle\Database\Driver\Postgres\Exception\PostgresException;
16
use Cycle\Database\Driver\Postgres\Schema\PostgresColumn;
17
use Cycle\Database\Driver\Postgres\Schema\PostgresTable;
18
use Cycle\Database\Exception\SchemaException;
19
use Cycle\Database\Schema\AbstractColumn;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractColumn was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
use Cycle\Database\Schema\AbstractTable;
21
22
/**
23
 * @property PostgresDriver $driver
24
 */
25
class PostgresHandler extends Handler
26
{
27
    /**
28
     * @psalm-param non-empty-string $table
29 522
     */
30
    public function getSchema(string $table, ?string $prefix = null): AbstractTable
31 522
    {
32
        return new PostgresTable($this->driver, $table, $prefix ?? '');
33
    }
34 920
35
    public function getTableNames(string $prefix = ''): array
36 920
    {
37
        $query = "SELECT table_schema, table_name
38
            FROM information_schema.tables
39
            WHERE table_type = 'BASE TABLE'";
40 920
41 920
        if ($this->driver->shouldUseDefinedSchemas()) {
42
            $query .= " AND table_schema in ('" . \implode("','", $this->driver->getSearchSchemas()) . "')";
43
        } else {
44
            $query .= " AND table_schema !~ '^pg_.*' AND table_schema != 'information_schema'";
45
        }
46 920
47 920
        $tables = [];
48 478
        foreach ($this->driver->query($query) as $row) {
49 2
            if ($prefix !== '' && !\str_starts_with($row['table_name'], $prefix)) {
50
                continue;
51
            }
52 478
53
            $tables[] = $row['table_schema'] . '.' . $row['table_name'];
54
        }
55 920
56
        return $tables;
57
    }
58
59
    /**
60
     * @psalm-param non-empty-string $table
61 534
     */
62
    public function hasTable(string $table): bool
63 534
    {
64
        [$schema, $name] = $this->driver->parseSchemaAndTable($table);
65 534
66
        $query = "SELECT COUNT(table_name)
67
            FROM information_schema.tables
68
            WHERE table_schema = ?
69
            AND table_type = 'BASE TABLE'
70
            AND table_name = ?";
71 534
72
        return (bool) $this->driver->query($query, [$schema, $name])->fetchColumn();
73
    }
74 12
75
    public function eraseTable(AbstractTable $table, bool $restartIdentity = false): void
76 12
    {
77 12
        $query = "TRUNCATE TABLE {$this->driver->identifier($table->getFullName())}";
78
79 10
        if ($restartIdentity) {
80
            $query .= ' RESTART IDENTITY CASCADE';
81
        }
82
83
        $this->driver->execute($query);
84
    }
85 2
86
    /**
87
     * @psalm-param non-empty-string $table
88 2
     * @psalm-param non-empty-string $name
89
     */
90 2
    public function renameTable(string $table, string $name): void
91 2
    {
92
        // New table name should not contain a schema
93
        [, $name] = $this->driver->parseSchemaAndTable($name);
94
95
        parent::renameTable($table, $name);
96 40
    }
97
98
    /**
99
     * @throws SchemaException
100
     */
101 40
    public function alterColumn(
102
        AbstractTable $table,
103
        AbstractColumn $initial,
104
        AbstractColumn $column,
105
    ): void {
106 40
        if (!$initial instanceof PostgresColumn || !$column instanceof PostgresColumn) {
107 18
            throw new SchemaException('Postgres handler can work only with Postgres columns');
108
        }
109
110 18
        //Rename is separate operation
111
        if ($column->getName() !== $initial->getName()) {
112
            $this->renameColumn($table, $initial, $column);
113
114 40
            //This call is required to correctly built set of alter operations
115 40
            $initial->setName($column->getName());
116 12
        }
117
118
        //Postgres columns should be altered using set of operations
119
        $operations = $column->alterOperations($this->driver, $initial);
120 28
        if (\count($operations) > 0) {
121 28
            //Postgres columns should be altered using set of operations
122 28
            $query = \sprintf(
123 28
                'ALTER TABLE %s %s',
124
                $this->identify($table),
125
                \trim(\implode(', ', $operations), ', '),
126 28
            );
127 28
128
            $this->run($query);
129
        }
130
131
        $operation = $column->commentOperation($this->driver, $initial);
132 516
        if ($operation !== null) {
133
            $this->run($operation);
134 516
        }
135
    }
136 516
137
    public function enableForeignKeyConstraints(): void
138
    {
139 516
        $this->run('SET CONSTRAINTS ALL IMMEDIATE;');
140
    }
141
142 18
    public function disableForeignKeyConstraints(): void
143
    {
144
        $this->run('SET CONSTRAINTS ALL DEFERRED;');
145
    }
146
147 18
    public function createTable(AbstractTable $table): void
148 18
    {
149 18
        if (!$table instanceof PostgresTable) {
150 18
            throw new SchemaException('Postgres handler can work only with Postgres table');
151 18
        }
152
153
        parent::createTable($table);
154 18
155 18
        foreach ($table->getColumns() as $column) {
156
            $this->createComment($column);
157
        }
158
    }
159
160
    public function createComment(PostgresColumn $column): void
161
    {
162
        if ($column->getComment() !== '') {
163
            $this->run($column->createComment($this->driver));
164
        }
165
    }
166
167
    /**
168
     * @psalm-param non-empty-string $statement
169
     */
170
    protected function run(string $statement, array $parameters = []): int
171
    {
172
        if ($this->driver instanceof PostgresDriver) {
0 ignored issues
show
introduced by
$this->driver is always a sub-type of Cycle\Database\Driver\Postgres\PostgresDriver.
Loading history...
173
            // invaliding primary key cache
174
            $this->driver->resetPrimaryKeys();
175
        }
176
177
        return parent::run($statement, $parameters);
178
    }
179
180
    /**
181
     * @throws PostgresException
182
     */
183
    protected function assertValid(AbstractColumn $column): void
184
    {
185
        if ($column->getDefaultValue() !== null && \in_array($column->getAbstractType(), ['json', 'jsonb'])) {
186
            try {
187
                \json_decode($column->getDefaultValue(), true, 512, JSON_THROW_ON_ERROR);
188
            } catch (\Throwable) {
189
                throw new PostgresException(
190
                    \sprintf('Column `%s` of type json/jsonb has an invalid default json value.', $column),
191
                );
192
            }
193
        }
194
    }
195
196
    private function renameColumn(
197
        AbstractTable $table,
198
        AbstractColumn $initial,
199
        AbstractColumn $column,
200
    ): void {
201
        $statement = \sprintf(
202
            'ALTER TABLE %s RENAME COLUMN %s TO %s',
203
            $this->identify($table),
204
            $this->identify($initial),
205
            $this->identify($column),
206
        );
207
208
        $this->run($statement);
209
    }
210
}
211