Merge remote-tracking branch 'upstream/master' into versionableDBO
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / database / editor / MySQLDatabaseEditor.class.php
CommitLineData
11ade432
AE
1<?php
2namespace wcf\system\database\editor;
76d74665 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
9f959ced 10 * @copyright 2001-2012 WoltLab GmbH
11ade432
AE
11 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
12 * @package com.woltlab.wcf
13 * @subpackage system.database.editor
9f959ced 14 * @category Community Framework
11ade432
AE
15 */
16class MySQLDatabaseEditor extends DatabaseEditor {
17 /**
9f959ced 18 * @see wcf\system\database\editor\DatabaseEditor::getTableNames()
11ade432
AE
19 */
20 public function getTableNames() {
21 $existingTables = array();
22 $sql = "SHOW TABLES FROM `".$this->dbObj->getDatabaseName()."`";
23 $statement = $this->dbObj->prepareStatement($sql);
24 $statement->execute();
25 while ($row = $statement->fetchArray(\PDO::FETCH_NUM)) {
26 $existingTables[] = $row[0];
27 }
28 return $existingTables;
29 }
30
31 /**
9f959ced 32 * @see wcf\system\database\editor\DatabaseEditor::getColumns()
11ade432
AE
33 */
34 public function getColumns($tableName) {
35 $columns = array();
36 $sql = "SHOW COLUMNS FROM ".$tableName;
37 $statement = $this->dbObj->prepareStatement($sql);
38 $statement->execute();
39 while ($row = $statement->fetchArray()) {
4d4a5125
K
40 $typeMatches = Regex::compile('([a-z]+)\(([0-9]+)\)', Regex::CASE_INSENSITIVE)->match($row['Type']);
41
42 $columns[] = array('name' => $row['Field'],
43 'data' => array(
44 'type' => $typeMatches[1],
45 'length' => $typeMatches[2],
46 'notNull' => (($row['Null'] == 'YES') ? true : false),
47 'key' => (($row['Key'] == 'PRI') ? 'PRIMARY' : (($row['Key'] == 'UNI') ? 'UNIQUE' : '')),
48 'default' => $row['Default'],
49 'autoIncrement' => ($row['Extra'] == 'auto_increment' ? true : false)
50 )
51 );
82a7de7f 52 }
9f959ced 53 return $columns;
11ade432
AE
54 }
55
56 /**
9f959ced 57 * @see wcf\system\database\editor\DatabaseEditor::getIndices()
11ade432
AE
58 */
59 public function getIndices($tableName) {
60 $indices = array();
61 $sql = "SHOW INDEX FROM ".$tableName;
62 $statement = $this->dbObj->prepareStatement($sql);
63 $statement->execute();
64 while ($row = $statement->fetchArray()) {
9f959ced
MS
65 $indices[] = $row['Key_name'];
66 }
67
68 return $indices;
11ade432
AE
69 }
70
71 /**
9f959ced 72 * @see wcf\system\database\editor\DatabaseEditor::createTable()
11ade432
AE
73 */
74 public function createTable($tableName, $columns, $indices = array()) {
75 $columnDefinition = $indexDefinition = '';
76
77 // build column definition
78 foreach ($columns as $column) {
79 if (!empty($columnDefinition)) $columnDefinition .= ',';
80 $columnDefinition .= $this->buildColumnDefinition($column['name'], $column['data']);
81 }
82
83 // build index definition
84 $hasFulltextIndex = false;
85 foreach ($indices as $index) {
86 if (!empty($indexDefinition)) $indexDefinition .= ',';
87 $indexDefinition .= $this->buildIndexDefinition($index['name'], $index['data']);
88 if ($index['data']['type'] == 'FULLTEXT') $hasFulltextIndex = true;
89 }
90
91 // create table
92 $sql = "CREATE TABLE ".$tableName." (
93 ".$columnDefinition."
94 ".(!empty($indexDefinition) ? ',' : '')."
95 ".$indexDefinition."
96 ) ENGINE=".($hasFulltextIndex ? 'MyISAM' : 'InnoDB')." DEFAULT CHARSET=utf8";
97 $statement = $this->dbObj->prepareStatement($sql);
98 $statement->execute();
99 }
100
101 /**
9f959ced 102 * @see wcf\system\database\editor\DatabaseEditor::dropTable()
11ade432
AE
103 */
104 public function dropTable($tableName) {
105 $sql = "DROP TABLE IF EXISTS ".$tableName;
106 $statement = $this->dbObj->prepareStatement($sql);
107 $statement->execute();
108 }
109
110 /**
9f959ced 111 * @see wcf\system\database\editor\DatabaseEditor::addColumn()
11ade432
AE
112 */
113 public function addColumn($tableName, $columnName, $columnData) {
114 $sql = "ALTER TABLE ".$tableName." ADD COLUMN ".$this->buildColumnDefinition($columnName, $columnData);
115 $statement = $this->dbObj->prepareStatement($sql);
116 $statement->execute();
117 }
118
119 /**
9f959ced 120 * @see wcf\system\database\editor\DatabaseEditor::alterColumn()
11ade432
AE
121 */
122 public function alterColumn($tableName, $oldColumnName, $newColumnName, $newColumnData) {
123 $sql = "ALTER TABLE ".$tableName." CHANGE COLUMN ".$oldColumnName." ".$this->buildColumnDefinition($newColumnName, $newColumnData);
124 $statement = $this->dbObj->prepareStatement($sql);
125 $statement->execute();
126 }
127
128 /**
9f959ced 129 * @see wcf\system\database\editor\DatabaseEditor::dropColumn()
11ade432
AE
130 */
131 public function dropColumn($tableName, $columnName) {
132 $sql = "ALTER TABLE ".$tableName." DROP COLUMN ".$columnName;
133 $statement = $this->dbObj->prepareStatement($sql);
134 $statement->execute();
135 }
136
137 /**
9f959ced 138 * @see wcf\system\database\editor\DatabaseEditor::addIndex()
11ade432
AE
139 */
140 public function addIndex($tableName, $indexName, $indexData) {
141 $sql = "ALTER TABLE ".$tableName." ADD ".$this->buildIndexDefinition($indexName, $indexData);
142 $statement = $this->dbObj->prepareStatement($sql);
143 $statement->execute();
144 }
145
146 /**
9f959ced 147 * @see wcf\system\database\editor\DatabaseEditor::addIndex()
11ade432
AE
148 */
149 public function addForeignKey($tableName, $indexName, $indexData) {
150 $sql = "ALTER TABLE ".$tableName." ADD";
151
152 // add index name
96017679 153 if (!empty($indexName)) $sql .= " CONSTRAINT `".$indexName."`";
11ade432
AE
154
155 // add columns
156 $sql .= " FOREIGN KEY (".str_replace(',', ',', preg_replace('/\s+/', '', $indexData['columns'])).")";
157
158 // add referenced table name
159 $sql .= " REFERENCES ".$indexData['referencedTable'];
160
161 // add referenced columns
162 $sql .= " (".str_replace(',', ',', preg_replace('/\s+/', '', $indexData['referencedColumns'])).")";
163
164 // add operation and action
165 if (!empty($indexData['operation'])) $sql .= " ON ".$indexData['operation']." ".$indexData['action'];
166
167 $statement = $this->dbObj->prepareStatement($sql);
168 $statement->execute();
169 }
170
171 /**
9f959ced 172 * @see wcf\system\database\editor\DatabaseEditor::dropIndex()
11ade432
AE
173 */
174 public function dropIndex($tableName, $indexName) {
175 $sql = "ALTER TABLE ".$tableName." DROP INDEX ".$indexName;
176 $statement = $this->dbObj->prepareStatement($sql);
177 $statement->execute();
178 }
179
b6ae7d04 180 /**
9f959ced 181 * @see wcf\system\database\editor\DatabaseEditor::dropForeignKey()
b6ae7d04
MW
182 */
183 public function dropForeignKey($tableName, $indexName) {
96017679 184 $sql = "ALTER TABLE ".$tableName." DROP FOREIGN KEY `".$indexName."`";
b6ae7d04
MW
185 $statement = $this->dbObj->prepareStatement($sql);
186 $statement->execute();
187 }
188
11ade432
AE
189 /**
190 * Builds a column definition for execution in a create table or alter table statement.
191 *
192 * @param string $columnName
193 * @param array $columnData
194 * @param string
195 */
196 protected function buildColumnDefinition($columnName, $columnData) {
197 // column name
198 $definition = $columnName;
199 // column type
200 $definition .= " ".$columnData['type'];
201 // column length and decimals
202 if (!empty($columnData['length'])) {
203 $definition .= "(".$columnData['length'].(!empty($columnData['decimals']) ? ",".$columnData['decimals'] : "").")";
204 }
205 // enum / set
206 if ($columnData['type'] == 'enum' && !empty($columnData['values'])) {
207 $definition .= "(".$columnData['values'].")";
208 }
209 // not null / null
210 if (!empty($columnData['notNull'])) $definition .= " NOT NULL";
211 // default
212 if (isset($columnData['default']) && $columnData['default'] !== '') $definition .= " DEFAULT ".$columnData['default'];
213 // auto_increment
214 if (!empty($columnData['autoIncrement'])) $definition .= " AUTO_INCREMENT";
215 // key
216 if (!empty($columnData['key'])) $definition .= " ".$columnData['key']." KEY";
217
218 return $definition;
219 }
220
221 /**
222 * Builds a index definition for execution in a create table or alter table statement.
223 *
224 * @param string $indexName
225 * @param array $indexData
226 * @param string
227 */
228 protected function buildIndexDefinition($indexName, $indexData) {
229 $definition = "";
230 // index type
231 if ($indexData['type'] == 'PRIMARY') $definition = "PRIMARY KEY";
232 else if ($indexData['type'] == 'UNIQUE') $definition = "UNIQUE KEY";
233 else if ($indexData['type'] == 'FULLTEXT') $definition = "FULLTEXT KEY";
234 else $definition = "KEY";
235
236 // index name
237 if (!empty($indexName)) $definition .= " ".$indexName."";
238 // columns
239 $definition .= " (".str_replace(',', ',', preg_replace('/\s+/', '', $indexData['columns'])).")";
240
241 return $definition;
242 }
76d74665
AE
243
244 /**
245 * @see wcf\system\database\editor\DatabaseEditor::dropConflictedTables()
246 */
247 public function dropConflictedTables(array $conflictedTables) {
248 $tables = array();
249 foreach ($conflictedTables as $tableName) {
250 $tables[$tableName] = array();
251 }
252
253 // get current database
254 $sql = "SELECT DATABASE() AS currentDB";
255 $statement = $this->dbObj->prepareStatement($sql);
256 $statement->execute();
257 $row = $statement->fetchArray();
258 $currentDB = $row['currentDB'];
259
260 // get constraints
261 $conditions = new PreparedStatementConditionBuilder();
262 $conditions->add("TABLE_SCHEMA = ?", array($currentDB));
263 $conditions->add("TABLE_NAME IN (?)", array($conflictedTables));
264 $conditions->add("REFERENCED_TABLE_NAME IS NOT NULL");
265 $conditions->add("CONSTRAINT_NAME LIKE ?", array('%_fk'));
266
267 $sql = "SELECT CONSTRAINT_NAME, TABLE_NAME
268 FROM information_schema.KEY_COLUMN_USAGE
269 ".$conditions;
270 $statement = $this->dbObj->prepareStatement($sql);
271 $statement->execute($conditions->getParameters());
272 while ($row = $statement->fetchArray()) {
273 $this->tables[$row['TABLE_NAME']][] = $row['CONSTRAINT_NAME'];
274 }
275
a1517977
AE
276 // handle foreign keys from 3rd party tables
277 $conditions = new PreparedStatementConditionBuilder();
278 $conditions->add("TABLE_SCHEMA = ?", array($currentDB));
279 $conditions->add("REFERENCED_TABLE_NAME IN (?)", array($conflictedTables));
280 $conditions->add("CONSTRAINT_NAME LIKE ?", array('%_fk'));
281
282 $sql = "SELECT CONSTRAINT_NAME, TABLE_NAME
283 FROM information_schema.KEY_COLUMN_USAGE
284 ".$conditions;
285 $statement = $this->dbObj->prepareStatement($sql);
286 $statement->execute($conditions->getParameters());
287 $foreignConstraints = array();
288 while ($row = $statement->fetchArray()) {
289 if (!isset($foreignConstraints[$row['TABLE_NAME']])) {
290 $foreignConstraints[$row['TABLE_NAME']] = array();
291 }
292
293 $foreignConstraints[$row['TABLE_NAME']][] = $row['CONSTRAINT_NAME'];
294 }
295
296 // drop foreign keys from 3rd party tables
297 foreach ($foreignConstraints as $tableName => $foreignKeys) {
298 foreach ($foreignKeys as $fk) {
299 $this->dropForeignKey($tableName, $fk);
300 }
301 }
302
76d74665
AE
303 // drop foreign keys
304 foreach ($this->tables as $tableName => $foreignKeys) {
305 foreach ($foreignKeys as $fk) {
306 $this->dropForeignKey($tableName, $fk);
307 }
308 }
309
310 // drop tables
311 foreach (array_keys($this->tables) as $tableName) {
312 $this->dropTable($tableName);
313 }
314 }
11ade432 315}