MysqlAdapter.php 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  1. <?php
  2. /**
  3. * Phinx
  4. *
  5. * (The MIT license)
  6. * Copyright (c) 2015 Rob Morgan
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy
  9. * of this software and associated * documentation files (the "Software"), to
  10. * deal in the Software without restriction, including without limitation the
  11. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  12. * sell copies of the Software, and to permit persons to whom the Software is
  13. * furnished to do so, subject to the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in
  16. * all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  23. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  24. * IN THE SOFTWARE.
  25. *
  26. * @package Phinx
  27. * @subpackage Phinx\Db\Adapter
  28. */
  29. namespace BBDDL\Db\Adapter;
  30. use BBDDL\Db\Table;
  31. use BBDDL\Db\Table\Column;
  32. use BBDDL\Db\Table\Index;
  33. use BBDDL\Db\Table\ForeignKey;
  34. /**
  35. * Phinx MySQL Adapter.
  36. *
  37. * @author Rob Morgan <robbym@gmail.com>
  38. */
  39. class MysqlAdapter extends PdoAdapter implements AdapterInterface
  40. {
  41. protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true, 'boolean' => true);
  42. const TEXT_TINY = 255;
  43. const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */
  44. const TEXT_REGULAR = 65535;
  45. const TEXT_MEDIUM = 16777215;
  46. const TEXT_LONG = 4294967295;
  47. // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT
  48. const BLOB_TINY = 255;
  49. const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */
  50. const BLOB_REGULAR = 65535;
  51. const BLOB_MEDIUM = 16777215;
  52. const BLOB_LONG = 4294967295;
  53. const INT_TINY = 255;
  54. const INT_SMALL = 65535;
  55. const INT_MEDIUM = 16777215;
  56. const INT_REGULAR = 4294967295;
  57. const INT_BIG = 18446744073709551615;
  58. const TYPE_YEAR = 'year';
  59. /**
  60. * {@inheritdoc}
  61. */
  62. public function connect()
  63. {
  64. if (null === $this->connection) {
  65. if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) {
  66. // @codeCoverageIgnoreStart
  67. throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.');
  68. // @codeCoverageIgnoreEnd
  69. }
  70. $db = null;
  71. $options = $this->getOptions();
  72. $dsn = 'mysql:';
  73. if (!empty($options['unix_socket'])) {
  74. // use socket connection
  75. $dsn .= 'unix_socket=' . $options['unix_socket'];
  76. } else {
  77. // use network connection
  78. $dsn .= 'host=' . $options['host'];
  79. if (!empty($options['port'])) {
  80. $dsn .= ';port=' . $options['port'];
  81. }
  82. }
  83. $dsn .= ';dbname=' . $options['name'];
  84. // charset support
  85. if (!empty($options['charset'])) {
  86. $dsn .= ';charset=' . $options['charset'];
  87. }
  88. $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION);
  89. // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO
  90. // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants
  91. foreach ($options as $key => $option) {
  92. if (strpos($key, 'mysql_attr_') === 0) {
  93. $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
  94. }
  95. }
  96. try {
  97. $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
  98. } catch (\PDOException $exception) {
  99. throw new \InvalidArgumentException(sprintf(
  100. 'There was a problem connecting to the database: %s',
  101. $exception->getMessage()
  102. ));
  103. }
  104. $this->setConnection($db);
  105. }
  106. }
  107. /**
  108. * {@inheritdoc}
  109. */
  110. public function disconnect()
  111. {
  112. $this->connection = null;
  113. }
  114. /**
  115. * {@inheritdoc}
  116. */
  117. public function hasTransactions()
  118. {
  119. return true;
  120. }
  121. /**
  122. * {@inheritdoc}
  123. */
  124. public function beginTransaction()
  125. {
  126. $this->execute('START TRANSACTION');
  127. }
  128. /**
  129. * {@inheritdoc}
  130. */
  131. public function commitTransaction()
  132. {
  133. $this->execute('COMMIT');
  134. }
  135. /**
  136. * {@inheritdoc}
  137. */
  138. public function rollbackTransaction()
  139. {
  140. $this->execute('ROLLBACK');
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. public function quoteTableName($tableName)
  146. {
  147. return str_replace('.', '`.`', $this->quoteColumnName($tableName));
  148. }
  149. /**
  150. * {@inheritdoc}
  151. */
  152. public function quoteColumnName($columnName)
  153. {
  154. return '`' . str_replace('`', '``', $columnName) . '`';
  155. }
  156. /**
  157. * {@inheritdoc}
  158. */
  159. public function hasTable($tableName)
  160. {
  161. $options = $this->getOptions();
  162. $exists = $this->fetchRow(sprintf(
  163. "SELECT TABLE_NAME
  164. FROM INFORMATION_SCHEMA.TABLES
  165. WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'",
  166. $options['name'], $tableName
  167. ));
  168. return !empty($exists);
  169. }
  170. /**
  171. * {@inheritdoc}
  172. */
  173. public function createTable(Table $table)
  174. {
  175. $this->startCommandTimer();
  176. // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html
  177. $defaultOptions = array(
  178. 'engine' => 'InnoDB',
  179. 'collation' => 'utf8_general_ci'
  180. );
  181. $options = array_merge($defaultOptions, $table->getOptions());
  182. // Add the default primary key
  183. $columns = $table->getPendingColumns();
  184. if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
  185. $column = new Column();
  186. $column->setName('id')
  187. ->setType('integer')
  188. ->setIdentity(true);
  189. array_unshift($columns, $column);
  190. $options['primary_key'] = 'id';
  191. } elseif (isset($options['id']) && is_string($options['id'])) {
  192. // Handle id => "field_name" to support AUTO_INCREMENT
  193. $column = new Column();
  194. $column->setName($options['id'])
  195. ->setType('integer')
  196. ->setIdentity(true);
  197. array_unshift($columns, $column);
  198. $options['primary_key'] = $options['id'];
  199. }
  200. // TODO - process table options like collation etc
  201. // process table engine (default to InnoDB)
  202. $optionsStr = 'ENGINE = InnoDB';
  203. if (isset($options['engine'])) {
  204. $optionsStr = sprintf('ENGINE = %s', $options['engine']);
  205. }
  206. // process table collation
  207. if (isset($options['collation'])) {
  208. $charset = explode('_', $options['collation']);
  209. $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
  210. $optionsStr .= sprintf(' COLLATE %s', $options['collation']);
  211. }
  212. // set the table comment
  213. if (isset($options['comment'])) {
  214. $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment']));
  215. }
  216. $sql = 'CREATE TABLE ';
  217. $sql .= $this->quoteTableName($table->getName()) . ' (';
  218. foreach ($columns as $column) {
  219. $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
  220. }
  221. // set the primary key(s)
  222. if (isset($options['primary_key'])) {
  223. $sql = rtrim($sql);
  224. $sql .= ' PRIMARY KEY (';
  225. if (is_string($options['primary_key'])) { // handle primary_key => 'id'
  226. $sql .= $this->quoteColumnName($options['primary_key']);
  227. } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
  228. // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the
  229. // anonymous function, but for now just hard-code the adapter quotes
  230. $sql .= implode(
  231. ',',
  232. array_map(
  233. function ($v) {
  234. return '`' . $v . '`';
  235. },
  236. $options['primary_key']
  237. )
  238. );
  239. }
  240. $sql .= ')';
  241. } else {
  242. $sql = substr(rtrim($sql), 0, -1); // no primary keys
  243. }
  244. // set the indexes
  245. $indexes = $table->getIndexes();
  246. if (!empty($indexes)) {
  247. foreach ($indexes as $index) {
  248. $sql .= ', ' . $this->getIndexSqlDefinition($index);
  249. }
  250. }
  251. // set the foreign keys
  252. $foreignKeys = $table->getForeignKeys();
  253. if (!empty($foreignKeys)) {
  254. foreach ($foreignKeys as $foreignKey) {
  255. $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
  256. }
  257. }
  258. $sql .= ') ' . $optionsStr;
  259. $sql = rtrim($sql) . ';';
  260. // execute the sql
  261. //$this->writeCommand('createTable', array($table->getName()));
  262. $this->execute($sql);
  263. $this->endCommandTimer();
  264. }
  265. /**
  266. * {@inheritdoc}
  267. */
  268. public function renameTable($tableName, $newTableName)
  269. {
  270. $this->startCommandTimer();
  271. //$this->writeCommand('renameTable', array($tableName, $newTableName));
  272. $this->execute(sprintf('RENAME TABLE %s TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
  273. $this->endCommandTimer();
  274. }
  275. /**
  276. * {@inheritdoc}
  277. */
  278. public function dropTable($tableName)
  279. {
  280. $this->startCommandTimer();
  281. // $this->writeCommand('dropTable', array($tableName));
  282. $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
  283. $this->endCommandTimer();
  284. }
  285. /**
  286. * {@inheritdoc}
  287. */
  288. public function getColumns($tableName)
  289. {
  290. $columns = array();
  291. $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
  292. foreach ($rows as $columnInfo) {
  293. $phinxType = $this->getPhinxType($columnInfo['Type']);
  294. $column = new Column();
  295. $column->setName($columnInfo['Field'])
  296. ->setNull($columnInfo['Null'] !== 'NO')
  297. ->setDefault($columnInfo['Default'])
  298. ->setType($phinxType['name'])
  299. ->setLimit($phinxType['limit']);
  300. if ($columnInfo['Extra'] === 'auto_increment') {
  301. $column->setIdentity(true);
  302. }
  303. $columns[] = $column;
  304. }
  305. return $columns;
  306. }
  307. /**
  308. * {@inheritdoc}
  309. */
  310. public function hasColumn($tableName, $columnName)
  311. {
  312. $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
  313. foreach ($rows as $column) {
  314. if (strcasecmp($column['Field'], $columnName) === 0) {
  315. return true;
  316. }
  317. }
  318. return false;
  319. }
  320. /**
  321. * Get the defintion for a `DEFAULT` statement.
  322. *
  323. * @param mixed $default
  324. * @return string
  325. */
  326. protected function getDefaultValueDefinition($default)
  327. {
  328. if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
  329. $default = $this->getConnection()->quote($default);
  330. } elseif (is_bool($default)) {
  331. $default = $this->castToBool($default);
  332. }
  333. return isset($default) ? ' DEFAULT ' . $default : '';
  334. }
  335. /**
  336. * {@inheritdoc}
  337. */
  338. public function addColumn(Table $table, Column $column)
  339. {
  340. $this->startCommandTimer();
  341. $sql = sprintf(
  342. 'ALTER TABLE %s ADD %s %s',
  343. $this->quoteTableName($table->getName()),
  344. $this->quoteColumnName($column->getName()),
  345. $this->getColumnSqlDefinition($column)
  346. );
  347. if ($column->getAfter()) {
  348. $sql .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
  349. }
  350. //$this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType()));
  351. $this->execute($sql);
  352. $this->endCommandTimer();
  353. }
  354. /**
  355. * {@inheritdoc}
  356. */
  357. public function renameColumn($tableName, $columnName, $newColumnName)
  358. {
  359. $this->startCommandTimer();
  360. $rows = $this->fetchAll(sprintf('DESCRIBE %s', $this->quoteTableName($tableName)));
  361. foreach ($rows as $row) {
  362. if (strcasecmp($row['Field'], $columnName) === 0) {
  363. $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL';
  364. $extra = ' ' . strtoupper($row['Extra']);
  365. if (!is_null($row['Default'])) {
  366. $extra .= $this->getDefaultValueDefinition($row['Default']);
  367. }
  368. $definition = $row['Type'] . ' ' . $null . $extra;
  369. //$this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName));
  370. $this->execute(
  371. sprintf(
  372. 'ALTER TABLE %s CHANGE COLUMN %s %s %s',
  373. $this->quoteTableName($tableName),
  374. $this->quoteColumnName($columnName),
  375. $this->quoteColumnName($newColumnName),
  376. $definition
  377. )
  378. );
  379. $this->endCommandTimer();
  380. return;
  381. }
  382. }
  383. throw new \InvalidArgumentException(sprintf(
  384. 'The specified column doesn\'t exist: '
  385. . $columnName
  386. ));
  387. }
  388. /**
  389. * {@inheritdoc}
  390. */
  391. public function changeColumn($tableName, $columnName, Column $newColumn)
  392. {
  393. $this->startCommandTimer();
  394. //$this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType()));
  395. $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
  396. $this->execute(
  397. sprintf(
  398. 'ALTER TABLE %s CHANGE %s %s %s%s',
  399. $this->quoteTableName($tableName),
  400. $this->quoteColumnName($columnName),
  401. $this->quoteColumnName($newColumn->getName()),
  402. $this->getColumnSqlDefinition($newColumn),
  403. $after
  404. )
  405. );
  406. $this->endCommandTimer();
  407. }
  408. /**
  409. * {@inheritdoc}
  410. */
  411. public function dropColumn($tableName, $columnName)
  412. {
  413. $this->startCommandTimer();
  414. //$this->writeCommand('dropColumn', array($tableName, $columnName));
  415. $this->execute(
  416. sprintf(
  417. 'ALTER TABLE %s DROP COLUMN %s',
  418. $this->quoteTableName($tableName),
  419. $this->quoteColumnName($columnName)
  420. )
  421. );
  422. $this->endCommandTimer();
  423. }
  424. /**
  425. * Get an array of indexes from a particular table.
  426. *
  427. * @param string $tableName Table Name
  428. * @return array
  429. */
  430. protected function getIndexes($tableName)
  431. {
  432. $indexes = array();
  433. $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
  434. foreach ($rows as $row) {
  435. if (!isset($indexes[$row['Key_name']])) {
  436. $indexes[$row['Key_name']] = array('columns' => array());
  437. }
  438. $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
  439. }
  440. return $indexes;
  441. }
  442. /**
  443. * {@inheritdoc}
  444. */
  445. public function hasIndex($tableName, $columns)
  446. {
  447. if (is_string($columns)) {
  448. $columns = array($columns); // str to array
  449. }
  450. $columns = array_map('strtolower', $columns);
  451. $indexes = $this->getIndexes($tableName);
  452. foreach ($indexes as $index) {
  453. if ($columns == $index['columns']) {
  454. return true;
  455. }
  456. }
  457. return false;
  458. }
  459. /**
  460. * {@inheritdoc}
  461. */
  462. public function hasIndexByName($tableName, $indexName)
  463. {
  464. $indexes = $this->getIndexes($tableName);
  465. foreach ($indexes as $name => $index) {
  466. if ($name === $indexName) {
  467. return true;
  468. }
  469. }
  470. return false;
  471. }
  472. /**
  473. * {@inheritdoc}
  474. */
  475. public function addIndex(Table $table, Index $index)
  476. {
  477. $this->startCommandTimer();
  478. //$this->writeCommand('addIndex', array($table->getName(), $index->getColumns()));
  479. $this->execute(
  480. sprintf(
  481. 'ALTER TABLE %s ADD %s',
  482. $this->quoteTableName($table->getName()),
  483. $this->getIndexSqlDefinition($index)
  484. )
  485. );
  486. $this->endCommandTimer();
  487. }
  488. /**
  489. * {@inheritdoc}
  490. */
  491. public function dropIndex($tableName, $columns)
  492. {
  493. $this->startCommandTimer();
  494. if (is_string($columns)) {
  495. $columns = array($columns); // str to array
  496. }
  497. $this->writeCommand('dropIndex', array($tableName, $columns));
  498. $indexes = $this->getIndexes($tableName);
  499. $columns = array_map('strtolower', $columns);
  500. foreach ($indexes as $indexName => $index) {
  501. if ($columns == $index['columns']) {
  502. $this->execute(
  503. sprintf(
  504. 'ALTER TABLE %s DROP INDEX %s',
  505. $this->quoteTableName($tableName),
  506. $this->quoteColumnName($indexName)
  507. )
  508. );
  509. $this->endCommandTimer();
  510. return;
  511. }
  512. }
  513. }
  514. /**
  515. * {@inheritdoc}
  516. */
  517. public function dropIndexByName($tableName, $indexName)
  518. {
  519. $this->startCommandTimer();
  520. $this->writeCommand('dropIndexByName', array($tableName, $indexName));
  521. $indexes = $this->getIndexes($tableName);
  522. foreach ($indexes as $name => $index) {
  523. //$a = array_diff($columns, $index['columns']);
  524. if ($name === $indexName) {
  525. $this->execute(
  526. sprintf(
  527. 'ALTER TABLE %s DROP INDEX %s',
  528. $this->quoteTableName($tableName),
  529. $this->quoteColumnName($indexName)
  530. )
  531. );
  532. $this->endCommandTimer();
  533. return;
  534. }
  535. }
  536. }
  537. /**
  538. * {@inheritdoc}
  539. */
  540. public function hasForeignKey($tableName, $columns, $constraint = null)
  541. {
  542. if (is_string($columns)) {
  543. $columns = array($columns); // str to array
  544. }
  545. $foreignKeys = $this->getForeignKeys($tableName);
  546. if ($constraint) {
  547. if (isset($foreignKeys[$constraint])) {
  548. return !empty($foreignKeys[$constraint]);
  549. }
  550. return false;
  551. } else {
  552. foreach ($foreignKeys as $key) {
  553. $a = array_diff($columns, $key['columns']);
  554. if ($columns == $key['columns']) {
  555. return true;
  556. }
  557. }
  558. return false;
  559. }
  560. }
  561. /**
  562. * Get an array of foreign keys from a particular table.
  563. *
  564. * @param string $tableName Table Name
  565. * @return array
  566. */
  567. protected function getForeignKeys($tableName)
  568. {
  569. $foreignKeys = array();
  570. $rows = $this->fetchAll(sprintf(
  571. "SELECT
  572. CONSTRAINT_NAME,
  573. TABLE_NAME,
  574. COLUMN_NAME,
  575. REFERENCED_TABLE_NAME,
  576. REFERENCED_COLUMN_NAME
  577. FROM information_schema.KEY_COLUMN_USAGE
  578. WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
  579. AND REFERENCED_TABLE_NAME IS NOT NULL
  580. AND TABLE_NAME = '%s'
  581. ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
  582. $tableName
  583. ));
  584. foreach ($rows as $row) {
  585. $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
  586. $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
  587. $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
  588. $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
  589. }
  590. return $foreignKeys;
  591. }
  592. /**
  593. * {@inheritdoc}
  594. */
  595. public function addForeignKey(Table $table, ForeignKey $foreignKey)
  596. {
  597. $this->startCommandTimer();
  598. $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns()));
  599. $this->execute(
  600. sprintf(
  601. 'ALTER TABLE %s ADD %s',
  602. $this->quoteTableName($table->getName()),
  603. $this->getForeignKeySqlDefinition($foreignKey)
  604. )
  605. );
  606. $this->endCommandTimer();
  607. }
  608. /**
  609. * {@inheritdoc}
  610. */
  611. public function dropForeignKey($tableName, $columns, $constraint = null)
  612. {
  613. $this->startCommandTimer();
  614. if (is_string($columns)) {
  615. $columns = array($columns); // str to array
  616. }
  617. $this->writeCommand('dropForeignKey', array($tableName, $columns));
  618. if ($constraint) {
  619. $this->execute(
  620. sprintf(
  621. 'ALTER TABLE %s DROP FOREIGN KEY %s',
  622. $this->quoteTableName($tableName),
  623. $constraint
  624. )
  625. );
  626. $this->endCommandTimer();
  627. return;
  628. } else {
  629. foreach ($columns as $column) {
  630. $rows = $this->fetchAll(sprintf(
  631. "SELECT
  632. CONSTRAINT_NAME
  633. FROM information_schema.KEY_COLUMN_USAGE
  634. WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
  635. AND REFERENCED_TABLE_NAME IS NOT NULL
  636. AND TABLE_NAME = '%s'
  637. AND COLUMN_NAME = '%s'
  638. ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
  639. $tableName,
  640. $column
  641. ));
  642. foreach ($rows as $row) {
  643. $this->dropForeignKey($tableName, $columns, $row['CONSTRAINT_NAME']);
  644. }
  645. }
  646. }
  647. $this->endCommandTimer();
  648. }
  649. /**
  650. * {@inheritdoc}
  651. */
  652. public function getSqlType($type, $limit = null)
  653. {
  654. switch ($type) {
  655. case static::PHINX_TYPE_STRING:
  656. return array('name' => 'varchar', 'limit' => $limit ? $limit : 255);
  657. break;
  658. case static::PHINX_TYPE_CHAR:
  659. return array('name' => 'char', 'limit' => $limit ? $limit : 255);
  660. break;
  661. case static::PHINX_TYPE_TEXT:
  662. if ($limit) {
  663. $sizes = array(
  664. // Order matters! Size must always be tested from longest to shortest!
  665. 'longtext' => static::TEXT_LONG,
  666. 'mediumtext' => static::TEXT_MEDIUM,
  667. 'text' => static::TEXT_REGULAR,
  668. 'tinytext' => static::TEXT_SMALL,
  669. );
  670. foreach ($sizes as $name => $length) {
  671. if ($limit >= $length) {
  672. return array('name' => $name);
  673. }
  674. }
  675. }
  676. return array('name' => 'text');
  677. break;
  678. case static::PHINX_TYPE_BINARY:
  679. return array('name' => 'binary', 'limit' => $limit ? $limit : 255);
  680. break;
  681. case static::PHINX_TYPE_VARBINARY:
  682. return array('name' => 'varbinary', 'limit' => $limit ? $limit : 255);
  683. break;
  684. case static::PHINX_TYPE_BLOB:
  685. if ($limit) {
  686. $sizes = array(
  687. // Order matters! Size must always be tested from longest to shortest!
  688. 'longblob' => static::BLOB_LONG,
  689. 'mediumblob' => static::BLOB_MEDIUM,
  690. 'blob' => static::BLOB_REGULAR,
  691. 'tinyblob' => static::BLOB_SMALL,
  692. );
  693. foreach ($sizes as $name => $length) {
  694. if ($limit >= $length) {
  695. return array('name' => $name);
  696. }
  697. }
  698. }
  699. return array('name' => 'blob');
  700. break;
  701. case static::PHINX_TYPE_INTEGER:
  702. if ($limit && $limit >= static::INT_TINY) {
  703. $sizes = array(
  704. // Order matters! Size must always be tested from longest to shortest!
  705. 'bigint' => static::INT_BIG,
  706. 'int' => static::INT_REGULAR,
  707. 'mediumint' => static::INT_MEDIUM,
  708. 'smallint' => static::INT_SMALL,
  709. 'tinyint' => static::INT_TINY,
  710. );
  711. $limits = array(
  712. 'int' => 11,
  713. 'bigint' => 20,
  714. );
  715. foreach ($sizes as $name => $length) {
  716. if ($limit >= $length) {
  717. $def = array('name' => $name);
  718. if (isset($limits[$name])) {
  719. $def['limit'] = $limits[$name];
  720. }
  721. return $def;
  722. }
  723. }
  724. } elseif (!$limit) {
  725. $limit = 11;
  726. }
  727. return array('name' => 'int', 'limit' => $limit);
  728. break;
  729. case static::PHINX_TYPE_BIG_INTEGER:
  730. return array('name' => 'bigint', 'limit' => 20);
  731. break;
  732. case static::PHINX_TYPE_FLOAT:
  733. return array('name' => 'float');
  734. break;
  735. case static::PHINX_TYPE_DECIMAL:
  736. return array('name' => 'decimal');
  737. break;
  738. case static::PHINX_TYPE_DATETIME:
  739. return array('name' => 'datetime');
  740. break;
  741. case static::PHINX_TYPE_TIMESTAMP:
  742. return array('name' => 'timestamp');
  743. break;
  744. case static::PHINX_TYPE_TIME:
  745. return array('name' => 'time');
  746. break;
  747. case static::PHINX_TYPE_DATE:
  748. return array('name' => 'date');
  749. break;
  750. case static::PHINX_TYPE_BOOLEAN:
  751. return array('name' => 'tinyint', 'limit' => 1);
  752. break;
  753. case static::PHINX_TYPE_UUID:
  754. return array('name' => 'char', 'limit' => 36);
  755. // Geospatial database types
  756. case static::PHINX_TYPE_GEOMETRY:
  757. case static::PHINX_TYPE_POINT:
  758. case static::PHINX_TYPE_LINESTRING:
  759. case static::PHINX_TYPE_POLYGON:
  760. return array('name' => $type);
  761. case static::PHINX_TYPE_ENUM:
  762. return array('name' => 'enum');
  763. break;
  764. case static::PHINX_TYPE_SET:
  765. return array('name' => 'set');
  766. break;
  767. case static::TYPE_YEAR:
  768. if (!$limit || in_array($limit, array(2, 4)))
  769. $limit = 4;
  770. return array('name' => 'year', 'limit' => $limit);
  771. break;
  772. case static::PHINX_TYPE_JSON:
  773. return array('name' => 'json');
  774. break;
  775. default:
  776. throw new \RuntimeException('The type: "' . $type . '" is not supported.');
  777. }
  778. }
  779. /**
  780. * Returns Phinx type by SQL type
  781. *
  782. * @param string $sqlTypeDef
  783. * @throws \RuntimeException
  784. * @internal param string $sqlType SQL type
  785. * @returns string Phinx type
  786. */
  787. public function getPhinxType($sqlTypeDef)
  788. {
  789. if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
  790. throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
  791. } else {
  792. $limit = null;
  793. $precision = null;
  794. $type = $matches[1];
  795. if (count($matches) > 2) {
  796. $limit = $matches[3] ? (int) $matches[3] : null;
  797. }
  798. if (count($matches) > 4) {
  799. $precision = (int) $matches[5];
  800. }
  801. if ($type === 'tinyint' && $limit === 1) {
  802. $type = static::PHINX_TYPE_BOOLEAN;
  803. $limit = null;
  804. }
  805. switch ($type) {
  806. case 'varchar':
  807. $type = static::PHINX_TYPE_STRING;
  808. if ($limit === 255) {
  809. $limit = null;
  810. }
  811. break;
  812. case 'char':
  813. $type = static::PHINX_TYPE_CHAR;
  814. if ($limit === 255) {
  815. $limit = null;
  816. }
  817. if ($limit === 36) {
  818. $type = static::PHINX_TYPE_UUID;
  819. }
  820. break;
  821. case 'tinyint':
  822. $type = static::PHINX_TYPE_INTEGER;
  823. $limit = static::INT_TINY;
  824. break;
  825. case 'smallint':
  826. $type = static::PHINX_TYPE_INTEGER;
  827. $limit = static::INT_SMALL;
  828. break;
  829. case 'mediumint':
  830. $type = static::PHINX_TYPE_INTEGER;
  831. $limit = static::INT_MEDIUM;
  832. break;
  833. case 'int':
  834. $type = static::PHINX_TYPE_INTEGER;
  835. if ($limit === 11) {
  836. $limit = null;
  837. }
  838. break;
  839. case 'bigint':
  840. if ($limit === 20) {
  841. $limit = null;
  842. }
  843. $type = static::PHINX_TYPE_BIG_INTEGER;
  844. break;
  845. case 'blob':
  846. $type = static::PHINX_TYPE_BINARY;
  847. break;
  848. case 'tinyblob':
  849. $type = static::PHINX_TYPE_BINARY;
  850. $limit = static::BLOB_TINY;
  851. break;
  852. case 'mediumblob':
  853. $type = static::PHINX_TYPE_BINARY;
  854. $limit = static::BLOB_MEDIUM;
  855. break;
  856. case 'longblob':
  857. $type = static::PHINX_TYPE_BINARY;
  858. $limit = static::BLOB_LONG;
  859. break;
  860. case 'tinytext':
  861. $type = static::PHINX_TYPE_TEXT;
  862. $limit = static::TEXT_TINY;
  863. break;
  864. case 'mediumtext':
  865. $type = static::PHINX_TYPE_TEXT;
  866. $limit = static::TEXT_MEDIUM;
  867. break;
  868. case 'longtext':
  869. $type = static::PHINX_TYPE_TEXT;
  870. $limit = static::TEXT_LONG;
  871. break;
  872. }
  873. $this->getSqlType($type, $limit);
  874. return array(
  875. 'name' => $type,
  876. 'limit' => $limit,
  877. 'precision' => $precision
  878. );
  879. }
  880. }
  881. /**
  882. * {@inheritdoc}
  883. */
  884. public function createDatabase($name, $options = array())
  885. {
  886. $this->startCommandTimer();
  887. $this->writeCommand('createDatabase', array($name));
  888. $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
  889. if (isset($options['collation'])) {
  890. $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
  891. } else {
  892. $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
  893. }
  894. $this->endCommandTimer();
  895. }
  896. /**
  897. * {@inheritdoc}
  898. */
  899. public function hasDatabase($name)
  900. {
  901. $rows = $this->fetchAll(
  902. sprintf(
  903. 'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
  904. $name
  905. )
  906. );
  907. foreach ($rows as $row) {
  908. if (!empty($row)) {
  909. return true;
  910. }
  911. }
  912. return false;
  913. }
  914. /**
  915. * {@inheritdoc}
  916. */
  917. public function dropDatabase($name)
  918. {
  919. $this->startCommandTimer();
  920. $this->writeCommand('dropDatabase', array($name));
  921. $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
  922. $this->endCommandTimer();
  923. }
  924. /**
  925. * Gets the MySQL Column Definition for a Column object.
  926. *
  927. * @param Column $column Column
  928. * @return string
  929. */
  930. protected function getColumnSqlDefinition(Column $column)
  931. {
  932. $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
  933. $def = '';
  934. $def .= strtoupper($sqlType['name']);
  935. if ($column->getPrecision() && $column->getScale()) {
  936. $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
  937. } elseif (isset($sqlType['limit'])) {
  938. $def .= '(' . $sqlType['limit'] . ')';
  939. }
  940. if (($values = $column->getValues()) && is_array($values)) {
  941. $def .= "('" . implode("', '", $values) . "')";
  942. }
  943. $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '' ;
  944. $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL';
  945. $def .= ($column->isIdentity()) ? ' AUTO_INCREMENT' : '';
  946. $def .= $this->getDefaultValueDefinition($column->getDefault());
  947. if ($column->getComment()) {
  948. $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
  949. }
  950. if ($column->getUpdate()) {
  951. $def .= ' ON UPDATE ' . $column->getUpdate();
  952. }
  953. return $def;
  954. }
  955. /**
  956. * Gets the MySQL Index Definition for an Index object.
  957. *
  958. * @param Index $index Index
  959. * @return string
  960. */
  961. protected function getIndexSqlDefinition(Index $index)
  962. {
  963. $def = '';
  964. $limit = '';
  965. if ($index->getLimit()) {
  966. $limit = '(' . $index->getLimit() . ')';
  967. }
  968. if ($index->getType() == Index::UNIQUE) {
  969. $def .= ' UNIQUE';
  970. }
  971. if ($index->getType() == Index::FULLTEXT) {
  972. $def .= ' FULLTEXT';
  973. }
  974. $def .= ' KEY';
  975. if (is_string($index->getName())) {
  976. $def .= ' `' . $index->getName() . '`';
  977. }
  978. $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';
  979. return $def;
  980. }
  981. /**
  982. * Gets the MySQL Foreign Key Definition for an ForeignKey object.
  983. *
  984. * @param ForeignKey $foreignKey
  985. * @return string
  986. */
  987. protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
  988. {
  989. $def = '';
  990. if ($foreignKey->getConstraint()) {
  991. $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
  992. }
  993. $columnNames = array();
  994. foreach ($foreignKey->getColumns() as $column) {
  995. $columnNames[] = $this->quoteColumnName($column);
  996. }
  997. $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
  998. $refColumnNames = array();
  999. foreach ($foreignKey->getReferencedColumns() as $column) {
  1000. $refColumnNames[] = $this->quoteColumnName($column);
  1001. }
  1002. $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
  1003. if ($foreignKey->getOnDelete()) {
  1004. $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
  1005. }
  1006. if ($foreignKey->getOnUpdate()) {
  1007. $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
  1008. }
  1009. return $def;
  1010. }
  1011. /**
  1012. * Describes a database table. This is a MySQL adapter specific method.
  1013. *
  1014. * @param string $tableName Table name
  1015. * @return array
  1016. */
  1017. public function describeTable($tableName)
  1018. {
  1019. $options = $this->getOptions();
  1020. // mysql specific
  1021. $sql = sprintf(
  1022. "SELECT *
  1023. FROM information_schema.tables
  1024. WHERE table_schema = '%s'
  1025. AND table_name = '%s'",
  1026. $options['name'],
  1027. $tableName
  1028. );
  1029. return $this->fetchRow($sql);
  1030. }
  1031. /**
  1032. * Returns MySQL column types (inherited and MySQL specified).
  1033. * @return array
  1034. */
  1035. public function getColumnTypes()
  1036. {
  1037. return array_merge(parent::getColumnTypes(), array ('enum', 'set', 'year', 'json'));
  1038. }
  1039. }