PageRenderTime 25ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/dibi/drivers/mysql.php

https://code.google.com/
PHP | 454 lines | 204 code | 101 blank | 149 comment | 32 complexity | 2758af41e3dc48b7559ed25d5c23e975 MD5 | raw file
Possible License(s): Apache-2.0, GPL-2.0
  1. <?php
  2. /**
  3. * This file is part of the "dibi" - smart database abstraction layer.
  4. *
  5. * Copyright (c) 2005, 2010 David Grudl (http://davidgrudl.com)
  6. *
  7. * This source file is subject to the "dibi license", and/or
  8. * GPL license. For more information please see http://dibiphp.com
  9. * @package dibi\drivers
  10. */
  11. require_once dirname(__FILE__) . '/mysql.reflector.php';
  12. /**
  13. * The dibi driver for MySQL database.
  14. *
  15. * Driver options:
  16. * - host => the MySQL server host name
  17. * - port (int) => the port number to attempt to connect to the MySQL server
  18. * - socket => the socket or named pipe
  19. * - username (or user)
  20. * - password (or pass)
  21. * - database => the database name to select
  22. * - flags (int) => driver specific constants (MYSQL_CLIENT_*)
  23. * - charset => character encoding to set (default is utf8)
  24. * - persistent (bool) => try to find a persistent link?
  25. * - unbuffered (bool) => sends query without fetching and buffering the result rows automatically?
  26. * - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
  27. * - resource (resource) => existing connection resource
  28. * - lazy, profiler, result, substitutes, ... => see DibiConnection options
  29. *
  30. * @author David Grudl
  31. * @package dibi\drivers
  32. */
  33. class DibiMySqlDriver extends DibiObject implements IDibiDriver, IDibiResultDriver
  34. {
  35. const ERROR_ACCESS_DENIED = 1045;
  36. const ERROR_DUPLICATE_ENTRY = 1062;
  37. const ERROR_DATA_TRUNCATED = 1265;
  38. /** @var resource Connection resource */
  39. private $connection;
  40. /** @var resource Resultset resource */
  41. private $resultSet;
  42. /** @var bool Is buffered (seekable and countable)? */
  43. private $buffered;
  44. /**
  45. * @throws DibiException
  46. */
  47. public function __construct()
  48. {
  49. if (!extension_loaded('mysql')) {
  50. throw new DibiDriverException("PHP extension 'mysql' is not loaded.");
  51. }
  52. }
  53. /**
  54. * Connects to a database.
  55. * @return void
  56. * @throws DibiException
  57. */
  58. public function connect(array &$config)
  59. {
  60. if (isset($config['resource'])) {
  61. $this->connection = $config['resource'];
  62. } else {
  63. // default values
  64. DibiConnection::alias($config, 'flags', 'options');
  65. if (!isset($config['charset'])) $config['charset'] = 'utf8';
  66. if (!isset($config['username'])) $config['username'] = ini_get('mysql.default_user');
  67. if (!isset($config['password'])) $config['password'] = ini_get('mysql.default_password');
  68. if (!isset($config['host'])) {
  69. $host = ini_get('mysql.default_host');
  70. if ($host) {
  71. $config['host'] = $host;
  72. $config['port'] = ini_get('mysql.default_port');
  73. } else {
  74. if (!isset($config['socket'])) $config['socket'] = ini_get('mysql.default_socket');
  75. $config['host'] = NULL;
  76. }
  77. }
  78. if (empty($config['socket'])) {
  79. $host = $config['host'] . (empty($config['port']) ? '' : ':' . $config['port']);
  80. } else {
  81. $host = ':' . $config['socket'];
  82. }
  83. if (empty($config['persistent'])) {
  84. $this->connection = @mysql_connect($host, $config['username'], $config['password'], TRUE, $config['flags']); // intentionally @
  85. } else {
  86. $this->connection = @mysql_pconnect($host, $config['username'], $config['password'], $config['flags']); // intentionally @
  87. }
  88. }
  89. if (!is_resource($this->connection)) {
  90. throw new DibiDriverException(mysql_error(), mysql_errno());
  91. }
  92. if (isset($config['charset'])) {
  93. $ok = FALSE;
  94. if (function_exists('mysql_set_charset')) {
  95. // affects the character set used by mysql_real_escape_string() (was added in MySQL 5.0.7 and PHP 5.2.3)
  96. $ok = @mysql_set_charset($config['charset'], $this->connection); // intentionally @
  97. }
  98. if (!$ok) {
  99. $this->query("SET NAMES '$config[charset]'");
  100. }
  101. }
  102. if (isset($config['database'])) {
  103. if (!@mysql_select_db($config['database'], $this->connection)) { // intentionally @
  104. throw new DibiDriverException(mysql_error($this->connection), mysql_errno($this->connection));
  105. }
  106. }
  107. if (isset($config['sqlmode'])) {
  108. $this->query("SET sql_mode='$config[sqlmode]'");
  109. }
  110. $this->query("SET time_zone='" . date('P') . "'");
  111. $this->buffered = empty($config['unbuffered']);
  112. }
  113. /**
  114. * Disconnects from a database.
  115. * @return void
  116. */
  117. public function disconnect()
  118. {
  119. mysql_close($this->connection);
  120. }
  121. /**
  122. * Executes the SQL query.
  123. * @param string SQL statement.
  124. * @return IDibiResultDriver|NULL
  125. * @throws DibiDriverException
  126. */
  127. public function query($sql)
  128. {
  129. if ($this->buffered) {
  130. $this->resultSet = @mysql_query($sql, $this->connection); // intentionally @
  131. } else {
  132. $this->resultSet = @mysql_unbuffered_query($sql, $this->connection); // intentionally @
  133. }
  134. if (mysql_errno($this->connection)) {
  135. throw new DibiDriverException(mysql_error($this->connection), mysql_errno($this->connection), $sql);
  136. }
  137. return is_resource($this->resultSet) ? clone $this : NULL;
  138. }
  139. /**
  140. * Retrieves information about the most recently executed query.
  141. * @return array
  142. */
  143. public function getInfo()
  144. {
  145. $res = array();
  146. preg_match_all('#(.+?): +(\d+) *#', mysql_info($this->connection), $matches, PREG_SET_ORDER);
  147. if (preg_last_error()) throw new PcreException;
  148. foreach ($matches as $m) {
  149. $res[$m[1]] = (int) $m[2];
  150. }
  151. return $res;
  152. }
  153. /**
  154. * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
  155. * @return int|FALSE number of rows or FALSE on error
  156. */
  157. public function getAffectedRows()
  158. {
  159. return mysql_affected_rows($this->connection);
  160. }
  161. /**
  162. * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
  163. * @return int|FALSE int on success or FALSE on failure
  164. */
  165. public function getInsertId($sequence)
  166. {
  167. return mysql_insert_id($this->connection);
  168. }
  169. /**
  170. * Begins a transaction (if supported).
  171. * @param string optional savepoint name
  172. * @return void
  173. * @throws DibiDriverException
  174. */
  175. public function begin($savepoint = NULL)
  176. {
  177. $this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
  178. }
  179. /**
  180. * Commits statements in a transaction.
  181. * @param string optional savepoint name
  182. * @return void
  183. * @throws DibiDriverException
  184. */
  185. public function commit($savepoint = NULL)
  186. {
  187. $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
  188. }
  189. /**
  190. * Rollback changes in a transaction.
  191. * @param string optional savepoint name
  192. * @return void
  193. * @throws DibiDriverException
  194. */
  195. public function rollback($savepoint = NULL)
  196. {
  197. $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
  198. }
  199. /**
  200. * Returns the connection resource.
  201. * @return mixed
  202. */
  203. public function getResource()
  204. {
  205. return $this->connection;
  206. }
  207. /**
  208. * Returns the connection reflector.
  209. * @return IDibiReflector
  210. */
  211. public function getReflector()
  212. {
  213. return new DibiMySqlReflector($this);
  214. }
  215. /********************* SQL ****************d*g**/
  216. /**
  217. * Encodes data for use in a SQL statement.
  218. * @param mixed value
  219. * @param string type (dibi::TEXT, dibi::BOOL, ...)
  220. * @return string encoded value
  221. * @throws InvalidArgumentException
  222. */
  223. public function escape($value, $type)
  224. {
  225. switch ($type) {
  226. case dibi::TEXT:
  227. return "'" . mysql_real_escape_string($value, $this->connection) . "'";
  228. case dibi::BINARY:
  229. return "_binary'" . mysql_real_escape_string($value, $this->connection) . "'";
  230. case dibi::IDENTIFIER:
  231. // @see http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
  232. return '`' . str_replace('`', '``', $value) . '`';
  233. case dibi::BOOL:
  234. return $value ? 1 : 0;
  235. case dibi::DATE:
  236. return $value instanceof DateTime ? $value->format("'Y-m-d'") : date("'Y-m-d'", $value);
  237. case dibi::DATETIME:
  238. return $value instanceof DateTime ? $value->format("'Y-m-d H:i:s'") : date("'Y-m-d H:i:s'", $value);
  239. default:
  240. throw new InvalidArgumentException('Unsupported type.');
  241. }
  242. }
  243. /**
  244. * Encodes string for use in a LIKE statement.
  245. * @param string
  246. * @param int
  247. * @return string
  248. */
  249. public function escapeLike($value, $pos)
  250. {
  251. $value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
  252. return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
  253. }
  254. /**
  255. * Decodes data from result set.
  256. * @param string value
  257. * @param string type (dibi::BINARY)
  258. * @return string decoded value
  259. * @throws InvalidArgumentException
  260. */
  261. public function unescape($value, $type)
  262. {
  263. if ($type === dibi::BINARY) {
  264. return $value;
  265. }
  266. throw new InvalidArgumentException('Unsupported type.');
  267. }
  268. /**
  269. * Injects LIMIT/OFFSET to the SQL query.
  270. * @param string &$sql The SQL query that will be modified.
  271. * @param int $limit
  272. * @param int $offset
  273. * @return void
  274. */
  275. public function applyLimit(&$sql, $limit, $offset)
  276. {
  277. if ($limit < 0 && $offset < 1) return;
  278. // see http://dev.mysql.com/doc/refman/5.0/en/select.html
  279. $sql .= ' LIMIT ' . ($limit < 0 ? '18446744073709551615' : (int) $limit)
  280. . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
  281. }
  282. /********************* result set ****************d*g**/
  283. /**
  284. * Returns the number of rows in a result set.
  285. * @return int
  286. */
  287. public function getRowCount()
  288. {
  289. if (!$this->buffered) {
  290. throw new DibiDriverException('Row count is not available for unbuffered queries.');
  291. }
  292. return mysql_num_rows($this->resultSet);
  293. }
  294. /**
  295. * Fetches the row at current position and moves the internal cursor to the next position.
  296. * @param bool TRUE for associative array, FALSE for numeric
  297. * @return array array on success, nonarray if no next record
  298. */
  299. public function fetch($assoc)
  300. {
  301. return mysql_fetch_array($this->resultSet, $assoc ? MYSQL_ASSOC : MYSQL_NUM);
  302. }
  303. /**
  304. * Moves cursor position without fetching row.
  305. * @param int the 0-based cursor pos to seek to
  306. * @return boolean TRUE on success, FALSE if unable to seek to specified record
  307. * @throws DibiException
  308. */
  309. public function seek($row)
  310. {
  311. if (!$this->buffered) {
  312. throw new DibiDriverException('Cannot seek an unbuffered result set.');
  313. }
  314. return mysql_data_seek($this->resultSet, $row);
  315. }
  316. /**
  317. * Frees the resources allocated for this result set.
  318. * @return void
  319. */
  320. public function free()
  321. {
  322. mysql_free_result($this->resultSet);
  323. $this->resultSet = NULL;
  324. }
  325. /**
  326. * Returns metadata for all columns in a result set.
  327. * @return array
  328. */
  329. public function getResultColumns()
  330. {
  331. $count = mysql_num_fields($this->resultSet);
  332. $columns = array();
  333. for ($i = 0; $i < $count; $i++) {
  334. $row = (array) mysql_fetch_field($this->resultSet, $i);
  335. $columns[] = array(
  336. 'name' => $row['name'],
  337. 'table' => $row['table'],
  338. 'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
  339. 'nativetype' => strtoupper($row['type']),
  340. 'vendor' => $row,
  341. );
  342. }
  343. return $columns;
  344. }
  345. /**
  346. * Returns the result set resource.
  347. * @return mixed
  348. */
  349. public function getResultResource()
  350. {
  351. return $this->resultSet;
  352. }
  353. }