MediaWiki  master
DatabaseMysqli.php
Go to the documentation of this file.
1 <?php
20 namespace Wikimedia\Rdbms;
21 
22 use mysqli;
23 use mysqli_result;
24 use Wikimedia\AtEase\AtEase;
25 use 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  $conn = $this->getBindingHandle();
192 
193  return $conn->close();
194  }
195 
196  public function insertId() {
197  $conn = $this->getBindingHandle();
198 
199  return (int)$conn->insert_id;
200  }
201 
205  public function lastErrno() {
206  if ( $this->conn instanceof mysqli ) {
207  return $this->conn->errno;
208  } else {
209  return mysqli_connect_errno();
210  }
211  }
212 
213  protected function fetchAffectedRowCount() {
214  $conn = $this->getBindingHandle();
215 
216  return $conn->affected_rows;
217  }
218 
223  protected function mysqlError( $conn = null ) {
224  if ( $conn === null ) {
225  return (string)mysqli_connect_error();
226  } else {
227  return $conn->error;
228  }
229  }
230 
231  protected function mysqlRealEscapeString( $s ) {
232  $conn = $this->getBindingHandle();
233 
234  return $conn->real_escape_string( (string)$s );
235  }
236 
240  protected function getBindingHandle() {
241  return parent::getBindingHandle();
242  }
243 }
244 
248 class_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.
Definition: Database.php:2853
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
const DBO_COMPRESS
Definition: defines.php:19
const DBO_PERSISTENT
Definition: defines.php:14