PageRenderTime 27ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/dibi/drivers/mysqli.php

https://github.com/premiumcombination/nts
PHP | 487 lines | 222 code | 106 blank | 159 comment | 32 complexity | 0f6df1dabbf6667375ef2668197f115a MD5 | raw file
  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. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. *
  10. * @package dibi\drivers
  11. */
  12. require_once dirname(__FILE__) . '/mysql.reflector.php';
  13. /**
  14. * The dibi driver for MySQL database via improved extension.
  15. *
  16. * Driver options:
  17. * - host => the MySQL server host name
  18. * - port (int) => the port number to attempt to connect to the MySQL server
  19. * - socket => the socket or named pipe
  20. * - username (or user)
  21. * - password (or pass)
  22. * - database => the database name to select
  23. * - options (array) => array of driver specific constants (MYSQLI_*) and values {@see mysqli_options}
  24. * - flags (int) => driver specific constants (MYSQLI_CLIENT_*) {@see mysqli_real_connect}
  25. * - charset => character encoding to set (default is utf8)
  26. * - persistent (bool) => try to find a persistent link?
  27. * - unbuffered (bool) => sends query without fetching and buffering the result rows automatically?
  28. * - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
  29. * - resource (mysqli) => existing connection resource
  30. * - lazy, profiler, result, substitutes, ... => see DibiConnection options
  31. *
  32. * @author David Grudl
  33. * @package dibi\drivers
  34. */
  35. class DibiMySqliDriver extends DibiObject implements IDibiDriver, IDibiResultDriver
  36. {
  37. const ERROR_ACCESS_DENIED = 1045;
  38. const ERROR_DUPLICATE_ENTRY = 1062;
  39. const ERROR_DATA_TRUNCATED = 1265;
  40. /** @var mysqli Connection resource */
  41. private $connection;
  42. /** @var mysqli_result Resultset resource */
  43. private $resultSet;
  44. /** @var bool Is buffered (seekable and countable)? */
  45. private $buffered;
  46. /**
  47. * @throws DibiNotSupportedException
  48. */
  49. public function __construct()
  50. {
  51. if (!extension_loaded('mysqli')) {
  52. throw new DibiNotSupportedException("PHP extension 'mysqli' is not loaded.");
  53. }
  54. }
  55. /**
  56. * Connects to a database.
  57. * @return void
  58. * @throws DibiException
  59. */
  60. public function connect(array &$config)
  61. {
  62. mysqli_report(MYSQLI_REPORT_OFF);
  63. if (isset($config['resource'])) {
  64. $this->connection = $config['resource'];
  65. } else {
  66. // default values
  67. if (!isset($config['charset'])) $config['charset'] = 'utf8';
  68. if (!isset($config['username'])) $config['username'] = ini_get('mysqli.default_user');
  69. if (!isset($config['password'])) $config['password'] = ini_get('mysqli.default_pw');
  70. if (!isset($config['socket'])) $config['socket'] = ini_get('mysqli.default_socket');
  71. if (!isset($config['port'])) $config['port'] = NULL;
  72. if (!isset($config['host'])) {
  73. $host = ini_get('mysqli.default_host');
  74. if ($host) {
  75. $config['host'] = $host;
  76. $config['port'] = ini_get('mysqli.default_port');
  77. } else {
  78. $config['host'] = NULL;
  79. $config['port'] = NULL;
  80. }
  81. }
  82. $foo = & $config['flags'];
  83. $foo = & $config['database'];
  84. $this->connection = mysqli_init();
  85. if (isset($config['options'])) {
  86. if (is_scalar($config['options'])) {
  87. $config['flags'] = $config['options']; // back compatibility
  88. trigger_error(__CLASS__ . ": configuration item 'options' must be array; for constants MYSQLI_CLIENT_* use 'flags'.", E_USER_NOTICE);
  89. } else {
  90. foreach ((array) $config['options'] as $key => $value) {
  91. mysqli_options($this->connection, $key, $value);
  92. }
  93. }
  94. }
  95. @mysqli_real_connect($this->connection, (empty($config['persistent']) ? '' : 'p:') . $config['host'], $config['username'], $config['password'], $config['database'], $config['port'], $config['socket'], $config['flags']); // intentionally @
  96. if ($errno = mysqli_connect_errno()) {
  97. throw new DibiDriverException(mysqli_connect_error(), $errno);
  98. }
  99. }
  100. if (isset($config['charset'])) {
  101. $ok = FALSE;
  102. if (version_compare(PHP_VERSION , '5.1.5', '>=')) {
  103. // affects the character set used by mysql_real_escape_string() (was added in MySQL 5.0.7 and PHP 5.0.5, fixed in PHP 5.1.5)
  104. $ok = @mysqli_set_charset($this->connection, $config['charset']); // intentionally @
  105. }
  106. if (!$ok) {
  107. $this->query("SET NAMES '$config[charset]'");
  108. }
  109. }
  110. if (isset($config['sqlmode'])) {
  111. $this->query("SET sql_mode='$config[sqlmode]'");
  112. }
  113. $this->query("SET time_zone='" . date('P') . "'");
  114. $this->buffered = empty($config['unbuffered']);
  115. }
  116. /**
  117. * Disconnects from a database.
  118. * @return void
  119. */
  120. public function disconnect()
  121. {
  122. mysqli_close($this->connection);
  123. }
  124. /**
  125. * Executes the SQL query.
  126. * @param string SQL statement.
  127. * @return IDibiResultDriver|NULL
  128. * @throws DibiDriverException
  129. */
  130. public function query($sql)
  131. {
  132. $res = @mysqli_query($this->connection, $sql, $this->buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT); // intentionally @
  133. if (mysqli_errno($this->connection)) {
  134. throw new DibiDriverException(mysqli_error($this->connection), mysqli_errno($this->connection), $sql);
  135. } elseif (is_object($res)) {
  136. return $this->createResultDriver($res);
  137. }
  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+) *#', mysqli_info($this->connection), $matches, PREG_SET_ORDER);
  147. if (preg_last_error()) throw new DibiPcreException;
  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 mysqli_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 mysqli_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 mysqli
  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. /**
  216. * Result set driver factory.
  217. * @param mysqli_result
  218. * @return IDibiResultDriver
  219. */
  220. public function createResultDriver(mysqli_result $resource)
  221. {
  222. $res = clone $this;
  223. $res->resultSet = $resource;
  224. return $res;
  225. }
  226. /********************* SQL ****************d*g**/
  227. /**
  228. * Encodes data for use in a SQL statement.
  229. * @param mixed value
  230. * @param string type (dibi::TEXT, dibi::BOOL, ...)
  231. * @return string encoded value
  232. * @throws InvalidArgumentException
  233. */
  234. public function escape($value, $type)
  235. {
  236. switch ($type) {
  237. case dibi::TEXT:
  238. return "'" . mysqli_real_escape_string($this->connection, $value) . "'";
  239. case dibi::BINARY:
  240. return "_binary'" . mysqli_real_escape_string($this->connection, $value) . "'";
  241. case dibi::IDENTIFIER:
  242. return '`' . str_replace('`', '``', $value) . '`';
  243. case dibi::BOOL:
  244. return $value ? 1 : 0;
  245. case dibi::DATE:
  246. return $value instanceof DateTime ? $value->format("'Y-m-d'") : date("'Y-m-d'", $value);
  247. case dibi::DATETIME:
  248. return $value instanceof DateTime ? $value->format("'Y-m-d H:i:s'") : date("'Y-m-d H:i:s'", $value);
  249. default:
  250. throw new InvalidArgumentException('Unsupported type.');
  251. }
  252. }
  253. /**
  254. * Encodes string for use in a LIKE statement.
  255. * @param string
  256. * @param int
  257. * @return string
  258. */
  259. public function escapeLike($value, $pos)
  260. {
  261. $value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
  262. return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
  263. }
  264. /**
  265. * Decodes data from result set.
  266. * @param string value
  267. * @param string type (dibi::BINARY)
  268. * @return string decoded value
  269. * @throws InvalidArgumentException
  270. */
  271. public function unescape($value, $type)
  272. {
  273. if ($type === dibi::BINARY) {
  274. return $value;
  275. }
  276. throw new InvalidArgumentException('Unsupported type.');
  277. }
  278. /**
  279. * Injects LIMIT/OFFSET to the SQL query.
  280. * @param string &$sql The SQL query that will be modified.
  281. * @param int $limit
  282. * @param int $offset
  283. * @return void
  284. */
  285. public function applyLimit(&$sql, $limit, $offset)
  286. {
  287. if ($limit < 0 && $offset < 1) return;
  288. // see http://dev.mysql.com/doc/refman/5.0/en/select.html
  289. $sql .= ' LIMIT ' . ($limit < 0 ? '18446744073709551615' : (int) $limit)
  290. . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
  291. }
  292. /********************* result set ****************d*g**/
  293. /**
  294. * Automatically frees the resources allocated for this result set.
  295. * @return void
  296. */
  297. public function __destruct()
  298. {
  299. $this->resultSet && @$this->free();
  300. }
  301. /**
  302. * Returns the number of rows in a result set.
  303. * @return int
  304. */
  305. public function getRowCount()
  306. {
  307. if (!$this->buffered) {
  308. throw new DibiNotSupportedException('Row count is not available for unbuffered queries.');
  309. }
  310. return mysqli_num_rows($this->resultSet);
  311. }
  312. /**
  313. * Fetches the row at current position and moves the internal cursor to the next position.
  314. * @param bool TRUE for associative array, FALSE for numeric
  315. * @return array array on success, nonarray if no next record
  316. */
  317. public function fetch($assoc)
  318. {
  319. return mysqli_fetch_array($this->resultSet, $assoc ? MYSQLI_ASSOC : MYSQLI_NUM);
  320. }
  321. /**
  322. * Moves cursor position without fetching row.
  323. * @param int the 0-based cursor pos to seek to
  324. * @return boolean TRUE on success, FALSE if unable to seek to specified record
  325. * @throws DibiException
  326. */
  327. public function seek($row)
  328. {
  329. if (!$this->buffered) {
  330. throw new DibiNotSupportedException('Cannot seek an unbuffered result set.');
  331. }
  332. return mysqli_data_seek($this->resultSet, $row);
  333. }
  334. /**
  335. * Frees the resources allocated for this result set.
  336. * @return void
  337. */
  338. public function free()
  339. {
  340. mysqli_free_result($this->resultSet);
  341. $this->resultSet = NULL;
  342. }
  343. /**
  344. * Returns metadata for all columns in a result set.
  345. * @return array
  346. */
  347. public function getResultColumns()
  348. {
  349. static $types;
  350. if (empty($types)) {
  351. $consts = get_defined_constants(TRUE);
  352. foreach ($consts['mysqli'] as $key => $value) {
  353. if (strncmp($key, 'MYSQLI_TYPE_', 12) === 0) {
  354. $types[$value] = substr($key, 12);
  355. }
  356. }
  357. $types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT';
  358. }
  359. $count = mysqli_num_fields($this->resultSet);
  360. $columns = array();
  361. for ($i = 0; $i < $count; $i++) {
  362. $row = (array) mysqli_fetch_field_direct($this->resultSet, $i);
  363. $columns[] = array(
  364. 'name' => $row['name'],
  365. 'table' => $row['orgtable'],
  366. 'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
  367. 'nativetype' => $types[$row['type']],
  368. 'vendor' => $row,
  369. );
  370. }
  371. return $columns;
  372. }
  373. /**
  374. * Returns the result set resource.
  375. * @return mysqli_result
  376. */
  377. public function getResultResource()
  378. {
  379. return $this->resultSet;
  380. }
  381. }