Mysql.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <?php
  2. /**
  3. *
  4. * Cube Framework $Id$ d2ibV6IrsImXE7ik3/w9UNhhd5mwvaSQ8uOcH9btDJo=
  5. *
  6. * @link http://codecu.be/framework
  7. * @copyright Copyright (c) 2014 CodeCube SRL
  8. * @license http://codecu.be/framework/license Commercial License
  9. *
  10. * @version 1.0
  11. */
  12. /**
  13. * pdo mysql database adapter
  14. */
  15. namespace Cube\Db\Adapter\PDO;
  16. use Cube\Db\Adapter\AbstractAdapter,
  17. Cube\Db\Select,
  18. Cube\Exception;
  19. class Mysql extends AbstractAdapter
  20. {
  21. /**
  22. *
  23. * default statement class for this adapter
  24. *
  25. * @var string
  26. */
  27. protected $_defaultStmtClass = '\\Cube\\Db\\Statement\\Pdo';
  28. /**
  29. *
  30. * adapter type
  31. *
  32. * @var string
  33. */
  34. protected $_pdoType = 'mysql';
  35. /**
  36. * Creates a PDO DSN for the adapter from $this->_config settings.
  37. *
  38. * @return string
  39. */
  40. protected function _dsn()
  41. {
  42. // baseline of DSN parts
  43. $dsn = $this->_config;
  44. // don't pass the username, password, charset, persistent and driver_options in the DSN
  45. unset($dsn['username']);
  46. unset($dsn['password']);
  47. unset($dsn['options']);
  48. unset($dsn['charset']);
  49. unset($dsn['persistent']);
  50. unset($dsn['driver_options']);
  51. // use all remaining parts in the DSN
  52. foreach ($dsn as $key => $val) {
  53. $dsn[$key] = "$key=$val";
  54. }
  55. $dsn = implode(';', $dsn);
  56. if (isset($this->_config['charset'])) {
  57. $dsn .= ';charset=' . (string)$this->_config['charset'];
  58. }
  59. return $this->_pdoType . ':' . $dsn;
  60. }
  61. /**
  62. * Creates a PDO object and connects to the database.
  63. *
  64. * @throws \RuntimeException
  65. * @throws \Cube\Exception
  66. * @return void
  67. */
  68. protected function _connect()
  69. {
  70. if ($this->_connection) {
  71. return;
  72. }
  73. if (isset($this->_config['charset'])) {
  74. $initCommand = "SET NAMES '" . $this->_config['charset'] . "'";
  75. $this->_config['driver_options'][1002] = $initCommand; // 1002 = PDO::MYSQL_ATTR_INIT_COMMAND
  76. }
  77. // get the dsn first, because some adapters alter the $_pdoType
  78. $dsn = $this->_dsn();
  79. // check for PDO extension
  80. if (!extension_loaded('pdo')) {
  81. /**
  82. * @see Zend_Db_Adapter_Exception
  83. */
  84. throw new \RuntimeException('The PDO extension is required for this adapter but the extension is not loaded');
  85. }
  86. // create PDO connection
  87. try {
  88. $this->_connection = @new \PDO(
  89. $dsn,
  90. $this->_config['username'],
  91. $this->_config['password'],
  92. $this->_config['driver_options']
  93. );
  94. $this->_connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
  95. } catch (\PDOException $e) {
  96. throw new Exception($e->getMessage(), $e->getCode(), $e);
  97. }
  98. }
  99. /**
  100. * Test if a connection is active
  101. *
  102. * @return bool
  103. */
  104. public function isConnected()
  105. {
  106. return ((bool)($this->_connection instanceof \PDO));
  107. }
  108. /**
  109. * connect and return whether the connection was successful
  110. *
  111. * @return bool
  112. */
  113. public function canConnect()
  114. {
  115. $this->_connect();
  116. return $this->isConnected();
  117. }
  118. /**
  119. * Force the connection to close.
  120. *
  121. * @return void
  122. */
  123. public function closeConnection()
  124. {
  125. $this->_connection = null;
  126. }
  127. /**
  128. * Prepares an SQL statement.
  129. *
  130. * @param string $sql The SQL statement with placeholders.
  131. *
  132. * @throws \RuntimeException
  133. * @return \PDOStatement
  134. */
  135. public function prepare($sql)
  136. {
  137. $this->_connect();
  138. $stmtClass = $this->_defaultStmtClass;
  139. if (!class_exists($stmtClass)) {
  140. throw new \RuntimeException(
  141. sprintf("Could not load the statement class '%s'", $stmtClass));
  142. }
  143. /** @var \Cube\Db\Statement\AbstractStatement $stmt */
  144. $stmt = new $stmtClass($this, $sql);
  145. $stmt->setFetchMode($this->_fetchMode);
  146. return $stmt;
  147. }
  148. /**
  149. * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
  150. *
  151. * As a convention, on RDBMS brands that support sequences
  152. * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
  153. * from the arguments and returns the last id generated by that sequence.
  154. * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
  155. * returns the last value generated for such a column, and the table name
  156. * argument is disregarded.
  157. *
  158. * On RDBMS brands that don't support sequences, $tableName and $primaryKey
  159. * are ignored.
  160. *
  161. * @param string $tableName OPTIONAL Name of table.
  162. * @param string $primaryKey OPTIONAL Name of primary key column.
  163. *
  164. * @return string
  165. */
  166. public function lastInsertId($tableName = null, $primaryKey = null)
  167. {
  168. $this->_connect();
  169. return $this->_connection->lastInsertId();
  170. }
  171. /**
  172. * Special handling for PDO query().
  173. * All bind parameter names must begin with ':'
  174. *
  175. * @param string|\Cube\Db\Select $sql The SQL statement with placeholders.
  176. * @param array $bind An array of data to bind to the placeholders.
  177. *
  178. * @return \Cube\Db\Adapter\AbstractAdapter
  179. * @throws \Cube\Exception To re-throw PDOException.
  180. */
  181. public function query($sql, $bind = array())
  182. {
  183. if (empty($bind) && $sql instanceof Select) {
  184. $bind = $sql->getBind();
  185. }
  186. if (is_array($bind)) {
  187. foreach ($bind as $name => $value) {
  188. if (!is_int($name) && !preg_match('/^:/', $name)) {
  189. $newName = ":$name";
  190. unset($bind[$name]);
  191. $bind[$newName] = $value;
  192. }
  193. }
  194. }
  195. try {
  196. return parent::query($sql, $bind);
  197. } catch (\PDOException $e) {
  198. throw new Exception($e->getMessage(), $e->getCode(), $e);
  199. }
  200. }
  201. /**
  202. * Executes an SQL statement and return the number of affected rows
  203. *
  204. * @param mixed $sql The SQL statement with placeholders.
  205. * May be a string or a \Cube\Db\Select object.
  206. *
  207. * @throws \Cube\Exception
  208. * @return integer Number of rows that were modified
  209. * or deleted by the SQL statement
  210. */
  211. public function exec($sql)
  212. {
  213. if ($sql instanceof Select) {
  214. $sql = $sql->assemble();
  215. }
  216. try {
  217. $affected = $this->getConnection()->exec($sql);
  218. if ($affected === false) {
  219. $errorInfo = $this->getConnection()->errorInfo();
  220. throw new Exception($errorInfo[2]);
  221. }
  222. return $affected;
  223. } catch (\PDOException $e) {
  224. throw new Exception($e->getMessage(), $e->getCode(), $e);
  225. }
  226. }
  227. /**
  228. * Quote a raw string.
  229. *
  230. * @param string $value Raw string
  231. *
  232. * @return string Quoted string
  233. */
  234. protected function _quote($value)
  235. {
  236. if (is_int($value) || is_float($value)) {
  237. return $value;
  238. }
  239. $this->_connect();
  240. return $this->_connection->quote($value);
  241. }
  242. /**
  243. * Begin a transaction.
  244. */
  245. protected function _beginTransaction()
  246. {
  247. $this->_connect();
  248. $this->_connection->beginTransaction();
  249. }
  250. /**
  251. * Commit a transaction.
  252. */
  253. protected function _commit()
  254. {
  255. $this->_connect();
  256. $this->_connection->commit();
  257. }
  258. /**
  259. * Roll-back a transaction.
  260. */
  261. protected function _rollBack()
  262. {
  263. $this->_connect();
  264. $this->_connection->rollBack();
  265. }
  266. /**
  267. * Set the PDO fetch mode.
  268. *
  269. * @param int $mode A PDO fetch mode.
  270. *
  271. * @throws \RuntimeException
  272. * @throws \InvalidArgumentException
  273. * @return void
  274. */
  275. public function setFetchMode($mode)
  276. {
  277. //check for PDO extension
  278. if (!extension_loaded('pdo')) {
  279. throw new \RuntimeException('The PDO extension is required for this adapter but the extension is not loaded');
  280. }
  281. switch ($mode) {
  282. case \PDO::FETCH_LAZY:
  283. case \PDO::FETCH_ASSOC:
  284. case \PDO::FETCH_NUM:
  285. case \PDO::FETCH_BOTH:
  286. case \PDO::FETCH_NAMED:
  287. case \PDO::FETCH_OBJ:
  288. $this->_fetchMode = $mode;
  289. break;
  290. default:
  291. throw new \InvalidArgumentException("Invalid fetch mode '$mode' specified");
  292. break;
  293. }
  294. }
  295. /**
  296. * Check if the adapter supports real SQL parameters.
  297. *
  298. * @param string $type 'positional' or 'named'
  299. *
  300. * @return bool
  301. */
  302. public function supportsParameters($type)
  303. {
  304. switch ($type) {
  305. case 'positional':
  306. case 'named':
  307. default:
  308. return true;
  309. }
  310. }
  311. /**
  312. * @return string
  313. */
  314. public function getQuoteIdentifierSymbol()
  315. {
  316. return "`";
  317. }
  318. /**
  319. * Returns the column descriptions for a table.
  320. *
  321. * The return value is an associative array keyed by the column name,
  322. * as returned by the RDBMS.
  323. *
  324. * The value of each array element is an associative array
  325. * with the following keys:
  326. *
  327. * TABLE_NAME => string;
  328. * COLUMN_NAME => string; column name
  329. * COLUMN_POSITION => number; ordinal position of column in table
  330. * DATA_TYPE => string; SQL datatype name of column
  331. * DEFAULT => string; default expression of column, null if none
  332. * NULLABLE => bool; true if column can have nulls
  333. * LENGTH => number; length of CHAR/VARCHAR
  334. * SCALE => number; scale of NUMERIC/DECIMAL
  335. * PRECISION => number; precision of NUMERIC/DECIMAL
  336. * UNSIGNED => bool; unsigned property of an integer type
  337. * PRIMARY => bool; true if column is part of the primary key
  338. * PRIMARY_POSITION => integer; position of column in primary key
  339. * IDENTITY => integer; true if column is auto-generated with unique values
  340. *
  341. * @param string $tableName
  342. *
  343. * @return array
  344. */
  345. public function describeTable($tableName)
  346. {
  347. $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true);
  348. $stmt = $this->query($sql);
  349. // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
  350. $result = $stmt->fetchAll(\PDO::FETCH_NUM);
  351. $field = 0;
  352. $type = 1;
  353. $null = 2;
  354. $key = 3;
  355. $default = 4;
  356. $extra = 5;
  357. $desc = array();
  358. $i = 1;
  359. $p = 1;
  360. foreach ($result as $row) {
  361. list($length, $scale, $precision, $unsigned, $primary, $primaryPosition, $identity)
  362. = array(null, null, null, null, false, null, false);
  363. if (preg_match('/unsigned/', $row[$type])) {
  364. $unsigned = true;
  365. }
  366. if (preg_match('/^((?:var)?char)\((\d+)\)/', $row[$type], $matches)) {
  367. $row[$type] = $matches[1];
  368. $length = $matches[2];
  369. }
  370. else if (preg_match('/^decimal\((\d+),(\d+)\)/', $row[$type], $matches)) {
  371. $row[$type] = 'decimal';
  372. $precision = $matches[1];
  373. $scale = $matches[2];
  374. }
  375. else if (preg_match('/^float\((\d+),(\d+)\)/', $row[$type], $matches)) {
  376. $row[$type] = 'float';
  377. $precision = $matches[1];
  378. $scale = $matches[2];
  379. }
  380. else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row[$type], $matches)) {
  381. $row[$type] = $matches[1];
  382. }
  383. if (strtoupper($row[$key]) == 'PRI') {
  384. $primary = true;
  385. $primaryPosition = $p;
  386. if ($row[$extra] == 'auto_increment') {
  387. $identity = true;
  388. }
  389. else {
  390. $identity = false;
  391. }
  392. $p++;
  393. }
  394. $desc[$row[$field]] = array(
  395. 'TABLE_NAME' => $tableName,
  396. 'COLUMN_NAME' => $row[$field],
  397. 'COLUMN_POSITION' => $i,
  398. 'DATA_TYPE' => $row[$type],
  399. 'DEFAULT' => $row[$default],
  400. 'NULLABLE' => (bool)($row[$null] == 'YES'),
  401. 'LENGTH' => $length,
  402. 'SCALE' => $scale,
  403. 'PRECISION' => $precision,
  404. 'UNSIGNED' => $unsigned,
  405. 'PRIMARY' => $primary,
  406. 'PRIMARY_POSITION' => $primaryPosition,
  407. 'IDENTITY' => $identity
  408. );
  409. $i++;
  410. }
  411. return $desc;
  412. }
  413. /**
  414. * Adds an adapter-specific LIMIT clause to the SELECT statement.
  415. *
  416. * @param string $sql
  417. * @param integer $count
  418. * @param integer $offset OPTIONAL
  419. *
  420. * @throws \InvalidArgumentException
  421. * @return string
  422. */
  423. public function limit($sql, $count, $offset = 0)
  424. {
  425. $count = intval($count);
  426. if ($count <= 0) {
  427. /** @see Zend_Db_Adapter_Exception */
  428. throw new \InvalidArgumentException("LIMIT argument count=$count is not valid");
  429. }
  430. $offset = intval($offset);
  431. if ($offset < 0) {
  432. throw new \InvalidArgumentException("LIMIT argument offset=$offset is not valid");
  433. }
  434. $sql .= " LIMIT $count";
  435. if ($offset > 0) {
  436. $sql .= " OFFSET $offset";
  437. }
  438. return $sql;
  439. }
  440. }