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