Passed
Push — main ( 1130cd...e96663 )
by Thierry
04:33 queued 02:34
created

Table::tableStatus()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 7
rs 10
c 0
b 0
f 0
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") :
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 tablesStatuses(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 fields(string $table)
82
    {
83
        $fields = [];
84
        foreach ($this->driver->rows("SHOW FULL COLUMNS FROM " . $this->driver->table($table)) as $row) {
85
            preg_match('~^([^( ]+)(?:\((.+)\))?( unsigned)?( zerofill)?$~', $row["Type"], $match);
86
            $matchCount = count($match);
87
            $match1 = $matchCount > 1 ? $match[1] : '';
88
            $match2 = $matchCount > 2 ? $match[2] : '';
89
            $match3 = $matchCount > 3 ? $match[3] : '';
90
            $match4 = $matchCount > 4 ? $match[4] : '';
91
92
            $field = new TableFieldEntity();
93
94
            $field->name = $row["Field"];
95
            $field->fullType = $row["Type"];
96
            $field->type = $match1;
97
            $field->length = $match2;
0 ignored issues
show
Documentation Bug introduced by
It seems like $match2 can also be of type string. However, the property $length is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
98
            $field->unsigned = ltrim($match3 . $match4);
99
            $field->default = ($row["Default"] != "" || preg_match("~char|set~", $match1) ?
100
                (preg_match('~text~', $match1) ? stripslashes(preg_replace("~^'(.*)'\$~", '\1',
101
                $row["Default"])) : $row["Default"]) : null);
102
            $field->null = ($row["Null"] == "YES");
103
            $field->autoIncrement = ($row["Extra"] == "auto_increment");
104
            $field->onUpdate = (preg_match('~^on update (.+)~i', $row["Extra"], $match) ? $match1 : ""); //! available since MySQL 5.1.23
105
            $field->collation = $row["Collation"];
106
            $field->privileges = array_flip(preg_split('~, *~', $row["Privileges"]));
107
            $field->comment = $row["Comment"];
108
            $field->primary = ($row["Key"] == "PRI");
109
            // https://mariadb.com/kb/en/library/show-columns/
110
            // https://github.com/vrana/adminer/pull/359#pullrequestreview-276677186
111
            $field->generated = (preg_match('~^(VIRTUAL|PERSISTENT|STORED)~', $row["Extra"]));
0 ignored issues
show
Documentation Bug introduced by
The property $generated was declared of type boolean, but preg_match('~^(VIRTUAL|P...ORED)~', $row['Extra']) is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
112
113
            $fields[$field->name] = $field;
114
        }
115
        return $fields;
116
    }
117
118
    /**
119
     * @inheritDoc
120
     */
121
    public function supportForeignKeys(TableEntity $tableStatus)
122
    {
123
        return preg_match('~InnoDB|IBMDB2I~i', $tableStatus->engine)
124
            || (preg_match('~NDB~i', $tableStatus->engine) && $this->driver->minVersion(5.6));
125
    }
126
127
    /**
128
     * @inheritDoc
129
     */
130
    public function isView(TableEntity $tableStatus)
131
    {
132
        return $tableStatus->engine === null;
133
    }
134
135
    /**
136
     * @inheritDoc
137
     */
138
    public function indexes(string $table, ConnectionInterface $connection = null)
139
    {
140
        $indexes = [];
141
        foreach ($this->driver->rows("SHOW INDEX FROM " . $this->driver->table($table), $connection) as $row) {
142
            $index = new IndexEntity();
143
144
            $name = $row["Key_name"];
145
            $index->type = ($name == "PRIMARY" ? "PRIMARY" :
146
                ($row["Index_type"] == "FULLTEXT" ? "FULLTEXT" : ($row["Non_unique"] ?
147
                ($row["Index_type"] == "SPATIAL" ? "SPATIAL" : "INDEX") : "UNIQUE")));
148
            $index->columns[] = $row["Column_name"];
149
            $index->lengths[] = ($row["Index_type"] == "SPATIAL" ? null : $row["Sub_part"]);
150
            $index->descs[] = null;
151
152
            $indexes[$name] = $index;
153
        }
154
        return $indexes;
155
    }
156
157
    /**
158
     * @inheritDoc
159
     */
160
    public function foreignKeys(string $table)
161
    {
162
        static $pattern = '(?:`(?:[^`]|``)+`|"(?:[^"]|"")+")';
163
        $foreignKeys = [];
164
        $onActions = $this->driver->actions();
165
        $create_table = $this->connection->result("SHOW CREATE TABLE " . $this->driver->table($table), 1);
166
        if ($create_table) {
167
            preg_match_all("~CONSTRAINT ($pattern) FOREIGN KEY ?\\(((?:$pattern,? ?)+)\\) REFERENCES " .
168
                "($pattern)(?:\\.($pattern))? \\(((?:$pattern,? ?)+)\\)(?: ON DELETE ($onActions))" .
169
                "?(?: ON UPDATE ($onActions))?~", $create_table, $matches, PREG_SET_ORDER);
170
171
            foreach ($matches as $match) {
172
                $matchCount = count($match);
173
                $match1 = $matchCount > 1 ? $match[1] : '';
174
                $match2 = $matchCount > 2 ? $match[2] : '';
175
                $match3 = $matchCount > 3 ? $match[3] : '';
176
                $match4 = $matchCount > 4 ? $match[4] : '';
177
                $match5 = $matchCount > 5 ? $match[5] : '';
178
179
                preg_match_all("~$pattern~", $match2, $source);
180
                preg_match_all("~$pattern~", $match5, $target);
181
182
                $foreignKey = new ForeignKeyEntity();
183
184
                $foreignKey->database = $this->driver->unescapeId($match4 != "" ? $match3 : $match4);
185
                $foreignKey->table = $this->driver->unescapeId($match4 != "" ? $match4 : $match3);
186
                $foreignKey->source = array_map(function ($idf) {
187
                    return $this->driver->unescapeId($idf);
188
                }, $source[0]);
189
                $foreignKey->target = array_map(function ($idf) {
190
                    return $this->driver->unescapeId($idf);
191
                }, $target[0]);
192
                $foreignKey->onDelete = $matchCount > 6 ? $match[6] : "RESTRICT";
193
                $foreignKey->onUpdate = $matchCount > 7 ? $match[7] : "RESTRICT";
194
195
                $foreignKeys[$this->driver->unescapeId($match1)] = $foreignKey;
196
            }
197
        }
198
        return $foreignKeys;
199
    }
200
201
    /**
202
     * @inheritDoc
203
     */
204
    public function alterTable(string $table, string $name, array $fields, array $foreign,
205
        string $comment, string $engine, string $collation, int $autoIncrement, string $partitioning)
206
    {
207
        $alter = [];
208
        foreach ($fields as $field) {
209
            $alter[] = ($field[1] ? ($table != "" ? ($field[0] != "" ? "CHANGE " .
210
                $this->driver->escapeId($field[0]) : "ADD") : " ") . " " .
211
                implode($field[1]) . ($table != "" ? $field[2] : "") :
212
                "DROP " . $this->driver->escapeId($field[0])
213
            );
214
        }
215
        $alter = array_merge($alter, $foreign);
216
        $status = " COMMENT=" . $this->driver->quote($comment) .
217
            ($engine ? " ENGINE=" . $this->driver->quote($engine) : "") .
218
            ($collation ? " COLLATE " . $this->driver->quote($collation) : "") .
219
            ($autoIncrement != "" ? " AUTO_INCREMENT=$autoIncrement" : "");
220
        if ($table == "") {
221
            return $this->driver->execute("CREATE TABLE " . $this->driver->table($name) .
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver->ex....$status.$partitioning) also could return the type Lagdo\DbAdmin\Driver\Db\StatementInterface which is incompatible with the return type mandated by Lagdo\DbAdmin\Driver\Db\...Interface::alterTable() of boolean.
Loading history...
222
                " (\n" . implode(",\n", $alter) . "\n)$status$partitioning");
223
        }
224
        if ($table != $name) {
225
            $alter[] = "RENAME TO " . $this->driver->table($name);
226
        }
227
        if ($status) {
228
            $alter[] = ltrim($status);
229
        }
230
        return ($alter || $partitioning ? $this->driver->execute("ALTER TABLE " .
0 ignored issues
show
Bug Best Practice introduced by
The expression return $alter || $partit.... $partitioning) : true also could return the type Lagdo\DbAdmin\Driver\Db\StatementInterface which is incompatible with the return type mandated by Lagdo\DbAdmin\Driver\Db\...Interface::alterTable() of boolean.
Loading history...
231
            $this->driver->table($table) . "\n" . implode(",\n", $alter) . $partitioning) : true);
