Passed
Branch main (a6702e)
by Thierry
04:09 queued 02:04
created

Table::alterTable()   F

Complexity

Conditions 26
Paths 8784

Size

Total Lines 57
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 26
eloc 39
c 0
b 0
f 0
nc 8784
nop 9
dl 0
loc 57
rs 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Table::trigger() 0 19 4

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Lagdo\DbAdmin\Driver\PgSql\Db;
4
5
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
6
use Lagdo\DbAdmin\Driver\Entity\TableEntity;
7
use Lagdo\DbAdmin\Driver\Entity\IndexEntity;
8
use Lagdo\DbAdmin\Driver\Entity\ForeignKeyEntity;
9
use Lagdo\DbAdmin\Driver\Entity\TriggerEntity;
10
11
use Lagdo\DbAdmin\Driver\Db\ConnectionInterface;
12
13
use Lagdo\DbAdmin\Driver\Db\Table as AbstractTable;
14
15
class Table extends AbstractTable
16
{
17
    /**
18
     * @param string $table
19
     *
20
     * @return array
21
     */
22
    private function queryStatus(string $table = '')
23
    {
24
        $query = "SELECT c.relname AS \"Name\", CASE c.relkind " .
25
            "WHEN 'r' THEN 'table' WHEN 'm' THEN 'materialized view' ELSE 'view' END AS \"Engine\", " .
26
            "pg_relation_size(c.oid) AS \"Data_length\", " .
27
            "pg_total_relation_size(c.oid) - pg_relation_size(c.oid) AS \"Index_length\", " .
28
            "obj_description(c.oid, 'pg_class') AS \"Comment\", " .
29
            ($this->driver->minVersion(12) ? "''" : "CASE WHEN c.relhasoids THEN 'oid' ELSE '' END") .
30
            " AS \"Oid\", c.reltuples as \"Rows\", n.nspname FROM pg_class c " .
31
            "JOIN pg_namespace n ON(n.nspname = current_schema() AND n.oid = c.relnamespace) " .
32
            "WHERE relkind IN ('r', 'm', 'v', 'f', 'p') " .
33
            ($table != "" ? "AND relname = " . $this->driver->quote($table) : "ORDER BY relname");
34
        return $this->driver->rows($query);
35
    }
36
37
    /**
38
     * @param array $row
39
     *
40
     * @return TableEntity
41
     */
42
    private function makeStatus(array $row)
43
    {
44
        $status = new TableEntity($row['Name']);
45
        $status->engine = $row['Engine'];
46
        $status->schema = $row['nspname'];
47
        $status->dataLength = $row['Data_length'];
48
        $status->indexLength = $row['Index_length'];
49
        $status->oid = $row['Oid'];
50
        $status->rows = $row['Rows'];
51
        $status->comment = $row['Comment'];
52
53
        return $status;
54
    }
55
56
    /**
57
     * @inheritDoc
58
     */
59
    public function tableStatus(string $table, bool $fast = false)
60
    {
61
        $rows = $this->queryStatus($table);
62
        if (!($row = reset($rows))) {
63
            return null;
64
        }
65
        return $this->makeStatus($row);
66
    }
67
68
    /**
69
     * @inheritDoc
70
     */
71
    public function tableStatuses(bool $fast = false)
72
    {
73
        $tables = [];
74
        $rows = $this->queryStatus();
75
        foreach ($rows as $row) {
76
            $tables[$row["Name"]] = $this->makeStatus($row);
77
        }
78
        return $tables;
79
    }
80
81
    /**
82
     * @inheritDoc
83
     */
84
    public function tableNames()
85
    {
86
        $tables = [];
87
        $rows = $this->queryStatus();
88
        foreach ($rows as $row) {
89
            $tables[] = $row["Name"];
90
        }
91
        return $tables;
92
    }
93
94
    /**
95
     * @inheritDoc
96
     */
97
    public function isView(TableEntity $tableStatus)
98
    {
99
        return in_array($tableStatus->engine, ["view", "materialized view"]);
100
    }
101
102
    /**
103
     * @inheritDoc
104
     */
105
    public function supportForeignKeys(TableEntity $tableStatus)
106
    {
107
        return true;
108
    }
109
110
    /**
111
     * @inheritDoc
112
     */
113
    public function referencableTables(string $table)
114
    {
115
        $fields = []; // table_name => [field]
116
        foreach ($this->tableNames() as $tableName) {
117
            if ($tableName === $table) {
118
                continue;
119
            }
120
            foreach ($this->fields($tableName) as $field) {
121
                if ($field->primary) {
122
                    if (!isset($fields[$tableName])) {
123
                        $fields[$tableName] = $field;
124
                    } else {
125
                        // No multi column primary key
126
                        $fields[$tableName] = null;
127
                    }
128
                }
129
            }
130
        }
131
        return array_filter($fields, function($field) {
132
            return $field !== null;
133
        });
134
    }
135
136
    /**
137
     * Get the primary key of a table
138
     * Same as indexes(), but the columns of the primary key are returned in a array
139
     *
140
     * @param string $table
141
     *
142
     * @return array
143
     */
144
    private function primaryKeyColumns(string $table)
145
    {
146
        $indexes = [];
147
        $table_oid = $this->connection->result("SELECT oid FROM pg_class WHERE " .
148
            "relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema()) " .
149
            "AND relname = " . $this->driver->quote($table));
150
        $columns = $this->driver->keyValues("SELECT attnum, attname FROM pg_attribute WHERE " .
151
            "attrelid = $table_oid AND attnum > 0");
152
        foreach ($this->driver->rows("SELECT relname, indisunique::int, indisprimary::int, indkey, " .
153
            "indoption, (indpred IS NOT NULL)::int as indispartial FROM pg_index i, pg_class ci " .
154
            "WHERE i.indrelid = $table_oid AND ci.oid = i.indexrelid") as $row)
155
        {
156
            // $relname = $row["relname"];
157
            if ($row["indisprimary"]) {
158
                foreach (explode(" ", $row["indkey"]) as $indkey) {
159
                    $indexes[] = $columns[$indkey];
160
                }
161
            }
162
        }
163
        return $indexes;
164
    }
165
166
    /**
167
     * @inheritDoc
168
     */
169
    public function fields(string $table)
170
    {
171
        $fields = [];
172
        $aliases = [
173
            'timestamp without time zone' => 'timestamp',
174
            'timestamp with time zone' => 'timestamptz',
175
        ];
176
177
        // Primary keys
178
        $primaryKeyColumns = $this->primaryKeyColumns($table);
179
180
        $identity_column = $this->driver->minVersion(10) ? 'a.attidentity' : '0';
181
        $query = "SELECT a.attname AS field, format_type(a.atttypid, a.atttypmod) AS full_type, " .
182
            "pg_get_expr(d.adbin, d.adrelid) AS default, a.attnotnull::int, " .
183
            "col_description(c.oid, a.attnum) AS comment, $identity_column AS identity FROM pg_class c " .
184
            "JOIN pg_namespace n ON c.relnamespace = n.oid JOIN pg_attribute a ON c.oid = a.attrelid " .
185
            "LEFT JOIN pg_attrdef d ON c.oid = d.adrelid AND a.attnum = d.adnum WHERE c.relname = " .
186
            $this->driver->quote($table) .
187
            " AND n.nspname = current_schema() AND NOT a.attisdropped AND a.attnum > 0 ORDER BY a.attnum";
188
        foreach ($this->driver->rows($query) as $row)
189
        {
190
            $field = new TableFieldEntity();
191
192
            $field->name = $row["field"];
193
            $field->primary = \in_array($field->name, $primaryKeyColumns);
194
            $field->fullType = $row["full_type"];
195
            $field->default = $row["default"];
196
            $field->comment = $row["comment"];
197
            //! No collation, no info about primary keys
198
            preg_match('~([^([]+)(\((.*)\))?([a-z ]+)?((\[[0-9]*])*)$~', $field->fullType, $match);
199
            list(, $type, $length, $field->length, $addon, $array) = $match;
200
            $field->length .= $array;
201
            $check_type = $type . $addon;
202
            if (isset($aliases[$check_type])) {
203
                $field->type = $aliases[$check_type];
204
                $field->fullType = $field->type . $length . $array;
205
            } else {
206
                $field->type = $type;
207
                $field->fullType = $field->type . $length . $addon . $array;
208
            }
209
            if (in_array($row['identity'], ['a', 'd'])) {
210
                $field->default = 'GENERATED ' . ($row['identity'] == 'd' ? 'BY DEFAULT' : 'ALWAYS') . ' AS IDENTITY';
211
            }
212
            $field->null = !$row["attnotnull"];
213
            $field->autoIncrement = $row['identity'] || preg_match('~^nextval\(~i', $row["default"]);
214
            $field->privileges = ["insert" => 1, "select" => 1, "update" => 1];
215
            if (preg_match('~(.+)::[^,)]+(.*)~', $row["default"], $match)) {
216
                $match1 = $match[1] ?? '';
217
                $match10 = $match1[0] ?? '';
218
                $match2 = $match[2] ?? '';
219
                $field->default = ($match1 == "NULL" ? null :
220
                    (($match10 == "'" ? $this->driver->unescapeId($match1) : $match1) . $match2));
221
            }
222
223
            $fields[$field->name] = $field;
224
        }
225
        return $fields;
226
    }
227
228
    /**
229
     * @inheritDoc
230
     */
231
    public function indexes(string $table, ConnectionInterface $connection = null)
232
    {
233
        if (!$connection) {
234
            $connection = $this->connection;
235
        }
236
        $indexes = [];
237
        $table_oid = $connection->result("SELECT oid FROM pg_class WHERE " .
238
            "relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema()) " .
239
            "AND relname = " . $this->driver->quote($table));
240
        $columns = $this->driver->keyValues("SELECT attnum, attname FROM pg_attribute WHERE " .
241
            "attrelid = $table_oid AND attnum > 0", $connection);
242
        foreach ($this->driver->rows("SELECT relname, indisunique::int, indisprimary::int, indkey, " .
243
            "indoption, (indpred IS NOT NULL)::int as indispartial FROM pg_index i, pg_class ci " .
244
            "WHERE i.indrelid = $table_oid AND ci.oid = i.indexrelid", $connection) as $row)
245
        {
246
            $index = new IndexEntity();
247
248
            $relname = $row["relname"];
249
            $index->type = ($row["indispartial"] ? "INDEX" :
250
                ($row["indisprimary"] ? "PRIMARY" : ($row["indisunique"] ? "UNIQUE" : "INDEX")));
251
            $index->columns = [];
252
            foreach (explode(" ", $row["indkey"]) as $indkey) {
253
                $index->columns[] = $columns[$indkey];
254
            }
255
            $index->descs = [];
256
            foreach (explode(" ", $row["indoption"]) as $indoption) {
257
                $index->descs[] = ($indoption & 1 ? '1' : null); // 1 - INDOPTION_DESC
258
            }
259
            $index->lengths = [];
260
261
            $indexes[$relname] = $index;
262
        }
263
        return $indexes;
264
    }
265
266
    /**
267
     * @inheritDoc
268
     */
269
    public function foreignKeys(string $table)
270
    {
271
        $foreignKeys = [];
272
        $onActions = $this->driver->actions();
273
        $query = "SELECT conname, condeferrable::int AS deferrable, pg_get_constraintdef(oid) " .
274
            "AS definition FROM pg_constraint WHERE conrelid = (SELECT pc.oid FROM pg_class AS pc " .
275
            "INNER JOIN pg_namespace AS pn ON (pn.oid = pc.relnamespace) WHERE pc.relname = " .
276
            $this->driver->quote($table) .
277
            " AND pn.nspname = current_schema()) AND contype = 'f'::char ORDER BY conkey, conname";
278
        foreach ($this->driver->rows($query) as $row) {
279
            if (preg_match('~FOREIGN KEY\s*\((.+)\)\s*REFERENCES (.+)\((.+)\)(.*)$~iA', $row['definition'], $match)) {
280
                $match1 = $match[1] ?? '';
281
                $match2 = $match[2] ?? '';
282
                $match3 = $match[3] ?? '';
283
                $match4 = $match[4] ?? '';
284
                $match11 = '';
285
286
                $foreignKey = new ForeignKeyEntity();
287
288
                $foreignKey->source = array_map('trim', explode(',', $match1));
289
                $foreignKey->target = array_map('trim', explode(',', $match3));
290
                $foreignKey->onDelete = preg_match("~ON DELETE ($onActions)~", $match4, $match10) ? $match11 : 'NO ACTION';
291
                $foreignKey->onUpdate = preg_match("~ON UPDATE ($onActions)~", $match4, $match10) ? $match11 : 'NO ACTION';
292
293
                if (preg_match('~^(("([^"]|"")+"|[^"]+)\.)?"?("([^"]|"")+"|[^"]+)$~', $match2, $match10)) {
294
                    // $match11 = $match10[1] ?? '';
295
                    $match12 = $match10[2] ?? '';
296
                    // $match13 = $match10[3] ?? '';
297
                    $match14 = $match10[4] ?? '';
298
                    $foreignKey->schema = str_replace('""', '"', preg_replace('~^"(.+)"$~', '\1', $match12));
299
                    $foreignKey->table = str_replace('""', '"', preg_replace('~^"(.+)"$~', '\1', $match14));
300
                }
301
302
                $foreignKeys[$row['conname']] = $foreignKey;
303
            }
304
        }
305
        return $foreignKeys;
306
    }
307
308
    /**
309
     * @inheritDoc
310
     */
311
    public function trigger(string $name, string $table = '')
312
    {
313
        if ($name == '') {
314
            return new TriggerEntity('', '', 'EXECUTE PROCEDURE ()');
315
        }
316
        if ($table === '') {
317
            $table = $this->util->input()->getTable();
318
        }
319
        $query = 'SELECT t.trigger_name AS "Trigger", t.action_timing AS "Timing", ' .
320
            '(SELECT STRING_AGG(event_manipulation, \' OR \') FROM information_schema.triggers ' .
321
            'WHERE event_object_table = t.event_object_table AND trigger_name = t.trigger_name ) AS "Events", ' .
322
            't.event_manipulation AS "Event", \'FOR EACH \' || t.action_orientation AS "Type", ' .
323
            't.action_statement AS "Statement" FROM information_schema.triggers t WHERE t.event_object_table = ' .
324
            $this->driver->quote($table) . ' AND t.trigger_name = ' . $this->driver->quote($name);
325
        $rows = $this->driver->rows($query);
326
        if (!($row = reset($rows))) {
327
            return null;
328
        }
329
        return new TriggerEntity($row['Timing'], $row['Event'], $row['Statement'], '', $row['Trigger']);
330
    }
331
332
    /**
333
     * @inheritDoc
334
     */
335
    public function triggers(string $table)
336
    {
337
        $triggers = [];
338
        $query = "SELECT * FROM information_schema.triggers WHERE trigger_schema = current_schema() " .
339
            "AND event_object_table = " . $this->driver->quote($table);
340
        foreach ($this->driver->rows($query) as $row) {
341
            $triggers[$row["trigger_name"]] = new TriggerEntity($row["action_timing"],
342
                $row["event_manipulation"], '', '', $row["trigger_name"]);
343
        }
344
        return $triggers;
345
    }
346
347
    /**
348
     * @inheritDoc
349
     */
350
    public function triggerOptions()
351
    {
352
        return [
353
            "Timing" => ["BEFORE", "AFTER"],
354
            "Event" => ["INSERT", "UPDATE", "DELETE"],
355
            "Type" => ["FOR EACH ROW", "FOR EACH STATEMENT"],
356
        ];
357
    }
358
359
    /**
360
     * @inheritDoc
361
     */
362
    public function tableHelp(string $name)
363
    {
364
        $links = [
365
            "information_schema" => "infoschema",
366
            "pg_catalog" => "catalog",
367
        ];
368
        $link = $links[$this->driver->schema()];
369
        if ($link) {
370
            return "$link-" . str_replace("_", "-", $name) . ".html";
371
        }
372
    }
373
}
374