Schema.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. <?php
  2. namespace Drupal\Core\Database\Driver\mysql;
  3. use Drupal\Core\Database\Query\Condition;
  4. use Drupal\Core\Database\SchemaException;
  5. use Drupal\Core\Database\SchemaObjectExistsException;
  6. use Drupal\Core\Database\SchemaObjectDoesNotExistException;
  7. use Drupal\Core\Database\Schema as DatabaseSchema;
  8. use Drupal\Component\Utility\Unicode;
  9. /**
  10. * @addtogroup schemaapi
  11. * @{
  12. */
  13. /**
  14. * MySQL implementation of \Drupal\Core\Database\Schema.
  15. */
  16. class Schema extends DatabaseSchema {
  17. /**
  18. * Maximum length of a table comment in MySQL.
  19. */
  20. const COMMENT_MAX_TABLE = 60;
  21. /**
  22. * Maximum length of a column comment in MySQL.
  23. */
  24. const COMMENT_MAX_COLUMN = 255;
  25. /**
  26. * @var array
  27. * List of MySQL string types.
  28. */
  29. protected $mysqlStringTypes = [
  30. 'VARCHAR',
  31. 'CHAR',
  32. 'TINYTEXT',
  33. 'MEDIUMTEXT',
  34. 'LONGTEXT',
  35. 'TEXT',
  36. ];
  37. /**
  38. * Get information about the table and database name from the prefix.
  39. *
  40. * @return
  41. * A keyed array with information about the database, table name and prefix.
  42. */
  43. protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
  44. $info = ['prefix' => $this->connection->tablePrefix($table)];
  45. if ($add_prefix) {
  46. $table = $info['prefix'] . $table;
  47. }
  48. if (($pos = strpos($table, '.')) !== FALSE) {
  49. $info['database'] = substr($table, 0, $pos);
  50. $info['table'] = substr($table, ++$pos);
  51. }
  52. else {
  53. $info['database'] = $this->connection->getConnectionOptions()['database'];
  54. $info['table'] = $table;
  55. }
  56. return $info;
  57. }
  58. /**
  59. * Build a condition to match a table name against a standard information_schema.
  60. *
  61. * MySQL uses databases like schemas rather than catalogs so when we build
  62. * a condition to query the information_schema.tables, we set the default
  63. * database as the schema unless specified otherwise, and exclude table_catalog
  64. * from the condition criteria.
  65. */
  66. protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
  67. $table_info = $this->getPrefixInfo($table_name, $add_prefix);
  68. $condition = new Condition('AND');
  69. $condition->condition('table_schema', $table_info['database']);
  70. $condition->condition('table_name', $table_info['table'], $operator);
  71. return $condition;
  72. }
  73. /**
  74. * Generate SQL to create a new table from a Drupal schema definition.
  75. *
  76. * @param $name
  77. * The name of the table to create.
  78. * @param $table
  79. * A Schema API table definition array.
  80. * @return
  81. * An array of SQL statements to create the table.
  82. */
  83. protected function createTableSql($name, $table) {
  84. $info = $this->connection->getConnectionOptions();
  85. // Provide defaults if needed.
  86. $table += [
  87. 'mysql_engine' => 'InnoDB',
  88. 'mysql_character_set' => 'utf8mb4',
  89. ];
  90. $sql = "CREATE TABLE {" . $name . "} (\n";
  91. // Add the SQL statement for each field.
  92. foreach ($table['fields'] as $field_name => $field) {
  93. $sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n";
  94. }
  95. // Process keys & indexes.
  96. if (!empty($table['primary key']) && is_array($table['primary key'])) {
  97. $this->ensureNotNullPrimaryKey($table['primary key'], $table['fields']);
  98. }
  99. $keys = $this->createKeysSql($table);
  100. if (count($keys)) {
  101. $sql .= implode(", \n", $keys) . ", \n";
  102. }
  103. // Remove the last comma and space.
  104. $sql = substr($sql, 0, -3) . "\n) ";
  105. $sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set'];
  106. // By default, MySQL uses the default collation for new tables, which is
  107. // 'utf8mb4_general_ci' (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) for
  108. // utf8mb4. If an alternate collation has been set, it needs to be
  109. // explicitly specified.
  110. // @see \Drupal\Core\Database\Driver\mysql\Schema
  111. if (!empty($info['collation'])) {
  112. $sql .= ' COLLATE ' . $info['collation'];
  113. }
  114. // Add table comment.
  115. if (!empty($table['description'])) {
  116. $sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);
  117. }
  118. return [$sql];
  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 string $name
  127. * Name of the field.
  128. * @param array $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. if (in_array($spec['mysql_type'], $this->mysqlStringTypes)) {
  134. if (isset($spec['length'])) {
  135. $sql .= '(' . $spec['length'] . ')';
  136. }
  137. if (isset($spec['type']) && $spec['type'] == 'varchar_ascii') {
  138. $sql .= ' CHARACTER SET ascii';
  139. }
  140. if (!empty($spec['binary'])) {
  141. $sql .= ' BINARY';
  142. }
  143. // Note we check for the "type" key here. "mysql_type" is VARCHAR:
  144. elseif (isset($spec['type']) && $spec['type'] == 'varchar_ascii') {
  145. $sql .= ' COLLATE ascii_general_ci';
  146. }
  147. }
  148. elseif (isset($spec['precision']) && isset($spec['scale'])) {
  149. $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
  150. }
  151. if (!empty($spec['unsigned'])) {
  152. $sql .= ' unsigned';
  153. }
  154. if (isset($spec['not null'])) {
  155. if ($spec['not null']) {
  156. $sql .= ' NOT NULL';
  157. }
  158. else {
  159. $sql .= ' NULL';
  160. }
  161. }
  162. if (!empty($spec['auto_increment'])) {
  163. $sql .= ' auto_increment';
  164. }
  165. // $spec['default'] can be NULL, so we explicitly check for the key here.
  166. if (array_key_exists('default', $spec)) {
  167. $sql .= ' DEFAULT ' . $this->escapeDefaultValue($spec['default']);
  168. }
  169. if (empty($spec['not null']) && !isset($spec['default'])) {
  170. $sql .= ' DEFAULT NULL';
  171. }
  172. // Add column comment.
  173. if (!empty($spec['description'])) {
  174. $sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN);
  175. }
  176. return $sql;
  177. }
  178. /**
  179. * Set database-engine specific properties for a field.
  180. *
  181. * @param $field
  182. * A field description array, as specified in the schema documentation.
  183. */
  184. protected function processField($field) {
  185. if (!isset($field['size'])) {
  186. $field['size'] = 'normal';
  187. }
  188. // Set the correct database-engine specific datatype.
  189. // In case one is already provided, force it to uppercase.
  190. if (isset($field['mysql_type'])) {
  191. $field['mysql_type'] = mb_strtoupper($field['mysql_type']);
  192. }
  193. else {
  194. $map = $this->getFieldTypeMap();
  195. $field['mysql_type'] = $map[$field['type'] . ':' . $field['size']];
  196. }
  197. if (isset($field['type']) && $field['type'] == 'serial') {
  198. $field['auto_increment'] = TRUE;
  199. }
  200. return $field;
  201. }
  202. /**
  203. * {@inheritdoc}
  204. */
  205. public function getFieldTypeMap() {
  206. // Put :normal last so it gets preserved by array_flip. This makes
  207. // it much easier for modules (such as schema.module) to map
  208. // database types back into schema types.
  209. // $map does not use drupal_static as its value never changes.
  210. static $map = [
  211. 'varchar_ascii:normal' => 'VARCHAR',
  212. 'varchar:normal' => 'VARCHAR',
  213. 'char:normal' => 'CHAR',
  214. 'text:tiny' => 'TINYTEXT',
  215. 'text:small' => 'TINYTEXT',
  216. 'text:medium' => 'MEDIUMTEXT',
  217. 'text:big' => 'LONGTEXT',
  218. 'text:normal' => 'TEXT',
  219. 'serial:tiny' => 'TINYINT',
  220. 'serial:small' => 'SMALLINT',
  221. 'serial:medium' => 'MEDIUMINT',
  222. 'serial:big' => 'BIGINT',
  223. 'serial:normal' => 'INT',
  224. 'int:tiny' => 'TINYINT',
  225. 'int:small' => 'SMALLINT',
  226. 'int:medium' => 'MEDIUMINT',
  227. 'int:big' => 'BIGINT',
  228. 'int:normal' => 'INT',
  229. 'float:tiny' => 'FLOAT',
  230. 'float:small' => 'FLOAT',
  231. 'float:medium' => 'FLOAT',
  232. 'float:big' => 'DOUBLE',
  233. 'float:normal' => 'FLOAT',
  234. 'numeric:normal' => 'DECIMAL',
  235. 'blob:big' => 'LONGBLOB',
  236. 'blob:normal' => 'BLOB',
  237. ];
  238. return $map;
  239. }
  240. protected function createKeysSql($spec) {
  241. $keys = [];
  242. if (!empty($spec['primary key'])) {
  243. $keys[] = 'PRIMARY KEY (' . $this->createKeySql($spec['primary key']) . ')';
  244. }
  245. if (!empty($spec['unique keys'])) {
  246. foreach ($spec['unique keys'] as $key => $fields) {
  247. $keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeySql($fields) . ')';
  248. }
  249. }
  250. if (!empty($spec['indexes'])) {
  251. $indexes = $this->getNormalizedIndexes($spec);
  252. foreach ($indexes as $index => $fields) {
  253. $keys[] = 'INDEX `' . $index . '` (' . $this->createKeySql($fields) . ')';
  254. }
  255. }
  256. return $keys;
  257. }
  258. /**
  259. * Gets normalized indexes from a table specification.
  260. *
  261. * Shortens indexes to 191 characters if they apply to utf8mb4-encoded
  262. * fields, in order to comply with the InnoDB index limitation of 756 bytes.
  263. *
  264. * @param array $spec
  265. * The table specification.
  266. *
  267. * @return array
  268. * List of shortened indexes.
  269. *
  270. * @throws \Drupal\Core\Database\SchemaException
  271. * Thrown if field specification is missing.
  272. */
  273. protected function getNormalizedIndexes(array $spec) {
  274. $indexes = isset($spec['indexes']) ? $spec['indexes'] : [];
  275. foreach ($indexes as $index_name => $index_fields) {
  276. foreach ($index_fields as $index_key => $index_field) {
  277. // Get the name of the field from the index specification.
  278. $field_name = is_array($index_field) ? $index_field[0] : $index_field;
  279. // Check whether the field is defined in the table specification.
  280. if (isset($spec['fields'][$field_name])) {
  281. // Get the MySQL type from the processed field.
  282. $mysql_field = $this->processField($spec['fields'][$field_name]);
  283. if (in_array($mysql_field['mysql_type'], $this->mysqlStringTypes)) {
  284. // Check whether we need to shorten the index.
  285. if ((!isset($mysql_field['type']) || $mysql_field['type'] != 'varchar_ascii') && (!isset($mysql_field['length']) || $mysql_field['length'] > 191)) {
  286. // Limit the index length to 191 characters.
  287. $this->shortenIndex($indexes[$index_name][$index_key]);
  288. }
  289. }
  290. }
  291. else {
  292. throw new SchemaException("MySQL needs the '$field_name' field specification in order to normalize the '$index_name' index");
  293. }
  294. }
  295. }
  296. return $indexes;
  297. }
  298. /**
  299. * Helper function for normalizeIndexes().
  300. *
  301. * Shortens an index to 191 characters.
  302. *
  303. * @param array $index
  304. * The index array to be used in createKeySql.
  305. *
  306. * @see Drupal\Core\Database\Driver\mysql\Schema::createKeySql()
  307. * @see Drupal\Core\Database\Driver\mysql\Schema::normalizeIndexes()
  308. */
  309. protected function shortenIndex(&$index) {
  310. if (is_array($index)) {
  311. if ($index[1] > 191) {
  312. $index[1] = 191;
  313. }
  314. }
  315. else {
  316. $index = [$index, 191];
  317. }
  318. }
  319. protected function createKeySql($fields) {
  320. $return = [];
  321. foreach ($fields as $field) {
  322. if (is_array($field)) {
  323. $return[] = '`' . $field[0] . '`(' . $field[1] . ')';
  324. }
  325. else {
  326. $return[] = '`' . $field . '`';
  327. }
  328. }
  329. return implode(', ', $return);
  330. }
  331. /**
  332. * {@inheritdoc}
  333. */
  334. public function renameTable($table, $new_name) {
  335. if (!$this->tableExists($table)) {
  336. throw new SchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", ['@table' => $table, '@table_new' => $new_name]));
  337. }
  338. if ($this->tableExists($new_name)) {
  339. throw new SchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", ['@table' => $table, '@table_new' => $new_name]));
  340. }
  341. $info = $this->getPrefixInfo($new_name);
  342. return $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO `' . $info['table'] . '`');
  343. }
  344. /**
  345. * {@inheritdoc}
  346. */
  347. public function dropTable($table) {
  348. if (!$this->tableExists($table)) {
  349. return FALSE;
  350. }
  351. $this->connection->query('DROP TABLE {' . $table . '}');
  352. return TRUE;
  353. }
  354. /**
  355. * {@inheritdoc}
  356. */
  357. public function addField($table, $field, $spec, $keys_new = []) {
  358. if (!$this->tableExists($table)) {
  359. throw new SchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", ['@field' => $field, '@table' => $table]));
  360. }
  361. if ($this->fieldExists($table, $field)) {
  362. throw new SchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", ['@field' => $field, '@table' => $table]));
  363. }
  364. // Fields that are part of a PRIMARY KEY must be added as NOT NULL.
  365. $is_primary_key = isset($keys_new['primary key']) && in_array($field, $keys_new['primary key'], TRUE);
  366. if ($is_primary_key) {
  367. $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field => $spec]);
  368. }
  369. $fixnull = FALSE;
  370. if (!empty($spec['not null']) && !isset($spec['default']) && !$is_primary_key) {
  371. $fixnull = TRUE;
  372. $spec['not null'] = FALSE;
  373. }
  374. $query = 'ALTER TABLE {' . $table . '} ADD ';
  375. $query .= $this->createFieldSql($field, $this->processField($spec));
  376. if ($keys_sql = $this->createKeysSql($keys_new)) {
  377. // Make sure to drop the existing primary key before adding a new one.
  378. // This is only needed when adding a field because this method, unlike
  379. // changeField(), is supposed to handle primary keys automatically.
  380. if (isset($keys_new['primary key']) && $this->indexExists($table, 'PRIMARY')) {
  381. $query .= ', DROP PRIMARY KEY';
  382. }
  383. $query .= ', ADD ' . implode(', ADD ', $keys_sql);
  384. }
  385. $this->connection->query($query);
  386. if (isset($spec['initial_from_field'])) {
  387. if (isset($spec['initial'])) {
  388. $expression = 'COALESCE(' . $spec['initial_from_field'] . ', :default_initial_value)';
  389. $arguments = [':default_initial_value' => $spec['initial']];
  390. }
  391. else {
  392. $expression = $spec['initial_from_field'];
  393. $arguments = [];
  394. }
  395. $this->connection->update($table)
  396. ->expression($field, $expression, $arguments)
  397. ->execute();
  398. }
  399. elseif (isset($spec['initial'])) {
  400. $this->connection->update($table)
  401. ->fields([$field => $spec['initial']])
  402. ->execute();
  403. }
  404. if ($fixnull) {
  405. $spec['not null'] = TRUE;
  406. $this->changeField($table, $field, $field, $spec);
  407. }
  408. }
  409. /**
  410. * {@inheritdoc}
  411. */
  412. public function dropField($table, $field) {
  413. if (!$this->fieldExists($table, $field)) {
  414. return FALSE;
  415. }
  416. // When dropping a field that is part of a composite primary key MySQL
  417. // automatically removes the field from the primary key, which can leave the
  418. // table in an invalid state. MariaDB 10.2.8 requires explicitly dropping
  419. // the primary key first for this reason. We perform this deletion
  420. // explicitly which also makes the behavior on both MySQL and MariaDB
  421. // consistent with PostgreSQL.
  422. // @see https://mariadb.com/kb/en/library/alter-table
  423. $primary_key = $this->findPrimaryKeyColumns($table);
  424. if ((count($primary_key) > 1) && in_array($field, $primary_key, TRUE)) {
  425. $this->dropPrimaryKey($table);
  426. }
  427. $this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`');
  428. return TRUE;
  429. }
  430. /**
  431. * {@inheritdoc}
  432. */
  433. public function fieldSetDefault($table, $field, $default) {
  434. if (!$this->fieldExists($table, $field)) {
  435. throw new SchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", ['@table' => $table, '@field' => $field]));
  436. }
  437. $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $this->escapeDefaultValue($default));
  438. }
  439. /**
  440. * {@inheritdoc}
  441. */
  442. public function fieldSetNoDefault($table, $field) {
  443. if (!$this->fieldExists($table, $field)) {
  444. throw new SchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", ['@table' => $table, '@field' => $field]));
  445. }
  446. $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT');
  447. }
  448. /**
  449. * {@inheritdoc}
  450. */
  451. public function indexExists($table, $name) {
  452. // Returns one row for each column in the index. Result is string or FALSE.
  453. // Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html
  454. $row = $this->connection->query('SHOW INDEX FROM {' . $table . '} WHERE key_name = ' . $this->connection->quote($name))->fetchAssoc();
  455. return isset($row['Key_name']);
  456. }
  457. /**
  458. * {@inheritdoc}
  459. */
  460. public function addPrimaryKey($table, $fields) {
  461. if (!$this->tableExists($table)) {
  462. throw new SchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", ['@table' => $table]));
  463. }
  464. if ($this->indexExists($table, 'PRIMARY')) {
  465. throw new SchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", ['@table' => $table]));
  466. }
  467. $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')');
  468. }
  469. /**
  470. * {@inheritdoc}
  471. */
  472. public function dropPrimaryKey($table) {
  473. if (!$this->indexExists($table, 'PRIMARY')) {
  474. return FALSE;
  475. }
  476. $this->connection->query('ALTER TABLE {' . $table . '} DROP PRIMARY KEY');
  477. return TRUE;
  478. }
  479. /**
  480. * {@inheritdoc}
  481. */
  482. protected function findPrimaryKeyColumns($table) {
  483. if (!$this->tableExists($table)) {
  484. return FALSE;
  485. }
  486. $result = $this->connection->query("SHOW KEYS FROM {" . $table . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
  487. return array_keys($result);
  488. }
  489. /**
  490. * {@inheritdoc}
  491. */
  492. public function addUniqueKey($table, $name, $fields) {
  493. if (!$this->tableExists($table)) {
  494. throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", ['@table' => $table, '@name' => $name]));
  495. }
  496. if ($this->indexExists($table, $name)) {
  497. throw new SchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", ['@table' => $table, '@name' => $name]));
  498. }
  499. $this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')');
  500. }
  501. /**
  502. * {@inheritdoc}
  503. */
  504. public function dropUniqueKey($table, $name) {
  505. if (!$this->indexExists($table, $name)) {
  506. return FALSE;
  507. }
  508. $this->connection->query('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`');
  509. return TRUE;
  510. }
  511. /**
  512. * {@inheritdoc}
  513. */
  514. public function addIndex($table, $name, $fields, array $spec) {
  515. if (!$this->tableExists($table)) {
  516. throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", ['@table' => $table, '@name' => $name]));
  517. }
  518. if ($this->indexExists($table, $name)) {
  519. throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", ['@table' => $table, '@name' => $name]));
  520. }
  521. $spec['indexes'][$name] = $fields;
  522. $indexes = $this->getNormalizedIndexes($spec);
  523. $this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($indexes[$name]) . ')');
  524. }
  525. /**
  526. * {@inheritdoc}
  527. */
  528. public function dropIndex($table, $name) {
  529. if (!$this->indexExists($table, $name)) {
  530. return FALSE;
  531. }
  532. $this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`');
  533. return TRUE;
  534. }
  535. /**
  536. * {@inheritdoc}
  537. */
  538. public function changeField($table, $field, $field_new, $spec, $keys_new = []) {
  539. if (!$this->fieldExists($table, $field)) {
  540. throw new SchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", ['@table' => $table, '@name' => $field]));
  541. }
  542. if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
  543. throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", ['@table' => $table, '@name' => $field, '@name_new' => $field_new]));
  544. }
  545. if (isset($keys_new['primary key']) && in_array($field_new, $keys_new['primary key'], TRUE)) {
  546. $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field_new => $spec]);
  547. }
  548. $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));
  549. if ($keys_sql = $this->createKeysSql($keys_new)) {
  550. $sql .= ', ADD ' . implode(', ADD ', $keys_sql);
  551. }
  552. $this->connection->query($sql);
  553. }
  554. /**
  555. * {@inheritdoc}
  556. */
  557. public function prepareComment($comment, $length = NULL) {
  558. // Truncate comment to maximum comment length.
  559. if (isset($length)) {
  560. // Add table prefixes before truncating.
  561. $comment = Unicode::truncate($this->connection->prefixTables($comment), $length, TRUE, TRUE);
  562. }
  563. // Remove semicolons to avoid triggering multi-statement check.
  564. $comment = strtr($comment, [';' => '.']);
  565. return $this->connection->quote($comment);
  566. }
  567. /**
  568. * Retrieve a table or column comment.
  569. */
  570. public function getComment($table, $column = NULL) {
  571. $condition = $this->buildTableNameCondition($table);
  572. if (isset($column)) {
  573. $condition->condition('column_name', $column);
  574. $condition->compile($this->connection, $this);
  575. // Don't use {} around information_schema.columns table.
  576. return $this->connection->query("SELECT column_comment as column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
  577. }
  578. $condition->compile($this->connection, $this);
  579. // Don't use {} around information_schema.tables table.
  580. $comment = $this->connection->query("SELECT table_comment as table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
  581. // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
  582. return preg_replace('/; InnoDB free:.*$/', '', $comment);
  583. }
  584. /**
  585. * {@inheritdoc}
  586. */
  587. public function tableExists($table) {
  588. // The information_schema table is very slow to query under MySQL 5.0.
  589. // Instead, we try to select from the table in question. If it fails,
  590. // the most likely reason is that it does not exist. That is dramatically
  591. // faster than using information_schema.
  592. // @link http://bugs.mysql.com/bug.php?id=19588
  593. // @todo This override should be removed once we require a version of MySQL
  594. // that has that bug fixed.
  595. try {
  596. $this->connection->queryRange("SELECT 1 FROM {" . $table . "}", 0, 1);
  597. return TRUE;
  598. }
  599. catch (\Exception $e) {
  600. return FALSE;
  601. }
  602. }
  603. /**
  604. * {@inheritdoc}
  605. */
  606. public function fieldExists($table, $column) {
  607. // The information_schema table is very slow to query under MySQL 5.0.
  608. // Instead, we try to select from the table and field in question. If it
  609. // fails, the most likely reason is that it does not exist. That is
  610. // dramatically faster than using information_schema.
  611. // @link http://bugs.mysql.com/bug.php?id=19588
  612. // @todo This override should be removed once we require a version of MySQL
  613. // that has that bug fixed.
  614. try {
  615. $this->connection->queryRange("SELECT $column FROM {" . $table . "}", 0, 1);
  616. return TRUE;
  617. }
  618. catch (\Exception $e) {
  619. return FALSE;
  620. }
  621. }
  622. }
  623. /**
  624. * @} End of "addtogroup schemaapi".
  625. */