DbiMysqli.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Interface to the improved MySQL extension (MySQLi)
  5. *
  6. * @package PhpMyAdmin-DBI
  7. * @subpackage MySQLi
  8. */
  9. namespace PhpMyAdmin\Dbi;
  10. use PhpMyAdmin\DatabaseInterface;
  11. if (! defined('PHPMYADMIN')) {
  12. exit;
  13. }
  14. /**
  15. * some PHP versions are reporting extra messages like "No index used in query"
  16. */
  17. mysqli_report(MYSQLI_REPORT_OFF);
  18. /**
  19. * some older mysql client libs are missing these constants ...
  20. */
  21. if (! defined('MYSQLI_BINARY_FLAG')) {
  22. define('MYSQLI_BINARY_FLAG', 128);
  23. }
  24. /**
  25. * @see https://bugs.php.net/36007
  26. */
  27. if (! defined('MYSQLI_TYPE_NEWDECIMAL')) {
  28. define('MYSQLI_TYPE_NEWDECIMAL', 246);
  29. }
  30. if (! defined('MYSQLI_TYPE_BIT')) {
  31. define('MYSQLI_TYPE_BIT', 16);
  32. }
  33. if (! defined('MYSQLI_TYPE_JSON')) {
  34. define('MYSQLI_TYPE_JSON', 245);
  35. }
  36. /* vim: set expandtab sw=4 ts=4 sts=4: */
  37. /**
  38. * Interface to the improved MySQL extension (MySQLi)
  39. *
  40. * @package PhpMyAdmin-DBI
  41. * @subpackage MySQLi
  42. */
  43. class DbiMysqli implements DbiExtension
  44. {
  45. static private $pma_mysqli_flag_names = array(
  46. MYSQLI_NUM_FLAG => 'num',
  47. MYSQLI_PART_KEY_FLAG => 'part_key',
  48. MYSQLI_SET_FLAG => 'set',
  49. MYSQLI_TIMESTAMP_FLAG => 'timestamp',
  50. MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment',
  51. MYSQLI_ENUM_FLAG => 'enum',
  52. MYSQLI_ZEROFILL_FLAG => 'zerofill',
  53. MYSQLI_UNSIGNED_FLAG => 'unsigned',
  54. MYSQLI_BLOB_FLAG => 'blob',
  55. MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key',
  56. MYSQLI_UNIQUE_KEY_FLAG => 'unique_key',
  57. MYSQLI_PRI_KEY_FLAG => 'primary_key',
  58. MYSQLI_NOT_NULL_FLAG => 'not_null',
  59. );
  60. /**
  61. * connects to the database server
  62. *
  63. * @param string $user mysql user name
  64. * @param string $password mysql user password
  65. * @param array $server host/port/socket/persistent
  66. *
  67. * @return mixed false on error or a mysqli object on success
  68. */
  69. public function connect(
  70. $user, $password, array $server
  71. ) {
  72. if ($server) {
  73. $server['host'] = (empty($server['host']))
  74. ? 'localhost'
  75. : $server['host'];
  76. }
  77. // NULL enables connection to the default socket
  78. $link = mysqli_init();
  79. $client_flags = 0;
  80. /* Optionally compress connection */
  81. if ($server['compress'] && defined('MYSQLI_CLIENT_COMPRESS')) {
  82. $client_flags |= MYSQLI_CLIENT_COMPRESS;
  83. }
  84. /* Optionally enable SSL */
  85. if ($server['ssl']) {
  86. $client_flags |= MYSQLI_CLIENT_SSL;
  87. if (! empty($server['ssl_key']) ||
  88. ! empty($server['ssl_cert']) ||
  89. ! empty($server['ssl_ca']) ||
  90. ! empty($server['ssl_ca_path']) ||
  91. ! empty($server['ssl_ciphers'])
  92. ) {
  93. mysqli_ssl_set(
  94. $link,
  95. $server['ssl_key'],
  96. $server['ssl_cert'],
  97. $server['ssl_ca'],
  98. $server['ssl_ca_path'],
  99. $server['ssl_ciphers']
  100. );
  101. }
  102. /*
  103. * disables SSL certificate validation on mysqlnd for MySQL 5.6 or later
  104. * @link https://bugs.php.net/bug.php?id=68344
  105. * @link https://github.com/phpmyadmin/phpmyadmin/pull/11838
  106. */
  107. if (! $server['ssl_verify']) {
  108. mysqli_options(
  109. $link,
  110. MYSQLI_OPT_SSL_VERIFY_SERVER_CERT,
  111. $server['ssl_verify']
  112. );
  113. $client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
  114. }
  115. }
  116. if ($GLOBALS['cfg']['PersistentConnections']) {
  117. $host = 'p:' . $server['host'];
  118. } else {
  119. $host = $server['host'];
  120. }
  121. $return_value = mysqli_real_connect(
  122. $link,
  123. $host,
  124. $user,
  125. $password,
  126. '',
  127. $server['port'],
  128. $server['socket'],
  129. $client_flags
  130. );
  131. if ($return_value === false || is_null($return_value)) {
  132. /*
  133. * Switch to SSL if server asked us to do so, unfortunately
  134. * there are more ways MySQL server can tell this:
  135. *
  136. * - MySQL 8.0 and newer should return error 3159
  137. * - #2001 - SSL Connection is required. Please specify SSL options and retry.
  138. * - #9002 - SSL connection is required. Please specify SSL options and retry.
  139. */
  140. $error_number = mysqli_connect_errno();
  141. $error_message = mysqli_connect_error();
  142. if (! $server['ssl'] && ($error_number == 3159 ||
  143. (($error_number == 2001 || $error_number == 9002) && stripos($error_message, 'SSL Connection is required') !== false))
  144. ) {
  145. trigger_error(
  146. __('SSL connection enforced by server, automatically enabling it.'),
  147. E_USER_WARNING
  148. );
  149. $server['ssl'] = true;
  150. return self::connect($user, $password, $server);
  151. }
  152. return false;
  153. }
  154. if (defined('PMA_ENABLE_LDI')) {
  155. mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, true);
  156. } else {
  157. mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, false);
  158. }
  159. return $link;
  160. }
  161. /**
  162. * selects given database
  163. *
  164. * @param string $dbname database name to select
  165. * @param mysqli $link the mysqli object
  166. *
  167. * @return boolean
  168. */
  169. public function selectDb($dbname, $link)
  170. {
  171. return mysqli_select_db($link, $dbname);
  172. }
  173. /**
  174. * runs a query and returns the result
  175. *
  176. * @param string $query query to execute
  177. * @param mysqli $link mysqli object
  178. * @param int $options query options
  179. *
  180. * @return mysqli_result|bool
  181. */
  182. public function realQuery($query, $link, $options)
  183. {
  184. if ($options == ($options | DatabaseInterface::QUERY_STORE)) {
  185. $method = MYSQLI_STORE_RESULT;
  186. } elseif ($options == ($options | DatabaseInterface::QUERY_UNBUFFERED)) {
  187. $method = MYSQLI_USE_RESULT;
  188. } else {
  189. $method = 0;
  190. }
  191. return mysqli_query($link, $query, $method);
  192. }
  193. /**
  194. * Run the multi query and output the results
  195. *
  196. * @param mysqli $link mysqli object
  197. * @param string $query multi query statement to execute
  198. *
  199. * @return mysqli_result collection | boolean(false)
  200. */
  201. public function realMultiQuery($link, $query)
  202. {
  203. return mysqli_multi_query($link, $query);
  204. }
  205. /**
  206. * returns array of rows with associative and numeric keys from $result
  207. *
  208. * @param mysqli_result $result result set identifier
  209. *
  210. * @return array
  211. */
  212. public function fetchArray($result)
  213. {
  214. return mysqli_fetch_array($result, MYSQLI_BOTH);
  215. }
  216. /**
  217. * returns array of rows with associative keys from $result
  218. *
  219. * @param mysqli_result $result result set identifier
  220. *
  221. * @return array
  222. */
  223. public function fetchAssoc($result)
  224. {
  225. return mysqli_fetch_array($result, MYSQLI_ASSOC);
  226. }
  227. /**
  228. * returns array of rows with numeric keys from $result
  229. *
  230. * @param mysqli_result $result result set identifier
  231. *
  232. * @return array
  233. */
  234. public function fetchRow($result)
  235. {
  236. return mysqli_fetch_array($result, MYSQLI_NUM);
  237. }
  238. /**
  239. * Adjusts the result pointer to an arbitrary row in the result
  240. *
  241. * @param mysqli_result $result database result
  242. * @param integer $offset offset to seek
  243. *
  244. * @return bool true on success, false on failure
  245. */
  246. public function dataSeek($result, $offset)
  247. {
  248. return mysqli_data_seek($result, $offset);
  249. }
  250. /**
  251. * Frees memory associated with the result
  252. *
  253. * @param mysqli_result $result database result
  254. *
  255. * @return void
  256. */
  257. public function freeResult($result)
  258. {
  259. if ($result instanceof mysqli_result) {
  260. mysqli_free_result($result);
  261. }
  262. }
  263. /**
  264. * Check if there are any more query results from a multi query
  265. *
  266. * @param mysqli $link the mysqli object
  267. *
  268. * @return bool true or false
  269. */
  270. public function moreResults($link)
  271. {
  272. return mysqli_more_results($link);
  273. }
  274. /**
  275. * Prepare next result from multi_query
  276. *
  277. * @param mysqli $link the mysqli object
  278. *
  279. * @return bool true or false
  280. */
  281. public function nextResult($link)
  282. {
  283. return mysqli_next_result($link);
  284. }
  285. /**
  286. * Store the result returned from multi query
  287. *
  288. * @param mysqli $link the mysqli object
  289. *
  290. * @return mixed false when empty results / result set when not empty
  291. */
  292. public function storeResult($link)
  293. {
  294. return mysqli_store_result($link);
  295. }
  296. /**
  297. * Returns a string representing the type of connection used
  298. *
  299. * @param resource $link mysql link
  300. *
  301. * @return string type of connection used
  302. */
  303. public function getHostInfo($link)
  304. {
  305. return mysqli_get_host_info($link);
  306. }
  307. /**
  308. * Returns the version of the MySQL protocol used
  309. *
  310. * @param resource $link mysql link
  311. *
  312. * @return integer version of the MySQL protocol used
  313. */
  314. public function getProtoInfo($link)
  315. {
  316. return mysqli_get_proto_info($link);
  317. }
  318. /**
  319. * returns a string that represents the client library version
  320. *
  321. * @return string MySQL client library version
  322. */
  323. public function getClientInfo()
  324. {
  325. return mysqli_get_client_info();
  326. }
  327. /**
  328. * returns last error message or false if no errors occurred
  329. *
  330. * @param resource $link mysql link
  331. *
  332. * @return string|bool $error or false
  333. */
  334. public function getError($link)
  335. {
  336. $GLOBALS['errno'] = 0;
  337. if (null !== $link && false !== $link) {
  338. $error_number = mysqli_errno($link);
  339. $error_message = mysqli_error($link);
  340. } else {
  341. $error_number = mysqli_connect_errno();
  342. $error_message = mysqli_connect_error();
  343. }
  344. if (0 == $error_number) {
  345. return false;
  346. }
  347. // keep the error number for further check after
  348. // the call to getError()
  349. $GLOBALS['errno'] = $error_number;
  350. return $GLOBALS['dbi']->formatError($error_number, $error_message);
  351. }
  352. /**
  353. * returns the number of rows returned by last query
  354. *
  355. * @param mysqli_result $result result set identifier
  356. *
  357. * @return string|int
  358. */
  359. public function numRows($result)
  360. {
  361. // see the note for tryQuery();
  362. if (is_bool($result)) {
  363. return 0;
  364. }
  365. return @mysqli_num_rows($result);
  366. }
  367. /**
  368. * returns the number of rows affected by last query
  369. *
  370. * @param mysqli $link the mysqli object
  371. *
  372. * @return int
  373. */
  374. public function affectedRows($link)
  375. {
  376. return mysqli_affected_rows($link);
  377. }
  378. /**
  379. * returns metainfo for fields in $result
  380. *
  381. * @param mysqli_result $result result set identifier
  382. *
  383. * @return array meta info for fields in $result
  384. */
  385. public function getFieldsMeta($result)
  386. {
  387. // Build an associative array for a type look up
  388. $typeAr = array();
  389. $typeAr[MYSQLI_TYPE_DECIMAL] = 'real';
  390. $typeAr[MYSQLI_TYPE_NEWDECIMAL] = 'real';
  391. $typeAr[MYSQLI_TYPE_BIT] = 'int';
  392. $typeAr[MYSQLI_TYPE_TINY] = 'int';
  393. $typeAr[MYSQLI_TYPE_SHORT] = 'int';
  394. $typeAr[MYSQLI_TYPE_LONG] = 'int';
  395. $typeAr[MYSQLI_TYPE_FLOAT] = 'real';
  396. $typeAr[MYSQLI_TYPE_DOUBLE] = 'real';
  397. $typeAr[MYSQLI_TYPE_NULL] = 'null';
  398. $typeAr[MYSQLI_TYPE_TIMESTAMP] = 'timestamp';
  399. $typeAr[MYSQLI_TYPE_LONGLONG] = 'int';
  400. $typeAr[MYSQLI_TYPE_INT24] = 'int';
  401. $typeAr[MYSQLI_TYPE_DATE] = 'date';
  402. $typeAr[MYSQLI_TYPE_TIME] = 'time';
  403. $typeAr[MYSQLI_TYPE_DATETIME] = 'datetime';
  404. $typeAr[MYSQLI_TYPE_YEAR] = 'year';
  405. $typeAr[MYSQLI_TYPE_NEWDATE] = 'date';
  406. $typeAr[MYSQLI_TYPE_ENUM] = 'unknown';
  407. $typeAr[MYSQLI_TYPE_SET] = 'unknown';
  408. $typeAr[MYSQLI_TYPE_TINY_BLOB] = 'blob';
  409. $typeAr[MYSQLI_TYPE_MEDIUM_BLOB] = 'blob';
  410. $typeAr[MYSQLI_TYPE_LONG_BLOB] = 'blob';
  411. $typeAr[MYSQLI_TYPE_BLOB] = 'blob';
  412. $typeAr[MYSQLI_TYPE_VAR_STRING] = 'string';
  413. $typeAr[MYSQLI_TYPE_STRING] = 'string';
  414. // MySQL returns MYSQLI_TYPE_STRING for CHAR
  415. // and MYSQLI_TYPE_CHAR === MYSQLI_TYPE_TINY
  416. // so this would override TINYINT and mark all TINYINT as string
  417. // see https://github.com/phpmyadmin/phpmyadmin/issues/8569
  418. //$typeAr[MYSQLI_TYPE_CHAR] = 'string';
  419. $typeAr[MYSQLI_TYPE_GEOMETRY] = 'geometry';
  420. $typeAr[MYSQLI_TYPE_BIT] = 'bit';
  421. $typeAr[MYSQLI_TYPE_JSON] = 'json';
  422. $fields = mysqli_fetch_fields($result);
  423. // this happens sometimes (seen under MySQL 4.0.25)
  424. if (!is_array($fields)) {
  425. return false;
  426. }
  427. foreach ($fields as $k => $field) {
  428. $fields[$k]->_type = $field->type;
  429. $fields[$k]->type = $typeAr[$field->type];
  430. $fields[$k]->_flags = $field->flags;
  431. $fields[$k]->flags = $this->fieldFlags($result, $k);
  432. // Enhance the field objects for mysql-extension compatibility
  433. //$flags = explode(' ', $fields[$k]->flags);
  434. //array_unshift($flags, 'dummy');
  435. $fields[$k]->multiple_key
  436. = (int) (bool) ($fields[$k]->_flags & MYSQLI_MULTIPLE_KEY_FLAG);
  437. $fields[$k]->primary_key
  438. = (int) (bool) ($fields[$k]->_flags & MYSQLI_PRI_KEY_FLAG);
  439. $fields[$k]->unique_key
  440. = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNIQUE_KEY_FLAG);
  441. $fields[$k]->not_null
  442. = (int) (bool) ($fields[$k]->_flags & MYSQLI_NOT_NULL_FLAG);
  443. $fields[$k]->unsigned
  444. = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNSIGNED_FLAG);
  445. $fields[$k]->zerofill
  446. = (int) (bool) ($fields[$k]->_flags & MYSQLI_ZEROFILL_FLAG);
  447. $fields[$k]->numeric
  448. = (int) (bool) ($fields[$k]->_flags & MYSQLI_NUM_FLAG);
  449. $fields[$k]->blob
  450. = (int) (bool) ($fields[$k]->_flags & MYSQLI_BLOB_FLAG);
  451. }
  452. return $fields;
  453. }
  454. /**
  455. * return number of fields in given $result
  456. *
  457. * @param mysqli_result $result result set identifier
  458. *
  459. * @return int field count
  460. */
  461. public function numFields($result)
  462. {
  463. return mysqli_num_fields($result);
  464. }
  465. /**
  466. * returns the length of the given field $i in $result
  467. *
  468. * @param mysqli_result $result result set identifier
  469. * @param int $i field
  470. *
  471. * @return int length of field
  472. */
  473. public function fieldLen($result, $i)
  474. {
  475. return mysqli_fetch_field_direct($result, $i)->length;
  476. }
  477. /**
  478. * returns name of $i. field in $result
  479. *
  480. * @param mysqli_result $result result set identifier
  481. * @param int $i field
  482. *
  483. * @return string name of $i. field in $result
  484. */
  485. public function fieldName($result, $i)
  486. {
  487. return mysqli_fetch_field_direct($result, $i)->name;
  488. }
  489. /**
  490. * returns concatenated string of human readable field flags
  491. *
  492. * @param mysqli_result $result result set identifier
  493. * @param int $i field
  494. *
  495. * @return string field flags
  496. */
  497. public function fieldFlags($result, $i)
  498. {
  499. $f = mysqli_fetch_field_direct($result, $i);
  500. $type = $f->type;
  501. $charsetnr = $f->charsetnr;
  502. $f = $f->flags;
  503. $flags = array();
  504. foreach (self::$pma_mysqli_flag_names as $flag => $name) {
  505. if ($f & $flag) {
  506. $flags[] = $name;
  507. }
  508. }
  509. // See https://dev.mysql.com/doc/refman/6.0/en/c-api-datatypes.html:
  510. // to determine if a string is binary, we should not use MYSQLI_BINARY_FLAG
  511. // but instead the charsetnr member of the MYSQL_FIELD
  512. // structure. Watch out: some types like DATE returns 63 in charsetnr
  513. // so we have to check also the type.
  514. // Unfortunately there is no equivalent in the mysql extension.
  515. if (($type == MYSQLI_TYPE_TINY_BLOB || $type == MYSQLI_TYPE_BLOB
  516. || $type == MYSQLI_TYPE_MEDIUM_BLOB || $type == MYSQLI_TYPE_LONG_BLOB
  517. || $type == MYSQLI_TYPE_VAR_STRING || $type == MYSQLI_TYPE_STRING)
  518. && 63 == $charsetnr
  519. ) {
  520. $flags[] = 'binary';
  521. }
  522. return implode(' ', $flags);
  523. }
  524. /**
  525. * returns properly escaped string for use in MySQL queries
  526. *
  527. * @param mixed $link database link
  528. * @param string $str string to be escaped
  529. *
  530. * @return string a MySQL escaped string
  531. */
  532. public function escapeString($link, $str)
  533. {
  534. return mysqli_real_escape_string($link, $str);
  535. }
  536. }