Mysqli.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Db
  17. * @subpackage Adapter
  18. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @see Zend_Db_Adapter_Abstract
  24. */
  25. #require_once 'Zend/Db/Adapter/Abstract.php';
  26. /**
  27. * @see Zend_Db_Profiler
  28. */
  29. #require_once 'Zend/Db/Profiler.php';
  30. /**
  31. * @see Zend_Db_Select
  32. */
  33. #require_once 'Zend/Db/Select.php';
  34. /**
  35. * @see Zend_Db_Statement_Mysqli
  36. */
  37. #require_once 'Zend/Db/Statement/Mysqli.php';
  38. /**
  39. * @category Zend
  40. * @package Zend_Db
  41. * @subpackage Adapter
  42. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  43. * @license http://framework.zend.com/license/new-bsd New BSD License
  44. */
  45. class Zend_Db_Adapter_Mysqli extends Zend_Db_Adapter_Abstract
  46. {
  47. /**
  48. * Keys are UPPERCASE SQL datatypes or the constants
  49. * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
  50. *
  51. * Values are:
  52. * 0 = 32-bit integer
  53. * 1 = 64-bit integer
  54. * 2 = float or decimal
  55. *
  56. * @var array Associative array of datatypes to values 0, 1, or 2.
  57. */
  58. protected $_numericDataTypes = array(
  59. Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
  60. Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
  61. Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
  62. 'INT' => Zend_Db::INT_TYPE,
  63. 'INTEGER' => Zend_Db::INT_TYPE,
  64. 'MEDIUMINT' => Zend_Db::INT_TYPE,
  65. 'SMALLINT' => Zend_Db::INT_TYPE,
  66. 'TINYINT' => Zend_Db::INT_TYPE,
  67. 'BIGINT' => Zend_Db::BIGINT_TYPE,
  68. 'SERIAL' => Zend_Db::BIGINT_TYPE,
  69. 'DEC' => Zend_Db::FLOAT_TYPE,
  70. 'DECIMAL' => Zend_Db::FLOAT_TYPE,
  71. 'DOUBLE' => Zend_Db::FLOAT_TYPE,
  72. 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
  73. 'FIXED' => Zend_Db::FLOAT_TYPE,
  74. 'FLOAT' => Zend_Db::FLOAT_TYPE
  75. );
  76. /**
  77. * @var Zend_Db_Statement_Mysqli
  78. */
  79. protected $_stmt = null;
  80. /**
  81. * Default class name for a DB statement.
  82. *
  83. * @var string
  84. */
  85. protected $_defaultStmtClass = 'Zend_Db_Statement_Mysqli';
  86. /**
  87. * Quote a raw string.
  88. *
  89. * @param mixed $value Raw string
  90. *
  91. * @return string Quoted string
  92. */
  93. protected function _quote($value)
  94. {
  95. if (is_int($value) || is_float($value)) {
  96. return $value;
  97. }
  98. $this->_connect();
  99. return "'" . $this->_connection->real_escape_string($value) . "'";
  100. }
  101. /**
  102. * Returns the symbol the adapter uses for delimiting identifiers.
  103. *
  104. * @return string
  105. */
  106. public function getQuoteIdentifierSymbol()
  107. {
  108. return "`";
  109. }
  110. /**
  111. * Returns a list of the tables in the database.
  112. *
  113. * @return array
  114. */
  115. public function listTables()
  116. {
  117. $result = array();
  118. // Use mysqli extension API, because SHOW doesn't work
  119. // well as a prepared statement on MySQL 4.1.
  120. $sql = 'SHOW TABLES';
  121. if ($queryResult = $this->getConnection()->query($sql)) {
  122. while ($row = $queryResult->fetch_row()) {
  123. $result[] = $row[0];
  124. }
  125. $queryResult->close();
  126. } else {
  127. /**
  128. * @see Zend_Db_Adapter_Mysqli_Exception
  129. */
  130. #require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
  131. throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error);
  132. }
  133. return $result;
  134. }
  135. /**
  136. * Returns the column descriptions for a table.
  137. *
  138. * The return value is an associative array keyed by the column name,
  139. * as returned by the RDBMS.
  140. *
  141. * The value of each array element is an associative array
  142. * with the following keys:
  143. *
  144. * SCHEMA_NAME => string; name of database or schema
  145. * TABLE_NAME => string;
  146. * COLUMN_NAME => string; column name
  147. * COLUMN_POSITION => number; ordinal position of column in table
  148. * DATA_TYPE => string; SQL datatype name of column
  149. * DEFAULT => string; default expression of column, null if none
  150. * NULLABLE => boolean; true if column can have nulls
  151. * LENGTH => number; length of CHAR/VARCHAR
  152. * SCALE => number; scale of NUMERIC/DECIMAL
  153. * PRECISION => number; precision of NUMERIC/DECIMAL
  154. * UNSIGNED => boolean; unsigned property of an integer type
  155. * PRIMARY => boolean; true if column is part of the primary key
  156. * PRIMARY_POSITION => integer; position of column in primary key
  157. * IDENTITY => integer; true if column is auto-generated with unique values
  158. *
  159. * @param string $tableName
  160. * @param string $schemaName OPTIONAL
  161. * @return array
  162. */
  163. public function describeTable($tableName, $schemaName = null)
  164. {
  165. /**
  166. * @todo use INFORMATION_SCHEMA someday when
  167. * MySQL's implementation isn't too slow.
  168. */
  169. if ($schemaName) {
  170. $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true);
  171. } else {
  172. $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true);
  173. }
  174. /**
  175. * Use mysqli extension API, because DESCRIBE doesn't work
  176. * well as a prepared statement on MySQL 4.1.
  177. */
  178. if ($queryResult = $this->getConnection()->query($sql)) {
  179. while ($row = $queryResult->fetch_assoc()) {
  180. $result[] = $row;
  181. }
  182. $queryResult->close();
  183. } else {
  184. /**
  185. * @see Zend_Db_Adapter_Mysqli_Exception
  186. */
  187. #require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
  188. throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error);
  189. }
  190. $desc = array();
  191. $row_defaults = array(
  192. 'Length' => null,
  193. 'Scale' => null,
  194. 'Precision' => null,
  195. 'Unsigned' => null,
  196. 'Primary' => false,
  197. 'PrimaryPosition' => null,
  198. 'Identity' => false
  199. );
  200. $i = 1;
  201. $p = 1;
  202. foreach ($result as $key => $row) {
  203. $row = array_merge($row_defaults, $row);
  204. if (preg_match('/unsigned/', $row['Type'])) {
  205. $row['Unsigned'] = true;
  206. }
  207. if (preg_match('/^((?:var)?char)\((\d+)\)/', $row['Type'], $matches)) {
  208. $row['Type'] = $matches[1];
  209. $row['Length'] = $matches[2];
  210. } else if (preg_match('/^decimal\((\d+),(\d+)\)/', $row['Type'], $matches)) {
  211. $row['Type'] = 'decimal';
  212. $row['Precision'] = $matches[1];
  213. $row['Scale'] = $matches[2];
  214. } else if (preg_match('/^float\((\d+),(\d+)\)/', $row['Type'], $matches)) {
  215. $row['Type'] = 'float';
  216. $row['Precision'] = $matches[1];
  217. $row['Scale'] = $matches[2];
  218. } else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row['Type'], $matches)) {
  219. $row['Type'] = $matches[1];
  220. /**
  221. * The optional argument of a MySQL int type is not precision
  222. * or length; it is only a hint for display width.
  223. */
  224. }
  225. if (strtoupper($row['Key']) == 'PRI') {
  226. $row['Primary'] = true;
  227. $row['PrimaryPosition'] = $p;
  228. if ($row['Extra'] == 'auto_increment') {
  229. $row['Identity'] = true;
  230. } else {
  231. $row['Identity'] = false;
  232. }
  233. ++$p;
  234. }
  235. $desc[$this->foldCase($row['Field'])] = array(
  236. 'SCHEMA_NAME' => null, // @todo
  237. 'TABLE_NAME' => $this->foldCase($tableName),
  238. 'COLUMN_NAME' => $this->foldCase($row['Field']),
  239. 'COLUMN_POSITION' => $i,
  240. 'DATA_TYPE' => $row['Type'],
  241. 'DEFAULT' => $row['Default'],
  242. 'NULLABLE' => (bool) ($row['Null'] == 'YES'),
  243. 'LENGTH' => $row['Length'],
  244. 'SCALE' => $row['Scale'],
  245. 'PRECISION' => $row['Precision'],
  246. 'UNSIGNED' => $row['Unsigned'],
  247. 'PRIMARY' => $row['Primary'],
  248. 'PRIMARY_POSITION' => $row['PrimaryPosition'],
  249. 'IDENTITY' => $row['Identity']
  250. );
  251. ++$i;
  252. }
  253. return $desc;
  254. }
  255. /**
  256. * Creates a connection to the database.
  257. *
  258. * @return void
  259. * @throws Zend_Db_Adapter_Mysqli_Exception
  260. */
  261. protected function _connect()
  262. {
  263. if ($this->_connection) {
  264. return;
  265. }
  266. if (!extension_loaded('mysqli')) {
  267. /**
  268. * @see Zend_Db_Adapter_Mysqli_Exception
  269. */
  270. #require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
  271. throw new Zend_Db_Adapter_Mysqli_Exception('The Mysqli extension is required for this adapter but the extension is not loaded');
  272. }
  273. if (isset($this->_config['port'])) {
  274. $port = (integer) $this->_config['port'];
  275. } else {
  276. $port = null;
  277. }
  278. if (isset($this->_config['socket'])) {
  279. $socket = $this->_config['socket'];
  280. } else {
  281. $socket = null;
  282. }
  283. $this->_connection = mysqli_init();
  284. if(!empty($this->_config['driver_options'])) {
  285. foreach($this->_config['driver_options'] as $option=>$value) {
  286. if(is_string($option)) {
  287. // Suppress warnings here
  288. // Ignore it if it's not a valid constant
  289. $option = @constant(strtoupper($option));
  290. if($option === null)
  291. continue;
  292. }
  293. mysqli_options($this->_connection, $option, $value);
  294. }
  295. }
  296. // Suppress connection warnings here.
  297. // Throw an exception instead.
  298. $_isConnected = @mysqli_real_connect(
  299. $this->_connection,
  300. $this->_config['host'],
  301. $this->_config['username'],
  302. $this->_config['password'],
  303. $this->_config['dbname'],
  304. $port,
  305. $socket
  306. );
  307. if ($_isConnected === false || mysqli_connect_errno()) {
  308. $this->closeConnection();
  309. /**
  310. * @see Zend_Db_Adapter_Mysqli_Exception
  311. */
  312. #require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
  313. throw new Zend_Db_Adapter_Mysqli_Exception(mysqli_connect_error());
  314. }
  315. if (!empty($this->_config['charset'])) {
  316. mysqli_set_charset($this->_connection, $this->_config['charset']);
  317. }
  318. }
  319. /**
  320. * Test if a connection is active
  321. *
  322. * @return boolean
  323. */
  324. public function isConnected()
  325. {
  326. return ((bool) ($this->_connection instanceof mysqli));
  327. }
  328. /**
  329. * Force the connection to close.
  330. *
  331. * @return void
  332. */
  333. public function closeConnection()
  334. {
  335. if ($this->isConnected()) {
  336. $this->_connection->close();
  337. }
  338. $this->_connection = null;
  339. }
  340. /**
  341. * Prepare a statement and return a PDOStatement-like object.
  342. *
  343. * @param string $sql SQL query
  344. * @return Zend_Db_Statement_Mysqli
  345. */
  346. public function prepare($sql)
  347. {
  348. $this->_connect();
  349. if ($this->_stmt) {
  350. $this->_stmt->close();
  351. }
  352. $stmtClass = $this->_defaultStmtClass;
  353. if (!class_exists($stmtClass)) {
  354. #require_once 'Zend/Loader.php';
  355. Zend_Loader::loadClass($stmtClass);
  356. }
  357. $stmt = new $stmtClass($this, $sql);
  358. if ($stmt === false) {
  359. return false;
  360. }
  361. $stmt->setFetchMode($this->_fetchMode);
  362. $this->_stmt = $stmt;
  363. return $stmt;
  364. }
  365. /**
  366. * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
  367. *
  368. * As a convention, on RDBMS brands that support sequences
  369. * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
  370. * from the arguments and returns the last id generated by that sequence.
  371. * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
  372. * returns the last value generated for such a column, and the table name
  373. * argument is disregarded.
  374. *
  375. * MySQL does not support sequences, so $tableName and $primaryKey are ignored.
  376. *
  377. * @param string $tableName OPTIONAL Name of table.
  378. * @param string $primaryKey OPTIONAL Name of primary key column.
  379. * @return string
  380. * @todo Return value should be int?
  381. */
  382. public function lastInsertId($tableName = null, $primaryKey = null)
  383. {
  384. $mysqli = $this->_connection;
  385. return (string) $mysqli->insert_id;
  386. }
  387. /**
  388. * Begin a transaction.
  389. *
  390. * @return void
  391. */
  392. protected function _beginTransaction()
  393. {
  394. $this->_connect();
  395. $this->_connection->autocommit(false);
  396. }
  397. /**
  398. * Commit a transaction.
  399. *
  400. * @return void
  401. */
  402. protected function _commit()
  403. {
  404. $this->_connect();
  405. $this->_connection->commit();
  406. $this->_connection->autocommit(true);
  407. }
  408. /**
  409. * Roll-back a transaction.
  410. *
  411. * @return void
  412. */
  413. protected function _rollBack()
  414. {
  415. $this->_connect();
  416. $this->_connection->rollback();
  417. $this->_connection->autocommit(true);
  418. }
  419. /**
  420. * Set the fetch mode.
  421. *
  422. * @param int $mode
  423. * @return void
  424. * @throws Zend_Db_Adapter_Mysqli_Exception
  425. */
  426. public function setFetchMode($mode)
  427. {
  428. switch ($mode) {
  429. case Zend_Db::FETCH_LAZY:
  430. case Zend_Db::FETCH_ASSOC:
  431. case Zend_Db::FETCH_NUM:
  432. case Zend_Db::FETCH_BOTH:
  433. case Zend_Db::FETCH_NAMED:
  434. case Zend_Db::FETCH_OBJ:
  435. $this->_fetchMode = $mode;
  436. break;
  437. case Zend_Db::FETCH_BOUND: // bound to PHP variable
  438. /**
  439. * @see Zend_Db_Adapter_Mysqli_Exception
  440. */
  441. #require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
  442. throw new Zend_Db_Adapter_Mysqli_Exception('FETCH_BOUND is not supported yet');
  443. break;
  444. default:
  445. /**
  446. * @see Zend_Db_Adapter_Mysqli_Exception
  447. */
  448. #require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
  449. throw new Zend_Db_Adapter_Mysqli_Exception("Invalid fetch mode '$mode' specified");
  450. }
  451. }
  452. /**
  453. * Adds an adapter-specific LIMIT clause to the SELECT statement.
  454. *
  455. * @param string $sql
  456. * @param int $count
  457. * @param int $offset OPTIONAL
  458. * @return string
  459. */
  460. public function limit($sql, $count, $offset = 0)
  461. {
  462. $count = intval($count);
  463. if ($count <= 0) {
  464. /**
  465. * @see Zend_Db_Adapter_Mysqli_Exception
  466. */
  467. #require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
  468. throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument count=$count is not valid");
  469. }
  470. $offset = intval($offset);
  471. if ($offset < 0) {
  472. /**
  473. * @see Zend_Db_Adapter_Mysqli_Exception
  474. */
  475. #require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
  476. throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument offset=$offset is not valid");
  477. }
  478. $sql .= " LIMIT $count";
  479. if ($offset > 0) {
  480. $sql .= " OFFSET $offset";
  481. }
  482. return $sql;
  483. }
  484. /**
  485. * Check if the adapter supports real SQL parameters.
  486. *
  487. * @param string $type 'positional' or 'named'
  488. * @return bool
  489. */
  490. public function supportsParameters($type)
  491. {
  492. switch ($type) {
  493. case 'positional':
  494. return true;
  495. case 'named':
  496. default:
  497. return false;
  498. }
  499. }
  500. /**
  501. * Retrieve server version in PHP style
  502. *
  503. *@return string
  504. */
  505. public function getServerVersion()
  506. {
  507. $this->_connect();
  508. $version = $this->_connection->server_version;
  509. $major = (int) ($version / 10000);
  510. $minor = (int) ($version % 10000 / 100);
  511. $revision = (int) ($version % 100);
  512. return $major . '.' . $minor . '.' . $revision;
  513. }
  514. }