/fuelphp/fuel/core/classes/database/mysqli/connection.php

http://github.com/eryx/php-framework-benchmark · PHP · 486 lines · 352 code · 64 blank · 70 comment · 32 complexity · 068d1ec86598555b90ed985437a4852b MD5 · raw file

  1. <?php
  2. /**
  3. * MySQLi database connection.
  4. *
  5. * @package Fuel/Database
  6. * @category Drivers
  7. * @author Kohana Team
  8. * @copyright (c) 2008-2009 Kohana Team
  9. * @license http://kohanaphp.com/license
  10. */
  11. namespace Fuel\Core;
  12. class Database_MySQLi_Connection extends \Database_Connection
  13. {
  14. /**
  15. * @var \MySQLi Raw server connection
  16. */
  17. protected $_connection;
  18. /**
  19. * @var array Database in use by each connection
  20. */
  21. protected static $_current_databases = array();
  22. /**
  23. * @var bool Use SET NAMES to set the character set
  24. */
  25. protected static $_set_names;
  26. /**
  27. * @var string Identifier for this connection within the PHP driver
  28. */
  29. protected $_connection_id;
  30. /**
  31. * @var string MySQL uses a backtick for identifiers
  32. */
  33. protected $_identifier = '`';
  34. /**
  35. * @var bool Allows transactions
  36. */
  37. protected $_in_transaction = false;
  38. /**
  39. * @var string Which kind of DB is used
  40. */
  41. public $_db_type = 'mysql';
  42. public function connect()
  43. {
  44. if ($this->_connection)
  45. {
  46. return;
  47. }
  48. if (static::$_set_names === null)
  49. {
  50. // Determine if we can use mysqli_set_charset(), which is only
  51. // available on PHP 5.2.3+ when compiled against MySQL 5.0+
  52. static::$_set_names = ! function_exists('mysqli_set_charset');
  53. }
  54. // Extract the connection parameters, adding required variables
  55. extract($this->_config['connection'] + array(
  56. 'database' => '',
  57. 'hostname' => '',
  58. 'port' => '',
  59. 'socket' => '',
  60. 'username' => '',
  61. 'password' => '',
  62. 'persistent' => false,
  63. 'compress' => true,
  64. ));
  65. // Prevent this information from showing up in traces
  66. unset($this->_config['connection']['username'], $this->_config['connection']['password']);
  67. try
  68. {
  69. if ($socket != '')
  70. {
  71. $port = null;
  72. }
  73. elseif ($port != '')
  74. {
  75. $socket = null;
  76. }
  77. else
  78. {
  79. $socket = null;
  80. $port = null;
  81. }
  82. if ($persistent)
  83. {
  84. // Create a persistent connection
  85. if ($compress)
  86. {
  87. $mysqli = mysqli_init();
  88. $mysqli->real_connect('p:'.$hostname, $username, $password, $database, $port, $socket, MYSQLI_CLIENT_COMPRESS);
  89. $this->_connection = $mysqli;
  90. }
  91. else
  92. {
  93. $this->_connection = new \MySQLi('p:'.$hostname, $username, $password, $database, $port, $socket);
  94. }
  95. }
  96. else
  97. {
  98. // Create a connection and force it to be a new link
  99. if ($compress)
  100. {
  101. $mysqli = mysqli_init();
  102. $mysqli->real_connect($hostname, $username, $password, $database, $port, $socket, MYSQLI_CLIENT_COMPRESS);
  103. $this->_connection = $mysqli;
  104. }
  105. else
  106. {
  107. $this->_connection = new \MySQLi($hostname, $username, $password, $database, $port, $socket);
  108. }
  109. }
  110. if ($this->_connection->error)
  111. {
  112. // Unable to connect, select database, etc
  113. throw new \Database_Exception($this->_connection->error, $this->_connection->errno);
  114. }
  115. }
  116. catch (\ErrorException $e)
  117. {
  118. // No connection exists
  119. $this->_connection = null;
  120. throw new \Database_Exception('No MySQLi Connection', 0);
  121. }
  122. // \xFF is a better delimiter, but the PHP driver uses underscore
  123. $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password);
  124. if ( ! empty($this->_config['charset']))
  125. {
  126. // Set the character set
  127. $this->set_charset($this->_config['charset']);
  128. }
  129. static::$_current_databases[$this->_connection_id] = $database;
  130. }
  131. /**
  132. * Select the database
  133. *
  134. * @param string Database
  135. * @return void
  136. */
  137. protected function _select_db($database)
  138. {
  139. if ($this->_config['connection']['database'] !== static::$_current_databases[$this->_connection_id])
  140. {
  141. if ($this->_connection->select_db($database) !== true)
  142. {
  143. // Unable to select database
  144. throw new \Database_Exception($this->_connection->error, $this->_connection->errno);
  145. }
  146. }
  147. static::$_current_databases[$this->_connection_id] = $database;
  148. }
  149. public function disconnect()
  150. {
  151. try
  152. {
  153. // Database is assumed disconnected
  154. $status = true;
  155. if ($this->_connection instanceof \MySQLi)
  156. {
  157. $status = $this->_connection->close();
  158. }
  159. }
  160. catch (\Exception $e)
  161. {
  162. // Database is probably not disconnected
  163. $status = ! ($this->_connection instanceof \MySQLi);
  164. }
  165. return $status;
  166. }
  167. public function set_charset($charset)
  168. {
  169. // Make sure the database is connected
  170. $this->_connection or $this->connect();
  171. $status = $this->_connection->set_charset($charset);
  172. if ($status === false)
  173. {
  174. throw new \Database_Exception($this->_connection->error, $this->_connection->errno);
  175. }
  176. }
  177. public function query($type, $sql, $as_object)
  178. {
  179. // Make sure the database is connected
  180. if ($this->_connection)
  181. {
  182. // Make sure the connection is still alive
  183. if ( ! $this->_connection->ping())
  184. {
  185. throw new \Database_Exception($this->_connection->error.' [ '.$sql.' ]', $this->_connection->errno);
  186. }
  187. }
  188. else
  189. {
  190. $this->connect();
  191. }
  192. if ( ! empty($this->_config['profiling']))
  193. {
  194. // Benchmark this query for the current instance
  195. $benchmark = \Profiler::start("Database ({$this->_instance})", $sql);
  196. }
  197. if ( ! empty($this->_config['connection']['persistent']) and $this->_config['connection']['database'] !== static::$_current_databases[$this->_connection_id])
  198. {
  199. // Select database on persistent connections
  200. $this->_select_db($this->_config['connection']['database']);
  201. }
  202. // Execute the query
  203. if (($result = $this->_connection->query($sql)) === false)
  204. {
  205. if (isset($benchmark))
  206. {
  207. // This benchmark is worthless
  208. \Profiler::delete($benchmark);
  209. }
  210. throw new \Database_Exception($this->_connection->error.' [ '.$sql.' ]', $this->_connection->errno);
  211. }
  212. // check for multiresults, we don't support those at the moment
  213. while($this->_connection->more_results() and $this->_connection->next_result())
  214. {
  215. if ($more_result = $this->_connection->use_result())
  216. {
  217. throw new \Database_Exception('The MySQLi driver does not support multiple resultsets', 0);
  218. }
  219. }
  220. if (isset($benchmark))
  221. {
  222. \Profiler::stop($benchmark);
  223. }
  224. // Set the last query
  225. $this->last_query = $sql;
  226. if ($type === \DB::SELECT)
  227. {
  228. // Return an iterator of results
  229. return new \Database_MySQLi_Result($result, $sql, $as_object);
  230. }
  231. elseif ($type === \DB::INSERT)
  232. {
  233. // Return a list of insert id and rows created
  234. return array(
  235. $this->_connection->insert_id,
  236. $this->_connection->affected_rows,
  237. );
  238. }
  239. else
  240. {
  241. // Return the number of rows affected
  242. return $this->_connection->affected_rows;
  243. }
  244. }
  245. public function datatype($type)
  246. {
  247. static $types = array
  248. (
  249. 'blob' => array('type' => 'string', 'binary' => true, 'character_maximum_length' => '65535'),
  250. 'bool' => array('type' => 'bool'),
  251. 'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'),
  252. 'datetime' => array('type' => 'string'),
  253. 'decimal unsigned' => array('type' => 'float', 'exact' => true, 'min' => '0'),
  254. 'double' => array('type' => 'float'),
  255. 'double precision unsigned' => array('type' => 'float', 'min' => '0'),
  256. 'double unsigned' => array('type' => 'float', 'min' => '0'),
  257. 'enum' => array('type' => 'string'),
  258. 'fixed' => array('type' => 'float', 'exact' => true),
  259. 'fixed unsigned' => array('type' => 'float', 'exact' => true, 'min' => '0'),
  260. 'float unsigned' => array('type' => 'float', 'min' => '0'),
  261. 'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
  262. 'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
  263. 'longblob' => array('type' => 'string', 'binary' => true, 'character_maximum_length' => '4294967295'),
  264. 'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'),
  265. 'mediumblob' => array('type' => 'string', 'binary' => true, 'character_maximum_length' => '16777215'),
  266. 'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'),
  267. 'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'),
  268. 'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'),
  269. 'national varchar' => array('type' => 'string'),
  270. 'numeric unsigned' => array('type' => 'float', 'exact' => true, 'min' => '0'),
  271. 'nvarchar' => array('type' => 'string'),
  272. 'point' => array('type' => 'string', 'binary' => true),
  273. 'real unsigned' => array('type' => 'float', 'min' => '0'),
  274. 'set' => array('type' => 'string'),
  275. 'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'),
  276. 'text' => array('type' => 'string', 'character_maximum_length' => '65535'),
  277. 'tinyblob' => array('type' => 'string', 'binary' => true, 'character_maximum_length' => '255'),
  278. 'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'),
  279. 'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'),
  280. 'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'),
  281. 'varchar' => array('type' => 'string', 'exact' => true),
  282. 'year' => array('type' => 'string'),
  283. );
  284. $type = str_replace(' zerofill', '', $type);
  285. if (isset($types[$type]))
  286. return $types[$type];
  287. return parent::datatype($type);
  288. }
  289. public function list_tables($like = null)
  290. {
  291. if (is_string($like))
  292. {
  293. // Search for table names
  294. $result = $this->query(\DB::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), false);
  295. }
  296. else
  297. {
  298. // Find all table names
  299. $result = $this->query(\DB::SELECT, 'SHOW TABLES', false);
  300. }
  301. $tables = array();
  302. foreach ($result as $row)
  303. {
  304. $tables[] = reset($row);
  305. }
  306. return $tables;
  307. }
  308. public function list_columns($table, $like = null)
  309. {
  310. // Quote the table name
  311. $table = $this->quote_table($table);
  312. if (is_string($like))
  313. {
  314. // Search for column names
  315. $result = $this->query(\DB::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), false);
  316. }
  317. else
  318. {
  319. // Find all column names
  320. $result = $this->query(\DB::SELECT, 'SHOW FULL COLUMNS FROM '.$table, false);
  321. }
  322. $count = 0;
  323. $columns = array();
  324. foreach ($result as $row)
  325. {
  326. list($type, $length) = $this->_parse_type($row['Type']);
  327. $column = $this->datatype($type);
  328. $column['name'] = $row['Field'];
  329. $column['default'] = $row['Default'];
  330. $column['data_type'] = $type;
  331. $column['null'] = ($row['Null'] == 'YES');
  332. $column['ordinal_position'] = ++$count;
  333. switch ($column['type'])
  334. {
  335. case 'float':
  336. if (isset($length))
  337. {
  338. list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length);
  339. }
  340. break;
  341. case 'int':
  342. if (isset($length))
  343. {
  344. // MySQL attribute
  345. $column['display'] = $length;
  346. }
  347. break;
  348. case 'string':
  349. switch ($column['data_type'])
  350. {
  351. case 'binary':
  352. case 'varbinary':
  353. $column['character_maximum_length'] = $length;
  354. break;
  355. case 'char':
  356. case 'varchar':
  357. $column['character_maximum_length'] = $length;
  358. case 'text':
  359. case 'tinytext':
  360. case 'mediumtext':
  361. case 'longtext':
  362. $column['collation_name'] = $row['Collation'];
  363. break;
  364. case 'enum':
  365. case 'set':
  366. $column['collation_name'] = $row['Collation'];
  367. $column['options'] = explode('\',\'', substr($length, 1, -1));
  368. break;
  369. }
  370. break;
  371. }
  372. // MySQL attributes
  373. $column['comment'] = $row['Comment'];
  374. $column['extra'] = $row['Extra'];
  375. $column['key'] = $row['Key'];
  376. $column['privileges'] = $row['Privileges'];
  377. $columns[$row['Field']] = $column;
  378. }
  379. return $columns;
  380. }
  381. public function escape($value)
  382. {
  383. // Make sure the database is connected
  384. $this->_connection or $this->connect();
  385. if (($value = $this->_connection->real_escape_string((string) $value)) === false)
  386. {
  387. throw new \Database_Exception($this->_connection->error, $this->_connection->errno);
  388. }
  389. // SQL standard is to use single-quotes for all values
  390. return "'$value'";
  391. }
  392. public function error_info()
  393. {
  394. $errno = $this->_connection->errno;
  395. return array($errno, empty($errno)? null : $errno, empty($errno) ? null : $this->_connection->error);
  396. }
  397. public function in_transaction()
  398. {
  399. return $this->_in_transaction;
  400. }
  401. public function start_transaction()
  402. {
  403. $this->query(0, 'SET AUTOCOMMIT=0', false);
  404. $this->query(0, 'START TRANSACTION', false);
  405. $this->_in_transaction = true;
  406. return true;
  407. }
  408. public function commit_transaction()
  409. {
  410. $this->query(0, 'COMMIT', false);
  411. $this->query(0, 'SET AUTOCOMMIT=1', false);
  412. $this->_in_transaction = false;
  413. return true;
  414. }
  415. public function rollback_transaction()
  416. {
  417. $this->query(0, 'ROLLBACK', false);
  418. $this->query(0, 'SET AUTOCOMMIT=1', false);
  419. $this->_in_transaction = false;
  420. return true;
  421. }
  422. }