3 namespace wcf\system\database\editor
;
5 use wcf\system\database\exception\DatabaseQueryExecutionException
;
6 use wcf\system\database\util\PreparedStatementConditionBuilder
;
10 * Database editor implementation for MySQL4.1 or higher.
13 * @copyright 2001-2019 WoltLab GmbH
14 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
15 * @package WoltLabSuite\Core\System\Database\Editor
17 class MySQLDatabaseEditor
extends DatabaseEditor
22 public function getTableNames()
25 $sql = "SHOW TABLES FROM `" . $this->dbObj
->getDatabaseName() . "`";
26 $statement = $this->dbObj
->prepareStatement($sql);
27 $statement->execute();
28 while ($row = $statement->fetchArray(\PDO
::FETCH_NUM
)) {
29 $existingTables[] = $row[0];
32 return $existingTables;
38 public function getColumns($tableName)
41 $regex = new Regex('([a-z]+)\((.+)\)', Regex
::CASE_INSENSITIVE
);
43 $sql = "SHOW COLUMNS FROM `" . $tableName . "`";
44 $statement = $this->dbObj
->prepareStatement($sql);
45 $statement->execute();
46 while ($row = $statement->fetchArray()) {
47 $regex->match($row['Type']);
48 $typeMatches = $regex->getMatches();
54 if (!empty($typeMatches)) {
55 $type = $typeMatches[1];
60 $enumValues = $typeMatches[2];
66 $pieces = \
explode(',', $typeMatches[2]);
67 switch (\
count($pieces)) {
73 [$length, $decimals] = $pieces;
80 if ($typeMatches[2] == (int)$typeMatches[2]) {
81 $length = $typeMatches[2];
88 'name' => $row['Field'],
92 'notNull' => $row['Null'] == 'YES' ?
false : true,
93 'key' => ($row['Key'] == 'PRI') ?
'PRIMARY' : (($row['Key'] == 'UNI') ?
'UNIQUE' : ''),
94 'default' => $row['Default'],
95 'autoIncrement' => $row['Extra'] == 'auto_increment' ?
true : false,
96 'enumValues' => $enumValues,
97 'decimals' => $decimals,
108 public function getForeignKeys($tableName)
110 $sql = "SELECT CONSTRAINT_NAME, DELETE_RULE, UPDATE_RULE
111 FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
112 WHERE CONSTRAINT_SCHEMA = ?
114 $statement = $this->dbObj
->prepareStatement($sql);
115 $statement->execute([
116 $this->dbObj
->getDatabaseName(),
119 $referentialConstraints = $statement->fetchAll(\PDO
::FETCH_ASSOC
);
121 $validActions = ['CASCADE', 'SET NULL', 'NO ACTION'];
124 foreach ($referentialConstraints as $information) {
125 $foreignKeys[$information['CONSTRAINT_NAME']] = [
127 'referencedColumns' => [],
128 'ON DELETE' => \
in_array(
129 $information['DELETE_RULE'],
131 ) ?
$information['DELETE_RULE'] : null,
132 'ON UPDATE' => \
in_array(
133 $information['UPDATE_RULE'],
135 ) ?
$information['UPDATE_RULE'] : null,
139 if (empty($foreignKeys)) {
143 $conditionBuilder = new PreparedStatementConditionBuilder();
144 $conditionBuilder->add('CONSTRAINT_NAME IN (?)', [\array_keys
($foreignKeys)]);
145 $conditionBuilder->add('TABLE_SCHEMA = ?', [$this->dbObj
->getDatabaseName()]);
146 $conditionBuilder->add('TABLE_NAME = ?', [$tableName]);
148 $sql = "SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
149 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
150 " . $conditionBuilder;
151 $statement = $this->dbObj
->prepareStatement($sql);
152 $statement->execute($conditionBuilder->getParameters());
153 $keyColumnUsage = $statement->fetchAll(\PDO
::FETCH_ASSOC
);
155 foreach ($keyColumnUsage as $information) {
156 $foreignKeys[$information['CONSTRAINT_NAME']]['columns'][] = $information['COLUMN_NAME'];
157 $foreignKeys[$information['CONSTRAINT_NAME']]['referencedColumns'][] = $information['REFERENCED_COLUMN_NAME'];
158 $foreignKeys[$information['CONSTRAINT_NAME']]['referencedTable'] = $information['REFERENCED_TABLE_NAME'];
161 foreach ($foreignKeys as $keyName => $keyData) {
162 $foreignKeys[$keyName]['columns'] = \array_unique
($foreignKeys[$keyName]['columns']);
163 $foreignKeys[$keyName]['referencedColumns'] = \array_unique
($foreignKeys[$keyName]['referencedColumns']);
172 public function getIndexInformation($tableName)
175 FROM `" . $tableName . "`";
176 $statement = $this->dbObj
->prepareStatement($sql);
177 $statement->execute();
178 $indices = $statement->fetchAll(\PDO
::FETCH_ASSOC
);
180 $indexInformation = [];
181 foreach ($indices as $index) {
182 if (!isset($indexInformation[$index['Key_name']])) {
184 if ($index['Index_type'] === 'FULLTEXT') {
186 } elseif ($index['Key_name'] === 'PRIMARY') {
188 } elseif ($index['Non_unique'] == 0) {
192 $indexInformation[$index['Key_name']] = [
193 'columns' => [$index['Column_name']],
197 $indexInformation[$index['Key_name']]['columns'][] = $index['Column_name'];
201 return $indexInformation;
207 public function getIndices($tableName)
210 $sql = "SHOW INDEX FROM `" . $tableName . "`";
211 $statement = $this->dbObj
->prepareStatement($sql);
212 $statement->execute();
213 while ($row = $statement->fetchArray()) {
214 $indices[] = $row['Key_name'];
217 return \array_unique
($indices);
223 public function createTable($tableName, $columns, $indices = [])
225 $columnDefinition = $indexDefinition = '';
227 // build column definition
228 foreach ($columns as $column) {
229 if (!empty($columnDefinition)) {
230 $columnDefinition .= ',';
232 $columnDefinition .= $this->buildColumnDefinition($column['name'], $column['data']);
235 // build index definition
236 foreach ($indices as $index) {
237 if (!empty($indexDefinition)) {
238 $indexDefinition .= ',';
240 $indexDefinition .= $this->buildIndexDefinition($index['name'], $index['data']);
244 $sql = "CREATE TABLE `" . $tableName . "` (
245 " . $columnDefinition . "
246 " . (!empty($indexDefinition) ?
',' : '') . "
247 " . $indexDefinition . "
248 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
249 $statement = $this->dbObj
->prepareStatement($sql);
250 $statement->execute();
256 public function dropTable($tableName)
258 $sql = "DROP TABLE IF EXISTS `" . $tableName . "`";
259 $statement = $this->dbObj
->prepareStatement($sql);
260 $statement->execute();
266 public function addColumn($tableName, $columnName, $columnData)
268 $sql = "ALTER TABLE `" . $tableName . "` ADD COLUMN " . $this->buildColumnDefinition($columnName, $columnData);
269 $statement = $this->dbObj
->prepareStatement($sql);
270 $statement->execute();
276 public function alterColumn($tableName, $oldColumnName, $newColumnName, $newColumnData)
278 $sql = "ALTER TABLE `" . $tableName . "` CHANGE COLUMN `" . $oldColumnName . "` " . $this->buildColumnDefinition(
282 $statement = $this->dbObj
->prepareStatement($sql);
283 $statement->execute();
289 public function alterColumns($tableName, $alterData)
292 foreach ($alterData as $columnName => $data) {
293 switch ($data['action']) {
295 $queries .= "ADD COLUMN {$this->buildColumnDefinition($columnName, $data['data'])},";
299 $newColumnName = $columnName;
300 if (isset($data['oldColumnName'])) {
302 * @deprecated 5.4 `oldColumnName` was an incorrect name for the index
303 * that is kept for backwards compatibility for now
305 $newColumnName = $data['oldColumnName'];
306 } elseif (isset($data['newColumnName'])) {
307 $newColumnName = $data['newColumnName'];
310 $queries .= "CHANGE COLUMN `{$columnName}` {$this->buildColumnDefinition($newColumnName, $data['data'])},";
314 $queries .= "DROP COLUMN `{$columnName}`,";
319 $this->dbObj
->prepareStatement("ALTER TABLE `{$tableName}` " . \rtrim
($queries, ','))->execute();
325 public function dropColumn($tableName, $columnName)
328 $sql = "ALTER TABLE `" . $tableName . "` DROP COLUMN `" . $columnName . "`";
329 $statement = $this->dbObj
->prepareStatement($sql);
330 $statement->execute();
331 } catch (DatabaseQueryExecutionException
$e) {
332 if ($e->getCode() != '42000') {
335 if (\
in_array($columnName, \array_column
($this->getColumns($tableName), 'name'))) {
344 public function addIndex($tableName, $indexName, $indexData)
346 $sql = "ALTER TABLE `" . $tableName . "` ADD " . $this->buildIndexDefinition($indexName, $indexData);
347 $statement = $this->dbObj
->prepareStatement($sql);
348 $statement->execute();
354 public function addForeignKey($tableName, $indexName, $indexData)
356 $sql = "ALTER TABLE `" . $tableName . "` ADD";
359 if (!empty($indexName)) {
360 $sql .= " CONSTRAINT `" . $indexName . "`";
364 $sql .= " FOREIGN KEY (`" . \
str_replace(',', '`,`', \
preg_replace('/\s+/', '', $indexData['columns'])) . "`)";
366 // add referenced table name
367 $sql .= " REFERENCES `" . $indexData['referencedTable'] . "`";
369 // add referenced columns
370 $sql .= " (`" . \
str_replace(',', '`,`', \
preg_replace('/\s+/', '', $indexData['referencedColumns'])) . "`)";
372 // add operation and action
373 if (!empty($indexData['operation'])) {
374 $sql .= " ON " . $indexData['operation'] . " " . $indexData['action'];
376 if (!empty($indexData['ON DELETE'])) {
377 $sql .= " ON DELETE " . $indexData['ON DELETE'];
379 if (!empty($indexData['ON UPDATE'])) {
380 $sql .= " ON UPDATE " . $indexData['ON UPDATE'];
383 $statement = $this->dbObj
->prepareStatement($sql);
384 $statement->execute();
390 public function dropIndex($tableName, $indexName)
393 $sql = "ALTER TABLE `" . $tableName . "` DROP INDEX `" . $indexName . "`";
394 $statement = $this->dbObj
->prepareStatement($sql);
395 $statement->execute();
396 } catch (DatabaseQueryExecutionException
$e) {
397 if ($e->getCode() != '42000') {
400 if (\
in_array($indexName, $this->getIndices($tableName))) {
409 public function dropPrimaryKey($tableName)
412 $sql = "ALTER TABLE " . $tableName . " DROP PRIMARY KEY";
413 $statement = $this->dbObj
->prepareStatement($sql);
414 $statement->execute();
415 } catch (DatabaseQueryExecutionException
$e) {
416 if ($e->getCode() != '42000') {
419 if (\
in_array("PRIMARY", $this->getIndices($tableName))) {
428 public function dropForeignKey($tableName, $indexName)
431 $sql = "ALTER TABLE `" . $tableName . "` DROP FOREIGN KEY `" . $indexName . "`";
432 $statement = $this->dbObj
->prepareStatement($sql);
433 $statement->execute();
434 } catch (DatabaseQueryExecutionException
$e) {
435 if ($e->getCode() != '42000') {
438 if (\
in_array($indexName, \array_keys
($this->getForeignKeys($tableName)))) {
445 * Builds a column definition for execution in a create table or alter table statement.
447 * @param string $columnName
448 * @param array $columnData
451 protected function buildColumnDefinition($columnName, $columnData)
454 $definition = "`" . $columnName . "`";
456 $definition .= " " . $columnData['type'];
458 // column length and decimals
459 if (!empty($columnData['length'])) {
460 $definition .= "(" . $columnData['length'] . (!empty($columnData['decimals']) ?
"," . $columnData['decimals'] : "") . ")";
463 if ($columnData['type'] == 'enum' && !empty($columnData['values'])) {
464 $definition .= "(" . $columnData['values'] . ")";
467 if (!empty($columnData['notNull'])) {
468 $definition .= " NOT NULL";
471 if (isset($columnData['default']) && $columnData['default'] !== '') {
472 $definition .= " DEFAULT " . $columnData['default'];
475 if (!empty($columnData['autoIncrement'])) {
476 $definition .= " AUTO_INCREMENT";
479 if (!empty($columnData['key'])) {
480 $definition .= " " . $columnData['key'] . " KEY";
487 * Builds a index definition for execution in a create table or alter table statement.
489 * @param string $indexName
490 * @param array $indexData
493 protected function buildIndexDefinition($indexName, $indexData)
496 if ($indexData['type'] == 'PRIMARY') {
497 $definition = "PRIMARY KEY";
498 } elseif ($indexData['type'] == 'UNIQUE') {
499 $definition = "UNIQUE KEY";
500 } elseif ($indexData['type'] == 'FULLTEXT') {
501 $definition = "FULLTEXT KEY";
507 if (!empty($indexName)) {
508 $definition .= " `" . $indexName . "`";
511 $definition .= " (`" . \
str_replace(',', '`,`', \
preg_replace('/\s+/', '', $indexData['columns'])) . "`)";