PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/system/database/drivers/mysqli/mysqli_driver.php

http://github.com/EllisLab/CodeIgniter
PHP | 540 lines | 239 code | 71 blank | 230 comment | 29 complexity | 3a0a7cdeb2e60485b2cd0a9f6e058ee1 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP
  6. *
  7. * This content is released under the MIT License (MIT)
  8. *
  9. * Copyright (c) 2014 - 2019, British Columbia Institute of Technology
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in
  19. * all copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  27. * THE SOFTWARE.
  28. *
  29. * @package CodeIgniter
  30. * @author EllisLab Dev Team
  31. * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
  32. * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
  33. * @license https://opensource.org/licenses/MIT MIT License
  34. * @link https://codeigniter.com
  35. * @since Version 1.3.0
  36. * @filesource
  37. */
  38. defined('BASEPATH') OR exit('No direct script access allowed');
  39. /**
  40. * MySQLi Database Adapter Class
  41. *
  42. * Note: _DB is an extender class that the app controller
  43. * creates dynamically based on whether the query builder
  44. * class is being used or not.
  45. *
  46. * @package CodeIgniter
  47. * @subpackage Drivers
  48. * @category Database
  49. * @author EllisLab Dev Team
  50. * @link https://codeigniter.com/userguide3/database/
  51. */
  52. class CI_DB_mysqli_driver extends CI_DB {
  53. /**
  54. * Database driver
  55. *
  56. * @var string
  57. */
  58. public $dbdriver = 'mysqli';
  59. /**
  60. * Compression flag
  61. *
  62. * @var bool
  63. */
  64. public $compress = FALSE;
  65. /**
  66. * DELETE hack flag
  67. *
  68. * Whether to use the MySQL "delete hack" which allows the number
  69. * of affected rows to be shown. Uses a preg_replace when enabled,
  70. * adding a bit more processing to all queries.
  71. *
  72. * @var bool
  73. */
  74. public $delete_hack = TRUE;
  75. /**
  76. * Strict ON flag
  77. *
  78. * Whether we're running in strict SQL mode.
  79. *
  80. * @var bool
  81. */
  82. public $stricton;
  83. // --------------------------------------------------------------------
  84. /**
  85. * Identifier escape character
  86. *
  87. * @var string
  88. */
  89. protected $_escape_char = '`';
  90. // --------------------------------------------------------------------
  91. /**
  92. * MySQLi object
  93. *
  94. * Has to be preserved without being assigned to $conn_id.
  95. *
  96. * @var MySQLi
  97. */
  98. protected $_mysqli;
  99. // --------------------------------------------------------------------
  100. /**
  101. * Database connection
  102. *
  103. * @param bool $persistent
  104. * @return object
  105. */
  106. public function db_connect($persistent = FALSE)
  107. {
  108. // Do we have a socket path?
  109. if ($this->hostname[0] === '/')
  110. {
  111. $hostname = NULL;
  112. $port = NULL;
  113. $socket = $this->hostname;
  114. }
  115. else
  116. {
  117. $hostname = ($persistent === TRUE)
  118. ? 'p:'.$this->hostname : $this->hostname;
  119. $port = empty($this->port) ? NULL : $this->port;
  120. $socket = NULL;
  121. }
  122. $client_flags = ($this->compress === TRUE) ? MYSQLI_CLIENT_COMPRESS : 0;
  123. $this->_mysqli = mysqli_init();
  124. $this->_mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 10);
  125. if (isset($this->stricton))
  126. {
  127. if ($this->stricton)
  128. {
  129. $this->_mysqli->options(MYSQLI_INIT_COMMAND, 'SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")');
  130. }
  131. else
  132. {
  133. $this->_mysqli->options(MYSQLI_INIT_COMMAND,
  134. 'SET SESSION sql_mode =
  135. REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
  136. @@sql_mode,
  137. "STRICT_ALL_TABLES,", ""),
  138. ",STRICT_ALL_TABLES", ""),
  139. "STRICT_ALL_TABLES", ""),
  140. "STRICT_TRANS_TABLES,", ""),
  141. ",STRICT_TRANS_TABLES", ""),
  142. "STRICT_TRANS_TABLES", "")'
  143. );
  144. }
  145. }
  146. if (is_array($this->encrypt))
  147. {
  148. $ssl = array();
  149. empty($this->encrypt['ssl_key']) OR $ssl['key'] = $this->encrypt['ssl_key'];
  150. empty($this->encrypt['ssl_cert']) OR $ssl['cert'] = $this->encrypt['ssl_cert'];
  151. empty($this->encrypt['ssl_ca']) OR $ssl['ca'] = $this->encrypt['ssl_ca'];
  152. empty($this->encrypt['ssl_capath']) OR $ssl['capath'] = $this->encrypt['ssl_capath'];
  153. empty($this->encrypt['ssl_cipher']) OR $ssl['cipher'] = $this->encrypt['ssl_cipher'];
  154. if (isset($this->encrypt['ssl_verify']))
  155. {
  156. $client_flags |= MYSQLI_CLIENT_SSL;
  157. if ($this->encrypt['ssl_verify'])
  158. {
  159. defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT') && $this->_mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, TRUE);
  160. }
  161. // Apparently (when it exists), setting MYSQLI_OPT_SSL_VERIFY_SERVER_CERT
  162. // to FALSE didn't do anything, so PHP 5.6.16 introduced yet another
  163. // constant ...
  164. //
  165. // https://secure.php.net/ChangeLog-5.php#5.6.16
  166. // https://bugs.php.net/bug.php?id=68344
  167. elseif (defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT'))
  168. {
  169. $client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
  170. }
  171. }
  172. if ( ! empty($ssl))
  173. {
  174. $client_flags |= MYSQLI_CLIENT_SSL;
  175. $this->_mysqli->ssl_set(
  176. isset($ssl['key']) ? $ssl['key'] : NULL,
  177. isset($ssl['cert']) ? $ssl['cert'] : NULL,
  178. isset($ssl['ca']) ? $ssl['ca'] : NULL,
  179. isset($ssl['capath']) ? $ssl['capath'] : NULL,
  180. isset($ssl['cipher']) ? $ssl['cipher'] : NULL
  181. );
  182. }
  183. }
  184. if ($this->_mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, $socket, $client_flags))
  185. {
  186. // Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails
  187. if (
  188. ($client_flags & MYSQLI_CLIENT_SSL)
  189. && version_compare($this->_mysqli->client_info, '5.7.3', '<=')
  190. && empty($this->_mysqli->query("SHOW STATUS LIKE 'ssl_cipher'")->fetch_object()->Value)
  191. )
  192. {
  193. $this->_mysqli->close();
  194. $message = 'MySQLi was configured for an SSL connection, but got an unencrypted connection instead!';
  195. log_message('error', $message);
  196. return ($this->db_debug) ? $this->display_error($message, '', TRUE) : FALSE;
  197. }
  198. if ( ! $this->_mysqli->set_charset($this->char_set))
  199. {
  200. log_message('error', "Database: Unable to set the configured connection charset ('{$this->char_set}').");
  201. $this->_mysqli->close();
  202. return ($this->db->db_debug) ? $this->display_error('db_unable_to_set_charset', $this->char_set) : FALSE;
  203. }
  204. return $this->_mysqli;
  205. }
  206. return FALSE;
  207. }
  208. // --------------------------------------------------------------------
  209. /**
  210. * Reconnect
  211. *
  212. * Keep / reestablish the db connection if no queries have been
  213. * sent for a length of time exceeding the server's idle timeout
  214. *
  215. * @return void
  216. */
  217. public function reconnect()
  218. {
  219. if ($this->conn_id !== FALSE && $this->conn_id->ping() === FALSE)
  220. {
  221. $this->conn_id = FALSE;
  222. }
  223. }
  224. // --------------------------------------------------------------------
  225. /**
  226. * Select the database
  227. *
  228. * @param string $database
  229. * @return bool
  230. */
  231. public function db_select($database = '')
  232. {
  233. if ($database === '')
  234. {
  235. $database = $this->database;
  236. }
  237. if ($this->conn_id->select_db($database))
  238. {
  239. $this->database = $database;
  240. $this->data_cache = array();
  241. return TRUE;
  242. }
  243. return FALSE;
  244. }
  245. // --------------------------------------------------------------------
  246. /**
  247. * Database version number
  248. *
  249. * @return string
  250. */
  251. public function version()
  252. {
  253. if (isset($this->data_cache['version']))
  254. {
  255. return $this->data_cache['version'];
  256. }
  257. return $this->data_cache['version'] = $this->conn_id->server_info;
  258. }
  259. // --------------------------------------------------------------------
  260. /**
  261. * Execute the query
  262. *
  263. * @param string $sql an SQL query
  264. * @return mixed
  265. */
  266. protected function _execute($sql)
  267. {
  268. return $this->conn_id->query($this->_prep_query($sql));
  269. }
  270. // --------------------------------------------------------------------
  271. /**
  272. * Prep the query
  273. *
  274. * If needed, each database adapter can prep the query string
  275. *
  276. * @param string $sql an SQL query
  277. * @return string
  278. */
  279. protected function _prep_query($sql)
  280. {
  281. // mysqli_affected_rows() returns 0 for "DELETE FROM TABLE" queries. This hack
  282. // modifies the query so that it a proper number of affected rows is returned.
  283. if ($this->delete_hack === TRUE && preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql))
  284. {
  285. return trim($sql).' WHERE 1=1';
  286. }
  287. return $sql;
  288. }
  289. // --------------------------------------------------------------------
  290. /**
  291. * Begin Transaction
  292. *
  293. * @return bool
  294. */
  295. protected function _trans_begin()
  296. {
  297. $this->conn_id->autocommit(FALSE);
  298. return is_php('5.5')
  299. ? $this->conn_id->begin_transaction()
  300. : $this->simple_query('START TRANSACTION'); // can also be BEGIN or BEGIN WORK
  301. }
  302. // --------------------------------------------------------------------
  303. /**
  304. * Commit Transaction
  305. *
  306. * @return bool
  307. */
  308. protected function _trans_commit()
  309. {
  310. if ($this->conn_id->commit())
  311. {
  312. $this->conn_id->autocommit(TRUE);
  313. return TRUE;
  314. }
  315. return FALSE;
  316. }
  317. // --------------------------------------------------------------------
  318. /**
  319. * Rollback Transaction
  320. *
  321. * @return bool
  322. */
  323. protected function _trans_rollback()
  324. {
  325. if ($this->conn_id->rollback())
  326. {
  327. $this->conn_id->autocommit(TRUE);
  328. return TRUE;
  329. }
  330. return FALSE;
  331. }
  332. // --------------------------------------------------------------------
  333. /**
  334. * Platform-dependent string escape
  335. *
  336. * @param string
  337. * @return string
  338. */
  339. protected function _escape_str($str)
  340. {
  341. return $this->conn_id->real_escape_string($str);
  342. }
  343. // --------------------------------------------------------------------
  344. /**
  345. * Affected Rows
  346. *
  347. * @return int
  348. */
  349. public function affected_rows()
  350. {
  351. return $this->conn_id->affected_rows;
  352. }
  353. // --------------------------------------------------------------------
  354. /**
  355. * Insert ID
  356. *
  357. * @return int
  358. */
  359. public function insert_id()
  360. {
  361. return $this->conn_id->insert_id;
  362. }
  363. // --------------------------------------------------------------------
  364. /**
  365. * List table query
  366. *
  367. * Generates a platform-specific query string so that the table names can be fetched
  368. *
  369. * @param bool $prefix_limit
  370. * @return string
  371. */
  372. protected function _list_tables($prefix_limit = FALSE)
  373. {
  374. $sql = 'SHOW TABLES FROM '.$this->_escape_char.$this->database.$this->_escape_char;
  375. if ($prefix_limit !== FALSE && $this->dbprefix !== '')
  376. {
  377. return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'";
  378. }
  379. return $sql;
  380. }
  381. // --------------------------------------------------------------------
  382. /**
  383. * Show column query
  384. *
  385. * Generates a platform-specific query string so that the column names can be fetched
  386. *
  387. * @param string $table
  388. * @return string
  389. */
  390. protected function _list_columns($table = '')
  391. {
  392. return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE);
  393. }
  394. // --------------------------------------------------------------------
  395. /**
  396. * Returns an object with field data
  397. *
  398. * @param string $table
  399. * @return array
  400. */
  401. public function field_data($table)
  402. {
  403. if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE)
  404. {
  405. return FALSE;
  406. }
  407. $query = $query->result_object();
  408. $retval = array();
  409. for ($i = 0, $c = count($query); $i < $c; $i++)
  410. {
  411. $retval[$i] = new stdClass();
  412. $retval[$i]->name = $query[$i]->Field;
  413. sscanf($query[$i]->Type, '%[a-z](%d)',
  414. $retval[$i]->type,
  415. $retval[$i]->max_length
  416. );
  417. $retval[$i]->default = $query[$i]->Default;
  418. $retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI');
  419. }
  420. return $retval;
  421. }
  422. // --------------------------------------------------------------------
  423. /**
  424. * Error
  425. *
  426. * Returns an array containing code and message of the last
  427. * database error that has occurred.
  428. *
  429. * @return array
  430. */
  431. public function error()
  432. {
  433. if ( ! empty($this->_mysqli->connect_errno))
  434. {
  435. return array(
  436. 'code' => $this->_mysqli->connect_errno,
  437. 'message' => $this->_mysqli->connect_error
  438. );
  439. }
  440. return array('code' => $this->conn_id->errno, 'message' => $this->conn_id->error);
  441. }
  442. // --------------------------------------------------------------------
  443. /**
  444. * FROM tables
  445. *
  446. * Groups tables in FROM clauses if needed, so there is no confusion
  447. * about operator precedence.
  448. *
  449. * @return string
  450. */
  451. protected function _from_tables()
  452. {
  453. if ( ! empty($this->qb_join) && count($this->qb_from) > 1)
  454. {
  455. return '('.implode(', ', $this->qb_from).')';
  456. }
  457. return implode(', ', $this->qb_from);
  458. }
  459. // --------------------------------------------------------------------
  460. /**
  461. * Close DB Connection
  462. *
  463. * @return void
  464. */
  465. protected function _close()
  466. {
  467. $this->conn_id->close();
  468. }
  469. }