PageRenderTime 59ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://github.com/abdelm/stationwagon
PHP | 458 lines | 327 code | 61 blank | 70 comment | 30 complexity | bbff0ce91b95fbdf7f77077c46b587d8 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. ));
  64. // Prevent this information from showing up in traces
  65. unset($this->_config['connection']['username'], $this->_config['connection']['password']);
  66. try
  67. {
  68. if ($socket != '')
  69. {
  70. $port = null;
  71. }
  72. elseif ($port != '')
  73. {
  74. $socket = null;
  75. }
  76. else
  77. {
  78. $socket = null;
  79. $port = null;
  80. }
  81. if ($persistent)
  82. {
  83. // Create a persistent connection
  84. $this->_connection = new \MySQLi('p:'.$hostname, $username, $password, $database, $port, $socket);
  85. }
  86. else
  87. {
  88. // Create a connection and force it to be a new link
  89. $this->_connection = new \MySQLi($hostname, $username, $password, $database, $port, $socket);
  90. }
  91. if ($this->_connection->error)
  92. {
  93. // Unable to connect, select database, etc
  94. throw new \Database_Exception($this->_connection->error, $this->_connection->errno);
  95. }
  96. }
  97. catch (\ErrorException $e)
  98. {
  99. // No connection exists
  100. $this->_connection = null;
  101. throw new \Database_Exception('No MySQLi Connection', 0);
  102. }
  103. // \xFF is a better delimiter, but the PHP driver uses underscore
  104. $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password);
  105. if ( ! empty($this->_config['charset']))
  106. {
  107. // Set the character set
  108. $this->set_charset($this->_config['charset']);
  109. }
  110. static::$_current_databases[$this->_connection_id] = $database;
  111. }
  112. /**
  113. * Select the database
  114. *
  115. * @param string Database
  116. * @return void
  117. */
  118. protected function _select_db($database)
  119. {
  120. if ($this->_config['connection']['database'] !== static::$_current_databases[$this->_connection_id])
  121. {
  122. if ($this->_connection->select_db($database) !== true)
  123. {
  124. // Unable to select database
  125. throw new \Database_Exception($this->_connection->error, $this->_connection->errno);
  126. }
  127. }
  128. static::$_current_databases[$this->_connection_id] = $database;
  129. }
  130. public function disconnect()
  131. {
  132. try
  133. {
  134. // Database is assumed disconnected
  135. $status = true;
  136. if ($this->_connection instanceof \MySQLi)
  137. {
  138. $status = $this->_connection->close();
  139. }
  140. }
  141. catch (\Exception $e)
  142. {
  143. // Database is probably not disconnected
  144. $status = ! ($this->_connection instanceof \MySQLi);
  145. }
  146. return $status;
  147. }
  148. public function set_charset($charset)
  149. {
  150. // Make sure the database is connected
  151. $this->_connection or $this->connect();
  152. $status = $this->_connection->set_charset($charset);
  153. if ($status === false)
  154. {
  155. throw new \Database_Exception($this->_connection->error, $this->_connection->errno);
  156. }
  157. }
  158. public function query($type, $sql, $as_object)
  159. {
  160. // Make sure the database is connected
  161. if ($this->_connection)
  162. {
  163. // Make sure the connection is still alive
  164. if ( ! $this->_connection->ping())
  165. {
  166. throw new \Database_Exception($this->_connection->error.' [ '.$sql.' ]', $this->_connection->errno);
  167. }
  168. }
  169. else
  170. {
  171. $this->connect();
  172. }
  173. if ( ! empty($this->_config['profiling']))
  174. {
  175. // Benchmark this query for the current instance
  176. $benchmark = \Profiler::start("Database ({$this->_instance})", $sql);
  177. }
  178. if ( ! empty($this->_config['connection']['persistent']) and $this->_config['connection']['database'] !== static::$_current_databases[$this->_connection_id])
  179. {
  180. // Select database on persistent connections
  181. $this->_select_db($this->_config['connection']['database']);
  182. }
  183. // Execute the query
  184. if (($result = $this->_connection->query($sql)) === false)
  185. {
  186. if (isset($benchmark))
  187. {
  188. // This benchmark is worthless
  189. \Profiler::delete($benchmark);
  190. }
  191. throw new \Database_Exception($this->_connection->error.' [ '.$sql.' ]', $this->_connection->errno);
  192. }
  193. // check for multiresults, we don't support those at the moment
  194. while($this->_connection->more_results() and $this->_connection->next_result())
  195. {
  196. if ($more_result = $this->_connection->use_result())
  197. {
  198. throw new \Database_Exception('The MySQLi driver does not support multiple resultsets', 0);
  199. }
  200. }
  201. if (isset($benchmark))
  202. {
  203. \Profiler::stop($benchmark);
  204. }
  205. // Set the last query
  206. $this->last_query = $sql;
  207. if ($type === \DB::SELECT)
  208. {
  209. // Return an iterator of results
  210. return new \Database_MySQLi_Result($result, $sql, $as_object);
  211. }
  212. elseif ($type === \DB::INSERT)
  213. {
  214. // Return a list of insert id and rows created
  215. return array(
  216. $this->_connection->insert_id,
  217. $this->_connection->affected_rows,
  218. );
  219. }
  220. else
  221. {
  222. // Return the number of rows affected
  223. return $this->_connection->affected_rows;
  224. }
  225. }
  226. public function datatype($type)
  227. {
  228. static $types = array
  229. (
  230. 'blob' => array('type' => 'string', 'binary' => true, 'character_maximum_length' => '65535'),
  231. 'bool' => array('type' => 'bool'),
  232. 'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'),
  233. 'datetime' => array('type' => 'string'),
  234. 'decimal unsigned' => array('type' => 'float', 'exact' => true, 'min' => '0'),
  235. 'double' => array('type' => 'float'),
  236. 'double precision unsigned' => array('type' => 'float', 'min' => '0'),
  237. 'double unsigned' => array('type' => 'float', 'min' => '0'),
  238. 'enum' => array('type' => 'string'),
  239. 'fixed' => array('type' => 'float', 'exact' => true),
  240. 'fixed unsigned' => array('type' => 'float', 'exact' => true, 'min' => '0'),
  241. 'float unsigned' => array('type' => 'float', 'min' => '0'),
  242. 'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
  243. 'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
  244. 'longblob' => array('type' => 'string', 'binary' => true, 'character_maximum_length' => '4294967295'),
  245. 'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'),
  246. 'mediumblob' => array('type' => 'string', 'binary' => true, 'character_maximum_length' => '16777215'),
  247. 'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'),
  248. 'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'),
  249. 'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'),
  250. 'national varchar' => array('type' => 'string'),
  251. 'numeric unsigned' => array('type' => 'float', 'exact' => true, 'min' => '0'),
  252. 'nvarchar' => array('type' => 'string'),
  253. 'point' => array('type' => 'string', 'binary' => true),
  254. 'real unsigned' => array('type' => 'float', 'min' => '0'),
  255. 'set' => array('type' => 'string'),
  256. 'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'),
  257. 'text' => array('type' => 'string', 'character_maximum_length' => '65535'),
  258. 'tinyblob' => array('type' => 'string', 'binary' => true, 'character_maximum_length' => '255'),
  259. 'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'),
  260. 'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'),
  261. 'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'),
  262. 'year' => array('type' => 'string'),
  263. );
  264. $type = str_replace(' zerofill', '', $type);
  265. if (isset($types[$type]))
  266. return $types[$type];
  267. return parent::datatype($type);
  268. }
  269. public function list_tables($like = null)
  270. {
  271. if (is_string($like))
  272. {
  273. // Search for table names
  274. $result = $this->query(\DB::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), false);
  275. }
  276. else
  277. {
  278. // Find all table names
  279. $result = $this->query(\DB::SELECT, 'SHOW TABLES', false);
  280. }
  281. $tables = array();
  282. foreach ($result as $row)
  283. {
  284. $tables[] = reset($row);
  285. }
  286. return $tables;
  287. }
  288. public function list_columns($table, $like = null)
  289. {
  290. // Quote the table name
  291. $table = $this->quote_table($table);
  292. if (is_string($like))
  293. {
  294. // Search for column names
  295. $result = $this->query(\DB::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), false);
  296. }
  297. else
  298. {
  299. // Find all column names
  300. $result = $this->query(\DB::SELECT, 'SHOW FULL COLUMNS FROM '.$table, false);
  301. }
  302. $count = 0;
  303. $columns = array();
  304. foreach ($result as $row)
  305. {
  306. list($type, $length) = $this->_parse_type($row['Type']);
  307. $column = $this->datatype($type);
  308. $column['name'] = $row['Field'];
  309. $column['default'] = $row['Default'];
  310. $column['data_type'] = $type;
  311. $column['null'] = ($row['Null'] == 'YES');
  312. $column['ordinal_position'] = ++$count;
  313. switch ($column['type'])
  314. {
  315. case 'float':
  316. if (isset($length))
  317. {
  318. list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length);
  319. }
  320. break;
  321. case 'int':
  322. if (isset($length))
  323. {
  324. // MySQL attribute
  325. $column['display'] = $length;
  326. }
  327. break;
  328. case 'string':
  329. switch ($column['data_type'])
  330. {
  331. case 'binary':
  332. case 'varbinary':
  333. $column['character_maximum_length'] = $length;
  334. break;
  335. case 'char':
  336. case 'varchar':
  337. $column['character_maximum_length'] = $length;
  338. case 'text':
  339. case 'tinytext':
  340. case 'mediumtext':
  341. case 'longtext':
  342. $column['collation_name'] = $row['Collation'];
  343. break;
  344. case 'enum':
  345. case 'set':
  346. $column['collation_name'] = $row['Collation'];
  347. $column['options'] = explode('\',\'', substr($length, 1, -1));
  348. break;
  349. }
  350. break;
  351. }
  352. // MySQL attributes
  353. $column['comment'] = $row['Comment'];
  354. $column['extra'] = $row['Extra'];
  355. $column['key'] = $row['Key'];
  356. $column['privileges'] = $row['Privileges'];
  357. $columns[$row['Field']] = $column;
  358. }
  359. return $columns;
  360. }
  361. public function escape($value)
  362. {
  363. // Make sure the database is connected
  364. $this->_connection or $this->connect();
  365. if (($value = $this->_connection->real_escape_string((string) $value)) === false)
  366. {
  367. throw new \Database_Exception($this->_connection->error, $this->_connection->errno);
  368. }
  369. // SQL standard is to use single-quotes for all values
  370. return "'$value'";
  371. }
  372. public function in_transaction()
  373. {
  374. return $this->_in_transaction;
  375. }
  376. public function start_transaction()
  377. {
  378. $this->query(0, 'SET AUTOCOMMIT=0', false);
  379. $this->query(0, 'START TRANSACTION', false);
  380. $this->_in_transaction = true;
  381. return true;
  382. }
  383. public function commit_transaction()
  384. {
  385. $this->query(0, 'COMMIT', false);
  386. $this->query(0, 'SET AUTOCOMMIT=1', false);
  387. $this->_in_transaction = false;
  388. return true;
  389. }
  390. public function rollback_transaction()
  391. {
  392. $this->query(0, 'ROLLBACK', false);
  393. $this->query(0, 'SET AUTOCOMMIT=1', false);
  394. $this->_in_transaction = false;
  395. return true;
  396. }
  397. }