Simpletest Coverage - includes/database/mysql/schema.inc

1 <?php
2 // $Id: schema.inc,v 1.23 2009/08/10 21:00:31 dries Exp $
3
4 /**
5 * @file
6 * Database schema code for MySQL database servers.
7 */
8
9
10 /**
11 * @ingroup schemaapi
12 * @{
13 */
14
15 class DatabaseSchema_mysql extends DatabaseSchema {
16
17 /**
18 * Maximum length of a table comment in MySQL.
19 */
20 const COMMENT_MAX_TABLE = 60;
21
22 /**
23 * Maximum length of a column comment in MySQL.
24 */
25 const COMMENT_MAX_COLUMN = 255;
26
27 /**
28 * Build a condition to match a table name against a standard information_schema.
29 *
30 * MySQL uses databases like schemas rather than catalogs so when we build
31 * a condition to query the information_schema.tables, we set the default
32 * database as the schema unless specified otherwise, and exclude table_catalog
33 * from the condition criteria.
34 */
35 protected function buildTableNameCondition($table_name, $operator = '=') {
36 $info = Database::getConnectionInfo();
37
38 if (strpos($table_name, '.')) {
39 list($schema, $table_name) = explode('.', $table_name);
40 }
41 else {
42 $schema = $info['default']['database'];
43 }
44
45 $condition = new DatabaseCondition('AND');
46 $condition->condition('table_schema', $schema);
47 $condition->condition('table_name', $table_name, $operator);
48 return $condition;
49 }
50
51 /**
52 * Generate SQL to create a new table from a Drupal schema definition.
53 *
54 * @param $name
55 * The name of the table to create.
56 * @param $table
57 * A Schema API table definition array.
58 * @return
59 * An array of SQL statements to create the table.
60 */
61 protected function createTableSql($name, $table) {
62 // Provide some defaults if needed
63 $table += array(
64 'mysql_engine' => 'InnoDB',
65 'mysql_character_set' => 'UTF8',
66 );
67
68 $sql = "CREATE TABLE {" . $name . "} (\n";
69
70 // Add the SQL statement for each field.
71 foreach ($table['fields'] as $field_name => $field) {
72 $sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n";
73 }
74
75 // Process keys & indexes.
76 $keys = $this->createKeysSql($table);
77 if (count($keys)) {
78 $sql .= implode(", \n", $keys) . ", \n";
79 }
80
81 // Remove the last comma and space.
82 $sql = substr($sql, 0, -3) . "\n) ";
83
84 $sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set'];
85
86 // Add table comment.
87 if (!empty($table['description'])) {
88 $sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);
89 }
90
91 return array($sql);
92 }
93
94 /**
95 * Create an SQL string for a field to be used in table creation or alteration.
96 *
97 * Before passing a field out of a schema definition into this function it has
98 * to be processed by _db_process_field().
99 *
100 * @param $name
101 * Name of the field.
102 * @param $spec
103 * The field specification, as per the schema data structure format.
104 */
105 protected function createFieldSql($name, $spec) {
106 $sql = "`" . $name . "` " . $spec['mysql_type'];
107
108 if (in_array($spec['type'], array('varchar', 'char', 'text')) && isset($spec['length'])) {
109 $sql .= '(' . $spec['length'] . ')';
110 }
111 elseif (isset($spec['precision']) && isset($spec['scale'])) {
112 $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
113 }
114
115 if (!empty($spec['unsigned'])) {
116 $sql .= ' unsigned';
117 }
118
119 if (!empty($spec['not null'])) {
120 $sql .= ' NOT NULL';
121 }
122
123 if (!empty($spec['auto_increment'])) {
124 $sql .= ' auto_increment';
125 }
126
127 // $spec['default'] can be NULL, so we explicitly check for the key here.
128 if (array_key_exists('default', $spec)) {
129 if (is_string($spec['default'])) {
130 $spec['default'] = "'" . $spec['default'] . "'";
131 }
132 elseif (is_null($spec['default'])) {
133 $spec['default'] = 'NULL';
134 }
135 $sql .= ' DEFAULT ' . $spec['default'];
136 }
137
138 if (empty($spec['not null']) && !isset($spec['default'])) {
139 $sql .= ' DEFAULT NULL';
140 }
141
142 // Add column comment.
143 if (!empty($spec['description'])) {
144 $sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN);
145 }
146
147 return $sql;
148 }
149
150 /**
151 * Set database-engine specific properties for a field.
152 *
153 * @param $field
154 * A field description array, as specified in the schema documentation.
155 */
156 protected function processField($field) {
157
158 if (!isset($field['size'])) {
159 $field['size'] = 'normal';
160 }
161
162 // Set the correct database-engine specific datatype.
163 if (!isset($field['mysql_type'])) {
164 $map = db_type_map();
165 $field['mysql_type'] = $map[$field['type'] . ':' . $field['size']];
166 }
167
168 if ($field['type'] == 'serial') {
169 $field['auto_increment'] = TRUE;
170 }
171
172 return $field;
173 }
174
175 public function getFieldTypeMap() {
176 // Put :normal last so it gets preserved by array_flip. This makes
177 // it much easier for modules (such as schema.module) to map
178 // database types back into schema types.
179 // $map does not use drupal_static as its value never changes.
180 static $map = array(
181 'varchar:normal' => 'VARCHAR',
182 'char:normal' => 'CHAR',
183
184 'text:tiny' => 'TINYTEXT',
185 'text:small' => 'TINYTEXT',
186 'text:medium' => 'MEDIUMTEXT',
187 'text:big' => 'LONGTEXT',
188 'text:normal' => 'TEXT',
189
190 'serial:tiny' => 'TINYINT',
191 'serial:small' => 'SMALLINT',
192 'serial:medium' => 'MEDIUMINT',
193 'serial:big' => 'BIGINT',
194 'serial:normal' => 'INT',
195
196 'int:tiny' => 'TINYINT',
197 'int:small' => 'SMALLINT',
198 'int:medium' => 'MEDIUMINT',
199 'int:big' => 'BIGINT',
200 'int:normal' => 'INT',
201
202 'float:tiny' => 'FLOAT',
203 'float:small' => 'FLOAT',
204 'float:medium' => 'FLOAT',
205 'float:big' => 'DOUBLE',
206 'float:normal' => 'FLOAT',
207
208 'numeric:normal' => 'DECIMAL',
209
210 'blob:big' => 'LONGBLOB',
211 'blob:normal' => 'BLOB',
212
213 'datetime:normal' => 'DATETIME',
214 );
215 return $map;
216 }
217
218
219
220
221 protected function createKeysSql($spec) {
222 $keys = array();
223
224 if (!empty($spec['primary key'])) {
225 $keys[] = 'PRIMARY KEY (' . $this->createKeysSqlHelper($spec['primary key']) . ')';
226 }
227 if (!empty($spec['unique keys'])) {
228 foreach ($spec['unique keys'] as $key => $fields) {
229 $keys[] = 'UNIQUE KEY ' . $key . ' (' . $this->createKeysSqlHelper($fields) . ')';
230 }
231 }
232 if (!empty($spec['indexes'])) {
233 foreach ($spec['indexes'] as $index => $fields) {
234 $keys[] = 'INDEX ' . $index . ' (' . $this->createKeysSqlHelper($fields) . ')';
235 }
236 }
237
238 return $keys;
239 }
240
241 protected function createKeySql($fields) {
242 $ret = array();
243 foreach ($fields as $field) {
244 if (is_array($field)) {
245 $ret[] = $field[0] . '(' . $field[1] . ')';
246 }
247 else {
248 $ret[] = $field;
249 }
250 }
251 return implode(', ', $ret);
252 }
253
254 protected function createKeysSqlHelper($fields) {
255 $ret = array();
256 foreach ($fields as $field) {
257 if (is_array($field)) {
258 $ret[] = $field[0] . '(' . $field[1] . ')';
259 }
260 else {
261 $ret[] = $field;
262 }
263 }
264 return implode(', ', $ret);
265 }
266
267 public function renameTable(&$ret, $table, $new_name) {
268 $ret[] = update_sql('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}');
269 }
270
271 public function dropTable(&$ret, $table) {
272 $ret[] = update_sql('DROP TABLE {' . $table . '}');
273 }
274
275 public function addField(&$ret, $table, $field, $spec, $keys_new = array()) {
276 $fixnull = FALSE;
277 if (!empty($spec['not null']) && !isset($spec['default'])) {
278 $fixnull = TRUE;
279 $spec['not null'] = FALSE;
280 }
281 $query = 'ALTER TABLE {' . $table . '} ADD ';
282 $query .= $this->createFieldSql($field, $this->processField($spec));
283 if (count($keys_new)) {
284 $query .= ', ADD ' . implode(', ADD ', $this->createKeysSql($keys_new));
285 }
286 $ret[] = update_sql($query);
287 if (isset($spec['initial'])) {
288 // All this because update_sql does not support %-placeholders.
289 $sql = 'UPDATE {' . $table . '} SET ' . $field . ' = :value';
290 $result = db_query($sql, array(':value' => $spec['initial']));
291 $ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql . ' (' . $spec['initial'] . ')'));
292 }
293 if ($fixnull) {
294 $spec['not null'] = TRUE;
295 $this->changeField($ret, $table, $field, $field, $spec);
296 }
297 }
298
299 public function dropField(&$ret, $table, $field) {
300 $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP ' . $field);
301 }
302
303 public function fieldSetDefault(&$ret, $table, $field, $default) {
304 if (is_null($default)) {
305 $default = 'NULL';
306 }
307 else {
308 $default = is_string($default) ? "'$default'" : $default;
309 }
310
311 $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN ' . $field . ' SET DEFAULT ' . $default);
312 }
313
314 public function fieldSetNoDefault(&$ret, $table, $field) {
315 $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN ' . $field . ' DROP DEFAULT');
316 }
317
318 public function addPrimaryKey(&$ret, $table, $fields) {
319 $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')');
320 }
321
322 public function dropPrimaryKey(&$ret, $table) {
323 $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP PRIMARY KEY');
324 }
325
326 public function addUniqueKey(&$ret, $table, $name, $fields) {
327 $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD UNIQUE KEY ' . $name . ' (' . $this->createKeySql($fields) . ')');
328 }
329
330 public function dropUniqueKey(&$ret, $table, $name) {
331 $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP KEY ' . $name);
332 }
333
334 public function addIndex(&$ret, $table, $name, $fields) {
335 $query = 'ALTER TABLE {' . $table . '} ADD INDEX ' . $name . ' (' . $this->createKeySql($fields) . ')';
336 $ret[] = update_sql($query);
337 }
338
339 public function dropIndex(&$ret, $table, $name) {
340 $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP INDEX ' . $name);
341 }
342
343 public function changeField(&$ret, $table, $field, $field_new, $spec, $keys_new = array()) {
344 $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));
345 if (count($keys_new)) {
346 $sql .= ', ADD ' . implode(', ADD ', $this->createKeysSql($keys_new));
347 }
348 $ret[] = update_sql($sql);
349 }
350
351 public function prepareComment($comment, $length = NULL) {
352 // Work around a bug in some versions of PDO, see http://bugs.php.net/bug.php?id=41125
353 $comment = str_replace("'", '’', $comment);
354
355 // Truncate comment to maximum comment length.
356 if (isset($length)) {
357 // Add table prefixes before truncating.
358 $comment = truncate_utf8($this->connection->prefixTables($comment), $length, TRUE, TRUE);
359 }
360
361 return $this->connection->quote($comment);
362 }
363
364 /**
365 * Retrieve a table or column comment.
366 */
367 public function getComment($table, $column = NULL) {
368 $condition = $this->buildTableNameCondition($this->connection->prefixTables('{' . $table . '}'));
369 if (isset($column)) {
370 $condition->condition('column_name', $column);
371 $condition->compile($this->connection);
372 // Don't use {} around information_schema.columns table.
373 return db_query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
374 }
375 $condition->compile($this->connection);
376 // Don't use {} around information_schema.tables table.
377 $comment = db_query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
378 // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
379 return preg_replace('/; InnoDB free:.*$/', '', $comment);
380 }
381
382 }
383
384 /**
385 * @} End of "ingroup schemaapi".
386 */
387

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.