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

/classes/database/mysqli/connection.php

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