Merge "TitleOptionWidget: Documentation type fix"
[lhc/web/wiklou.git] / includes / libs / rdbms / database / DatabaseMysqli.php
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
25 use mysqli;
26 use mysqli_result;
27 use IP;
28 use stdClass;
29
30 /**
31 * Database abstraction object for PHP extension mysqli.
32 *
33 * @ingroup Database
34 * @since 1.22
35 * @see Database
36 */
37 class DatabaseMysqli extends DatabaseMysqlBase {
38 /**
39 * @param string $sql
40 * @return mysqli_result|bool
41 */
42 protected function doQuery( $sql ) {
43 return $this->getBindingHandle()->query( $sql );
44 }
45
46 /**
47 * @param string $realServer
48 * @param string|null $dbName
49 * @return mysqli|null
50 * @throws DBConnectionError
51 */
52 protected function mysqlConnect( $realServer, $dbName ) {
53 if ( !function_exists( 'mysqli_init' ) ) {
54 throw $this->newExceptionAfterConnectError(
55 "MySQLi functions missing, have you compiled PHP with the --with-mysqli option?"
56 );
57 }
58
59 // Other than mysql_connect, mysqli_real_connect expects an explicit port number
60 // e.g. "localhost:1234" or "127.0.0.1:1234"
61 // or Unix domain socket path
62 // e.g. "localhost:/socket_path" or "localhost:/foo/bar:bar:bar"
63 // colons are known to be used by Google AppEngine,
64 // see <https://cloud.google.com/sql/docs/mysql/connect-app-engine>
65 //
66 // We need to parse the port or socket path out of $realServer
67 $port = null;
68 $socket = null;
69 $hostAndPort = IP::splitHostAndPort( $realServer );
70 if ( $hostAndPort ) {
71 $realServer = $hostAndPort[0];
72 if ( $hostAndPort[1] ) {
73 $port = $hostAndPort[1];
74 }
75 } elseif ( substr_count( $realServer, ':/' ) == 1 ) {
76 // If we have a colon slash instead of a colon and a port number
77 // after the ip or hostname, assume it's the Unix domain socket path
78 list( $realServer, $socket ) = explode( ':', $realServer, 2 );
79 }
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
103 if ( $this->utf8Mode ) {
104 // Tell the server we're communicating with it in UTF-8.
105 // This may engage various charset conversions.
106 $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'utf8' );
107 } else {
108 $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'binary' );
109 }
110 $mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
111
112 if ( $mysqli->real_connect(
113 $realServer,
114 $this->user,
115 $this->password,
116 $dbName,
117 $port,
118 $socket,
119 $connFlags
120 ) ) {
121 return $mysqli;
122 }
123
124 return null;
125 }
126
127 /**
128 * @return bool
129 */
130 protected function closeConnection() {
131 $conn = $this->getBindingHandle();
132
133 return $conn->close();
134 }
135
136 /**
137 * @return int
138 */
139 function insertId() {
140 $conn = $this->getBindingHandle();
141
142 return (int)$conn->insert_id;
143 }
144
145 /**
146 * @return int
147 */
148 function lastErrno() {
149 if ( $this->conn instanceof mysqli ) {
150 return $this->conn->errno;
151 } else {
152 return mysqli_connect_errno();
153 }
154 }
155
156 /**
157 * @return int
158 */
159 protected function fetchAffectedRowCount() {
160 $conn = $this->getBindingHandle();
161
162 return $conn->affected_rows;
163 }
164
165 /**
166 * @param mysqli_result $res
167 * @return bool
168 */
169 protected function mysqlFreeResult( $res ) {
170 $res->free_result();
171
172 return true;
173 }
174
175 /**
176 * @param mysqli_result $res
177 * @return stdClass|bool
178 */
179 protected function mysqlFetchObject( $res ) {
180 $object = $res->fetch_object();
181 if ( $object === null ) {
182 return false;
183 }
184
185 return $object;
186 }
187
188 /**
189 * @param mysqli_result $res
190 * @return array|false
191 */
192 protected function mysqlFetchArray( $res ) {
193 $array = $res->fetch_array();
194 if ( $array === null ) {
195 return false;
196 }
197
198 return $array;
199 }
200
201 /**
202 * @param mysqli_result $res
203 * @return mixed
204 */
205 protected function mysqlNumRows( $res ) {
206 return $res->num_rows;
207 }
208
209 /**
210 * @param mysqli_result $res
211 * @return mixed
212 */
213 protected function mysqlNumFields( $res ) {
214 return $res->field_count;
215 }
216
217 /**
218 * @param mysqli_result $res
219 * @param int $n
220 * @return mixed
221 */
222 protected function mysqlFetchField( $res, $n ) {
223 $field = $res->fetch_field_direct( $n );
224
225 // Add missing properties to result (using flags property)
226 // which will be part of function mysql-fetch-field for backward compatibility
227 $field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
228 $field->primary_key = $field->flags & MYSQLI_PRI_KEY_FLAG;
229 $field->unique_key = $field->flags & MYSQLI_UNIQUE_KEY_FLAG;
230 $field->multiple_key = $field->flags & MYSQLI_MULTIPLE_KEY_FLAG;
231 $field->binary = $field->flags & MYSQLI_BINARY_FLAG;
232 $field->numeric = $field->flags & MYSQLI_NUM_FLAG;
233 $field->blob = $field->flags & MYSQLI_BLOB_FLAG;
234 $field->unsigned = $field->flags & MYSQLI_UNSIGNED_FLAG;
235 $field->zerofill = $field->flags & MYSQLI_ZEROFILL_FLAG;
236
237 return $field;
238 }
239
240 /**
241 * @param mysqli_result $res
242 * @param int $n
243 * @return mixed
244 */
245 protected function mysqlFieldName( $res, $n ) {
246 $field = $res->fetch_field_direct( $n );
247
248 return $field->name;
249 }
250
251 /**
252 * @param mysqli_result $res
253 * @param int $n
254 * @return mixed
255 */
256 protected function mysqlFieldType( $res, $n ) {
257 $field = $res->fetch_field_direct( $n );
258
259 return $field->type;
260 }
261
262 /**
263 * @param mysqli_result $res
264 * @param int $row
265 * @return mixed
266 */
267 protected function mysqlDataSeek( $res, $row ) {
268 return $res->data_seek( $row );
269 }
270
271 /**
272 * @param mysqli|null $conn Optional connection object
273 * @return string
274 */
275 protected function mysqlError( $conn = null ) {
276 if ( $conn === null ) {
277 return mysqli_connect_error();
278 } else {
279 return $conn->error;
280 }
281 }
282
283 /**
284 * Escapes special characters in a string for use in an SQL statement
285 * @param string $s
286 * @return string
287 */
288 protected function mysqlRealEscapeString( $s ) {
289 $conn = $this->getBindingHandle();
290
291 return $conn->real_escape_string( (string)$s );
292 }
293
294 /**
295 * @return mysqli
296 */
297 protected function getBindingHandle() {
298 return parent::getBindingHandle();
299 }
300 }
301
302 /**
303 * @deprecated since 1.29
304 */
305 class_alias( DatabaseMysqli::class, 'DatabaseMysqli' );