DatabaseMysqli.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <?php
  2. /**
  3. * This is the MySQLi database abstraction layer.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup Database
  22. */
  23. namespace Wikimedia\Rdbms;
  24. use mysqli;
  25. use mysqli_result;
  26. use IP;
  27. use stdClass;
  28. use Wikimedia\AtEase\AtEase;
  29. /**
  30. * Database abstraction object for PHP extension mysqli.
  31. *
  32. * @ingroup Database
  33. * @since 1.22
  34. * @see Database
  35. * @phan-file-suppress PhanParamSignatureMismatch resource vs mysqli_result
  36. */
  37. class DatabaseMysqli extends DatabaseMysqlBase {
  38. /**
  39. * @param string $sql
  40. * @return mysqli_result|bool
  41. */
  42. protected function doQuery( $sql ) {
  43. AtEase::suppressWarnings();
  44. $res = $this->getBindingHandle()->query( $sql );
  45. AtEase::restoreWarnings();
  46. return $res;
  47. }
  48. /**
  49. * @param string $realServer
  50. * @param string|null $dbName
  51. * @return mysqli|null
  52. * @throws DBConnectionError
  53. */
  54. protected function mysqlConnect( $realServer, $dbName ) {
  55. if ( !function_exists( 'mysqli_init' ) ) {
  56. throw $this->newExceptionAfterConnectError(
  57. "MySQLi functions missing, have you compiled PHP with the --with-mysqli option?"
  58. );
  59. }
  60. // Other than mysql_connect, mysqli_real_connect expects an explicit port number
  61. // e.g. "localhost:1234" or "127.0.0.1:1234"
  62. // or Unix domain socket path
  63. // e.g. "localhost:/socket_path" or "localhost:/foo/bar:bar:bar"
  64. // colons are known to be used by Google AppEngine,
  65. // see <https://cloud.google.com/sql/docs/mysql/connect-app-engine>
  66. //
  67. // We need to parse the port or socket path out of $realServer
  68. $port = null;
  69. $socket = null;
  70. $hostAndPort = IP::splitHostAndPort( $realServer );
  71. if ( $hostAndPort ) {
  72. $realServer = $hostAndPort[0];
  73. if ( $hostAndPort[1] ) {
  74. $port = $hostAndPort[1];
  75. }
  76. } elseif ( substr_count( $realServer, ':/' ) == 1 ) {
  77. // If we have a colon slash instead of a colon and a port number
  78. // after the ip or hostname, assume it's the Unix domain socket path
  79. list( $realServer, $socket ) = explode( ':', $realServer, 2 );
  80. }
  81. $mysqli = mysqli_init();
  82. // Make affectedRows() for UPDATE reflect the number of matching rows, regardless
  83. // of whether any column values changed. This is what callers want to know and is
  84. // consistent with what Postgres, SQLite, and SQL Server return.
  85. $connFlags = MYSQLI_CLIENT_FOUND_ROWS;
  86. if ( $this->getFlag( self::DBO_SSL ) ) {
  87. $connFlags |= MYSQLI_CLIENT_SSL;
  88. $mysqli->ssl_set(
  89. $this->sslKeyPath,
  90. $this->sslCertPath,
  91. $this->sslCAFile,
  92. $this->sslCAPath,
  93. $this->sslCiphers
  94. );
  95. }
  96. if ( $this->getFlag( self::DBO_COMPRESS ) ) {
  97. $connFlags |= MYSQLI_CLIENT_COMPRESS;
  98. }
  99. if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
  100. $realServer = 'p:' . $realServer;
  101. }
  102. if ( $this->utf8Mode ) {
  103. // Tell the server we're communicating with it in UTF-8.
  104. // This may engage various charset conversions.
  105. $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'utf8' );
  106. } else {
  107. $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'binary' );
  108. }
  109. $mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
  110. if ( $mysqli->real_connect(
  111. $realServer,
  112. $this->user,
  113. $this->password,
  114. $dbName,
  115. $port,
  116. $socket,
  117. $connFlags
  118. ) ) {
  119. return $mysqli;
  120. }
  121. return null;
  122. }
  123. /**
  124. * @return bool
  125. */
  126. protected function closeConnection() {
  127. $conn = $this->getBindingHandle();
  128. return $conn->close();
  129. }
  130. /**
  131. * @return int
  132. */
  133. function insertId() {
  134. $conn = $this->getBindingHandle();
  135. return (int)$conn->insert_id;
  136. }
  137. /**
  138. * @return int
  139. */
  140. function lastErrno() {
  141. if ( $this->conn instanceof mysqli ) {
  142. return $this->conn->errno;
  143. } else {
  144. return mysqli_connect_errno();
  145. }
  146. }
  147. /**
  148. * @return int
  149. */
  150. protected function fetchAffectedRowCount() {
  151. $conn = $this->getBindingHandle();
  152. return $conn->affected_rows;
  153. }
  154. /**
  155. * @param mysqli_result $res
  156. * @return bool
  157. */
  158. protected function mysqlFreeResult( $res ) {
  159. $res->free_result();
  160. return true;
  161. }
  162. /**
  163. * @param mysqli_result $res
  164. * @return stdClass|bool
  165. */
  166. protected function mysqlFetchObject( $res ) {
  167. $object = $res->fetch_object();
  168. if ( $object === null ) {
  169. return false;
  170. }
  171. return $object;
  172. }
  173. /**
  174. * @param mysqli_result $res
  175. * @return array|false
  176. */
  177. protected function mysqlFetchArray( $res ) {
  178. $array = $res->fetch_array();
  179. if ( $array === null ) {
  180. return false;
  181. }
  182. return $array;
  183. }
  184. /**
  185. * @param mysqli_result $res
  186. * @return mixed
  187. */
  188. protected function mysqlNumRows( $res ) {
  189. return $res->num_rows;
  190. }
  191. /**
  192. * @param mysqli_result $res
  193. * @return mixed
  194. */
  195. protected function mysqlNumFields( $res ) {
  196. return $res->field_count;
  197. }
  198. /**
  199. * @param mysqli_result $res
  200. * @param int $n
  201. * @return mixed
  202. */
  203. protected function mysqlFetchField( $res, $n ) {
  204. $field = $res->fetch_field_direct( $n );
  205. // Add missing properties to result (using flags property)
  206. // which will be part of function mysql-fetch-field for backward compatibility
  207. $field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
  208. $field->primary_key = $field->flags & MYSQLI_PRI_KEY_FLAG;
  209. $field->unique_key = $field->flags & MYSQLI_UNIQUE_KEY_FLAG;
  210. $field->multiple_key = $field->flags & MYSQLI_MULTIPLE_KEY_FLAG;
  211. $field->binary = $field->flags & MYSQLI_BINARY_FLAG;
  212. $field->numeric = $field->flags & MYSQLI_NUM_FLAG;
  213. $field->blob = $field->flags & MYSQLI_BLOB_FLAG;
  214. $field->unsigned = $field->flags & MYSQLI_UNSIGNED_FLAG;
  215. $field->zerofill = $field->flags & MYSQLI_ZEROFILL_FLAG;
  216. return $field;
  217. }
  218. /**
  219. * @param mysqli_result $res
  220. * @param int $n
  221. * @return mixed
  222. */
  223. protected function mysqlFieldName( $res, $n ) {
  224. $field = $res->fetch_field_direct( $n );
  225. return $field->name;
  226. }
  227. /**
  228. * @param mysqli_result $res
  229. * @param int $n
  230. * @return mixed
  231. */
  232. protected function mysqlFieldType( $res, $n ) {
  233. $field = $res->fetch_field_direct( $n );
  234. return $field->type;
  235. }
  236. /**
  237. * @param mysqli_result $res
  238. * @param int $row
  239. * @return mixed
  240. */
  241. protected function mysqlDataSeek( $res, $row ) {
  242. return $res->data_seek( $row );
  243. }
  244. /**
  245. * @param mysqli|null $conn Optional connection object
  246. * @return string
  247. */
  248. protected function mysqlError( $conn = null ) {
  249. if ( $conn === null ) {
  250. return mysqli_connect_error();
  251. } else {
  252. return $conn->error;
  253. }
  254. }
  255. /**
  256. * Escapes special characters in a string for use in an SQL statement
  257. * @param string $s
  258. * @return string
  259. */
  260. protected function mysqlRealEscapeString( $s ) {
  261. $conn = $this->getBindingHandle();
  262. return $conn->real_escape_string( (string)$s );
  263. }
  264. /**
  265. * @return mysqli
  266. */
  267. protected function getBindingHandle() {
  268. return parent::getBindingHandle();
  269. }
  270. }
  271. /**
  272. * @deprecated since 1.29
  273. */
  274. class_alias( DatabaseMysqli::class, 'DatabaseMysqli' );