232
    }
233
234
    /**
235
     * @inheritDoc
236
     */
237
    public function alterIndexes(string $table, array $alter)
238
    {
239
        foreach ($alter as $key => $val) {
240
            $alter[$key] = (
241
                $val[2] == "DROP"
242
                ? "\nDROP INDEX " . $this->driver->escapeId($val[1])
243
                : "\nADD $val[0] " . ($val[0] == "PRIMARY" ? "KEY " : "") . ($val[1] != "" ? $this->driver->escapeId($val[1]) . " " : "") . "(" . implode(", ", $val[2]) . ")"
244
            );
245
        }
246
        return $this->driver->execute("ALTER TABLE " . $this->driver->table($table) . implode(",", $alter));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver->ex.... implode(',', $alter)) also could return the type Lagdo\DbAdmin\Driver\Db\StatementInterface which is incompatible with the return type mandated by Lagdo\DbAdmin\Driver\Db\...terface::alterIndexes() of boolean.
Loading history...
247
    }
248
249
    /**
250
     * @inheritDoc
251
     */
252
    public function trigger(string $trigger)
253
    {
254
        if ($trigger == "") {
255
            return null;
256
        }
257
        $rows = $this->driver->rows("SHOW TRIGGERS WHERE `Trigger` = " . $this->driver->quote($trigger));
258
        if (!($row = reset($rows))) {
259
            return null;
260
        }
261
        return new TriggerEntity($row["Timing"], $row["Event"]);
262
    }
