PageRenderTime 48ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Cake/Model/Datasource/Database/Mysql.php

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