MediaWiki REL1_40
DatabaseMysqli.php
Go to the documentation of this file.
1<?php
20namespace Wikimedia\Rdbms;
21
22use mysqli;
23use mysqli_result;
24use Wikimedia\AtEase\AtEase;
25use Wikimedia\IPUtils;
26
40 protected function doSingleStatementQuery( string $sql ): QueryStatus {
41 // Hide packet warnings caused by things like dropped connections
42 AtEase::suppressWarnings();
43 $res = $this->getBindingHandle()->query( $sql );
44 AtEase::restoreWarnings();
45
46 return new QueryStatus(
47 $res instanceof mysqli_result ? new MysqliResultWrapper( $this, $res ) : $res,
48 $this->affectedRows(),
49 $this->lastError(),
50 $this->lastErrno()
51 );
52 }
53
54 protected function doMultiStatementQuery( array $sqls ): array {
55 $qsByStatementId = [];
56
57 $conn = $this->getBindingHandle();
58 // Clear any previously left over result
59 while ( $conn->more_results() && $conn->next_result() ) {
60 $mysqliResult = $conn->store_result();
61 $mysqliResult->free();
62 }
63
64 $combinedSql = implode( ";\n", $sqls );
65 // Hide packet warnings caused by things like dropped connections
66 AtEase::suppressWarnings();
67 $conn->multi_query( $combinedSql );
68 AtEase::restoreWarnings();
69
70 reset( $sqls );
71 $done = false;
72 do {
73 $mysqliResult = $conn->store_result();
74 $statementId = key( $sqls );
75 if ( $statementId !== null ) {
76 // Database uses "true" for successful queries without result sets
77 if ( $mysqliResult === false ) {
78 $res = ( $conn->errno === 0 );
79 } elseif ( $mysqliResult instanceof mysqli_result ) {
80 $res = new MysqliResultWrapper( $this, $mysqliResult );
81 } else {
82 $res = $mysqliResult;
83 }
84 $qsByStatementId[$statementId] = new QueryStatus(
85 $res,
86 $conn->affected_rows,
87 $conn->error,
88 $conn->errno
89 );
90 next( $sqls );
91 }
92 if ( $conn->more_results() ) {
93 $conn->next_result();
94 } else {
95 $done = true;
96 }
97 } while ( !$done );
98 // Fill in status for statements aborted due to prior statement failure
99 while ( ( $statementId = key( $sqls ) ) !== null ) {
100 $qsByStatementId[$statementId] = new QueryStatus( false, 0, 'Query aborted', 0 );
101 next( $sqls );
102 }
103
104 return $qsByStatementId;
105 }
106
115 protected function mysqlConnect( $server, $user, $password, $db ) {
116 if ( !function_exists( 'mysqli_init' ) ) {
117 throw $this->newExceptionAfterConnectError(
118 "MySQLi functions missing, have you compiled PHP with the --with-mysqli option?"
119 );
120 }
121
122 // PHP 8.1.0+ throws exceptions by default. Turn that off for consistency.
123 mysqli_report( MYSQLI_REPORT_OFF );
124
125 // Other than mysql_connect, mysqli_real_connect expects an explicit port number
126 // e.g. "localhost:1234" or "127.0.0.1:1234"
127 // or Unix domain socket path
128 // e.g. "localhost:/socket_path" or "localhost:/foo/bar:bar:bar"
129 // colons are known to be used by Google AppEngine,
130 // see <https://cloud.google.com/sql/docs/mysql/connect-app-engine>
131 //
132 // We need to parse the port or socket path out of $realServer
133 $port = null;
134 $socket = null;
135 $hostAndPort = IPUtils::splitHostAndPort( $server );
136 if ( $hostAndPort ) {
137 $realServer = $hostAndPort[0];
138 if ( $hostAndPort[1] ) {
139 $port = $hostAndPort[1];
140 }
141 } elseif ( substr_count( $server, ':/' ) == 1 ) {
142 // If we have a colon slash instead of a colon and a port number
143 // after the ip or hostname, assume it's the Unix domain socket path
144 [ $realServer, $socket ] = explode( ':', $server, 2 );
145 } else {
146 $realServer = $server;
147 }
148
149 $mysqli = mysqli_init();
150 // Make affectedRows() for UPDATE reflect the number of matching rows, regardless
151 // of whether any column values changed. This is what callers want to know and is
152 // consistent with what Postgres and SQLite return.
153 $flags = MYSQLI_CLIENT_FOUND_ROWS;
154 if ( $this->ssl ) {
155 $flags |= MYSQLI_CLIENT_SSL;
156 $mysqli->ssl_set(
157 $this->sslKeyPath,
158 $this->sslCertPath,
159 $this->sslCAFile,
160 $this->sslCAPath,
161 $this->sslCiphers
162 );
163 }
164 if ( $this->getFlag( self::DBO_COMPRESS ) ) {
165 $flags |= MYSQLI_CLIENT_COMPRESS;
166 }
167 if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
168 $realServer = 'p:' . $realServer;
169 }
170
171 if ( $this->utf8Mode ) {
172 // Tell the server we're communicating with it in UTF-8.
173 // This may engage various charset conversions.
174 $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'utf8' );
175 } else {
176 $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'binary' );
177 }
178
179 $mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, $this->connectTimeout ?: 3 );
180 if ( $this->receiveTimeout ) {
181 $mysqli->options( MYSQLI_OPT_READ_TIMEOUT, $this->receiveTimeout );
182 }
183
184 // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal socket seems set when used
185 $ok = $mysqli->real_connect( $realServer, $user, $password, $db, $port, $socket, $flags );
186
187 return $ok ? $mysqli : null;
188 }
189
190 protected function closeConnection() {
191 return ( $this->conn instanceof mysqli ) ? mysqli_close( $this->conn ) : true;
192 }
193
194 public function insertId() {
195 $conn = $this->getBindingHandle();
196
197 return (int)$conn->insert_id;
198 }
199
203 public function lastErrno() {
204 if ( $this->conn instanceof mysqli ) {
205 return $this->conn->errno;
206 } else {
207 return mysqli_connect_errno();
208 }
209 }
210
211 protected function fetchAffectedRowCount() {
212 $conn = $this->getBindingHandle();
213
214 return $conn->affected_rows;
215 }
216
221 protected function mysqlError( $conn = null ) {
222 if ( $conn === null ) {
223 return (string)mysqli_connect_error();
224 } else {
225 return $conn->error;
226 }
227 }
228
229 protected function mysqlRealEscapeString( $s ) {
230 $conn = $this->getBindingHandle();
231
232 return $conn->real_escape_string( (string)$s );
233 }
234
238 protected function getBindingHandle() {
239 return parent::getBindingHandle();
240 }
241}
242
246class_alias( DatabaseMysqli::class, 'DatabaseMysqli' );
MySQL database abstraction layer.
Database abstraction object for PHP extension mysqli.
closeConnection()
Closes underlying database connection.
doMultiStatementQuery(array $sqls)
Execute a batch of query statements, aborting remaining statements if one fails.
mysqlRealEscapeString( $s)
Escape special characters in a string for use in an SQL statement.
mysqlConnect( $server, $user, $password, $db)
doSingleStatementQuery(string $sql)
Run a query and return a QueryStatus instance with the query result information.
insertId()
Get the inserted value of an auto-increment row.
affectedRows()
Get the number of rows affected by the last attempted query statement.