PageRenderTime 27ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/Kohana/Database/MySQLi.php

http://github.com/kohana/database
PHP | 422 lines | 300 code | 56 blank | 66 comment | 24 complexity | 2070eb009ed04915201c50b4405c9883 MD5 | raw file
  1. <?php defined('SYSPATH') OR die('No direct script access.');
  2. /**
  3. * MySQLi database connection.
  4. *
  5. * @package Kohana/Database
  6. * @category Drivers
  7. * @author Kohana Team
  8. * @copyright (c) 2008-2009 Kohana Team
  9. * @license http://kohanaphp.com/license
  10. */
  11. class Kohana_Database_MySQLi extends Database {
  12. // Database in use by each connection
  13. protected static $_current_databases = array();
  14. // Use SET NAMES to set the character set
  15. protected static $_set_names;
  16. // Identifier for this connection within the PHP driver
  17. protected $_connection_id;
  18. // MySQL uses a backtick for identifiers
  19. protected $_identifier = '`';
  20. public function connect()
  21. {
  22. if ($this->_connection)
  23. return;
  24. if (Database_MySQLi::$_set_names === NULL)
  25. {
  26. // Determine if we can use mysqli_set_charset(), which is only
  27. // available on PHP 5.2.3+ when compiled against MySQL 5.0+
  28. Database_MySQLi::$_set_names = ! function_exists('mysqli_set_charset');
  29. }
  30. // Extract the connection parameters, adding required variabels
  31. extract($this->_config['connection'] + array(
  32. 'database' => '',
  33. 'hostname' => '',
  34. 'username' => '',
  35. 'password' => '',
  36. 'socket' => '',
  37. 'port' => 3306,
  38. 'ssl' => NULL,
  39. ));
  40. // Prevent this information from showing up in traces
  41. unset($this->_config['connection']['username'], $this->_config['connection']['password']);
  42. try
  43. {
  44. if(is_array($ssl))
  45. {
  46. $this->_connection = mysqli_init();
  47. $this->_connection->ssl_set(
  48. Arr::get($ssl, 'client_key_path'),
  49. Arr::get($ssl, 'client_cert_path'),
  50. Arr::get($ssl, 'ca_cert_path'),
  51. Arr::get($ssl, 'ca_dir_path'),
  52. Arr::get($ssl, 'cipher')
  53. );
  54. $this->_connection->real_connect($hostname, $username, $password, $database, $port, $socket, MYSQLI_CLIENT_SSL);
  55. }
  56. else
  57. {
  58. $this->_connection = new mysqli($hostname, $username, $password, $database, $port, $socket);
  59. }
  60. }
  61. catch (Exception $e)
  62. {
  63. // No connection exists
  64. $this->_connection = NULL;
  65. throw new Database_Exception(':error', array(':error' => $e->getMessage()), $e->getCode());
  66. }
  67. // \xFF is a better delimiter, but the PHP driver uses underscore
  68. $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password);
  69. if ( ! empty($this->_config['charset']))
  70. {
  71. // Set the character set
  72. $this->set_charset($this->_config['charset']);
  73. }
  74. if ( ! empty($this->_config['connection']['variables']))
  75. {
  76. // Set session variables
  77. $variables = array();
  78. foreach ($this->_config['connection']['variables'] as $var => $val)
  79. {
  80. $variables[] = 'SESSION '.$var.' = '.$this->quote($val);
  81. }
  82. $this->_connection->query('SET '.implode(', ', $variables));
  83. }
  84. }
  85. public function disconnect()
  86. {
  87. try
  88. {
  89. // Database is assumed disconnected
  90. $status = TRUE;
  91. if (is_resource($this->_connection))
  92. {
  93. if ($status = $this->_connection->close())
  94. {
  95. // Clear the connection
  96. $this->_connection = NULL;
  97. // Clear the instance
  98. parent::disconnect();
  99. }
  100. }
  101. }
  102. catch (Exception $e)
  103. {
  104. // Database is probably not disconnected
  105. $status = ! is_resource($this->_connection);
  106. }
  107. return $status;
  108. }
  109. public function set_charset($charset)
  110. {
  111. // Make sure the database is connected
  112. $this->_connection or $this->connect();
  113. if (Database_MySQLi::$_set_names === TRUE)
  114. {
  115. // PHP is compiled against MySQL 4.x
  116. $status = (bool) $this->_connection->query('SET NAMES '.$this->quote($charset));
  117. }
  118. else
  119. {
  120. // PHP is compiled against MySQL 5.x
  121. $status = $this->_connection->set_charset($charset);
  122. }
  123. if ($status === FALSE)
  124. {
  125. throw new Database_Exception(':error', array(':error' => $this->_connection->error), $this->_connection->errno);
  126. }
  127. }
  128. public function query($type, $sql, $as_object = FALSE, array $params = NULL)
  129. {
  130. // Make sure the database is connected
  131. $this->_connection or $this->connect();
  132. if (Kohana::$profiling)
  133. {
  134. // Benchmark this query for the current instance
  135. $benchmark = Profiler::start("Database ({$this->_instance})", $sql);
  136. }
  137. // Execute the query
  138. if (($result = $this->_connection->query($sql)) === FALSE)
  139. {
  140. if (isset($benchmark))
  141. {
  142. // This benchmark is worthless
  143. Profiler::delete($benchmark);
  144. }
  145. throw new Database_Exception(':error [ :query ]', array(
  146. ':error' => $this->_connection->error,
  147. ':query' => $sql
  148. ), $this->_connection->errno);
  149. }
  150. if (isset($benchmark))
  151. {
  152. Profiler::stop($benchmark);
  153. }
  154. // Set the last query
  155. $this->last_query = $sql;
  156. if ($type === Database::SELECT)
  157. {
  158. // Return an iterator of results
  159. return new Database_MySQLi_Result($result, $sql, $as_object, $params);
  160. }
  161. elseif ($type === Database::INSERT)
  162. {
  163. // Return a list of insert id and rows created
  164. return array(
  165. $this->_connection->insert_id,
  166. $this->_connection->affected_rows,
  167. );
  168. }
  169. else
  170. {
  171. // Return the number of rows affected
  172. return $this->_connection->affected_rows;
  173. }
  174. }
  175. public function datatype($type)
  176. {
  177. static $types = array
  178. (
  179. 'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'),
  180. 'bool' => array('type' => 'bool'),
  181. 'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'),
  182. 'datetime' => array('type' => 'string'),
  183. 'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
  184. 'double' => array('type' => 'float'),
  185. 'double precision unsigned' => array('type' => 'float', 'min' => '0'),
  186. 'double unsigned' => array('type' => 'float', 'min' => '0'),
  187. 'enum' => array('type' => 'string'),
  188. 'fixed' => array('type' => 'float', 'exact' => TRUE),
  189. 'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
  190. 'float unsigned' => array('type' => 'float', 'min' => '0'),
  191. 'geometry' => array('type' => 'string', 'binary' => TRUE),
  192. 'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
  193. 'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
  194. 'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'),
  195. 'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'),
  196. 'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'),
  197. 'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'),
  198. 'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'),
  199. 'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'),
  200. 'national varchar' => array('type' => 'string'),
  201. 'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
  202. 'nvarchar' => array('type' => 'string'),
  203. 'point' => array('type' => 'string', 'binary' => TRUE),
  204. 'real unsigned' => array('type' => 'float', 'min' => '0'),
  205. 'set' => array('type' => 'string'),
  206. 'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'),
  207. 'text' => array('type' => 'string', 'character_maximum_length' => '65535'),
  208. 'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'),
  209. 'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'),
  210. 'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'),
  211. 'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'),
  212. 'year' => array('type' => 'string'),
  213. );
  214. $type = str_replace(' zerofill', '', $type);
  215. if (isset($types[$type]))
  216. return $types[$type];
  217. return parent::datatype($type);
  218. }
  219. /**
  220. * Start a SQL transaction
  221. *
  222. * @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
  223. *
  224. * @param string $mode Isolation level
  225. * @return boolean
  226. */
  227. public function begin($mode = NULL)
  228. {
  229. // Make sure the database is connected
  230. $this->_connection or $this->connect();
  231. if ($mode AND ! $this->_connection->query("SET TRANSACTION ISOLATION LEVEL $mode"))
  232. {
  233. throw new Database_Exception(':error', array(
  234. ':error' => $this->_connection->error
  235. ), $this->_connection->errno);
  236. }
  237. return (bool) $this->_connection->query('START TRANSACTION');
  238. }
  239. /**
  240. * Commit a SQL transaction
  241. *
  242. * @return boolean
  243. */
  244. public function commit()
  245. {
  246. // Make sure the database is connected
  247. $this->_connection or $this->connect();
  248. return (bool) $this->_connection->query('COMMIT');
  249. }
  250. /**
  251. * Rollback a SQL transaction
  252. *
  253. * @return boolean
  254. */
  255. public function rollback()
  256. {
  257. // Make sure the database is connected
  258. $this->_connection or $this->connect();
  259. return (bool) $this->_connection->query('ROLLBACK');
  260. }
  261. public function list_tables($like = NULL)
  262. {
  263. if (is_string($like))
  264. {
  265. // Search for table names
  266. $result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE);
  267. }
  268. else
  269. {
  270. // Find all table names
  271. $result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE);
  272. }
  273. $tables = array();
  274. foreach ($result as $row)
  275. {
  276. $tables[] = reset($row);
  277. }
  278. return $tables;
  279. }
  280. public function list_columns($table, $like = NULL, $add_prefix = TRUE)
  281. {
  282. // Quote the table name
  283. $table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table;
  284. if (is_string($like))
  285. {
  286. // Search for column names
  287. $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE);
  288. }
  289. else
  290. {
  291. // Find all column names
  292. $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE);
  293. }
  294. $count = 0;
  295. $columns = array();
  296. foreach ($result as $row)
  297. {
  298. list($type, $length) = $this->_parse_type($row['Type']);
  299. $column = $this->datatype($type);
  300. $column['column_name'] = $row['Field'];
  301. $column['column_default'] = $row['Default'];
  302. $column['data_type'] = $type;
  303. $column['is_nullable'] = ($row['Null'] == 'YES');
  304. $column['ordinal_position'] = ++$count;
  305. switch ($column['type'])
  306. {
  307. case 'float':
  308. if (isset($length))
  309. {
  310. list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length);
  311. }
  312. break;
  313. case 'int':
  314. if (isset($length))
  315. {
  316. // MySQL attribute
  317. $column['display'] = $length;
  318. }
  319. break;
  320. case 'string':
  321. switch ($column['data_type'])
  322. {
  323. case 'binary':
  324. case 'varbinary':
  325. $column['character_maximum_length'] = $length;
  326. break;
  327. case 'char':
  328. case 'varchar':
  329. $column['character_maximum_length'] = $length;
  330. case 'text':
  331. case 'tinytext':
  332. case 'mediumtext':
  333. case 'longtext':
  334. $column['collation_name'] = $row['Collation'];
  335. break;
  336. case 'enum':
  337. case 'set':
  338. $column['collation_name'] = $row['Collation'];
  339. $column['options'] = explode('\',\'', substr($length, 1, -1));
  340. break;
  341. }
  342. break;
  343. }
  344. // MySQL attributes
  345. $column['comment'] = $row['Comment'];
  346. $column['extra'] = $row['Extra'];
  347. $column['key'] = $row['Key'];
  348. $column['privileges'] = $row['Privileges'];
  349. $columns[$row['Field']] = $column;
  350. }
  351. return $columns;
  352. }
  353. public function escape($value)
  354. {
  355. // Make sure the database is connected
  356. $this->_connection or $this->connect();
  357. if (($value = $this->_connection->real_escape_string( (string) $value)) === FALSE)
  358. {
  359. throw new Database_Exception(':error', array(
  360. ':error' => $this->_connection->error,
  361. ), $this->_connection->errno);
  362. }
  363. // SQL standard is to use single-quotes for all values
  364. return "'$value'";
  365. }
  366. } // End Database_MySQLi