PageRenderTime 85ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php

http://github.com/doctrine/dbal
PHP | 1191 lines | 711 code | 184 blank | 296 comment | 79 complexity | 111f7e0777b24e15bcb407744620f4cb MD5 | raw file
Possible License(s): Unlicense
  1. <?php
  2. namespace Doctrine\DBAL\Platforms;
  3. use Doctrine\DBAL\Schema\ForeignKeyConstraint;
  4. use Doctrine\DBAL\Schema\Identifier;
  5. use Doctrine\DBAL\Schema\Index;
  6. use Doctrine\DBAL\Schema\Table;
  7. use Doctrine\DBAL\Schema\TableDiff;
  8. use Doctrine\DBAL\TransactionIsolationLevel;
  9. use Doctrine\DBAL\Types\BlobType;
  10. use Doctrine\DBAL\Types\TextType;
  11. use InvalidArgumentException;
  12. use function array_diff_key;
  13. use function array_merge;
  14. use function array_unique;
  15. use function array_values;
  16. use function count;
  17. use function func_get_args;
  18. use function implode;
  19. use function in_array;
  20. use function is_numeric;
  21. use function is_string;
  22. use function sprintf;
  23. use function str_replace;
  24. use function strtoupper;
  25. use function trim;
  26. /**
  27. * The MySqlPlatform provides the behavior, features and SQL dialect of the
  28. * MySQL database platform. This platform represents a MySQL 5.0 or greater platform that
  29. * uses the InnoDB storage engine.
  30. *
  31. * @todo Rename: MySQLPlatform
  32. */
  33. class MySqlPlatform extends AbstractPlatform
  34. {
  35. public const LENGTH_LIMIT_TINYTEXT = 255;
  36. public const LENGTH_LIMIT_TEXT = 65535;
  37. public const LENGTH_LIMIT_MEDIUMTEXT = 16777215;
  38. public const LENGTH_LIMIT_TINYBLOB = 255;
  39. public const LENGTH_LIMIT_BLOB = 65535;
  40. public const LENGTH_LIMIT_MEDIUMBLOB = 16777215;
  41. /**
  42. * {@inheritDoc}
  43. */
  44. protected function doModifyLimitQuery($query, $limit, $offset)
  45. {
  46. if ($limit !== null) {
  47. $query .= ' LIMIT ' . $limit;
  48. if ($offset > 0) {
  49. $query .= ' OFFSET ' . $offset;
  50. }
  51. } elseif ($offset > 0) {
  52. // 2^64-1 is the maximum of unsigned BIGINT, the biggest limit possible
  53. $query .= ' LIMIT 18446744073709551615 OFFSET ' . $offset;
  54. }
  55. return $query;
  56. }
  57. /**
  58. * {@inheritDoc}
  59. */
  60. public function getIdentifierQuoteCharacter()
  61. {
  62. return '`';
  63. }
  64. /**
  65. * {@inheritDoc}
  66. */
  67. public function getRegexpExpression()
  68. {
  69. return 'RLIKE';
  70. }
  71. /**
  72. * {@inheritDoc}
  73. *
  74. * @deprecated Use application-generated UUIDs instead
  75. */
  76. public function getGuidExpression()
  77. {
  78. return 'UUID()';
  79. }
  80. /**
  81. * {@inheritDoc}
  82. */
  83. public function getLocateExpression($str, $substr, $startPos = false)
  84. {
  85. if ($startPos === false) {
  86. return 'LOCATE(' . $substr . ', ' . $str . ')';
  87. }
  88. return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')';
  89. }
  90. /**
  91. * {@inheritDoc}
  92. */
  93. public function getConcatExpression()
  94. {
  95. return sprintf('CONCAT(%s)', implode(', ', func_get_args()));
  96. }
  97. /**
  98. * {@inheritdoc}
  99. */
  100. protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
  101. {
  102. $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB';
  103. return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')';
  104. }
  105. /**
  106. * {@inheritDoc}
  107. */
  108. public function getDateDiffExpression($date1, $date2)
  109. {
  110. return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')';
  111. }
  112. /**
  113. * {@inheritDoc}
  114. */
  115. public function getListDatabasesSQL()
  116. {
  117. return 'SHOW DATABASES';
  118. }
  119. /**
  120. * {@inheritDoc}
  121. */
  122. public function getListTableConstraintsSQL($table)
  123. {
  124. return 'SHOW INDEX FROM ' . $table;
  125. }
  126. /**
  127. * {@inheritDoc}
  128. *
  129. * Two approaches to listing the table indexes. The information_schema is
  130. * preferred, because it doesn't cause problems with SQL keywords such as "order" or "table".
  131. */
  132. public function getListTableIndexesSQL($table, $currentDatabase = null)
  133. {
  134. if ($currentDatabase) {
  135. $currentDatabase = $this->quoteStringLiteral($currentDatabase);
  136. $table = $this->quoteStringLiteral($table);
  137. return 'SELECT NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, COLUMN_NAME AS Column_Name,' .
  138. ' SUB_PART AS Sub_Part, INDEX_TYPE AS Index_Type' .
  139. ' FROM information_schema.STATISTICS WHERE TABLE_NAME = ' . $table .
  140. ' AND TABLE_SCHEMA = ' . $currentDatabase .
  141. ' ORDER BY SEQ_IN_INDEX ASC';
  142. }
  143. return 'SHOW INDEX FROM ' . $table;
  144. }
  145. /**
  146. * {@inheritDoc}
  147. */
  148. public function getListViewsSQL($database)
  149. {
  150. $database = $this->quoteStringLiteral($database);
  151. return 'SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ' . $database;
  152. }
  153. /**
  154. * @param string $table
  155. * @param string|null $database
  156. *
  157. * @return string
  158. */
  159. public function getListTableForeignKeysSQL($table, $database = null)
  160. {
  161. $table = $this->quoteStringLiteral($table);
  162. if ($database !== null) {
  163. $database = $this->quoteStringLiteral($database);
  164. }
  165. $sql = 'SELECT DISTINCT k.`CONSTRAINT_NAME`, k.`COLUMN_NAME`, k.`REFERENCED_TABLE_NAME`, ' .
  166. 'k.`REFERENCED_COLUMN_NAME` /*!50116 , c.update_rule, c.delete_rule */ ' .
  167. 'FROM information_schema.key_column_usage k /*!50116 ' .
  168. 'INNER JOIN information_schema.referential_constraints c ON ' .
  169. ' c.constraint_name = k.constraint_name AND ' .
  170. ' c.table_name = ' . $table . ' */ WHERE k.table_name = ' . $table;
  171. $databaseNameSql = $database ?? 'DATABASE()';
  172. $sql .= ' AND k.table_schema = ' . $databaseNameSql . ' /*!50116 AND c.constraint_schema = ' . $databaseNameSql . ' */';
  173. $sql .= ' AND k.`REFERENCED_COLUMN_NAME` is not NULL';
  174. return $sql;
  175. }
  176. /**
  177. * {@inheritDoc}
  178. */
  179. public function getCreateViewSQL($name, $sql)
  180. {
  181. return 'CREATE VIEW ' . $name . ' AS ' . $sql;
  182. }
  183. /**
  184. * {@inheritDoc}
  185. */
  186. public function getDropViewSQL($name)
  187. {
  188. return 'DROP VIEW ' . $name;
  189. }
  190. /**
  191. * {@inheritDoc}
  192. */
  193. protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
  194. {
  195. return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
  196. : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
  197. }
  198. /**
  199. * {@inheritdoc}
  200. */
  201. protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
  202. {
  203. return $fixed ? 'BINARY(' . ($length ?: 255) . ')' : 'VARBINARY(' . ($length ?: 255) . ')';
  204. }
  205. /**
  206. * Gets the SQL snippet used to declare a CLOB column type.
  207. * TINYTEXT : 2 ^ 8 - 1 = 255
  208. * TEXT : 2 ^ 16 - 1 = 65535
  209. * MEDIUMTEXT : 2 ^ 24 - 1 = 16777215
  210. * LONGTEXT : 2 ^ 32 - 1 = 4294967295
  211. *
  212. * {@inheritDoc}
  213. */
  214. public function getClobTypeDeclarationSQL(array $field)
  215. {
  216. if (! empty($field['length']) && is_numeric($field['length'])) {
  217. $length = $field['length'];
  218. if ($length <= static::LENGTH_LIMIT_TINYTEXT) {
  219. return 'TINYTEXT';
  220. }
  221. if ($length <= static::LENGTH_LIMIT_TEXT) {
  222. return 'TEXT';
  223. }
  224. if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) {
  225. return 'MEDIUMTEXT';
  226. }
  227. }
  228. return 'LONGTEXT';
  229. }
  230. /**
  231. * {@inheritDoc}
  232. */
  233. public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
  234. {
  235. if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] === true) {
  236. return 'TIMESTAMP';
  237. }
  238. return 'DATETIME';
  239. }
  240. /**
  241. * {@inheritDoc}
  242. */
  243. public function getDateTypeDeclarationSQL(array $fieldDeclaration)
  244. {
  245. return 'DATE';
  246. }
  247. /**
  248. * {@inheritDoc}
  249. */
  250. public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
  251. {
  252. return 'TIME';
  253. }
  254. /**
  255. * {@inheritDoc}
  256. */
  257. public function getBooleanTypeDeclarationSQL(array $field)
  258. {
  259. return 'TINYINT(1)';
  260. }
  261. /**
  262. * Obtain DBMS specific SQL code portion needed to set the COLLATION
  263. * of a field declaration to be used in statements like CREATE TABLE.
  264. *
  265. * @deprecated Deprecated since version 2.5, Use {@link self::getColumnCollationDeclarationSQL()} instead.
  266. *
  267. * @param string $collation name of the collation
  268. *
  269. * @return string DBMS specific SQL code portion needed to set the COLLATION
  270. * of a field declaration.
  271. */
  272. public function getCollationFieldDeclaration($collation)
  273. {
  274. return $this->getColumnCollationDeclarationSQL($collation);
  275. }
  276. /**
  277. * {@inheritDoc}
  278. *
  279. * MySql prefers "autoincrement" identity columns since sequences can only
  280. * be emulated with a table.
  281. */
  282. public function prefersIdentityColumns()
  283. {
  284. return true;
  285. }
  286. /**
  287. * {@inheritDoc}
  288. *
  289. * MySql supports this through AUTO_INCREMENT columns.
  290. */
  291. public function supportsIdentityColumns()
  292. {
  293. return true;
  294. }
  295. /**
  296. * {@inheritDoc}
  297. */
  298. public function supportsInlineColumnComments()
  299. {
  300. return true;
  301. }
  302. /**
  303. * {@inheritDoc}
  304. */
  305. public function supportsColumnCollation()
  306. {
  307. return true;
  308. }
  309. /**
  310. * {@inheritDoc}
  311. */
  312. public function getListTablesSQL()
  313. {
  314. return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'";
  315. }
  316. /**
  317. * {@inheritDoc}
  318. */
  319. public function getListTableColumnsSQL($table, $database = null)
  320. {
  321. $table = $this->quoteStringLiteral($table);
  322. if ($database) {
  323. $database = $this->quoteStringLiteral($database);
  324. } else {
  325. $database = 'DATABASE()';
  326. }
  327. return 'SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ' .
  328. 'COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, ' .
  329. 'CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS Collation ' .
  330. 'FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ' . $database . ' AND TABLE_NAME = ' . $table .
  331. ' ORDER BY ORDINAL_POSITION ASC';
  332. }
  333. public function getListTableMetadataSQL(string $table, ?string $database = null) : string
  334. {
  335. return sprintf(
  336. <<<'SQL'
  337. SELECT ENGINE, AUTO_INCREMENT, TABLE_COLLATION, TABLE_COMMENT, CREATE_OPTIONS
  338. FROM information_schema.TABLES
  339. WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = %s AND TABLE_NAME = %s
  340. SQL
  341. ,
  342. $database ? $this->quoteStringLiteral($database) : 'DATABASE()',
  343. $this->quoteStringLiteral($table)
  344. );
  345. }
  346. /**
  347. * {@inheritDoc}
  348. */
  349. public function getCreateDatabaseSQL($name)
  350. {
  351. return 'CREATE DATABASE ' . $name;
  352. }
  353. /**
  354. * {@inheritDoc}
  355. */
  356. public function getDropDatabaseSQL($name)
  357. {
  358. return 'DROP DATABASE ' . $name;
  359. }
  360. /**
  361. * {@inheritDoc}
  362. */
  363. protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
  364. {
  365. $queryFields = $this->getColumnDeclarationListSQL($columns);
  366. if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
  367. foreach ($options['uniqueConstraints'] as $index => $definition) {
  368. $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition);
  369. }
  370. }
  371. // add all indexes
  372. if (isset($options['indexes']) && ! empty($options['indexes'])) {
  373. foreach ($options['indexes'] as $index => $definition) {
  374. $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition);
  375. }
  376. }
  377. // attach all primary keys
  378. if (isset($options['primary']) && ! empty($options['primary'])) {
  379. $keyColumns = array_unique(array_values($options['primary']));
  380. $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
  381. }
  382. $query = 'CREATE ';
  383. if (! empty($options['temporary'])) {
  384. $query .= 'TEMPORARY ';
  385. }
  386. $query .= 'TABLE ' . $tableName . ' (' . $queryFields . ') ';
  387. $query .= $this->buildTableOptions($options);
  388. $query .= $this->buildPartitionOptions($options);
  389. $sql = [$query];
  390. $engine = 'INNODB';
  391. if (isset($options['engine'])) {
  392. $engine = strtoupper(trim($options['engine']));
  393. }
  394. // Propagate foreign key constraints only for InnoDB.
  395. if (isset($options['foreignKeys']) && $engine === 'INNODB') {
  396. foreach ((array) $options['foreignKeys'] as $definition) {
  397. $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
  398. }
  399. }
  400. return $sql;
  401. }
  402. /**
  403. * {@inheritdoc}
  404. */
  405. public function getDefaultValueDeclarationSQL($field)
  406. {
  407. // Unset the default value if the given field definition does not allow default values.
  408. if ($field['type'] instanceof TextType || $field['type'] instanceof BlobType) {
  409. $field['default'] = null;
  410. }
  411. return parent::getDefaultValueDeclarationSQL($field);
  412. }
  413. /**
  414. * Build SQL for table options
  415. *
  416. * @param mixed[] $options
  417. *
  418. * @return string
  419. */
  420. private function buildTableOptions(array $options)
  421. {
  422. if (isset($options['table_options'])) {
  423. return $options['table_options'];
  424. }
  425. $tableOptions = [];
  426. // Charset
  427. if (! isset($options['charset'])) {
  428. $options['charset'] = 'utf8';
  429. }
  430. $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']);
  431. // Collate
  432. if (! isset($options['collate'])) {
  433. $options['collate'] = $options['charset'] . '_unicode_ci';
  434. }
  435. $tableOptions[] = $this->getColumnCollationDeclarationSQL($options['collate']);
  436. // Engine
  437. if (! isset($options['engine'])) {
  438. $options['engine'] = 'InnoDB';
  439. }
  440. $tableOptions[] = sprintf('ENGINE = %s', $options['engine']);
  441. // Auto increment
  442. if (isset($options['auto_increment'])) {
  443. $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']);
  444. }
  445. // Comment
  446. if (isset($options['comment'])) {
  447. $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment']));
  448. }
  449. // Row format
  450. if (isset($options['row_format'])) {
  451. $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']);
  452. }
  453. return implode(' ', $tableOptions);
  454. }
  455. /**
  456. * Build SQL for partition options.
  457. *
  458. * @param mixed[] $options
  459. *
  460. * @return string
  461. */
  462. private function buildPartitionOptions(array $options)
  463. {
  464. return isset($options['partition_options'])
  465. ? ' ' . $options['partition_options']
  466. : '';
  467. }
  468. /**
  469. * {@inheritDoc}
  470. */
  471. public function getAlterTableSQL(TableDiff $diff)
  472. {
  473. $columnSql = [];
  474. $queryParts = [];
  475. $newName = $diff->getNewName();
  476. if ($newName !== false) {
  477. $queryParts[] = 'RENAME TO ' . $newName->getQuotedName($this);
  478. }
  479. foreach ($diff->addedColumns as $column) {
  480. if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
  481. continue;
  482. }
  483. $columnArray = $column->toArray();
  484. $columnArray['comment'] = $this->getColumnComment($column);
  485. $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
  486. }
  487. foreach ($diff->removedColumns as $column) {
  488. if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
  489. continue;
  490. }
  491. $queryParts[] = 'DROP ' . $column->getQuotedName($this);
  492. }
  493. foreach ($diff->changedColumns as $columnDiff) {
  494. if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
  495. continue;
  496. }
  497. $column = $columnDiff->column;
  498. $columnArray = $column->toArray();
  499. // Don't propagate default value changes for unsupported column types.
  500. if ($columnDiff->hasChanged('default') &&
  501. count($columnDiff->changedProperties) === 1 &&
  502. ($columnArray['type'] instanceof TextType || $columnArray['type'] instanceof BlobType)
  503. ) {
  504. continue;
  505. }
  506. $columnArray['comment'] = $this->getColumnComment($column);
  507. $queryParts[] = 'CHANGE ' . ($columnDiff->getOldColumnName()->getQuotedName($this)) . ' '
  508. . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
  509. }
  510. foreach ($diff->renamedColumns as $oldColumnName => $column) {
  511. if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
  512. continue;
  513. }
  514. $oldColumnName = new Identifier($oldColumnName);
  515. $columnArray = $column->toArray();
  516. $columnArray['comment'] = $this->getColumnComment($column);
  517. $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' '
  518. . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
  519. }
  520. if (isset($diff->addedIndexes['primary'])) {
  521. $keyColumns = array_unique(array_values($diff->addedIndexes['primary']->getColumns()));
  522. $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
  523. unset($diff->addedIndexes['primary']);
  524. } elseif (isset($diff->changedIndexes['primary'])) {
  525. // Necessary in case the new primary key includes a new auto_increment column
  526. foreach ($diff->changedIndexes['primary']->getColumns() as $columnName) {
  527. if (isset($diff->addedColumns[$columnName]) && $diff->addedColumns[$columnName]->getAutoincrement()) {
  528. $keyColumns = array_unique(array_values($diff->changedIndexes['primary']->getColumns()));
  529. $queryParts[] = 'DROP PRIMARY KEY';
  530. $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
  531. unset($diff->changedIndexes['primary']);
  532. break;
  533. }
  534. }
  535. }
  536. $sql = [];
  537. $tableSql = [];
  538. if (! $this->onSchemaAlterTable($diff, $tableSql)) {
  539. if (count($queryParts) > 0) {
  540. $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(', ', $queryParts);
  541. }
  542. $sql = array_merge(
  543. $this->getPreAlterTableIndexForeignKeySQL($diff),
  544. $sql,
  545. $this->getPostAlterTableIndexForeignKeySQL($diff)
  546. );
  547. }
  548. return array_merge($sql, $tableSql, $columnSql);
  549. }
  550. /**
  551. * {@inheritDoc}
  552. */
  553. protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
  554. {
  555. $sql = [];
  556. $table = $diff->getName($this)->getQuotedName($this);
  557. foreach ($diff->changedIndexes as $changedIndex) {
  558. $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex));
  559. }
  560. foreach ($diff->removedIndexes as $remKey => $remIndex) {
  561. $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $remIndex));
  562. foreach ($diff->addedIndexes as $addKey => $addIndex) {
  563. if ($remIndex->getColumns() !== $addIndex->getColumns()) {
  564. continue;
  565. }
  566. $indexClause = 'INDEX ' . $addIndex->getName();
  567. if ($addIndex->isPrimary()) {
  568. $indexClause = 'PRIMARY KEY';
  569. } elseif ($addIndex->isUnique()) {
  570. $indexClause = 'UNIQUE INDEX ' . $addIndex->getName();
  571. }
  572. $query = 'ALTER TABLE ' . $table . ' DROP INDEX ' . $remIndex->getName() . ', ';
  573. $query .= 'ADD ' . $indexClause;
  574. $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addIndex) . ')';
  575. $sql[] = $query;
  576. unset($diff->removedIndexes[$remKey], $diff->addedIndexes[$addKey]);
  577. break;
  578. }
  579. }
  580. $engine = 'INNODB';
  581. if ($diff->fromTable instanceof Table && $diff->fromTable->hasOption('engine')) {
  582. $engine = strtoupper(trim($diff->fromTable->getOption('engine')));
  583. }
  584. // Suppress foreign key constraint propagation on non-supporting engines.
  585. if ($engine !== 'INNODB') {
  586. $diff->addedForeignKeys = [];
  587. $diff->changedForeignKeys = [];
  588. $diff->removedForeignKeys = [];
  589. }
  590. $sql = array_merge(
  591. $sql,
  592. $this->getPreAlterTableAlterIndexForeignKeySQL($diff),
  593. parent::getPreAlterTableIndexForeignKeySQL($diff),
  594. $this->getPreAlterTableRenameIndexForeignKeySQL($diff)
  595. );
  596. return $sql;
  597. }
  598. /**
  599. * @return string[]
  600. */
  601. private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index)
  602. {
  603. $sql = [];
  604. if (! $index->isPrimary() || ! $diff->fromTable instanceof Table) {
  605. return $sql;
  606. }
  607. $tableName = $diff->getName($this)->getQuotedName($this);
  608. // Dropping primary keys requires to unset autoincrement attribute on the particular column first.
  609. foreach ($index->getColumns() as $columnName) {
  610. if (! $diff->fromTable->hasColumn($columnName)) {
  611. continue;
  612. }
  613. $column = $diff->fromTable->getColumn($columnName);
  614. if ($column->getAutoincrement() !== true) {
  615. continue;
  616. }
  617. $column->setAutoincrement(false);
  618. $sql[] = 'ALTER TABLE ' . $tableName . ' MODIFY ' .
  619. $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
  620. // original autoincrement information might be needed later on by other parts of the table alteration
  621. $column->setAutoincrement(true);
  622. }
  623. return $sql;
  624. }
  625. /**
  626. * @param TableDiff $diff The table diff to gather the SQL for.
  627. *
  628. * @return string[]
  629. */
  630. private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff)
  631. {
  632. $sql = [];
  633. $table = $diff->getName($this)->getQuotedName($this);
  634. foreach ($diff->changedIndexes as $changedIndex) {
  635. // Changed primary key
  636. if (! $changedIndex->isPrimary() || ! ($diff->fromTable instanceof Table)) {
  637. continue;
  638. }
  639. foreach ($diff->fromTable->getPrimaryKeyColumns() as $columnName) {
  640. $column = $diff->fromTable->getColumn($columnName);
  641. // Check if an autoincrement column was dropped from the primary key.
  642. if (! $column->getAutoincrement() || in_array($columnName, $changedIndex->getColumns())) {
  643. continue;
  644. }
  645. // The autoincrement attribute needs to be removed from the dropped column
  646. // before we can drop and recreate the primary key.
  647. $column->setAutoincrement(false);
  648. $sql[] = 'ALTER TABLE ' . $table . ' MODIFY ' .
  649. $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
  650. // Restore the autoincrement attribute as it might be needed later on
  651. // by other parts of the table alteration.
  652. $column->setAutoincrement(true);
  653. }
  654. }
  655. return $sql;
  656. }
  657. /**
  658. * @param TableDiff $diff The table diff to gather the SQL for.
  659. *
  660. * @return string[]
  661. */
  662. protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff)
  663. {
  664. $sql = [];
  665. $tableName = $diff->getName($this)->getQuotedName($this);
  666. foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) {
  667. if (in_array($foreignKey, $diff->changedForeignKeys, true)) {
  668. continue;
  669. }
  670. $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
  671. }
  672. return $sql;
  673. }
  674. /**
  675. * Returns the remaining foreign key constraints that require one of the renamed indexes.
  676. *
  677. * "Remaining" here refers to the diff between the foreign keys currently defined in the associated
  678. * table and the foreign keys to be removed.
  679. *
  680. * @param TableDiff $diff The table diff to evaluate.
  681. *
  682. * @return ForeignKeyConstraint[]
  683. */
  684. private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff)
  685. {
  686. if (empty($diff->renamedIndexes) || ! $diff->fromTable instanceof Table) {
  687. return [];
  688. }
  689. $foreignKeys = [];
  690. /** @var ForeignKeyConstraint[] $remainingForeignKeys */
  691. $remainingForeignKeys = array_diff_key(
  692. $diff->fromTable->getForeignKeys(),
  693. $diff->removedForeignKeys
  694. );
  695. foreach ($remainingForeignKeys as $foreignKey) {
  696. foreach ($diff->renamedIndexes as $index) {
  697. if ($foreignKey->intersectsIndexColumns($index)) {
  698. $foreignKeys[] = $foreignKey;
  699. break;
  700. }
  701. }
  702. }
  703. return $foreignKeys;
  704. }
  705. /**
  706. * {@inheritdoc}
  707. */
  708. protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
  709. {
  710. return array_merge(
  711. parent::getPostAlterTableIndexForeignKeySQL($diff),
  712. $this->getPostAlterTableRenameIndexForeignKeySQL($diff)
  713. );
  714. }
  715. /**
  716. * @param TableDiff $diff The table diff to gather the SQL for.
  717. *
  718. * @return string[]
  719. */
  720. protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff)
  721. {
  722. $sql = [];
  723. $newName = $diff->getNewName();
  724. if ($newName !== false) {
  725. $tableName = $newName->getQuotedName($this);
  726. } else {
  727. $tableName = $diff->getName($this)->getQuotedName($this);
  728. }
  729. foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) {
  730. if (in_array($foreignKey, $diff->changedForeignKeys, true)) {
  731. continue;
  732. }
  733. $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
  734. }
  735. return $sql;
  736. }
  737. /**
  738. * {@inheritDoc}
  739. */
  740. protected function getCreateIndexSQLFlags(Index $index)
  741. {
  742. $type = '';
  743. if ($index->isUnique()) {
  744. $type .= 'UNIQUE ';
  745. } elseif ($index->hasFlag('fulltext')) {
  746. $type .= 'FULLTEXT ';
  747. } elseif ($index->hasFlag('spatial')) {
  748. $type .= 'SPATIAL ';
  749. }
  750. return $type;
  751. }
  752. /**
  753. * {@inheritDoc}
  754. */
  755. public function getIntegerTypeDeclarationSQL(array $field)
  756. {
  757. return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
  758. }
  759. /**
  760. * {@inheritDoc}
  761. */
  762. public function getBigIntTypeDeclarationSQL(array $field)
  763. {
  764. return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
  765. }
  766. /**
  767. * {@inheritDoc}
  768. */
  769. public function getSmallIntTypeDeclarationSQL(array $field)
  770. {
  771. return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
  772. }
  773. /**
  774. * {@inheritdoc}
  775. */
  776. public function getFloatDeclarationSQL(array $field)
  777. {
  778. return 'DOUBLE PRECISION' . $this->getUnsignedDeclaration($field);
  779. }
  780. /**
  781. * {@inheritdoc}
  782. */
  783. public function getDecimalTypeDeclarationSQL(array $columnDef)
  784. {
  785. return parent::getDecimalTypeDeclarationSQL($columnDef) . $this->getUnsignedDeclaration($columnDef);
  786. }
  787. /**
  788. * Get unsigned declaration for a column.
  789. *
  790. * @param mixed[] $columnDef
  791. *
  792. * @return string
  793. */
  794. private function getUnsignedDeclaration(array $columnDef)
  795. {
  796. return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
  797. }
  798. /**
  799. * {@inheritDoc}
  800. */
  801. protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
  802. {
  803. $autoinc = '';
  804. if (! empty($columnDef['autoincrement'])) {
  805. $autoinc = ' AUTO_INCREMENT';
  806. }
  807. return $this->getUnsignedDeclaration($columnDef) . $autoinc;
  808. }
  809. /**
  810. * {@inheritDoc}
  811. */
  812. public function getColumnCharsetDeclarationSQL($charset)
  813. {
  814. return 'CHARACTER SET ' . $charset;
  815. }
  816. /**
  817. * {@inheritDoc}
  818. */
  819. public function getColumnCollationDeclarationSQL($collation)
  820. {
  821. return 'COLLATE ' . $this->quoteSingleIdentifier($collation);
  822. }
  823. /**
  824. * {@inheritDoc}
  825. */
  826. public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
  827. {
  828. $query = '';
  829. if ($foreignKey->hasOption('match')) {
  830. $query .= ' MATCH ' . $foreignKey->getOption('match');
  831. }
  832. $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
  833. return $query;
  834. }
  835. /**
  836. * {@inheritDoc}
  837. */
  838. public function getDropIndexSQL($index, $table = null)
  839. {
  840. if ($index instanceof Index) {
  841. $indexName = $index->getQuotedName($this);
  842. } elseif (is_string($index)) {
  843. $indexName = $index;
  844. } else {
  845. throw new InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
  846. }
  847. if ($table instanceof Table) {
  848. $table = $table->getQuotedName($this);
  849. } elseif (! is_string($table)) {
  850. throw new InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
  851. }
  852. if ($index instanceof Index && $index->isPrimary()) {
  853. // mysql primary keys are always named "PRIMARY",
  854. // so we cannot use them in statements because of them being keyword.
  855. return $this->getDropPrimaryKeySQL($table);
  856. }
  857. return 'DROP INDEX ' . $indexName . ' ON ' . $table;
  858. }
  859. /**
  860. * @param string $table
  861. *
  862. * @return string
  863. */
  864. protected function getDropPrimaryKeySQL($table)
  865. {
  866. return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY';
  867. }
  868. /**
  869. * {@inheritDoc}
  870. */
  871. public function getSetTransactionIsolationSQL($level)
  872. {
  873. return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
  874. }
  875. /**
  876. * {@inheritDoc}
  877. */
  878. public function getName()
  879. {
  880. return 'mysql';
  881. }
  882. /**
  883. * {@inheritDoc}
  884. */
  885. public function getReadLockSQL()
  886. {
  887. return 'LOCK IN SHARE MODE';
  888. }
  889. /**
  890. * {@inheritDoc}
  891. */
  892. protected function initializeDoctrineTypeMappings()
  893. {
  894. $this->doctrineTypeMapping = [
  895. 'tinyint' => 'boolean',
  896. 'smallint' => 'smallint',
  897. 'mediumint' => 'integer',
  898. 'int' => 'integer',
  899. 'integer' => 'integer',
  900. 'bigint' => 'bigint',
  901. 'tinytext' => 'text',
  902. 'mediumtext' => 'text',
  903. 'longtext' => 'text',
  904. 'text' => 'text',
  905. 'varchar' => 'string',
  906. 'string' => 'string',
  907. 'char' => 'string',
  908. 'date' => 'date',
  909. 'datetime' => 'datetime',
  910. 'timestamp' => 'datetime',
  911. 'time' => 'time',
  912. 'float' => 'float',
  913. 'double' => 'float',
  914. 'real' => 'float',
  915. 'decimal' => 'decimal',
  916. 'numeric' => 'decimal',
  917. 'year' => 'date',
  918. 'longblob' => 'blob',
  919. 'blob' => 'blob',
  920. 'mediumblob' => 'blob',
  921. 'tinyblob' => 'blob',
  922. 'binary' => 'binary',
  923. 'varbinary' => 'binary',
  924. 'set' => 'simple_array',
  925. ];
  926. }
  927. /**
  928. * {@inheritDoc}
  929. */
  930. public function getVarcharMaxLength()
  931. {
  932. return 65535;
  933. }
  934. /**
  935. * {@inheritdoc}
  936. */
  937. public function getBinaryMaxLength()
  938. {
  939. return 65535;
  940. }
  941. /**
  942. * {@inheritDoc}
  943. */
  944. protected function getReservedKeywordsClass()
  945. {
  946. return Keywords\MySQLKeywords::class;
  947. }
  948. /**
  949. * {@inheritDoc}
  950. *
  951. * MySQL commits a transaction implicitly when DROP TABLE is executed, however not
  952. * if DROP TEMPORARY TABLE is executed.
  953. */
  954. public function getDropTemporaryTableSQL($table)
  955. {
  956. if ($table instanceof Table) {
  957. $table = $table->getQuotedName($this);
  958. } elseif (! is_string($table)) {
  959. throw new InvalidArgumentException('getDropTemporaryTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
  960. }
  961. return 'DROP TEMPORARY TABLE ' . $table;
  962. }
  963. /**
  964. * Gets the SQL Snippet used to declare a BLOB column type.
  965. * TINYBLOB : 2 ^ 8 - 1 = 255
  966. * BLOB : 2 ^ 16 - 1 = 65535
  967. * MEDIUMBLOB : 2 ^ 24 - 1 = 16777215
  968. * LONGBLOB : 2 ^ 32 - 1 = 4294967295
  969. *
  970. * {@inheritDoc}
  971. */
  972. public function getBlobTypeDeclarationSQL(array $field)
  973. {
  974. if (! empty($field['length']) && is_numeric($field['length'])) {
  975. $length = $field['length'];
  976. if ($length <= static::LENGTH_LIMIT_TINYBLOB) {
  977. return 'TINYBLOB';
  978. }
  979. if ($length <= static::LENGTH_LIMIT_BLOB) {
  980. return 'BLOB';
  981. }
  982. if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) {
  983. return 'MEDIUMBLOB';
  984. }
  985. }
  986. return 'LONGBLOB';
  987. }
  988. /**
  989. * {@inheritdoc}
  990. */
  991. public function quoteStringLiteral($str)
  992. {
  993. $str = str_replace('\\', '\\\\', $str); // MySQL requires backslashes to be escaped aswell.
  994. return parent::quoteStringLiteral($str);
  995. }
  996. /**
  997. * {@inheritdoc}
  998. */
  999. public function getDefaultTransactionIsolationLevel()
  1000. {
  1001. return TransactionIsolationLevel::REPEATABLE_READ;
  1002. }
  1003. public function supportsColumnLengthIndexes() : bool
  1004. {
  1005. return true;
  1006. }
  1007. }