263
264
    /**
265
     * @inheritDoc
266
     */
267
    public function triggers(string $table)
268
    {
269
        $triggers = [];
270
        foreach ($this->driver->rows("SHOW TRIGGERS LIKE " . $this->driver->quote(addcslashes($table, "%_\\"))) as $row) {
271
            $triggers[$row["Trigger"]] = new TriggerEntity($row["Timing"], $row["Event"]);
272
        }
273
        return $triggers;
274
    }
275
276
    /**
277
     * @inheritDoc
278
     */
279
    public function triggerOptions()
280
    {
281
        return [
282
            "Timing" => ["BEFORE", "AFTER"],
283
            "Event" => ["INSERT", "UPDATE", "DELETE"],
284
            "Type" => ["FOR EACH ROW"],
285
        ];
286
    }
287
288
    /**
289
     * @inheritDoc
290
     */
291
    public function tableHelp(string $name)
292
    {
293
        $maria = preg_match('~MariaDB~', $this->connection->serverInfo());
294
        if ($this->driver->isInformationSchema($this->driver->database())) {
295
            return strtolower(($maria ? "information-schema-$name-table/" : str_replace("_", "-", $name) . "-table.html"));
296
        }
297
        if ($this->driver->database() == "mysql") {
298
            return ($maria ? "mysql$name-table/" : "system-database.html"); //! more precise link
299
        }
300
    }
301
}
302