Mysql.php 23 KB

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