Resolve TODOs related to `wcf1_reaction_type` table
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / database / editor / MySQLDatabaseEditor.class.php
CommitLineData
11ade432
AE
1<?php
2namespace wcf\system\database\editor;
770f5de3 3use wcf\system\database\util\PreparedStatementConditionBuilder;
82a7de7f 4use wcf\system\Regex;
11ade432
AE
5
6/**
a17de04e
MS
7 * Database editor implementation for MySQL4.1 or higher.
8 *
11ade432 9 * @author Marcel Werk
7b7b9764 10 * @copyright 2001-2019 WoltLab GmbH
11ade432 11 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
e71525e4 12 * @package WoltLabSuite\Core\System\Database\Editor
11ade432
AE
13 */
14class MySQLDatabaseEditor extends DatabaseEditor {
15 /**
0fcfe5f6 16 * @inheritDoc
11ade432
AE
17 */
18 public function getTableNames() {
058cbd6a 19 $existingTables = [];
11ade432
AE
20 $sql = "SHOW TABLES FROM `".$this->dbObj->getDatabaseName()."`";
21 $statement = $this->dbObj->prepareStatement($sql);
22 $statement->execute();
23 while ($row = $statement->fetchArray(\PDO::FETCH_NUM)) {
24 $existingTables[] = $row[0];
25 }
26 return $existingTables;
27 }
28
29 /**
0fcfe5f6 30 * @inheritDoc
11ade432
AE
31 */
32 public function getColumns($tableName) {
058cbd6a 33 $columns = [];
f6e43f2f 34 $regex = new Regex('([a-z]+)\((.+)\)( unsigned)?', Regex::CASE_INSENSITIVE);
77bb36ca 35
4f2c4eb8 36 $sql = "SHOW COLUMNS FROM `".$tableName."`";
11ade432
AE
37 $statement = $this->dbObj->prepareStatement($sql);
38 $statement->execute();
39 while ($row = $statement->fetchArray()) {
14d48464 40 $regex->match($row['Type']);
41 $typeMatches = $regex->getMatches();
77bb36ca 42
4d2bd2ec
MS
43 $type = $row['Type'];
44 $length = '';
45 $decimals = '';
46 $enumValues = '';
f6e43f2f 47 $unsigned = false;
4d2bd2ec
MS
48 if (!empty($typeMatches)) {
49 $type = $typeMatches[1];
50
51 switch ($type) {
52 case 'enum':
53 case 'set':
54 $enumValues = $typeMatches[2];
55 break;
56
57 case 'decimal':
58 case 'double':
59 case 'float':
60 $pieces = explode(',', $typeMatches[2]);
61 switch (count($pieces)) {
62 case 1:
63 $length = $pieces[0];
64 break;
65
66 case 2:
67 list($length, $decimals) = $pieces;
68 break;
69 }
70
71 break;
72
73 default:
74 if ($typeMatches[2] == (int)$typeMatches[2]) {
75 $length = $typeMatches[2];
76 }
77 break;
78 }
f6e43f2f
MS
79
80 if (isset($typeMatches[3])) {
81 $unsigned = true;
82 }
4d2bd2ec
MS
83 }
84
058cbd6a 85 $columns[] = ['name' => $row['Field'], 'data' => [
4d2bd2ec
MS
86 'type' => $type,
87 'length' => $length,
88 'notNull' => $row['Null'] == 'YES' ? false : true,
63b9817b 89 'key' => ($row['Key'] == 'PRI') ? 'PRIMARY' : (($row['Key'] == 'UNI') ? 'UNIQUE' : ''),
5fe135db 90 'default' => $row['Default'],
4d2bd2ec
MS
91 'autoIncrement' => $row['Extra'] == 'auto_increment' ? true : false,
92 'enumValues' => $enumValues,
f6e43f2f
MS
93 'decimals' => $decimals,
94 'unsigned' => $unsigned
058cbd6a 95 ]];
ebe8e825 96 }
bf27dc91 97
9f959ced 98 return $columns;
11ade432
AE
99 }
100
dc62342b
MS
101 /**
102 * @inheritDoc
103 */
104 public function getForeignKeys($tableName) {
770f5de3
MS
105 $sql = "SELECT CONSTRAINT_NAME, DELETE_RULE, UPDATE_RULE
106 FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
107 WHERE CONSTRAINT_SCHEMA = ?
108 AND TABLE_NAME = ?";
dc62342b
MS
109 $statement = $this->dbObj->prepareStatement($sql);
110 $statement->execute([
111 $this->dbObj->getDatabaseName(),
112 $tableName
113 ]);
770f5de3 114 $referentialConstraints = $statement->fetchAll(\PDO::FETCH_ASSOC);
dc62342b
MS
115
116 $validActions = ['CASCADE', 'SET NULL', 'NO ACTION'];
117
118 $foreignKeys = [];
770f5de3
MS
119 foreach ($referentialConstraints as $information) {
120 $foreignKeys[$information['CONSTRAINT_NAME']] = [
121 'columns' => [],
122 'referencedColumns' => [],
123 'ON DELETE' => in_array($information['DELETE_RULE'], $validActions) ? $information['DELETE_RULE'] : null,
124 'ON UPDATE' => in_array($information['UPDATE_RULE'], $validActions) ? $information['UPDATE_RULE'] : null
125 ];
126 }
127
128 if (empty($foreignKeys)) {
129 return [];
130 }
131
132 $conditionBuilder = new PreparedStatementConditionBuilder();
133 $conditionBuilder->add('CONSTRAINT_NAME IN (?)', [array_keys($foreignKeys)]);
134 $conditionBuilder->add('TABLE_SCHEMA = ?', [$this->dbObj->getDatabaseName()]);
135 $conditionBuilder->add('TABLE_NAME = ?', [$tableName]);
136
137 $sql = "SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
138 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
139 " . $conditionBuilder;
140 $statement = $this->dbObj->prepareStatement($sql);
141 $statement->execute($conditionBuilder->getParameters());
142 $keyColumnUsage = $statement->fetchAll(\PDO::FETCH_ASSOC);
143
144 foreach ($keyColumnUsage as $information) {
145 $foreignKeys[$information['CONSTRAINT_NAME']]['columns'][] = $information['COLUMN_NAME'];
146 $foreignKeys[$information['CONSTRAINT_NAME']]['referencedColumns'][] = $information['REFERENCED_COLUMN_NAME'];
147 $foreignKeys[$information['CONSTRAINT_NAME']]['referencedTable'] = $information['REFERENCED_TABLE_NAME'];
dc62342b
MS
148 }
149
150 foreach ($foreignKeys as $keyName => $keyData) {
151 $foreignKeys[$keyName]['columns'] = array_unique($foreignKeys[$keyName]['columns']);
152 $foreignKeys[$keyName]['referencedColumns'] = array_unique($foreignKeys[$keyName]['referencedColumns']);
153 }
154
155 return $foreignKeys;
156 }
157
158 /**
159 * @inheritDoc
160 */
161 public function getIndexInformation($tableName) {
162 $sql = "SHOW INDEX
163 FROM `".$tableName."`";
164 $statement = $this->dbObj->prepareStatement($sql);
165 $statement->execute();
166 $indices = $statement->fetchAll(\PDO::FETCH_ASSOC);
167
168 $indexInformation = [];
169 foreach ($indices as $index) {
170 if (!isset($indexInformation[$index['Key_name']])) {
171 $type = null;
172 if ($index['Index_type'] === 'FULLTEXT') {
173 $type = 'FULLTEXT';
174 }
175 else if ($index['Key_name'] === 'PRIMARY') {
176 $type = 'PRIMARY';
177 }
178 else if ($index['Non_unique'] == 0) {
179 $type = 'UNIQUE';
180 }
181
182 $indexInformation[$index['Key_name']] = [
183 'columns' => [$index['Column_name']],
184 'type' => $type
185 ];
186 }
187 else {
188 $indexInformation[$index['Key_name']]['columns'][] = $index['Column_name'];
189 }
190 }
191
192 return $indexInformation;
193 }
194
11ade432 195 /**
0fcfe5f6 196 * @inheritDoc
11ade432
AE
197 */
198 public function getIndices($tableName) {
058cbd6a 199 $indices = [];
4f2c4eb8 200 $sql = "SHOW INDEX FROM `".$tableName."`";
11ade432
AE
201 $statement = $this->dbObj->prepareStatement($sql);
202 $statement->execute();
203 while ($row = $statement->fetchArray()) {
9f959ced
MS
204 $indices[] = $row['Key_name'];
205 }
206
f62c045d 207 return array_unique($indices);
11ade432
AE
208 }
209
210 /**
0fcfe5f6 211 * @inheritDoc
11ade432 212 */
058cbd6a 213 public function createTable($tableName, $columns, $indices = []) {
11ade432
AE
214 $columnDefinition = $indexDefinition = '';
215
216 // build column definition
217 foreach ($columns as $column) {
218 if (!empty($columnDefinition)) $columnDefinition .= ',';
219 $columnDefinition .= $this->buildColumnDefinition($column['name'], $column['data']);
220 }
221
222 // build index definition
223 $hasFulltextIndex = false;
224 foreach ($indices as $index) {
225 if (!empty($indexDefinition)) $indexDefinition .= ',';
226 $indexDefinition .= $this->buildIndexDefinition($index['name'], $index['data']);
227 if ($index['data']['type'] == 'FULLTEXT') $hasFulltextIndex = true;
228 }
229
230 // create table
4f2c4eb8 231 $sql = "CREATE TABLE `".$tableName."` (
11ade432
AE
232 ".$columnDefinition."
233 ".(!empty($indexDefinition) ? ',' : '')."
234 ".$indexDefinition."
ac677ff6 235 ) ENGINE=".($hasFulltextIndex ? 'MyISAM' : 'InnoDB')." DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
11ade432
AE
236 $statement = $this->dbObj->prepareStatement($sql);
237 $statement->execute();
238 }
239
240 /**
0fcfe5f6 241 * @inheritDoc
11ade432
AE
242 */
243 public function dropTable($tableName) {
4f2c4eb8 244 $sql = "DROP TABLE IF EXISTS `".$tableName."`";
11ade432
AE
245 $statement = $this->dbObj->prepareStatement($sql);
246 $statement->execute();
247 }
248
249 /**
0fcfe5f6 250 * @inheritDoc
11ade432
AE
251 */
252 public function addColumn($tableName, $columnName, $columnData) {
4f2c4eb8 253 $sql = "ALTER TABLE `".$tableName."` ADD COLUMN ".$this->buildColumnDefinition($columnName, $columnData);
11ade432
AE
254 $statement = $this->dbObj->prepareStatement($sql);
255 $statement->execute();
256 }
257
258 /**
0fcfe5f6 259 * @inheritDoc
11ade432
AE
260 */
261 public function alterColumn($tableName, $oldColumnName, $newColumnName, $newColumnData) {
4f2c4eb8 262 $sql = "ALTER TABLE `".$tableName."` CHANGE COLUMN `".$oldColumnName."` ".$this->buildColumnDefinition($newColumnName, $newColumnData);
11ade432
AE
263 $statement = $this->dbObj->prepareStatement($sql);
264 $statement->execute();
265 }
266
f6e43f2f
MS
267 /**
268 * @inheritDoc
269 */
270 public function alterColumns($tableName, $alterData) {
271 $queries = "";
272 foreach ($alterData as $columnName => $data) {
273 switch ($data['action']) {
274 case 'add':
275 $queries .= "ADD COLUMN {$this->buildColumnDefinition($columnName, $data['data'])},";
276 break;
277
278 case 'alter':
279 $queries .= "CHANGE COLUMN `{$columnName}` {$this->buildColumnDefinition($data['oldColumnName'], $data['data'])},";
280 break;
281
282 case 'drop':
283 $queries .= "DROP COLUMN `{$columnName}`,";
284 break;
285 }
286 }
287
288 $this->dbObj->prepareStatement("ALTER TABLE `{$tableName}` " . rtrim($queries, ','))->execute();
289 }
290
11ade432 291 /**
0fcfe5f6 292 * @inheritDoc
11ade432
AE
293 */
294 public function dropColumn($tableName, $columnName) {
4f2c4eb8 295 $sql = "ALTER TABLE `".$tableName."` DROP COLUMN `".$columnName."`";
11ade432
AE
296 $statement = $this->dbObj->prepareStatement($sql);
297 $statement->execute();
298 }
299
300 /**
0fcfe5f6 301 * @inheritDoc
11ade432
AE
302 */
303 public function addIndex($tableName, $indexName, $indexData) {
4f2c4eb8 304 $sql = "ALTER TABLE `".$tableName."` ADD ".$this->buildIndexDefinition($indexName, $indexData);
11ade432
AE
305 $statement = $this->dbObj->prepareStatement($sql);
306 $statement->execute();
307 }
308
309 /**
0fcfe5f6 310 * @inheritDoc
11ade432
AE
311 */
312 public function addForeignKey($tableName, $indexName, $indexData) {
4f2c4eb8 313 $sql = "ALTER TABLE `".$tableName."` ADD";
11ade432
AE
314
315 // add index name
96017679 316 if (!empty($indexName)) $sql .= " CONSTRAINT `".$indexName."`";
11ade432
AE
317
318 // add columns
4f2c4eb8 319 $sql .= " FOREIGN KEY (`".str_replace(',', '`,`', preg_replace('/\s+/', '', $indexData['columns']))."`)";
11ade432
AE
320
321 // add referenced table name
4f2c4eb8 322 $sql .= " REFERENCES `".$indexData['referencedTable']."`";
11ade432
AE
323
324 // add referenced columns
4f2c4eb8 325 $sql .= " (`".str_replace(',', '`,`', preg_replace('/\s+/', '', $indexData['referencedColumns']))."`)";
11ade432
AE
326
327 // add operation and action
328 if (!empty($indexData['operation'])) $sql .= " ON ".$indexData['operation']." ".$indexData['action'];
281ac362
TD
329 if (!empty($indexData['ON DELETE'])) $sql .= " ON DELETE ".$indexData['ON DELETE'];
330 if (!empty($indexData['ON UPDATE'])) $sql .= " ON UPDATE ".$indexData['ON UPDATE'];
11ade432
AE
331
332 $statement = $this->dbObj->prepareStatement($sql);
333 $statement->execute();
334 }
335
336 /**
0fcfe5f6 337 * @inheritDoc
11ade432
AE
338 */
339 public function dropIndex($tableName, $indexName) {
4f2c4eb8 340 $sql = "ALTER TABLE `".$tableName."` DROP INDEX `".$indexName."`";
11ade432
AE
341 $statement = $this->dbObj->prepareStatement($sql);
342 $statement->execute();
343 }
344
42a1e71f 345 /**
6b7d6653 346 * @inheritDoc
42a1e71f 347 */
348 public function dropPrimaryKey($tableName) {
349 $sql = "ALTER TABLE ".$tableName." DROP PRIMARY KEY";
350 $statement = $this->dbObj->prepareStatement($sql);
351 $statement->execute();
352 }
353
b6ae7d04 354 /**
0fcfe5f6 355 * @inheritDoc
b6ae7d04
MW
356 */
357 public function dropForeignKey($tableName, $indexName) {
4f2c4eb8 358 $sql = "ALTER TABLE `".$tableName."` DROP FOREIGN KEY `".$indexName."`";
b6ae7d04
MW
359 $statement = $this->dbObj->prepareStatement($sql);
360 $statement->execute();
361 }
362
11ade432
AE
363 /**
364 * Builds a column definition for execution in a create table or alter table statement.
365 *
366 * @param string $columnName
367 * @param array $columnData
71952a87 368 * @return string
11ade432
AE
369 */
370 protected function buildColumnDefinition($columnName, $columnData) {
371 // column name
4f2c4eb8 372 $definition = "`".$columnName."`";
11ade432
AE
373 // column type
374 $definition .= " ".$columnData['type'];
f6e43f2f
MS
375
376 if (!empty($columnData['unsigned'])) {
377 $definition .= ' UNSIGNED';
378 }
379
11ade432
AE
380 // column length and decimals
381 if (!empty($columnData['length'])) {
382 $definition .= "(".$columnData['length'].(!empty($columnData['decimals']) ? ",".$columnData['decimals'] : "").")";
383 }
384 // enum / set
385 if ($columnData['type'] == 'enum' && !empty($columnData['values'])) {
386 $definition .= "(".$columnData['values'].")";
387 }
388 // not null / null
389 if (!empty($columnData['notNull'])) $definition .= " NOT NULL";
390 // default
391 if (isset($columnData['default']) && $columnData['default'] !== '') $definition .= " DEFAULT ".$columnData['default'];
392 // auto_increment
393 if (!empty($columnData['autoIncrement'])) $definition .= " AUTO_INCREMENT";
394 // key
395 if (!empty($columnData['key'])) $definition .= " ".$columnData['key']." KEY";
396
397 return $definition;
398 }
399
400 /**
401 * Builds a index definition for execution in a create table or alter table statement.
402 *
403 * @param string $indexName
404 * @param array $indexData
71952a87 405 * @return string
11ade432
AE
406 */
407 protected function buildIndexDefinition($indexName, $indexData) {
11ade432
AE
408 // index type
409 if ($indexData['type'] == 'PRIMARY') $definition = "PRIMARY KEY";
410 else if ($indexData['type'] == 'UNIQUE') $definition = "UNIQUE KEY";
411 else if ($indexData['type'] == 'FULLTEXT') $definition = "FULLTEXT KEY";
412 else $definition = "KEY";
413
414 // index name
4f2c4eb8 415 if (!empty($indexName)) $definition .= " `".$indexName."`";
11ade432 416 // columns
4f2c4eb8 417 $definition .= " (`".str_replace(',', '`,`', preg_replace('/\s+/', '', $indexData['columns']))."`)";
11ade432
AE
418
419 return $definition;
420 }
421}