Passed
Pull Request — 2.x (#219)
by
unknown
17:24
created

PostgresTable::fetchColumns()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 72
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 55
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 72
ccs 30
cts 30
cp 1
crap 4
rs 8.9818

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
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\Schema;
13
14
use Cycle\Database\Driver\HandlerInterface;
15
use Cycle\Database\Driver\Postgres\PostgresDriver;
16
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...
17
use Cycle\Database\Schema\AbstractForeignKey;
18
use Cycle\Database\Schema\AbstractIndex;
19
use Cycle\Database\Schema\AbstractTable;
20
21
/**
22
 * @property PostgresDriver $driver
23
 */
24
class PostgresTable extends AbstractTable
25
{
26
    /**
27
     * Found table sequences.
28
     */
29
    private array $sequences = [];
30
31
    /**
32
     * Sequence object name usually defined only for primary keys and required by ORM to correctly
33
     * resolve inserted row id.
34
     */
35
    private ?string $primarySequence = null;
36
37
    /**
38
     * Sequence object name usually defined only for primary keys and required by ORM to correctly
39
     * resolve inserted row id.
40
     */
41
    public function getSequence(): ?string
42
    {
43
        return $this->primarySequence;
44
    }
45
46 6
    public function getName(): string
47
    {
48 6
        return $this->removeSchemaFromTableName($this->getFullName());
49
    }
50
51
    /**
52
     * SQLServer will reload schemas after successful save.
53
     */
54 516
    public function save(int $operation = HandlerInterface::DO_ALL, bool $reset = true): void
55
    {
56 516
        parent::save($operation, $reset);
57
58 516
        if ($reset) {
59 516
            foreach ($this->fetchColumns() as $column) {
60 516
                $currentColumn = $this->current->findColumn($column->getName());
61 516
                if ($currentColumn !== null && $column->compare($currentColumn)) {
62
                    //Ensure constrained columns
63 516
                    $this->current->registerColumn($column);
64
                }
65
            }
66
        }
67 516
    }
68
69 516
    public function getDependencies(): array
70
    {
71 516
        $tables = [];
72
        foreach ($this->current->getForeignKeys() as $foreignKey) {
73
            [$tableSchema, $tableName] = $this->driver->parseSchemaAndTable($foreignKey->getForeignTable());
74 516
            $tables[] = $tableSchema . '.' . $tableName;
75 516
        }
76
77
        return $tables;
78
    }
79
80
    protected function fetchColumns(): array
81 516
    {
82 516
        [$tableSchema, $tableName] = $this->driver->parseSchemaAndTable($this->getFullName());
83
84 516
        //Required for constraints fetch
85 516
        $tableOID = $this->driver->query(
86
            'SELECT pgc.oid
87
                FROM pg_class as pgc
88
                JOIN pg_namespace as pgn
89
                    ON (pgn.oid = pgc.relnamespace)
90
                WHERE pgn.nspname = ?
91 516
                AND pgc.relname = ?',
92
            [$tableSchema, $tableName],
93
        )->fetchColumn();
94 516
95 516
        $query = $this->driver->query(
96 516
            'SELECT columns.*, pg_type.*, pg_description.description
97
               FROM information_schema.columns
98 516
               JOIN pg_catalog.pg_type
99 438
                   ON (pg_type.typname = columns.udt_name)
100 516
               JOIN pg_catalog.pg_statio_all_tables
101 438
	               ON (pg_statio_all_tables.relname = columns.table_name
102
	               AND pg_statio_all_tables.schemaname = columns.table_schema)
103
               LEFT JOIN pg_catalog.pg_description
104
	               ON (pg_description.objoid = pg_statio_all_tables.relid
105
	               AND pg_description.objsubid = columns.ordinal_position)
106 366
               WHERE columns.table_schema = ?
107
               AND columns.table_name = ?',
108
            [$tableSchema, $tableName],
109 516
        );
110 516
111 516
        $primaryKeys = \array_column($this->driver->query(
112 516
            'SELECT key_column_usage.column_name
113
                FROM information_schema.table_constraints
114
                JOIN information_schema.key_column_usage
115
                    ON (
116 516
                            key_column_usage.table_name = table_constraints.table_name AND
117
                            key_column_usage.table_schema = table_constraints.table_schema AND
118
                            key_column_usage.constraint_name = table_constraints.constraint_name
119 508
                        )
120
                WHERE table_constraints.constraint_type = \'PRIMARY KEY\' AND
121 508
                      key_column_usage.ordinal_position IS NOT NULL AND
122
                      table_constraints.table_schema = ? AND
123 508
                      table_constraints.table_name = ?',
124
            [$tableSchema, $tableName],
125
        )->fetchAll(), 'column_name');
126
127
        $result = [];
128
        foreach ($query->fetchAll() as $schema) {
129
            $name = $schema['column_name'];
130
            if (
131
                \is_string($schema['column_default'])
132
                && \preg_match(
133
                    '/^nextval\([\'"]([a-z0-9_"]+)[\'"](?:::regclass)?\)$/i',
134 508
                    $schema['column_default'],
135 508
                    $matches,
136 364
                )
137
            ) {
138 364
                //Column is sequential
139
                $this->sequences[$name] = $matches[1];
140 116
            }
141
142
            $schema['is_primary'] = \in_array($schema['column_name'], $primaryKeys, true);
143 508
144
            $result[] = PostgresColumn::createInstance(
145
                $tableSchema . '.' . $tableName,
146 508
                $schema + ['tableOID' => $tableOID],
147
                $this->driver,
148 508
            );
149
        }
150
151 508
        return $result;
152
    }
153
154
    protected function fetchIndexes(bool $all = false): array
155
    {
156
        [$tableSchema, $tableName] = $this->driver->parseSchemaAndTable($this->getFullName());
157
158
        $query = <<<SQL
159
            SELECT i.indexname, i.indexdef, c.contype
160
            FROM pg_indexes i
161
            LEFT JOIN pg_namespace ns
162
                ON nspname = i.schemaname
163 508
            LEFT JOIN pg_constraint c
164 508
                ON c.conname = i.indexname
165 56
                AND c.connamespace = ns.oid
166 56
            WHERE i.schemaname = ? AND i.tablename = ?
167 56
            SQL;
168 56
169 56
        $result = [];
170
        foreach ($this->driver->query($query, [$tableSchema, $tableName]) as $schema) {
171
            if ($schema['contype'] === 'p') {
172 2
                //Skipping primary keys
173 2
                continue;
174
            }
175
            $result[] = PostgresIndex::createInstance($tableSchema . '.' . $tableName, $schema);
176 508
        }
177 508
178 56
        return $result;
179 56
    }
180 56
181
    protected function fetchReferences(): array
182
    {
183
        [$tableSchema, $tableName] = $this->driver->parseSchemaAndTable($this->getFullName());
184
185 508
        //Mindblowing
186
        $query = 'SELECT tc.constraint_name, tc.constraint_schema, tc.table_name, kcu.column_name, rc.update_rule, '
187
            . 'rc.delete_rule, ccu.table_name AS foreign_table_name, '
188 508
            . "ccu.column_name AS foreign_column_name\n"
189
            . "FROM information_schema.table_constraints AS tc\n"
190 508
            . "JOIN information_schema.key_column_usage AS kcu\n"
191
            . "   ON tc.constraint_name = kcu.constraint_name\n"
192 508
            . "JOIN information_schema.constraint_column_usage AS ccu\n"
193
            . "   ON ccu.constraint_name = tc.constraint_name\n"
194
            . "JOIN information_schema.referential_constraints AS rc\n"
195
            . "   ON rc.constraint_name = tc.constraint_name\n"
196
            . "WHERE constraint_type = 'FOREIGN KEY' AND tc.table_schema = ? AND tc.table_name = ?";
197
198
        $fks = [];
199
        foreach ($this->driver->query($query, [$tableSchema, $tableName]) as $schema) {
200
            if (!isset($fks[$schema['constraint_name']])) {
201
                $fks[$schema['constraint_name']] = $schema;
202
                $fks[$schema['constraint_name']]['column_name'] = [$schema['column_name']];
203
                $fks[$schema['constraint_name']]['foreign_column_name'] = [$schema['foreign_column_name']];
204 508
                continue;
205
            }
206 364
207
            $fks[$schema['constraint_name']]['column_name'][] = $schema['column_name'];
208 364
            $fks[$schema['constraint_name']]['foreign_column_name'][] = $schema['foreign_column_name'];
209
        }
210
211
        $result = [];
212
        foreach ($fks as $schema) {
213
            $result[] = PostgresForeignKey::createInstance(
214
                $tableSchema . '.' . $tableName,
215
                $this->getPrefix(),
216
                $schema,
217 364
            );
218
        }
219
220 148
        return $result;
221
    }
222
223
    protected function fetchPrimaryKeys(): array
224
    {
225
        [$tableSchema, $tableName] = $this->driver->parseSchemaAndTable($this->getFullName());
226 514
227
        $query = <<<SQL
228 514
            SELECT i.indexname, i.indexdef, c.contype
229 514
            FROM pg_indexes i
230 514
            INNER JOIN pg_namespace ns
231 514
                ON nspname = i.schemaname
232
            INNER JOIN pg_constraint c
233
                ON c.conname = i.indexname
234
                AND c.connamespace = ns.oid
235
            WHERE i.schemaname = ? AND i.tablename = ?
236
              AND c.contype = 'p'
237
            SQL;
238 116
239
        foreach ($this->driver->query($query, [$tableSchema, $tableName]) as $schema) {
240 116
            //To simplify definitions
241 116
            $index = PostgresIndex::createInstance($tableSchema . '.' . $tableName, $schema);
242 116
243
            if (\is_array($this->primarySequence) && \count($index->getColumns()) === 1) {
244
                $column = $index->getColumns()[0];
245
246
                if (isset($this->sequences[$column])) {
247
                    //We found our primary sequence
248
                    $this->primarySequence = $this->sequences[$column];
249 56
                }
250
            }
251 56
252 56
            return $index->getColumns();
253 56
        }
254 56
255
        return [];
256
    }
257
258
    /**
259
     * @psalm-param non-empty-string $name
260
     */
261 522
    protected function createColumn(string $name): AbstractColumn
262
    {
263 522
        return new PostgresColumn(
264
            $this->getNormalizedTableName(),
265 522
            $this->removeSchemaFromTableName($name),
266
            $this->driver->getTimezone(),
267
        );
268
    }
269
270
    /**
271 514
     * @psalm-param non-empty-string $name
272
     */
273 514
    protected function createIndex(string $name): AbstractIndex
274
    {
275 514
        return new PostgresIndex(
276
            $this->getNormalizedTableName(),
277
            $this->removeSchemaFromTableName($name),
278
        );
279
    }
280
281
    /**
282
     * @psalm-param non-empty-string $name
283 514
     */
284
    protected function createForeign(string $name): AbstractForeignKey
285 514
    {
286 122
        return new PostgresForeignKey(
287
            $this->getNormalizedTableName(),
288
            $this->getPrefix(),
289 514
            $this->removeSchemaFromTableName($name),
290
        );
291
    }
292
293
    /**
294
     * @psalm-param non-empty-string $name
295
     */
296
    protected function prefixTableName(string $name): string
297
    {
298
        [$schema, $name] = $this->driver->parseSchemaAndTable($name);
299
300
        return $schema . '.' . parent::prefixTableName($name);
301
    }
302
303
    /**
304
     * Get table name with schema. If table doesn't contain schema, schema will be added from config
305
     */
306
    protected function getNormalizedTableName(): string
307
    {
308
        [$schema, $name] = $this->driver->parseSchemaAndTable($this->getFullName());
309
310
        return $schema . '.' . $name;
311
    }
312
313
    /**
314
     * Return table name without schema
315
     *
316
     * @psalm-param non-empty-string $name
317
     */
318
    protected function removeSchemaFromTableName(string $name): string
319
    {
320
        if (\str_contains($name, '.')) {
321
            [, $name] = \explode('.', $name, 2);
322
        }
323
324
        return $name;
325
    }
326
}
327