| Drupal | PHP Cross Reference | Content Management Systems |
1 <?php 2 3 /** 4 * @file 5 * Database schema code for MySQL database servers. 6 */ 7 8 9 /** 10 * @addtogroup schemaapi 11 * @{ 12 */ 13 14 class DatabaseSchema_mysql extends DatabaseSchema { 15 16 /** 17 * Maximum length of a table comment in MySQL. 18 */ 19 const COMMENT_MAX_TABLE = 60; 20 21 /** 22 * Maximum length of a column comment in MySQL. 23 */ 24 const COMMENT_MAX_COLUMN = 255; 25 26 /** 27 * Get information about the table and database name from the prefix. 28 * 29 * @return 30 * A keyed array with information about the database, table name and prefix. 31 */ 32 protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { 33 $info = array('prefix' => $this->connection->tablePrefix($table)); 34 if ($add_prefix) { 35 $table = $info['prefix'] . $table; 36 } 37 if (($pos = strpos($table, '.')) !== FALSE) { 38 $info['database'] = substr($table, 0, $pos); 39 $info['table'] = substr($table, ++$pos); 40 } 41 else { 42 $db_info = Database::getConnectionInfo(); 43 $info['database'] = $db_info['default']['database']; 44 $info['table'] = $table; 45 } 46 return $info; 47 } 48 49 /** 50 * Build a condition to match a table name against a standard information_schema. 51 * 52 * MySQL uses databases like schemas rather than catalogs so when we build 53 * a condition to query the information_schema.tables, we set the default 54 * database as the schema unless specified otherwise, and exclude table_catalog 55 * from the condition criteria. 56 */ 57 protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) { 58 $info = $this->connection->getConnectionOptions(); 59 60 $table_info = $this->getPrefixInfo($table_name, $add_prefix); 61 62 $condition = new DatabaseCondition('AND'); 63 $condition->condition('table_schema', $table_info['database']); 64 $condition->condition('table_name', $table_info['table'], $operator); 65 return $condition; 66 } 67 68 /** 69 * Generate SQL to create a new table from a Drupal schema definition. 70 * 71 * @param $name 72 * The name of the table to create. 73 * @param $table 74 * A Schema API table definition array. 75 * @return 76 * An array of SQL statements to create the table. 77 */ 78 protected function createTableSql($name, $table) { 79 $info = $this->connection->getConnectionOptions(); 80 81 // Provide defaults if needed. 82 $table += array( 83 'mysql_engine' => 'InnoDB', 84 'mysql_character_set' => 'utf8', 85 ); 86 87 $sql = "CREATE TABLE {" . $name . "} (\n"; 88 89 // Add the SQL statement for each field. 90 foreach ($table['fields'] as $field_name => $field) { 91 $sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n"; 92 } 93 94 // Process keys & indexes. 95 $keys = $this->createKeysSql($table); 96 if (count($keys)) { 97 $sql .= implode(", \n", $keys) . ", \n"; 98 } 99 100 // Remove the last comma and space. 101 $sql = substr($sql, 0, -3) . "\n) "; 102 103 $sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set']; 104 // By default, MySQL uses the default collation for new tables, which is 105 // 'utf8_general_ci' for utf8. If an alternate collation has been set, it 106 // needs to be explicitly specified. 107 // @see DatabaseConnection_mysql 108 if (!empty($info['collation'])) { 109 $sql .= ' COLLATE ' . $info['collation']; 110 } 111 112 // Add table comment. 113 if (!empty($table['description'])) { 114 $sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE); 115 } 116 117 return array($sql); 118 } 119 120 /** 121 * Create an SQL string for a field to be used in table creation or alteration. 122 * 123 * Before passing a field out of a schema definition into this function it has 124 * to be processed by _db_process_field(). 125 * 126 * @param $name 127 * Name of the field. 128 * @param $spec 129 * The field specification, as per the schema data structure format. 130 */ 131 protected function createFieldSql($name, $spec) { 132 $sql = "`" . $name . "` " . $spec['mysql_type']; 133 134 if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT'))) { 135 if (isset($spec['length'])) { 136 $sql .= '(' . $spec['length'] . ')'; 137 } 138 if (!empty($spec['binary'])) { 139 $sql .= ' BINARY'; 140 } 141 } 142 elseif (isset($spec['precision']) && isset($spec['scale'])) { 143 $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')'; 144 } 145 146 if (!empty($spec['unsigned'])) { 147 $sql .= ' unsigned'; 148 } 149 150 if (isset($spec['not null'])) { 151 if ($spec['not null']) { 152 $sql .= ' NOT NULL'; 153 } 154 else { 155 $sql .= ' NULL'; 156 } 157 } 158 159 if (!empty($spec['auto_increment'])) { 160 $sql .= ' auto_increment'; 161 } 162 163 // $spec['default'] can be NULL, so we explicitly check for the key here. 164 if (array_key_exists('default', $spec)) { 165 if (is_string($spec['default'])) { 166 $spec['default'] = "'" . $spec['default'] . "'"; 167 } 168 elseif (!isset($spec['default'])) { 169 $spec['default'] = 'NULL'; 170 } 171 $sql .= ' DEFAULT ' . $spec['default']; 172 } 173 174 if (empty($spec['not null']) && !isset($spec['default'])) { 175 $sql .= ' DEFAULT NULL'; 176 } 177 178 // Add column comment. 179 if (!empty($spec['description'])) { 180 $sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN); 181 } 182 183 return $sql; 184 } 185 186 /** 187 * Set database-engine specific properties for a field. 188 * 189 * @param $field 190 * A field description array, as specified in the schema documentation. 191 */ 192 protected function processField($field) { 193 194 if (!isset($field['size'])) { 195 $field['size'] = 'normal'; 196 } 197 198 // Set the correct database-engine specific datatype. 199 // In case one is already provided, force it to uppercase. 200 if (isset($field['mysql_type'])) { 201 $field['mysql_type'] = drupal_strtoupper($field['mysql_type']); 202 } 203 else { 204 $map = $this->getFieldTypeMap(); 205 $field['mysql_type'] = $map[$field['type'] . ':' . $field['size']]; 206 } 207 208 if (isset($field['type']) && $field['type'] == 'serial') { 209 $field['auto_increment'] = TRUE; 210 } 211 212 return $field; 213 } 214 215 public function getFieldTypeMap() { 216 // Put :normal last so it gets preserved by array_flip. This makes 217 // it much easier for modules (such as schema.module) to map 218 // database types back into schema types. 219 // $map does not use drupal_static as its value never changes. 220 static $map = array( 221 'varchar:normal' => 'VARCHAR', 222 'char:normal' => 'CHAR', 223 224 'text:tiny' => 'TINYTEXT', 225 'text:small' => 'TINYTEXT', 226 'text:medium' => 'MEDIUMTEXT', 227 'text:big' => 'LONGTEXT', 228 'text:normal' => 'TEXT', 229 230 'serial:tiny' => 'TINYINT', 231 'serial:small' => 'SMALLINT', 232 'serial:medium' => 'MEDIUMINT', 233 'serial:big' => 'BIGINT', 234 'serial:normal' => 'INT', 235 236 'int:tiny' => 'TINYINT', 237 'int:small' => 'SMALLINT', 238 'int:medium' => 'MEDIUMINT', 239 'int:big' => 'BIGINT', 240 'int:normal' => 'INT', 241 242 'float:tiny' => 'FLOAT', 243 'float:small' => 'FLOAT', 244 'float:medium' => 'FLOAT', 245 'float:big' => 'DOUBLE', 246 'float:normal' => 'FLOAT', 247 248 'numeric:normal' => 'DECIMAL', 249 250 'blob:big' => 'LONGBLOB', 251 'blob:normal' => 'BLOB', 252 ); 253 return $map; 254 } 255 256 protected function createKeysSql($spec) { 257 $keys = array(); 258 259 if (!empty($spec['primary key'])) { 260 $keys[] = 'PRIMARY KEY (' . $this->createKeysSqlHelper($spec['primary key']) . ')'; 261 } 262 if (!empty($spec['unique keys'])) { 263 foreach ($spec['unique keys'] as $key => $fields) { 264 $keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeysSqlHelper($fields) . ')'; 265 } 266 } 267 if (!empty($spec['indexes'])) { 268 foreach ($spec['indexes'] as $index => $fields) { 269 $keys[] = 'INDEX `' . $index . '` (' . $this->createKeysSqlHelper($fields) . ')'; 270 } 271 } 272 273 return $keys; 274 } 275 276 protected function createKeySql($fields) { 277 $return = array(); 278 foreach ($fields as $field) { 279 if (is_array($field)) { 280 $return[] = '`' . $field[0] . '`(' . $field[1] . ')'; 281 } 282 else { 283 $return[] = '`' . $field . '`'; 284 } 285 } 286 return implode(', ', $return); 287 } 288 289 protected function createKeysSqlHelper($fields) { 290 $return = array(); 291 foreach ($fields as $field) { 292 if (is_array($field)) { 293 $return[] = '`' . $field[0] . '`(' . $field[1] . ')'; 294 } 295 else { 296 $return[] = '`' . $field . '`'; 297 } 298 } 299 return implode(', ', $return); 300 } 301 302 public function renameTable($table, $new_name) { 303 if (!$this->tableExists($table)) { 304 throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name))); 305 } 306 if ($this->tableExists($new_name)) { 307 throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name))); 308 } 309 310 $info = $this->getPrefixInfo($new_name); 311 return $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO `' . $info['table'] . '`'); 312 } 313 314 public function dropTable($table) { 315 if (!$this->tableExists($table)) { 316 return FALSE; 317 } 318 319 $this->connection->query('DROP TABLE {' . $table . '}'); 320 return TRUE; 321 } 322 323 public function addField($table, $field, $spec, $keys_new = array()) { 324 if (!$this->tableExists($table)) { 325 throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table))); 326 } 327 if ($this->fieldExists($table, $field)) { 328 throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table))); 329 } 330 331 $fixnull = FALSE; 332 if (!empty($spec['not null']) && !isset($spec['default'])) { 333 $fixnull = TRUE; 334 $spec['not null'] = FALSE; 335 } 336 $query = 'ALTER TABLE {' . $table . '} ADD '; 337 $query .= $this->createFieldSql($field, $this->processField($spec)); 338 if ($keys_sql = $this->createKeysSql($keys_new)) { 339 $query .= ', ADD ' . implode(', ADD ', $keys_sql); 340 } 341 $this->connection->query($query); 342 if (isset($spec['initial'])) { 343 $this->connection->update($table) 344 ->fields(array($field => $spec['initial'])) 345 ->execute(); 346 } 347 if ($fixnull) { 348 $spec['not null'] = TRUE; 349 $this->changeField($table, $field, $field, $spec); 350 } 351 } 352 353 public function dropField($table, $field) { 354 if (!$this->fieldExists($table, $field)) { 355 return FALSE; 356 } 357 358 $this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`'); 359 return TRUE; 360 } 361 362 public function fieldSetDefault($table, $field, $default) { 363 if (!$this->fieldExists($table, $field)) { 364 throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field))); 365 } 366 367 if (!isset($default)) { 368 $default = 'NULL'; 369 } 370 else { 371 $default = is_string($default) ? "'$default'" : $default; 372 } 373 374 $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $default); 375 } 376 377 public function fieldSetNoDefault($table, $field) { 378 if (!$this->fieldExists($table, $field)) { 379 throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field))); 380 } 381 382 $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT'); 383 } 384 385 public function indexExists($table, $name) { 386 // Returns one row for each column in the index. Result is string or FALSE. 387 // Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html 388 $row = $this->connection->query('SHOW INDEX FROM {' . $table . "} WHERE key_name = '$name'")->fetchAssoc(); 389 return isset($row['Key_name']); 390 } 391 392 public function addPrimaryKey($table, $fields) { 393 if (!$this->tableExists($table)) { 394 throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table))); 395 } 396 if ($this->indexExists($table, 'PRIMARY')) { 397 throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table))); 398 } 399 400 $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')'); 401 } 402 403 public function dropPrimaryKey($table) { 404 if (!$this->indexExists($table, 'PRIMARY')) { 405 return FALSE; 406 } 407 408 $this->connection->query('ALTER TABLE {' . $table . '} DROP PRIMARY KEY'); 409 return TRUE; 410 } 411 412 public function addUniqueKey($table, $name, $fields) { 413 if (!$this->tableExists($table)) { 414 throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name))); 415 } 416 if ($this->indexExists($table, $name)) { 417 throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name))); 418 } 419 420 $this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')'); 421 } 422 423 public function dropUniqueKey($table, $name) { 424 if (!$this->indexExists($table, $name)) { 425 return FALSE; 426 } 427 428 $this->connection->query('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`'); 429 return TRUE; 430 } 431 432 public function addIndex($table, $name, $fields) { 433 if (!$this->tableExists($table)) { 434 throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name))); 435 } 436 if ($this->indexExists($table, $name)) { 437 throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name))); 438 } 439 440 $this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')'); 441 } 442 443 public function dropIndex($table, $name) { 444 if (!$this->indexExists($table, $name)) { 445 return FALSE; 446 } 447 448 $this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`'); 449 return TRUE; 450 } 451 452 public function changeField($table, $field, $field_new, $spec, $keys_new = array()) { 453 if (!$this->fieldExists($table, $field)) { 454 throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field))); 455 } 456 if (($field != $field_new) && $this->fieldExists($table, $field_new)) { 457 throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new))); 458 } 459 460 $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec)); 461 if ($keys_sql = $this->createKeysSql($keys_new)) { 462 $sql .= ', ADD ' . implode(', ADD ', $keys_sql); 463 } 464 $this->connection->query($sql); 465 } 466 467 public function prepareComment($comment, $length = NULL) { 468 // Work around a bug in some versions of PDO, see http://bugs.php.net/bug.php?id=41125 469 $comment = str_replace("'", '’', $comment); 470 471 // Truncate comment to maximum comment length. 472 if (isset($length)) { 473 // Add table prefixes before truncating. 474 $comment = truncate_utf8($this->connection->prefixTables($comment), $length, TRUE, TRUE); 475 } 476 477 return $this->connection->quote($comment); 478 } 479 480 /** 481 * Retrieve a table or column comment. 482 */ 483 public function getComment($table, $column = NULL) { 484 $condition = $this->buildTableNameCondition($table); 485 if (isset($column)) { 486 $condition->condition('column_name', $column); 487 $condition->compile($this->connection, $this); 488 // Don't use {} around information_schema.columns table. 489 return $this->connection->query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField(); 490 } 491 $condition->compile($this->connection, $this); 492 // Don't use {} around information_schema.tables table. 493 $comment = $this->connection->query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField(); 494 // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379 495 return preg_replace('/; InnoDB free:.*$/', '', $comment); 496 } 497 498 public function tableExists($table) { 499 // The information_schema table is very slow to query under MySQL 5.0. 500 // Instead, we try to select from the table in question. If it fails, 501 // the most likely reason is that it does not exist. That is dramatically 502 // faster than using information_schema. 503 // @link http://bugs.mysql.com/bug.php?id=19588 504 // @todo: This override should be removed once we require a version of MySQL 505 // that has that bug fixed. 506 try { 507 $this->connection->queryRange("SELECT 1 FROM {" . $table . "}", 0, 1); 508 return TRUE; 509 } 510 catch (Exception $e) { 511 return FALSE; 512 } 513 } 514 515 public function fieldExists($table, $column) { 516 // The information_schema table is very slow to query under MySQL 5.0. 517 // Instead, we try to select from the table and field in question. If it 518 // fails, the most likely reason is that it does not exist. That is 519 // dramatically faster than using information_schema. 520 // @link http://bugs.mysql.com/bug.php?id=19588 521 // @todo: This override should be removed once we require a version of MySQL 522 // that has that bug fixed. 523 try { 524 $this->connection->queryRange("SELECT $column FROM {" . $table . "}", 0, 1); 525 return TRUE; 526 } 527 catch (Exception $e) { 528 return FALSE; 529 } 530 } 531 532 } 533 534 /** 535 * @} End of "addtogroup schemaapi". 536 */
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title