Passed
Push — main ( baf5f1...d136b0 )
by Thierry
01:41
created

Table::foreignKeys()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 34
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 25
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 34
rs 8.5866
1
<?php
2
3
namespace Lagdo\DbAdmin\Driver\MySql\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 bool $fast
19
     * @param string $table
20
     *
21
     * @return array
22
     */
23
    private function queryStatus(bool $fast, string $table = '')
24
    {
25
        $query = ($fast && $this->driver->minVersion(5)) ?
26
            "SELECT TABLE_NAME AS Name, ENGINE AS Engine, TABLE_COMMENT AS Comment " .
27
            "FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() " .
28
            ($table != "" ? "AND TABLE_NAME = " . $this->driver->quote($table) : "ORDER BY Name") :
0 ignored issues
show
Bug introduced by
The method quote() does not exist on Lagdo\DbAdmin\Driver\DriverInterface. Did you maybe mean quoteBinary()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

28
            ($table != "" ? "AND TABLE_NAME = " . $this->driver->/** @scrutinizer ignore-call */ quote($table) : "ORDER BY Name") :

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
29
            "SHOW TABLE STATUS" . ($table != "" ? " LIKE " . $this->driver->quote(addcslashes($table, "%_\\")) : "");
30
        return $this->driver->rows($query);
31
    }
32
33
    /**
34
     * @param array $row
35
     *
36
     * @return TableEntity
37
     */
38
    private function makeStatus(array $row)
39
    {
40
        $status = new TableEntity($row['Name']);
41
        $status->engine = $row['Engine'];
42
        if ($row["Engine"] == "InnoDB") {
43
            // ignore internal comment, unnecessary since MySQL 5.1.21
44
            $status->comment = preg_replace('~(?:(.+); )?InnoDB free: .*~', '\1', $row["Comment"]);
45
        }
46
        // if (!isset($row["Engine"])) {
47
        //     $row["Comment"] = "";
48
        // }
49
50
        return $status;
51
    }
52
53
    /**
54
     * @inheritDoc
55
     */
56
    public function tableStatus(string $table, bool $fast = false)
57
    {
58
        $rows = $this->queryStatus($fast, $table);
59
        if (!($row = reset($rows))) {
60
            return null;
61
        }
62
        return $this->makeStatus($row);
63
    }
64
65
    /**
66
     * @inheritDoc
67
     */
68
    public function tableStatuses(bool $fast = false)
