Mysql.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. <?php
  2. /**
  3. * MySQL layer for DBO
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * For full copyright and license information, please see the LICENSE.txt
  12. * Redistributions of files must retain the above copyright notice.
  13. *
  14. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  15. * @link http://cakephp.org CakePHP(tm) Project
  16. * @package Cake.Model.Datasource.Database
  17. * @since CakePHP(tm) v 0.10.5.1790
  18. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  19. */
  20. App::uses('DboSource', 'Model/Datasource');
  21. /**
  22. * MySQL DBO driver object
  23. *
  24. * Provides connection and SQL generation for MySQL RDMS
  25. *
  26. * @package Cake.Model.Datasource.Database
  27. */
  28. class Mysql extends DboSource {
  29. /**
  30. * Datasource description
  31. *
  32. * @var string
  33. */
  34. public $description = "MySQL DBO Driver";
  35. /**
  36. * Base configuration settings for MySQL driver
  37. *
  38. * @var array
  39. */
  40. protected $_baseConfig = array(
  41. 'persistent' => true,
  42. 'host' => 'localhost',
  43. 'login' => 'root',
  44. 'password' => '',
  45. 'database' => 'cake',
  46. 'port' => '3306'
  47. );
  48. /**
  49. * Reference to the PDO object connection
  50. *
  51. * @var PDO $_connection
  52. */
  53. protected $_connection = null;
  54. /**
  55. * Start quote
  56. *
  57. * @var string
  58. */
  59. public $startQuote = "`";
  60. /**
  61. * End quote
  62. *
  63. * @var string
  64. */
  65. public $endQuote = "`";
  66. /**
  67. * use alias for update and delete. Set to true if version >= 4.1
  68. *
  69. * @var boolean
  70. */
  71. protected $_useAlias = true;
  72. /**
  73. * List of engine specific additional field parameters used on table creating
  74. *
  75. * @var array
  76. */
  77. public $fieldParameters = array(
  78. 'charset' => array('value' => 'CHARACTER SET', 'quote' => false, 'join' => ' ', 'column' => false, 'position' => 'beforeDefault'),
  79. 'collate' => array('value' => 'COLLATE', 'quote' => false, 'join' => ' ', 'column' => 'Collation', 'position' => 'beforeDefault'),
  80. 'comment' => array('value' => 'COMMENT', 'quote' => true, 'join' => ' ', 'column' => 'Comment', 'position' => 'afterDefault')
  81. );
  82. /**
  83. * List of table engine specific parameters used on table creating
  84. *
  85. * @var array
  86. */
  87. public $tableParameters = array(
  88. 'charset' => array('value' => 'DEFAULT CHARSET', 'quote' => false, 'join' => '=', 'column' => 'charset'),
  89. 'collate' => array('value' => 'COLLATE', 'quote' => false, 'join' => '=', 'column' => 'Collation'),
  90. 'engine' => array('value' => 'ENGINE', 'quote' => false, 'join' => '=', 'column' => 'Engine')
  91. );
  92. /**
  93. * MySQL column definition
  94. *
  95. * @var array
  96. */
  97. public $columns = array(
  98. 'primary_key' => array('name' => 'NOT NULL AUTO_INCREMENT'),
  99. 'string' => array('name' => 'varchar', 'limit' => '255'),
  100. 'text' => array('name' => 'text'),
  101. 'biginteger' => array('name' => 'bigint', 'limit' => '20'),
  102. 'integer' => array('name' => 'int', 'limit' => '11', 'formatter' => 'intval'),
  103. 'float' => array('name' => 'float', 'formatter' => 'floatval'),
  104. 'numeric' => array('name' => 'decimal', 'formatter' => 'floatval'),
  105. 'decimal' => array('name' => 'decimal', 'formatter' => 'floatval'),
  106. 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
  107. 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
  108. 'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'),
  109. 'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
  110. 'binary' => array('name' => 'blob'),
  111. 'boolean' => array('name' => 'tinyint', 'limit' => '1')
  112. );
  113. /**
  114. * Mapping of collation names to character set names
  115. *
  116. * @var array
  117. */
  118. protected $_charsets = array();
  119. /**
  120. * Connects to the database using options in the given configuration array.
  121. *
  122. * MySQL supports a few additional options that other drivers do not:
  123. *
  124. * - `unix_socket` Set to the path of the MySQL sock file. Can be used in place
  125. * of host + port.
  126. * - `ssl_key` SSL key file for connecting via SSL. Must be combined with `ssl_cert`.
  127. * - `ssl_cert` The SSL certificate to use when connecting via SSL. Must be
  128. * combined with `ssl_key`.
  129. * - `ssl_ca` The certificate authority for SSL connections.
  130. *
  131. * @return boolean True if the database could be connected, else false
  132. * @throws MissingConnectionException
  133. */
  134. public function connect() {
  135. $config = $this->config;
  136. $this->connected = false;
  137. $flags = array(
  138. PDO::ATTR_PERSISTENT => $config['persistent'],
  139. PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
  140. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
  141. );
  142. if (!empty($config['encoding'])) {
  143. $flags[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $config['encoding'];
  144. }
  145. if (!empty($config['ssl_key']) && !empty($config['ssl_cert'])) {
  146. $flags[PDO::MYSQL_ATTR_SSL_KEY] = $config['ssl_key'];
  147. $flags[PDO::MYSQL_ATTR_SSL_CERT] = $config['ssl_cert'];
  148. }
  149. if (!empty($config['ssl_ca'])) {
  150. $flags[PDO::MYSQL_ATTR_SSL_CA] = $config['ssl_ca'];
  151. }
  152. if (empty($config['unix_socket'])) {
  153. $dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}";
  154. } else {
  155. $dsn = "mysql:unix_socket={$config['unix_socket']};dbname={$config['database']}";
  156. }
  157. try {
  158. $this->_connection = new PDO(
  159. $dsn,
  160. $config['login'],
  161. $config['password'],
  162. $flags
  163. );
  164. $this->connected = true;
  165. if (!empty($config['settings'])) {
  166. foreach ($config['settings'] as $key => $value) {
  167. $this->_execute("SET $key=$value");
  168. }
  169. }
  170. } catch (PDOException $e) {
  171. throw new MissingConnectionException(array(
  172. 'class' => get_class($this),
  173. 'message' => $e->getMessage()
  174. ));
  175. }
  176. $this->_charsets = array();
  177. $this->_useAlias = (bool)version_compare($this->getVersion(), "4.1", ">=");
  178. return $this->connected;
  179. }
  180. /**
  181. * Check whether the MySQL extension is installed/loaded
  182. *
  183. * @return boolean
  184. */
  185. public function enabled() {
  186. return in_array('mysql', PDO::getAvailableDrivers());
  187. }
  188. /**
  189. * Returns an array of sources (tables) in the database.
  190. *
  191. * @param mixed $data
  192. * @return array Array of table names in the database
  193. */
  194. public function listSources($data = null) {
  195. $cache = parent::listSources();
  196. if ($cache) {
  197. return $cache;
  198. }
  199. $result = $this->_execute('SHOW TABLES FROM ' . $this->name($this->config['database']));
  200. if (!$result) {
  201. $result->closeCursor();
  202. return array();
  203. }
  204. $tables = array();
  205. while ($line = $result->fetch(PDO::FETCH_NUM)) {
  206. $tables[] = $line[0];
  207. }
  208. $result->closeCursor();
  209. parent::listSources($tables);
  210. return $tables;
  211. }
  212. /**
  213. * Builds a map of the columns contained in a result
  214. *
  215. * @param PDOStatement $results
  216. * @return void
  217. */
  218. public function resultSet($results) {
  219. $this->map = array();
  220. $numFields = $results->columnCount();
  221. $index = 0;
  222. while ($numFields-- > 0) {
  223. $column = $results->getColumnMeta($index);
  224. if ($column['len'] === 1 && (empty($column['native_type']) || $column['native_type'] === 'TINY')) {
  225. $type = 'boolean';
  226. } else {
  227. $type = empty($column['native_type']) ? 'string' : $column['native_type'];
  228. }
  229. if (!empty($column['table']) && strpos($column['name'], $this->virtualFieldSeparator) === false) {
  230. $this->map[$index++] = array($column['table'], $column['name'], $type);
  231. } else {
  232. $this->map[$index++] = array(0, $column['name'], $type);
  233. }
  234. }
  235. }
  236. /**
  237. * Fetches the next row from the current result set
  238. *
  239. * @return mixed array with results fetched and mapped to column names or false if there is no results left to fetch
  240. */
  241. public function fetchResult() {
  242. if ($row = $this->_result->fetch(PDO::FETCH_NUM)) {
  243. $resultRow = array();
  244. foreach ($this->map as $col => $meta) {
  245. list($table, $column, $type) = $meta;
  246. $resultRow[$table][$column] = $row[$col];
  247. if ($type === 'boolean' && $row[$col] !== null) {
  248. $resultRow[$table][$column] = $this->boolean($resultRow[$table][$column]);
  249. }
  250. }
  251. return $resultRow;
  252. }
  253. $this->_result->closeCursor();
  254. return false;
  255. }
  256. /**
  257. * Gets the database encoding
  258. *
  259. * @return string The database encoding
  260. */
  261. public function getEncoding() {
  262. return $this->_execute('SHOW VARIABLES LIKE ?', array('character_set_client'))->fetchObject()->Value;
  263. }
  264. /**
  265. * Query charset by collation
  266. *
  267. * @param string $name Collation name
  268. * @return string Character set name
  269. */
  270. public function getCharsetName($name) {
  271. if ((bool)version_compare($this->getVersion(), "5", "<")) {
  272. return false;
  273. }
  274. if (isset($this->_charsets[$name])) {
  275. return $this->_charsets[$name];
  276. }
  277. $r = $this->_execute(
  278. 'SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME = ?',
  279. array($name)
  280. );
  281. $cols = $r->fetch(PDO::FETCH_ASSOC);
  282. if (isset($cols['CHARACTER_SET_NAME'])) {
  283. $this->_charsets[$name] = $cols['CHARACTER_SET_NAME'];
  284. } else {
  285. $this->_charsets[$name] = false;
  286. }
  287. return $this->_charsets[$name];
  288. }
  289. /**
  290. * Returns an array of the fields in given table name.
  291. *
  292. * @param Model|string $model Name of database table to inspect or model instance
  293. * @return array Fields in table. Keys are name and type
  294. * @throws CakeException
  295. */
  296. public function describe($model) {
  297. $key = $this->fullTableName($model, false);
  298. $cache = parent::describe($key);
  299. if ($cache) {
  300. return $cache;
  301. }
  302. $table = $this->fullTableName($model);
  303. $fields = false;
  304. $cols = $this->_execute('SHOW FULL COLUMNS FROM ' . $table);
  305. if (!$cols) {
  306. throw new CakeException(__d('cake_dev', 'Could not describe table for %s', $table));
  307. }
  308. while ($column = $cols->fetch(PDO::FETCH_OBJ)) {
  309. $fields[$column->Field] = array(
  310. 'type' => $this->column($column->Type),
  311. 'null' => ($column->Null === 'YES' ? true : false),
  312. 'default' => $column->Default,
  313. 'length' => $this->length($column->Type),
  314. );
  315. if (!empty($column->Key) && isset($this->index[$column->Key])) {
  316. $fields[$column->Field]['key'] = $this->index[$column->Key];
  317. }
  318. foreach ($this->fieldParameters as $name => $value) {
  319. if (!empty($column->{$value['column']})) {
  320. $fields[$column->Field][$name] = $column->{$value['column']};
  321. }
  322. }
  323. if (isset($fields[$column->Field]['collate'])) {
  324. $charset = $this->getCharsetName($fields[$column->Field]['collate']);
  325. if ($charset) {
  326. $fields[$column->Field]['charset'] = $charset;
  327. }
  328. }
  329. }
  330. $this->_cacheDescription($key, $fields);
  331. $cols->closeCursor();
  332. return $fields;
  333. }
  334. /**
  335. * Generates and executes an SQL UPDATE statement for given model, fields, and values.
  336. *
  337. * @param Model $model
  338. * @param array $fields
  339. * @param array $values
  340. * @param mixed $conditions
  341. * @return array
  342. */
  343. public function update(Model $model, $fields = array(), $values = null, $conditions = null) {
  344. if (!$this->_useAlias) {
  345. return parent::update($model, $fields, $values, $conditions);
  346. }
  347. if (!$values) {
  348. $combined = $fields;
  349. } else {
  350. $combined = array_combine($fields, $values);
  351. }
  352. $alias = $joins = false;
  353. $fields = $this->_prepareUpdateFields($model, $combined, empty($conditions), !empty($conditions));
  354. $fields = implode(', ', $fields);
  355. $table = $this->fullTableName($model);
  356. if (!empty($conditions)) {
  357. $alias = $this->name($model->alias);
  358. if ($model->name == $model->alias) {
  359. $joins = implode(' ', $this->_getJoins($model));
  360. }
  361. }
  362. $conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias), true, true, $model);
  363. if ($conditions === false) {
  364. return false;
  365. }
  366. if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) {
  367. $model->onError();
  368. return false;
  369. }
  370. return true;
  371. }
  372. /**
  373. * Generates and executes an SQL DELETE statement for given id/conditions on given model.
  374. *
  375. * @param Model $model
  376. * @param mixed $conditions
  377. * @return boolean Success
  378. */
  379. public function delete(Model $model, $conditions = null) {
  380. if (!$this->_useAlias) {
  381. return parent::delete($model, $conditions);
  382. }
  383. $alias = $this->name($model->alias);
  384. $table = $this->fullTableName($model);
  385. $joins = implode(' ', $this->_getJoins($model));
  386. if (empty($conditions)) {
  387. $alias = $joins = false;
  388. }
  389. $complexConditions = false;
  390. foreach ((array)$conditions as $key => $value) {
  391. if (strpos($key, $model->alias) === false) {
  392. $complexConditions = true;
  393. break;
  394. }
  395. }
  396. if (!$complexConditions) {
  397. $joins = false;
  398. }
  399. $conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias), true, true, $model);
  400. if ($conditions === false) {
  401. return false;
  402. }
  403. if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
  404. $model->onError();
  405. return false;
  406. }
  407. return true;
  408. }
  409. /**
  410. * Sets the database encoding
  411. *
  412. * @param string $enc Database encoding
  413. * @return boolean
  414. */
  415. public function setEncoding($enc) {
  416. return $this->_execute('SET NAMES ' . $enc) !== false;
  417. }
  418. /**
  419. * Returns an array of the indexes in given datasource name.
  420. *
  421. * @param string $model Name of model to inspect
  422. * @return array Fields in table. Keys are column and unique
  423. */
  424. public function index($model) {
  425. $index = array();
  426. $table = $this->fullTableName($model);
  427. $old = version_compare($this->getVersion(), '4.1', '<=');
  428. if ($table) {
  429. $indexes = $this->_execute('SHOW INDEX FROM ' . $table);
  430. // @codingStandardsIgnoreStart
  431. // MySQL columns don't match the cakephp conventions.
  432. while ($idx = $indexes->fetch(PDO::FETCH_OBJ)) {
  433. if ($old) {
  434. $idx = (object)current((array)$idx);
  435. }
  436. if (!isset($index[$idx->Key_name]['column'])) {
  437. $col = array();
  438. $index[$idx->Key_name]['column'] = $idx->Column_name;
  439. if ($idx->Index_type === 'FULLTEXT') {
  440. $index[$idx->Key_name]['type'] = strtolower($idx->Index_type);
  441. } else {
  442. $index[$idx->Key_name]['unique'] = intval($idx->Non_unique == 0);
  443. }
  444. } else {
  445. if (!empty($index[$idx->Key_name]['column']) && !is_array($index[$idx->Key_name]['column'])) {
  446. $col[] = $index[$idx->Key_name]['column'];
  447. }
  448. $col[] = $idx->Column_name;
  449. $index[$idx->Key_name]['column'] = $col;
  450. }
  451. if (!empty($idx->Sub_part)) {
  452. if (!isset($index[$idx->Key_name]['length'])) {
  453. $index[$idx->Key_name]['length'] = array();
  454. }
  455. $index[$idx->Key_name]['length'][$idx->Column_name] = $idx->Sub_part;
  456. }
  457. }
  458. // @codingStandardsIgnoreEnd
  459. $indexes->closeCursor();
  460. }
  461. return $index;
  462. }
  463. /**
  464. * Generate a MySQL Alter Table syntax for the given Schema comparison
  465. *
  466. * @param array $compare Result of a CakeSchema::compare()
  467. * @param string $table
  468. * @return array Array of alter statements to make.
  469. */
  470. public function alterSchema($compare, $table = null) {
  471. if (!is_array($compare)) {
  472. return false;
  473. }
  474. $out = '';
  475. $colList = array();
  476. foreach ($compare as $curTable => $types) {
  477. $indexes = $tableParameters = $colList = array();
  478. if (!$table || $table == $curTable) {
  479. $out .= 'ALTER TABLE ' . $this->fullTableName($curTable) . " \n";
  480. foreach ($types as $type => $column) {
  481. if (isset($column['indexes'])) {
  482. $indexes[$type] = $column['indexes'];
  483. unset($column['indexes']);
  484. }
  485. if (isset($column['tableParameters'])) {
  486. $tableParameters[$type] = $column['tableParameters'];
  487. unset($column['tableParameters']);
  488. }
  489. switch ($type) {
  490. case 'add':
  491. foreach ($column as $field => $col) {
  492. $col['name'] = $field;
  493. $alter = 'ADD ' . $this->buildColumn($col);
  494. if (isset($col['after'])) {
  495. $alter .= ' AFTER ' . $this->name($col['after']);
  496. }
  497. $colList[] = $alter;
  498. }
  499. break;
  500. case 'drop':
  501. foreach ($column as $field => $col) {
  502. $col['name'] = $field;
  503. $colList[] = 'DROP ' . $this->name($field);
  504. }
  505. break;
  506. case 'change':
  507. foreach ($column as $field => $col) {
  508. if (!isset($col['name'])) {
  509. $col['name'] = $field;
  510. }
  511. $colList[] = 'CHANGE ' . $this->name($field) . ' ' . $this->buildColumn($col);
  512. }
  513. break;
  514. }
  515. }
  516. $colList = array_merge($colList, $this->_alterIndexes($curTable, $indexes));
  517. $colList = array_merge($colList, $this->_alterTableParameters($curTable, $tableParameters));
  518. $out .= "\t" . implode(",\n\t", $colList) . ";\n\n";
  519. }
  520. }
  521. return $out;
  522. }
  523. /**
  524. * Generate a "drop table" statement for the given table
  525. *
  526. * @param type $table Name of the table to drop
  527. * @return string Drop table SQL statement
  528. */
  529. protected function _dropTable($table) {
  530. return 'DROP TABLE IF EXISTS ' . $this->fullTableName($table) . ";";
  531. }
  532. /**
  533. * Generate MySQL table parameter alteration statements for a table.
  534. *
  535. * @param string $table Table to alter parameters for.
  536. * @param array $parameters Parameters to add & drop.
  537. * @return array Array of table property alteration statements.
  538. */
  539. protected function _alterTableParameters($table, $parameters) {
  540. if (isset($parameters['change'])) {
  541. return $this->buildTableParameters($parameters['change']);
  542. }
  543. return array();
  544. }
  545. /**
  546. * Format indexes for create table
  547. *
  548. * @param array $indexes An array of indexes to generate SQL from
  549. * @param string $table Optional table name, not used
  550. * @return array An array of SQL statements for indexes
  551. * @see DboSource::buildIndex()
  552. */
  553. public function buildIndex($indexes, $table = null) {
  554. $join = array();
  555. foreach ($indexes as $name => $value) {
  556. $out = '';
  557. if ($name === 'PRIMARY') {
  558. $out .= 'PRIMARY ';
  559. $name = null;
  560. } else {
  561. if (!empty($value['unique'])) {
  562. $out .= 'UNIQUE ';
  563. }
  564. $name = $this->startQuote . $name . $this->endQuote;
  565. }
  566. if (isset($value['type']) && strtolower($value['type']) === 'fulltext') {
  567. $out .= 'FULLTEXT ';
  568. }
  569. $out .= 'KEY ' . $name . ' (';
  570. if (is_array($value['column'])) {
  571. if (isset($value['length'])) {
  572. $vals = array();
  573. foreach ($value['column'] as $column) {
  574. $name = $this->name($column);
  575. if (isset($value['length'])) {
  576. $name .= $this->_buildIndexSubPart($value['length'], $column);
  577. }
  578. $vals[] = $name;
  579. }
  580. $out .= implode(', ', $vals);
  581. } else {
  582. $out .= implode(', ', array_map(array(&$this, 'name'), $value['column']));
  583. }
  584. } else {
  585. $out .= $this->name($value['column']);
  586. if (isset($value['length'])) {
  587. $out .= $this->_buildIndexSubPart($value['length'], $value['column']);
  588. }
  589. }
  590. $out .= ')';
  591. $join[] = $out;
  592. }
  593. return $join;
  594. }
  595. /**
  596. * Generate MySQL index alteration statements for a table.
  597. *
  598. * @param string $table Table to alter indexes for
  599. * @param array $indexes Indexes to add and drop
  600. * @return array Index alteration statements
  601. */
  602. protected function _alterIndexes($table, $indexes) {
  603. $alter = array();
  604. if (isset($indexes['drop'])) {
  605. foreach ($indexes['drop'] as $name => $value) {
  606. $out = 'DROP ';
  607. if ($name === 'PRIMARY') {
  608. $out .= 'PRIMARY KEY';
  609. } else {
  610. $out .= 'KEY ' . $this->startQuote . $name . $this->endQuote;
  611. }
  612. $alter[] = $out;
  613. }
  614. }
  615. if (isset($indexes['add'])) {
  616. $add = $this->buildIndex($indexes['add']);
  617. foreach ($add as $index) {
  618. $alter[] = 'ADD ' . $index;
  619. }
  620. }
  621. return $alter;
  622. }
  623. /**
  624. * Format length for text indexes
  625. *
  626. * @param array $lengths An array of lengths for a single index
  627. * @param string $column The column for which to generate the index length
  628. * @return string Formatted length part of an index field
  629. */
  630. protected function _buildIndexSubPart($lengths, $column) {
  631. if ($lengths === null) {
  632. return '';
  633. }
  634. if (!isset($lengths[$column])) {
  635. return '';
  636. }
  637. return '(' . $lengths[$column] . ')';
  638. }
  639. /**
  640. * Returns an detailed array of sources (tables) in the database.
  641. *
  642. * @param string $name Table name to get parameters
  643. * @return array Array of table names in the database
  644. */
  645. public function listDetailedSources($name = null) {
  646. $condition = '';
  647. if (is_string($name)) {
  648. $condition = ' WHERE name = ' . $this->value($name);
  649. }
  650. $result = $this->_connection->query('SHOW TABLE STATUS ' . $condition, PDO::FETCH_ASSOC);
  651. if (!$result) {
  652. $result->closeCursor();
  653. return array();
  654. }
  655. $tables = array();
  656. foreach ($result as $row) {
  657. $tables[$row['Name']] = (array)$row;
  658. unset($tables[$row['Name']]['queryString']);
  659. if (!empty($row['Collation'])) {
  660. $charset = $this->getCharsetName($row['Collation']);
  661. if ($charset) {
  662. $tables[$row['Name']]['charset'] = $charset;
  663. }
  664. }
  665. }
  666. $result->closeCursor();
  667. if (is_string($name) && isset($tables[$name])) {
  668. return $tables[$name];
  669. }
  670. return $tables;
  671. }
  672. /**
  673. * Converts database-layer column types to basic types
  674. *
  675. * @param string $real Real database-layer column type (i.e. "varchar(255)")
  676. * @return string Abstract column type (i.e. "string")
  677. */
  678. public function column($real) {
  679. if (is_array($real)) {
  680. $col = $real['name'];
  681. if (isset($real['limit'])) {
  682. $col .= '(' . $real['limit'] . ')';
  683. }
  684. return $col;
  685. }
  686. $col = str_replace(')', '', $real);
  687. $limit = $this->length($real);
  688. if (strpos($col, '(') !== false) {
  689. list($col, $vals) = explode('(', $col);
  690. }
  691. if (in_array($col, array('date', 'time', 'datetime', 'timestamp'))) {
  692. return $col;
  693. }
  694. if (($col === 'tinyint' && $limit === 1) || $col === 'boolean') {
  695. return 'boolean';
  696. }
  697. if (strpos($col, 'bigint') !== false || $col === 'bigint') {
  698. return 'biginteger';
  699. }
  700. if (strpos($col, 'int') !== false) {
  701. return 'integer';
  702. }
  703. if (strpos($col, 'char') !== false || $col === 'tinytext') {
  704. return 'string';
  705. }
  706. if (strpos($col, 'text') !== false) {
  707. return 'text';
  708. }
  709. if (strpos($col, 'blob') !== false || $col === 'binary') {
  710. return 'binary';
  711. }
  712. if (strpos($col, 'float') !== false || strpos($col, 'double') !== false) {
  713. return 'float';
  714. }
  715. if (strpos($col, 'decimal') !== false || strpos($col, 'numeric') !== false) {
  716. return 'decimal';
  717. }
  718. if (strpos($col, 'enum') !== false) {
  719. return "enum($vals)";
  720. }
  721. return 'text';
  722. }
  723. /**
  724. * Gets the schema name
  725. *
  726. * @return string The schema name
  727. */
  728. public function getSchemaName() {
  729. return $this->config['database'];
  730. }
  731. /**
  732. * Check if the server support nested transactions
  733. *
  734. * @return boolean
  735. */
  736. public function nestedTransactionSupported() {
  737. return $this->useNestedTransactions && version_compare($this->getVersion(), '4.1', '>=');
  738. }
  739. }