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