69
    {
70
        $tables = [];
71
        $rows = $this->queryStatus($fast);
72
        foreach ($rows as $row) {
73
            $tables[$row["Name"]] = $this->makeStatus($row);
74
        }
75
        return $tables;
76
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81
    public function tableNames()
82
    {
83
        $tables = [];
84
        $rows = $this->queryStatus(true);
85
        foreach ($rows as $row) {
86
            $tables[] = $row["Name"];
87
        }
88
        return $tables;
89
    }
90
91
    /**
92
     * @inheritDoc
93
     */
94
    public function fields(string $table)
95
    {
96
        $fields = [];
97
        foreach ($this->driver->rows("SHOW FULL COLUMNS FROM " . $this->driver->table($table)) as $row) {
98
            preg_match('~^([^( ]+)(?:\((.+)\))?( unsigned)?( zerofill)?$~', $row["Type"], $match);
99
            $match = array_pad($match, 5, '');
100
101
            $field = new TableFieldEntity();
102
103
            $field->name = $row["Field"];
104
            $field->fullType = $row["Type"];
105
            $field->type = $match[1];
106
            $field->length = intval($match[2]);
107
            $field->unsigned = ltrim($match[3] . $match[4]);
108
            $field->default = ($row["Default"] != "" || preg_match("~char|set~", $match[1]) ?
109
                (preg_match('~text~', $match[1]) ? stripslashes(preg_replace("~^'(.*)'\$~", '\1',
110
                $row["Default"])) : $row["Default"]) : null);
111
            $field->null = ($row["Null"] == "YES");
112
            $field->autoIncrement = ($row["Extra"] == "auto_increment");
113
            $field->onUpdate = (preg_match('~^on update (.+)~i', $row["Extra"], $match) ? $match[1] : ""); //! available since MySQL 5.1.23
114
            $field->collation = $row["Collation"];
115
            $field->privileges = array_flip(preg_split('~, *~', $row["Privileges"]));
116
            $field->comment = $row["Comment"];
117
            $field->primary = ($row["Key"] == "PRI");
118
            // https://mariadb.com/kb/en/library/show-columns/
119
            // https://github.com/vrana/adminer/pull/359#pullrequestreview-276677186
120
            $field->generated = preg_match('~^(VIRTUAL|PERSISTENT|STORED)~', $row["Extra"]) > 0;
121
122
            $fields[$field->name] = $field;
123
        }
124
        return $fields;
125
    }
126
127
    /**
128
     * @inheritDoc
129
     */
130
    public function supportForeignKeys(TableEntity $tableStatus)
131
    {
132
        return preg_match('~InnoDB|IBMDB2I~i', $tableStatus->engine)
133
            || (preg_match('~NDB~i', $tableStatus->engine) && $this->driver->minVersion(5.6));
134
    }
135
136
    /**
137
     * @inheritDoc
138
     */
139
    public function referencableTables(string $table)
140
    {
141
        $fields = []; // table_name => [field]
142
        foreach ($this->tableStatuses(true) as $tableName => $tableStatus) {
143
            if ($tableName === $table || !$this->supportForeignKeys($tableStatus)) {
144
                continue;
145
            }
146
            foreach ($this->fields($tableName) as $field) {
147
                if ($field->primary) {
148
                    if (!isset($fields[$tableName])) {
149
                        $fields[$tableName] = $field;
150
                    } else {
151
                        // No multi column primary key
152
                        $fields[$tableName] = null;
153
                    }
154
                }
155
            }
156
        }
157
        return array_filter($fields, function($field) {
158
            return $field !== null;
159
        });
160
    }
161
162
    /**
163
     * @inheritDoc
164
     */
165
    public function isView(TableEntity $tableStatus)
166
    {
167
        return $tableStatus->engine === null;
168
    }
169
170
    /**
171
     * @inheritDoc
172
     */
173
    public function indexes(string $table, ConnectionInterface $connection = null)
174
    {
175
        $indexes = [];
176
        foreach ($this->driver->rows("SHOW INDEX FROM " . $this->driver->table($table), $connection) as $row) {
177
            $index = new IndexEntity();
178
179
            $name = $row["Key_name"];
180
            $index->type = ($name == "PRIMARY" ? "PRIMARY" :
181
                ($row["Index_type"] == "FULLTEXT" ? "FULLTEXT" : ($row["Non_unique"] ?
182
                ($row["Index_type"] == "SPATIAL" ? "SPATIAL" : "INDEX") : "UNIQUE")));
183
            $index->columns[] = $row["Column_name"];
184
            $index->lengths[] = ($row["Index_type"] == "SPATIAL" ? null : $row["Sub_part"]);
185
            $index->descs[] = null;
186
187
            $indexes[$name] = $index;
188
        }
189
        return $indexes;
190
    }
191
192
    /**
193
     * @inheritDoc
194
     */
195
    public function foreignKeys(string $table)
196
    {
197
        static $pattern = '(?:`(?:[^`]|``)+`|"(?:[^"]|"")+")';
198
        $foreignKeys = [];
199
        $onActions = $this->driver->actions();
200
        $create_table = $this->connection->result("SHOW CREATE TABLE " . $this->driver->table($table), 1);
201
        if ($create_table) {
202
            preg_match_all("~CONSTRAINT ($pattern) FOREIGN KEY ?\\(((?:$pattern,? ?)+)\\) REFERENCES " .
203
                "($pattern)(?:\\.($pattern))? \\(((?:$pattern,? ?)+)\\)(?: ON DELETE ($onActions))" .
204
                "?(?: ON UPDATE ($onActions))?~", $create_table, $matches, PREG_SET_ORDER);
205
206
            foreach ($matches as $match) {
207
                $match = array_pad($match, 8, '');
208
209
                preg_match_all("~$pattern~", $match[2], $source);
210
                preg_match_all("~$pattern~", $match[5], $target);
211
212
                $foreignKey = new ForeignKeyEntity();
213
214
                $foreignKey->database = $this->driver->unescapeId($match[4] != "" ? $match[3] : $match[4]);
215
                $foreignKey->table = $this->driver->unescapeId($match[4] != "" ? $match[4] : $match[3]);
216
                $foreignKey->source = array_map(function ($idf) {
217
                    return $this->driver->unescapeId($idf);
218
                }, $source[0]);
219
                $foreignKey->target = array_map(function ($idf) {
220
                    return $this->driver->unescapeId($idf);
221
                }, $target[0]);
222
                $foreignKey->onDelete = $match[6] ?: "RESTRICT";
223
                $foreignKey->onUpdate = $match[7] ?: "RESTRICT";
224
225
                $foreignKeys[$this->driver->unescapeId($match[1])] = $foreignKey;
226
            }
227
        }
228
        return $foreignKeys;
229
    }
230
231
    /**
232
     * @inheritDoc
233
     */
234
    public function trigger(string $name, string $table = '')
235
    {
236
        if ($name == "") {
237
            return null;
238
        }
239
        $rows = $this->driver->rows("SHOW TRIGGERS WHERE `Trigger` = " . $this->driver->quote($name));
240
        if (!($row = reset($rows))) {
241
            return null;
242
        }
243
        return new TriggerEntity($row["Timing"], $row["Event"], '', '', $row["Trigger"]);
244
    }
245
246
    /**
247
     * @inheritDoc
248
     */
249
    public function triggers(string $table)
250
    {
251
        $triggers = [];
252
        foreach ($this->driver->rows("SHOW TRIGGERS LIKE " . $this->driver->quote(addcslashes($table, "%_\\"))) as $row) {
253
            $triggers[$row["Trigger"]] = new TriggerEntity($row["Timing"], $row["Event"], '', '', $row["Trigger"]);
254
        }
255
        return $triggers;
256
    }
257
258
    /**
259
     * @inheritDoc
260
     */
261
    public function triggerOptions()
262
    {
263
        return [
264
            "Timing" => ["BEFORE", "AFTER"],
265
            "Event" => ["INSERT", "UPDATE", "DELETE"],
266
            "Type" => ["FOR EACH ROW"],
267
        ];
268
    }
269
270
    /**
271
     * @inheritDoc
272
     */
273
    public function tableHelp(string $name)
274
    {
275
        $maria = preg_match('~MariaDB~', $this->connection->serverInfo());
276
        if ($this->driver->isInformationSchema($this->driver->database())) {
277
            return strtolower(($maria ? "information-schema-$name-table/" : str_replace("_", "-", $name) . "-table.html"));
278
        }
279
        if ($this->driver->database() == "mysql") {
280
            return ($maria ? "mysql$name-table/" : "system-database.html"); //! more precise link
281
        }
282
    }
283
}
284