These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * This is the MySQL 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 | |||
24 | /** |
||
25 | * Database abstraction object for MySQL. |
||
26 | * Defines methods independent on used MySQL extension. |
||
27 | * |
||
28 | * @ingroup Database |
||
29 | * @since 1.22 |
||
30 | * @see Database |
||
31 | */ |
||
32 | abstract class DatabaseMysqlBase extends Database { |
||
33 | /** @var MysqlMasterPos */ |
||
34 | protected $lastKnownReplicaPos; |
||
35 | /** @var string Method to detect replica DB lag */ |
||
36 | protected $lagDetectionMethod; |
||
37 | /** @var array Method to detect replica DB lag */ |
||
38 | protected $lagDetectionOptions = []; |
||
39 | /** @var bool bool Whether to use GTID methods */ |
||
40 | protected $useGTIDs = false; |
||
41 | /** @var string|null */ |
||
42 | protected $sslKeyPath; |
||
43 | /** @var string|null */ |
||
44 | protected $sslCertPath; |
||
45 | /** @var string|null */ |
||
46 | protected $sslCAPath; |
||
47 | /** @var string[]|null */ |
||
48 | protected $sslCiphers; |
||
49 | /** @var string sql_mode value to send on connection */ |
||
50 | protected $sqlMode; |
||
51 | /** @var bool Use experimental UTF-8 transmission encoding */ |
||
52 | protected $utf8Mode; |
||
53 | |||
54 | /** @var string|null */ |
||
55 | private $serverVersion = null; |
||
56 | |||
57 | /** |
||
58 | * Additional $params include: |
||
59 | * - lagDetectionMethod : set to one of (Seconds_Behind_Master,pt-heartbeat). |
||
60 | * pt-heartbeat assumes the table is at heartbeat.heartbeat |
||
61 | * and uses UTC timestamps in the heartbeat.ts column. |
||
62 | * (https://www.percona.com/doc/percona-toolkit/2.2/pt-heartbeat.html) |
||
63 | * - lagDetectionOptions : if using pt-heartbeat, this can be set to an array map to change |
||
64 | * the default behavior. Normally, the heartbeat row with the server |
||
65 | * ID of this server's master will be used. Set the "conds" field to |
||
66 | * override the query conditions, e.g. ['shard' => 's1']. |
||
67 | * - useGTIDs : use GTID methods like MASTER_GTID_WAIT() when possible. |
||
68 | * - sslKeyPath : path to key file [default: null] |
||
69 | * - sslCertPath : path to certificate file [default: null] |
||
70 | * - sslCAPath : parth to certificate authority PEM files [default: null] |
||
71 | * - sslCiphers : array list of allowable ciphers [default: null] |
||
72 | * @param array $params |
||
73 | */ |
||
74 | function __construct( array $params ) { |
||
75 | $this->lagDetectionMethod = isset( $params['lagDetectionMethod'] ) |
||
76 | ? $params['lagDetectionMethod'] |
||
77 | : 'Seconds_Behind_Master'; |
||
78 | $this->lagDetectionOptions = isset( $params['lagDetectionOptions'] ) |
||
79 | ? $params['lagDetectionOptions'] |
||
80 | : []; |
||
81 | $this->useGTIDs = !empty( $params['useGTIDs' ] ); |
||
82 | foreach ( [ 'KeyPath', 'CertPath', 'CAPath', 'Ciphers' ] as $name ) { |
||
83 | $var = "ssl{$name}"; |
||
84 | if ( isset( $params[$var] ) ) { |
||
85 | $this->$var = $params[$var]; |
||
86 | } |
||
87 | } |
||
88 | $this->sqlMode = isset( $params['sqlMode'] ) ? $params['sqlMode'] : ''; |
||
89 | $this->utf8Mode = !empty( $params['utf8Mode'] ); |
||
90 | |||
91 | parent::__construct( $params ); |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * @return string |
||
96 | */ |
||
97 | public function getType() { |
||
98 | return 'mysql'; |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * @param string $server |
||
103 | * @param string $user |
||
104 | * @param string $password |
||
105 | * @param string $dbName |
||
106 | * @throws Exception|DBConnectionError |
||
107 | * @return bool |
||
108 | */ |
||
109 | public function open( $server, $user, $password, $dbName ) { |
||
110 | # Close/unset connection handle |
||
111 | $this->close(); |
||
112 | |||
113 | $this->mServer = $server; |
||
114 | $this->mUser = $user; |
||
115 | $this->mPassword = $password; |
||
116 | $this->mDBname = $dbName; |
||
117 | |||
118 | $this->installErrorHandler(); |
||
119 | try { |
||
120 | $this->mConn = $this->mysqlConnect( $this->mServer ); |
||
121 | } catch ( Exception $ex ) { |
||
122 | $this->restoreErrorHandler(); |
||
123 | throw $ex; |
||
124 | } |
||
125 | $error = $this->restoreErrorHandler(); |
||
126 | |||
127 | # Always log connection errors |
||
128 | if ( !$this->mConn ) { |
||
129 | if ( !$error ) { |
||
0 ignored issues
–
show
|
|||
130 | $error = $this->lastError(); |
||
131 | } |
||
132 | $this->connLogger->error( |
||
133 | "Error connecting to {db_server}: {error}", |
||
134 | $this->getLogContext( [ |
||
135 | 'method' => __METHOD__, |
||
136 | 'error' => $error, |
||
137 | ] ) |
||
138 | ); |
||
139 | $this->connLogger->debug( "DB connection error\n" . |
||
140 | "Server: $server, User: $user, Password: " . |
||
141 | substr( $password, 0, 3 ) . "..., error: " . $error . "\n" ); |
||
142 | |||
143 | $this->reportConnectionError( $error ); |
||
144 | } |
||
145 | |||
146 | if ( $dbName != '' ) { |
||
147 | MediaWiki\suppressWarnings(); |
||
148 | $success = $this->selectDB( $dbName ); |
||
149 | MediaWiki\restoreWarnings(); |
||
150 | if ( !$success ) { |
||
151 | $this->queryLogger->error( |
||
152 | "Error selecting database {db_name} on server {db_server}", |
||
153 | $this->getLogContext( [ |
||
154 | 'method' => __METHOD__, |
||
155 | ] ) |
||
156 | ); |
||
157 | $this->queryLogger->debug( |
||
158 | "Error selecting database $dbName on server {$this->mServer}" ); |
||
159 | |||
160 | $this->reportConnectionError( "Error selecting database $dbName" ); |
||
161 | } |
||
162 | } |
||
163 | |||
164 | // Tell the server what we're communicating with |
||
165 | if ( !$this->connectInitCharset() ) { |
||
166 | $this->reportConnectionError( "Error setting character set" ); |
||
167 | } |
||
168 | |||
169 | // Abstract over any insane MySQL defaults |
||
170 | $set = [ 'group_concat_max_len = 262144' ]; |
||
171 | // Set SQL mode, default is turning them all off, can be overridden or skipped with null |
||
172 | if ( is_string( $this->sqlMode ) ) { |
||
173 | $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode ); |
||
174 | } |
||
175 | // Set any custom settings defined by site config |
||
176 | // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html) |
||
177 | foreach ( $this->mSessionVars as $var => $val ) { |
||
178 | // Escape strings but not numbers to avoid MySQL complaining |
||
179 | if ( !is_int( $val ) && !is_float( $val ) ) { |
||
180 | $val = $this->addQuotes( $val ); |
||
181 | } |
||
182 | $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val; |
||
183 | } |
||
184 | |||
185 | if ( $set ) { |
||
186 | // Use doQuery() to avoid opening implicit transactions (DBO_TRX) |
||
187 | $success = $this->doQuery( 'SET ' . implode( ', ', $set ) ); |
||
188 | if ( !$success ) { |
||
189 | $this->queryLogger->error( |
||
190 | 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)', |
||
191 | $this->getLogContext( [ |
||
192 | 'method' => __METHOD__, |
||
193 | ] ) |
||
194 | ); |
||
195 | $this->reportConnectionError( |
||
196 | 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)' ); |
||
197 | } |
||
198 | } |
||
199 | |||
200 | $this->mOpened = true; |
||
201 | |||
202 | return true; |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * Set the character set information right after connection |
||
207 | * @return bool |
||
208 | */ |
||
209 | protected function connectInitCharset() { |
||
210 | if ( $this->utf8Mode ) { |
||
211 | // Tell the server we're communicating with it in UTF-8. |
||
212 | // This may engage various charset conversions. |
||
213 | return $this->mysqlSetCharset( 'utf8' ); |
||
214 | } else { |
||
215 | return $this->mysqlSetCharset( 'binary' ); |
||
216 | } |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * Open a connection to a MySQL server |
||
221 | * |
||
222 | * @param string $realServer |
||
223 | * @return mixed Raw connection |
||
224 | * @throws DBConnectionError |
||
225 | */ |
||
226 | abstract protected function mysqlConnect( $realServer ); |
||
227 | |||
228 | /** |
||
229 | * Set the character set of the MySQL link |
||
230 | * |
||
231 | * @param string $charset |
||
232 | * @return bool |
||
233 | */ |
||
234 | abstract protected function mysqlSetCharset( $charset ); |
||
235 | |||
236 | /** |
||
237 | * @param ResultWrapper|resource $res |
||
238 | * @throws DBUnexpectedError |
||
239 | */ |
||
240 | View Code Duplication | public function freeResult( $res ) { |
|
241 | if ( $res instanceof ResultWrapper ) { |
||
242 | $res = $res->result; |
||
243 | } |
||
244 | MediaWiki\suppressWarnings(); |
||
245 | $ok = $this->mysqlFreeResult( $res ); |
||
0 ignored issues
–
show
It seems like
$res can also be of type array or null ; however, DatabaseMysqlBase::mysqlFreeResult() does only seem to accept resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
246 | MediaWiki\restoreWarnings(); |
||
247 | if ( !$ok ) { |
||
248 | throw new DBUnexpectedError( $this, "Unable to free MySQL result" ); |
||
249 | } |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Free result memory |
||
254 | * |
||
255 | * @param resource $res Raw result |
||
256 | * @return bool |
||
257 | */ |
||
258 | abstract protected function mysqlFreeResult( $res ); |
||
259 | |||
260 | /** |
||
261 | * @param ResultWrapper|resource $res |
||
262 | * @return stdClass|bool |
||
263 | * @throws DBUnexpectedError |
||
264 | */ |
||
265 | View Code Duplication | public function fetchObject( $res ) { |
|
266 | if ( $res instanceof ResultWrapper ) { |
||
267 | $res = $res->result; |
||
268 | } |
||
269 | MediaWiki\suppressWarnings(); |
||
270 | $row = $this->mysqlFetchObject( $res ); |
||
0 ignored issues
–
show
It seems like
$res can also be of type array or null ; however, DatabaseMysqlBase::mysqlFetchObject() does only seem to accept resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
271 | MediaWiki\restoreWarnings(); |
||
272 | |||
273 | $errno = $this->lastErrno(); |
||
274 | // Unfortunately, mysql_fetch_object does not reset the last errno. |
||
275 | // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as |
||
276 | // these are the only errors mysql_fetch_object can cause. |
||
277 | // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html. |
||
278 | if ( $errno == 2000 || $errno == 2013 ) { |
||
279 | throw new DBUnexpectedError( |
||
280 | $this, |
||
281 | 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) |
||
282 | ); |
||
283 | } |
||
284 | |||
285 | return $row; |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * Fetch a result row as an object |
||
290 | * |
||
291 | * @param resource $res Raw result |
||
292 | * @return stdClass |
||
293 | */ |
||
294 | abstract protected function mysqlFetchObject( $res ); |
||
295 | |||
296 | /** |
||
297 | * @param ResultWrapper|resource $res |
||
298 | * @return array|bool |
||
299 | * @throws DBUnexpectedError |
||
300 | */ |
||
301 | View Code Duplication | public function fetchRow( $res ) { |
|
302 | if ( $res instanceof ResultWrapper ) { |
||
303 | $res = $res->result; |
||
304 | } |
||
305 | MediaWiki\suppressWarnings(); |
||
306 | $row = $this->mysqlFetchArray( $res ); |
||
0 ignored issues
–
show
It seems like
$res can also be of type array or null ; however, DatabaseMysqlBase::mysqlFetchArray() does only seem to accept resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
307 | MediaWiki\restoreWarnings(); |
||
308 | |||
309 | $errno = $this->lastErrno(); |
||
310 | // Unfortunately, mysql_fetch_array does not reset the last errno. |
||
311 | // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as |
||
312 | // these are the only errors mysql_fetch_array can cause. |
||
313 | // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html. |
||
314 | if ( $errno == 2000 || $errno == 2013 ) { |
||
315 | throw new DBUnexpectedError( |
||
316 | $this, |
||
317 | 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) |
||
318 | ); |
||
319 | } |
||
320 | |||
321 | return $row; |
||
322 | } |
||
323 | |||
324 | /** |
||
325 | * Fetch a result row as an associative and numeric array |
||
326 | * |
||
327 | * @param resource $res Raw result |
||
328 | * @return array |
||
329 | */ |
||
330 | abstract protected function mysqlFetchArray( $res ); |
||
331 | |||
332 | /** |
||
333 | * @throws DBUnexpectedError |
||
334 | * @param ResultWrapper|resource $res |
||
335 | * @return int |
||
336 | */ |
||
337 | function numRows( $res ) { |
||
338 | if ( $res instanceof ResultWrapper ) { |
||
339 | $res = $res->result; |
||
340 | } |
||
341 | MediaWiki\suppressWarnings(); |
||
342 | $n = $this->mysqlNumRows( $res ); |
||
0 ignored issues
–
show
It seems like
$res can also be of type array or null ; however, DatabaseMysqlBase::mysqlNumRows() does only seem to accept resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
343 | MediaWiki\restoreWarnings(); |
||
344 | |||
345 | // Unfortunately, mysql_num_rows does not reset the last errno. |
||
346 | // We are not checking for any errors here, since |
||
347 | // these are no errors mysql_num_rows can cause. |
||
348 | // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html. |
||
349 | // See https://phabricator.wikimedia.org/T44430 |
||
350 | return $n; |
||
351 | } |
||
352 | |||
353 | /** |
||
354 | * Get number of rows in result |
||
355 | * |
||
356 | * @param resource $res Raw result |
||
357 | * @return int |
||
358 | */ |
||
359 | abstract protected function mysqlNumRows( $res ); |
||
360 | |||
361 | /** |
||
362 | * @param ResultWrapper|resource $res |
||
363 | * @return int |
||
364 | */ |
||
365 | public function numFields( $res ) { |
||
366 | if ( $res instanceof ResultWrapper ) { |
||
367 | $res = $res->result; |
||
368 | } |
||
369 | |||
370 | return $this->mysqlNumFields( $res ); |
||
0 ignored issues
–
show
It seems like
$res can also be of type array or null ; however, DatabaseMysqlBase::mysqlNumFields() does only seem to accept resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
371 | } |
||
372 | |||
373 | /** |
||
374 | * Get number of fields in result |
||
375 | * |
||
376 | * @param resource $res Raw result |
||
377 | * @return int |
||
378 | */ |
||
379 | abstract protected function mysqlNumFields( $res ); |
||
380 | |||
381 | /** |
||
382 | * @param ResultWrapper|resource $res |
||
383 | * @param int $n |
||
384 | * @return string |
||
385 | */ |
||
386 | public function fieldName( $res, $n ) { |
||
387 | if ( $res instanceof ResultWrapper ) { |
||
388 | $res = $res->result; |
||
389 | } |
||
390 | |||
391 | return $this->mysqlFieldName( $res, $n ); |
||
0 ignored issues
–
show
It seems like
$res defined by $res->result on line 388 can also be of type array or null ; however, DatabaseMysqlBase::mysqlFieldName() does only seem to accept object<ResultWrapper>|resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
392 | } |
||
393 | |||
394 | /** |
||
395 | * Get the name of the specified field in a result |
||
396 | * |
||
397 | * @param ResultWrapper|resource $res |
||
398 | * @param int $n |
||
399 | * @return string |
||
400 | */ |
||
401 | abstract protected function mysqlFieldName( $res, $n ); |
||
402 | |||
403 | /** |
||
404 | * mysql_field_type() wrapper |
||
405 | * @param ResultWrapper|resource $res |
||
406 | * @param int $n |
||
407 | * @return string |
||
408 | */ |
||
409 | public function fieldType( $res, $n ) { |
||
410 | if ( $res instanceof ResultWrapper ) { |
||
411 | $res = $res->result; |
||
412 | } |
||
413 | |||
414 | return $this->mysqlFieldType( $res, $n ); |
||
415 | } |
||
416 | |||
417 | /** |
||
418 | * Get the type of the specified field in a result |
||
419 | * |
||
420 | * @param ResultWrapper|resource $res |
||
421 | * @param int $n |
||
422 | * @return string |
||
423 | */ |
||
424 | abstract protected function mysqlFieldType( $res, $n ); |
||
425 | |||
426 | /** |
||
427 | * @param ResultWrapper|resource $res |
||
428 | * @param int $row |
||
429 | * @return bool |
||
430 | */ |
||
431 | public function dataSeek( $res, $row ) { |
||
432 | if ( $res instanceof ResultWrapper ) { |
||
433 | $res = $res->result; |
||
434 | } |
||
435 | |||
436 | return $this->mysqlDataSeek( $res, $row ); |
||
0 ignored issues
–
show
It seems like
$res defined by $res->result on line 433 can also be of type array or null ; however, DatabaseMysqlBase::mysqlDataSeek() does only seem to accept object<ResultWrapper>|resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Move internal result pointer |
||
441 | * |
||
442 | * @param ResultWrapper|resource $res |
||
443 | * @param int $row |
||
444 | * @return bool |
||
445 | */ |
||
446 | abstract protected function mysqlDataSeek( $res, $row ); |
||
447 | |||
448 | /** |
||
449 | * @return string |
||
450 | */ |
||
451 | public function lastError() { |
||
452 | if ( $this->mConn ) { |
||
453 | # Even if it's non-zero, it can still be invalid |
||
454 | MediaWiki\suppressWarnings(); |
||
455 | $error = $this->mysqlError( $this->mConn ); |
||
456 | if ( !$error ) { |
||
457 | $error = $this->mysqlError(); |
||
458 | } |
||
459 | MediaWiki\restoreWarnings(); |
||
460 | } else { |
||
461 | $error = $this->mysqlError(); |
||
462 | } |
||
463 | if ( $error ) { |
||
464 | $error .= ' (' . $this->mServer . ')'; |
||
465 | } |
||
466 | |||
467 | return $error; |
||
468 | } |
||
469 | |||
470 | /** |
||
471 | * Returns the text of the error message from previous MySQL operation |
||
472 | * |
||
473 | * @param resource $conn Raw connection |
||
474 | * @return string |
||
475 | */ |
||
476 | abstract protected function mysqlError( $conn = null ); |
||
477 | |||
478 | /** |
||
479 | * @param string $table |
||
480 | * @param array $uniqueIndexes |
||
481 | * @param array $rows |
||
482 | * @param string $fname |
||
483 | * @return ResultWrapper |
||
484 | */ |
||
485 | public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) { |
||
486 | return $this->nativeReplace( $table, $rows, $fname ); |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * Estimate rows in dataset |
||
491 | * Returns estimated count, based on EXPLAIN output |
||
492 | * Takes same arguments as Database::select() |
||
493 | * |
||
494 | * @param string|array $table |
||
495 | * @param string|array $vars |
||
496 | * @param string|array $conds |
||
497 | * @param string $fname |
||
498 | * @param string|array $options |
||
499 | * @return bool|int |
||
500 | */ |
||
501 | public function estimateRowCount( $table, $vars = '*', $conds = '', |
||
502 | $fname = __METHOD__, $options = [] |
||
503 | ) { |
||
504 | $options['EXPLAIN'] = true; |
||
505 | $res = $this->select( $table, $vars, $conds, $fname, $options ); |
||
0 ignored issues
–
show
It seems like
$conds defined by parameter $conds on line 501 can also be of type array ; however, Database::select() does only seem to accept string , maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble.
Loading history...
It seems like
$options defined by parameter $options on line 502 can also be of type string ; however, Database::select() does only seem to accept array , maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble.
Loading history...
|
|||
506 | if ( $res === false ) { |
||
507 | return false; |
||
508 | } |
||
509 | if ( !$this->numRows( $res ) ) { |
||
0 ignored issues
–
show
It seems like
$res defined by $this->select($table, $v...onds, $fname, $options) on line 505 can also be of type boolean ; however, DatabaseMysqlBase::numRows() does only seem to accept object<ResultWrapper>|resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
510 | return 0; |
||
511 | } |
||
512 | |||
513 | $rows = 1; |
||
514 | foreach ( $res as $plan ) { |
||
0 ignored issues
–
show
The expression
$res of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
515 | $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero |
||
516 | } |
||
517 | |||
518 | return (int)$rows; |
||
519 | } |
||
520 | |||
521 | public function tableExists( $table, $fname = __METHOD__ ) { |
||
522 | $table = $this->tableName( $table, 'raw' ); |
||
523 | if ( isset( $this->mSessionTempTables[$table] ) ) { |
||
524 | return true; // already known to exist and won't show in SHOW TABLES anyway |
||
525 | } |
||
526 | |||
527 | $encLike = $this->buildLike( $table ); |
||
528 | |||
529 | return $this->query( "SHOW TABLES $encLike", $fname )->numRows() > 0; |
||
530 | } |
||
531 | |||
532 | /** |
||
533 | * @param string $table |
||
534 | * @param string $field |
||
535 | * @return bool|MySQLField |
||
536 | */ |
||
537 | public function fieldInfo( $table, $field ) { |
||
538 | $table = $this->tableName( $table ); |
||
539 | $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true ); |
||
540 | if ( !$res ) { |
||
541 | return false; |
||
542 | } |
||
543 | $n = $this->mysqlNumFields( $res->result ); |
||
544 | for ( $i = 0; $i < $n; $i++ ) { |
||
545 | $meta = $this->mysqlFetchField( $res->result, $i ); |
||
546 | if ( $field == $meta->name ) { |
||
547 | return new MySQLField( $meta ); |
||
548 | } |
||
549 | } |
||
550 | |||
551 | return false; |
||
552 | } |
||
553 | |||
554 | /** |
||
555 | * Get column information from a result |
||
556 | * |
||
557 | * @param resource $res Raw result |
||
558 | * @param int $n |
||
559 | * @return stdClass |
||
560 | */ |
||
561 | abstract protected function mysqlFetchField( $res, $n ); |
||
562 | |||
563 | /** |
||
564 | * Get information about an index into an object |
||
565 | * Returns false if the index does not exist |
||
566 | * |
||
567 | * @param string $table |
||
568 | * @param string $index |
||
569 | * @param string $fname |
||
570 | * @return bool|array|null False or null on failure |
||
571 | */ |
||
572 | public function indexInfo( $table, $index, $fname = __METHOD__ ) { |
||
573 | # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not. |
||
574 | # SHOW INDEX should work for 3.x and up: |
||
575 | # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html |
||
576 | $table = $this->tableName( $table ); |
||
577 | $index = $this->indexName( $index ); |
||
578 | |||
579 | $sql = 'SHOW INDEX FROM ' . $table; |
||
580 | $res = $this->query( $sql, $fname ); |
||
581 | |||
582 | if ( !$res ) { |
||
583 | return null; |
||
584 | } |
||
585 | |||
586 | $result = []; |
||
587 | |||
588 | foreach ( $res as $row ) { |
||
0 ignored issues
–
show
The expression
$res of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
589 | if ( $row->Key_name == $index ) { |
||
590 | $result[] = $row; |
||
591 | } |
||
592 | } |
||
593 | |||
594 | return empty( $result ) ? false : $result; |
||
595 | } |
||
596 | |||
597 | /** |
||
598 | * @param string $s |
||
599 | * @return string |
||
600 | */ |
||
601 | public function strencode( $s ) { |
||
602 | return $this->mysqlRealEscapeString( $s ); |
||
603 | } |
||
604 | |||
605 | /** |
||
606 | * @param string $s |
||
607 | * @return mixed |
||
608 | */ |
||
609 | abstract protected function mysqlRealEscapeString( $s ); |
||
610 | |||
611 | public function addQuotes( $s ) { |
||
612 | if ( is_bool( $s ) ) { |
||
613 | // Parent would transform to int, which does not play nice with MySQL type juggling. |
||
614 | // When searching for an int in a string column, the strings are cast to int, which |
||
615 | // means false would match any string not starting with a number. |
||
616 | $s = (string)(int)$s; |
||
617 | } |
||
618 | return parent::addQuotes( $s ); |
||
619 | } |
||
620 | |||
621 | /** |
||
622 | * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes". |
||
623 | * |
||
624 | * @param string $s |
||
625 | * @return string |
||
626 | */ |
||
627 | public function addIdentifierQuotes( $s ) { |
||
628 | // Characters in the range \u0001-\uFFFF are valid in a quoted identifier |
||
629 | // Remove NUL bytes and escape backticks by doubling |
||
630 | return '`' . str_replace( [ "\0", '`' ], [ '', '``' ], $s ) . '`'; |
||
631 | } |
||
632 | |||
633 | /** |
||
634 | * @param string $name |
||
635 | * @return bool |
||
636 | */ |
||
637 | public function isQuotedIdentifier( $name ) { |
||
638 | return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`'; |
||
639 | } |
||
640 | |||
641 | public function getLag() { |
||
642 | if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) { |
||
643 | return $this->getLagFromPtHeartbeat(); |
||
644 | } else { |
||
645 | return $this->getLagFromSlaveStatus(); |
||
646 | } |
||
647 | } |
||
648 | |||
649 | /** |
||
650 | * @return string |
||
651 | */ |
||
652 | protected function getLagDetectionMethod() { |
||
653 | return $this->lagDetectionMethod; |
||
654 | } |
||
655 | |||
656 | /** |
||
657 | * @return bool|int |
||
658 | */ |
||
659 | protected function getLagFromSlaveStatus() { |
||
660 | $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ ); |
||
661 | $row = $res ? $res->fetchObject() : false; |
||
662 | if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) { |
||
663 | return intval( $row->Seconds_Behind_Master ); |
||
664 | } |
||
665 | |||
666 | return false; |
||
667 | } |
||
668 | |||
669 | /** |
||
670 | * @return bool|float |
||
671 | */ |
||
672 | protected function getLagFromPtHeartbeat() { |
||
673 | $options = $this->lagDetectionOptions; |
||
674 | |||
675 | if ( isset( $options['conds'] ) ) { |
||
676 | // Best method for multi-DC setups: use logical channel names |
||
677 | $data = $this->getHeartbeatData( $options['conds'] ); |
||
678 | } else { |
||
679 | // Standard method: use master server ID (works with stock pt-heartbeat) |
||
680 | $masterInfo = $this->getMasterServerInfo(); |
||
681 | if ( !$masterInfo ) { |
||
682 | $this->queryLogger->error( |
||
683 | "Unable to query master of {db_server} for server ID", |
||
684 | $this->getLogContext( [ |
||
685 | 'method' => __METHOD__ |
||
686 | ] ) |
||
687 | ); |
||
688 | |||
689 | return false; // could not get master server ID |
||
690 | } |
||
691 | |||
692 | $conds = [ 'server_id' => intval( $masterInfo['serverId'] ) ]; |
||
693 | $data = $this->getHeartbeatData( $conds ); |
||
694 | } |
||
695 | |||
696 | list( $time, $nowUnix ) = $data; |
||
697 | if ( $time !== null ) { |
||
698 | // @time is in ISO format like "2015-09-25T16:48:10.000510" |
||
699 | $dateTime = new DateTime( $time, new DateTimeZone( 'UTC' ) ); |
||
700 | $timeUnix = (int)$dateTime->format( 'U' ) + $dateTime->format( 'u' ) / 1e6; |
||
701 | |||
702 | return max( $nowUnix - $timeUnix, 0.0 ); |
||
703 | } |
||
704 | |||
705 | $this->queryLogger->error( |
||
706 | "Unable to find pt-heartbeat row for {db_server}", |
||
707 | $this->getLogContext( [ |
||
708 | 'method' => __METHOD__ |
||
709 | ] ) |
||
710 | ); |
||
711 | |||
712 | return false; |
||
713 | } |
||
714 | |||
715 | protected function getMasterServerInfo() { |
||
716 | $cache = $this->srvCache; |
||
717 | $key = $cache->makeGlobalKey( |
||
718 | 'mysql', |
||
719 | 'master-info', |
||
720 | // Using one key for all cluster replica DBs is preferable |
||
721 | $this->getLBInfo( 'clusterMasterHost' ) ?: $this->getServer() |
||
722 | ); |
||
723 | |||
724 | return $cache->getWithSetCallback( |
||
725 | $key, |
||
726 | $cache::TTL_INDEFINITE, |
||
727 | function () use ( $cache, $key ) { |
||
728 | // Get and leave a lock key in place for a short period |
||
729 | if ( !$cache->lock( $key, 0, 10 ) ) { |
||
730 | return false; // avoid master connection spike slams |
||
731 | } |
||
732 | |||
733 | $conn = $this->getLazyMasterHandle(); |
||
734 | if ( !$conn ) { |
||
735 | return false; // something is misconfigured |
||
736 | } |
||
737 | |||
738 | // Connect to and query the master; catch errors to avoid outages |
||
739 | try { |
||
740 | $res = $conn->query( 'SELECT @@server_id AS id', __METHOD__ ); |
||
741 | $row = $res ? $res->fetchObject() : false; |
||
742 | $id = $row ? (int)$row->id : 0; |
||
743 | } catch ( DBError $e ) { |
||
744 | $id = 0; |
||
745 | } |
||
746 | |||
747 | // Cache the ID if it was retrieved |
||
748 | return $id ? [ 'serverId' => $id, 'asOf' => time() ] : false; |
||
749 | } |
||
750 | ); |
||
751 | } |
||
752 | |||
753 | /** |
||
754 | * @param array $conds WHERE clause conditions to find a row |
||
755 | * @return array (heartbeat `ts` column value or null, UNIX timestamp) for the newest beat |
||
756 | * @see https://www.percona.com/doc/percona-toolkit/2.1/pt-heartbeat.html |
||
757 | */ |
||
758 | protected function getHeartbeatData( array $conds ) { |
||
759 | $whereSQL = $this->makeList( $conds, self::LIST_AND ); |
||
760 | // Use ORDER BY for channel based queries since that field might not be UNIQUE. |
||
761 | // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the |
||
762 | // percision field is not supported in MySQL <= 5.5. |
||
763 | $res = $this->query( |
||
764 | "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1" |
||
765 | ); |
||
766 | $row = $res ? $res->fetchObject() : false; |
||
767 | |||
768 | return [ $row ? $row->ts : null, microtime( true ) ]; |
||
769 | } |
||
770 | |||
771 | protected function getApproximateLagStatus() { |
||
772 | if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) { |
||
773 | // Disable caching since this is fast enough and we don't wan't |
||
774 | // to be *too* pessimistic by having both the cache TTL and the |
||
775 | // pt-heartbeat interval count as lag in getSessionLagStatus() |
||
776 | return parent::getApproximateLagStatus(); |
||
777 | } |
||
778 | |||
779 | $key = $this->srvCache->makeGlobalKey( 'mysql-lag', $this->getServer() ); |
||
780 | $approxLag = $this->srvCache->get( $key ); |
||
781 | if ( !$approxLag ) { |
||
782 | $approxLag = parent::getApproximateLagStatus(); |
||
783 | $this->srvCache->set( $key, $approxLag, 1 ); |
||
784 | } |
||
785 | |||
786 | return $approxLag; |
||
787 | } |
||
788 | |||
789 | public function masterPosWait( DBMasterPos $pos, $timeout ) { |
||
790 | if ( !( $pos instanceof MySQLMasterPos ) ) { |
||
791 | throw new InvalidArgumentException( "Position not an instance of MySQLMasterPos" ); |
||
792 | } |
||
793 | |||
794 | if ( $this->getLBInfo( 'is static' ) === true ) { |
||
795 | return 0; // this is a copy of a read-only dataset with no master DB |
||
796 | } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) { |
||
797 | return 0; // already reached this point for sure |
||
798 | } |
||
799 | |||
800 | // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set |
||
801 | if ( $this->useGTIDs && $pos->gtids ) { |
||
802 | // Wait on the GTID set (MariaDB only) |
||
803 | $gtidArg = $this->addQuotes( implode( ',', $pos->gtids ) ); |
||
804 | $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" ); |
||
805 | } else { |
||
806 | // Wait on the binlog coordinates |
||
807 | $encFile = $this->addQuotes( $pos->file ); |
||
808 | $encPos = intval( $pos->pos ); |
||
809 | $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" ); |
||
810 | } |
||
811 | |||
812 | $row = $res ? $this->fetchRow( $res ) : false; |
||
813 | if ( !$row ) { |
||
814 | throw new DBExpectedError( $this, "Failed to query MASTER_POS_WAIT()" ); |
||
815 | } |
||
816 | |||
817 | // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual |
||
818 | $status = ( $row[0] !== null ) ? intval( $row[0] ) : null; |
||
819 | if ( $status === null ) { |
||
820 | // T126436: jobs programmed to wait on master positions might be referencing binlogs |
||
821 | // with an old master hostname. Such calls make MASTER_POS_WAIT() return null. Try |
||
822 | // to detect this and treat the replica DB as having reached the position; a proper master |
||
823 | // switchover already requires that the new master be caught up before the switch. |
||
824 | $replicationPos = $this->getReplicaPos(); |
||
825 | if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) { |
||
826 | $this->lastKnownReplicaPos = $replicationPos; |
||
827 | $status = 0; |
||
828 | } |
||
829 | } elseif ( $status >= 0 ) { |
||
830 | // Remember that this position was reached to save queries next time |
||
831 | $this->lastKnownReplicaPos = $pos; |
||
832 | } |
||
833 | |||
834 | return $status; |
||
835 | } |
||
836 | |||
837 | /** |
||
838 | * Get the position of the master from SHOW SLAVE STATUS |
||
839 | * |
||
840 | * @return MySQLMasterPos|bool |
||
841 | */ |
||
842 | public function getReplicaPos() { |
||
843 | $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ ); |
||
844 | $row = $this->fetchObject( $res ); |
||
0 ignored issues
–
show
It seems like
$res defined by $this->query('SHOW SLAVE STATUS', __METHOD__) on line 843 can also be of type boolean ; however, DatabaseMysqlBase::fetchObject() does only seem to accept object<ResultWrapper>|resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
845 | |||
846 | if ( $row ) { |
||
847 | $pos = isset( $row->Exec_master_log_pos ) |
||
848 | ? $row->Exec_master_log_pos |
||
849 | : $row->Exec_Master_Log_Pos; |
||
850 | // Also fetch the last-applied GTID set (MariaDB) |
||
851 | View Code Duplication | if ( $this->useGTIDs ) { |
|
852 | $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_slave_pos'", __METHOD__ ); |
||
853 | $gtidRow = $this->fetchObject( $res ); |
||
854 | $gtidSet = $gtidRow ? $gtidRow->Value : ''; |
||
855 | } else { |
||
856 | $gtidSet = ''; |
||
857 | } |
||
858 | |||
859 | return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos, $gtidSet ); |
||
860 | } else { |
||
861 | return false; |
||
862 | } |
||
863 | } |
||
864 | |||
865 | /** |
||
866 | * Get the position of the master from SHOW MASTER STATUS |
||
867 | * |
||
868 | * @return MySQLMasterPos|bool |
||
869 | */ |
||
870 | public function getMasterPos() { |
||
871 | $res = $this->query( 'SHOW MASTER STATUS', __METHOD__ ); |
||
872 | $row = $this->fetchObject( $res ); |
||
0 ignored issues
–
show
It seems like
$res defined by $this->query('SHOW MASTER STATUS', __METHOD__) on line 871 can also be of type boolean ; however, DatabaseMysqlBase::fetchObject() does only seem to accept object<ResultWrapper>|resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
873 | |||
874 | if ( $row ) { |
||
875 | // Also fetch the last-written GTID set (MariaDB) |
||
876 | View Code Duplication | if ( $this->useGTIDs ) { |
|
877 | $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_pos'", __METHOD__ ); |
||
878 | $gtidRow = $this->fetchObject( $res ); |
||
879 | $gtidSet = $gtidRow ? $gtidRow->Value : ''; |
||
880 | } else { |
||
881 | $gtidSet = ''; |
||
882 | } |
||
883 | |||
884 | return new MySQLMasterPos( $row->File, $row->Position, $gtidSet ); |
||
885 | } else { |
||
886 | return false; |
||
887 | } |
||
888 | } |
||
889 | |||
890 | public function serverIsReadOnly() { |
||
891 | $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ ); |
||
892 | $row = $this->fetchObject( $res ); |
||
893 | |||
894 | return $row ? ( strtolower( $row->Value ) === 'on' ) : false; |
||
895 | } |
||
896 | |||
897 | /** |
||
898 | * @param string $index |
||
899 | * @return string |
||
900 | */ |
||
901 | function useIndexClause( $index ) { |
||
902 | return "FORCE INDEX (" . $this->indexName( $index ) . ")"; |
||
903 | } |
||
904 | |||
905 | /** |
||
906 | * @param string $index |
||
907 | * @return string |
||
908 | */ |
||
909 | function ignoreIndexClause( $index ) { |
||
910 | return "IGNORE INDEX (" . $this->indexName( $index ) . ")"; |
||
911 | } |
||
912 | |||
913 | /** |
||
914 | * @return string |
||
915 | */ |
||
916 | function lowPriorityOption() { |
||
917 | return 'LOW_PRIORITY'; |
||
918 | } |
||
919 | |||
920 | /** |
||
921 | * @return string |
||
922 | */ |
||
923 | public function getSoftwareLink() { |
||
924 | // MariaDB includes its name in its version string; this is how MariaDB's version of |
||
925 | // the mysql command-line client identifies MariaDB servers (see mariadb_connection() |
||
926 | // in libmysql/libmysql.c). |
||
927 | $version = $this->getServerVersion(); |
||
928 | if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) { |
||
929 | return '[{{int:version-db-mariadb-url}} MariaDB]'; |
||
930 | } |
||
931 | |||
932 | // Percona Server's version suffix is not very distinctive, and @@version_comment |
||
933 | // doesn't give the necessary info for source builds, so assume the server is MySQL. |
||
934 | // (Even Percona's version of mysql doesn't try to make the distinction.) |
||
935 | return '[{{int:version-db-mysql-url}} MySQL]'; |
||
936 | } |
||
937 | |||
938 | /** |
||
939 | * @return string |
||
940 | */ |
||
941 | public function getServerVersion() { |
||
942 | // Not using mysql_get_server_info() or similar for consistency: in the handshake, |
||
943 | // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip |
||
944 | // it off (see RPL_VERSION_HACK in include/mysql_com.h). |
||
945 | if ( $this->serverVersion === null ) { |
||
946 | $this->serverVersion = $this->selectField( '', 'VERSION()', '', __METHOD__ ); |
||
947 | } |
||
948 | return $this->serverVersion; |
||
949 | } |
||
950 | |||
951 | /** |
||
952 | * @param array $options |
||
953 | */ |
||
954 | public function setSessionOptions( array $options ) { |
||
955 | if ( isset( $options['connTimeout'] ) ) { |
||
956 | $timeout = (int)$options['connTimeout']; |
||
957 | $this->query( "SET net_read_timeout=$timeout" ); |
||
958 | $this->query( "SET net_write_timeout=$timeout" ); |
||
959 | } |
||
960 | } |
||
961 | |||
962 | /** |
||
963 | * @param string $sql |
||
964 | * @param string $newLine |
||
965 | * @return bool |
||
966 | */ |
||
967 | public function streamStatementEnd( &$sql, &$newLine ) { |
||
968 | if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) { |
||
969 | preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m ); |
||
970 | $this->delimiter = $m[1]; |
||
971 | $newLine = ''; |
||
972 | } |
||
973 | |||
974 | return parent::streamStatementEnd( $sql, $newLine ); |
||
975 | } |
||
976 | |||
977 | /** |
||
978 | * Check to see if a named lock is available. This is non-blocking. |
||
979 | * |
||
980 | * @param string $lockName Name of lock to poll |
||
981 | * @param string $method Name of method calling us |
||
982 | * @return bool |
||
983 | * @since 1.20 |
||
984 | */ |
||
985 | View Code Duplication | public function lockIsFree( $lockName, $method ) { |
|
986 | $encName = $this->addQuotes( $this->makeLockName( $lockName ) ); |
||
987 | $result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method ); |
||
988 | $row = $this->fetchObject( $result ); |
||
0 ignored issues
–
show
It seems like
$result defined by $this->query("SELECT IS_...S lockstatus", $method) on line 987 can also be of type boolean ; however, DatabaseMysqlBase::fetchObject() does only seem to accept object<ResultWrapper>|resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
989 | |||
990 | return ( $row->lockstatus == 1 ); |
||
991 | } |
||
992 | |||
993 | /** |
||
994 | * @param string $lockName |
||
995 | * @param string $method |
||
996 | * @param int $timeout |
||
997 | * @return bool |
||
998 | */ |
||
999 | View Code Duplication | public function lock( $lockName, $method, $timeout = 5 ) { |
|
1000 | $encName = $this->addQuotes( $this->makeLockName( $lockName ) ); |
||
1001 | $result = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method ); |
||
1002 | $row = $this->fetchObject( $result ); |
||
0 ignored issues
–
show
It seems like
$result defined by $this->query("SELECT GET...S lockstatus", $method) on line 1001 can also be of type boolean ; however, DatabaseMysqlBase::fetchObject() does only seem to accept object<ResultWrapper>|resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
1003 | |||
1004 | if ( $row->lockstatus == 1 ) { |
||
1005 | parent::lock( $lockName, $method, $timeout ); // record |
||
1006 | return true; |
||
1007 | } |
||
1008 | |||
1009 | $this->queryLogger->warning( __METHOD__ . " failed to acquire lock '$lockName'\n" ); |
||
1010 | |||
1011 | return false; |
||
1012 | } |
||
1013 | |||
1014 | /** |
||
1015 | * FROM MYSQL DOCS: |
||
1016 | * http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock |
||
1017 | * @param string $lockName |
||
1018 | * @param string $method |
||
1019 | * @return bool |
||
1020 | */ |
||
1021 | View Code Duplication | public function unlock( $lockName, $method ) { |
|
1022 | $encName = $this->addQuotes( $this->makeLockName( $lockName ) ); |
||
1023 | $result = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method ); |
||
1024 | $row = $this->fetchObject( $result ); |
||
0 ignored issues
–
show
It seems like
$result defined by $this->query("SELECT REL...s lockstatus", $method) on line 1023 can also be of type boolean ; however, DatabaseMysqlBase::fetchObject() does only seem to accept object<ResultWrapper>|resource , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
1025 | |||
1026 | if ( $row->lockstatus == 1 ) { |
||
1027 | parent::unlock( $lockName, $method ); // record |
||
1028 | return true; |
||
1029 | } |
||
1030 | |||
1031 | $this->queryLogger->warning( __METHOD__ . " failed to release lock '$lockName'\n" ); |
||
1032 | |||
1033 | return false; |
||
1034 | } |
||
1035 | |||
1036 | private function makeLockName( $lockName ) { |
||
1037 | // http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock |
||
1038 | // Newer version enforce a 64 char length limit. |
||
1039 | return ( strlen( $lockName ) > 64 ) ? sha1( $lockName ) : $lockName; |
||
1040 | } |
||
1041 | |||
1042 | public function namedLocksEnqueue() { |
||
1043 | return true; |
||
1044 | } |
||
1045 | |||
1046 | /** |
||
1047 | * @param array $read |
||
1048 | * @param array $write |
||
1049 | * @param string $method |
||
1050 | * @param bool $lowPriority |
||
1051 | * @return bool |
||
1052 | */ |
||
1053 | public function lockTables( $read, $write, $method, $lowPriority = true ) { |
||
1054 | $items = []; |
||
1055 | |||
1056 | foreach ( $write as $table ) { |
||
1057 | $tbl = $this->tableName( $table ) . |
||
1058 | ( $lowPriority ? ' LOW_PRIORITY' : '' ) . |
||
1059 | ' WRITE'; |
||
1060 | $items[] = $tbl; |
||
1061 | } |
||
1062 | foreach ( $read as $table ) { |
||
1063 | $items[] = $this->tableName( $table ) . ' READ'; |
||
1064 | } |
||
1065 | $sql = "LOCK TABLES " . implode( ',', $items ); |
||
1066 | $this->query( $sql, $method ); |
||
1067 | |||
1068 | return true; |
||
1069 | } |
||
1070 | |||
1071 | /** |
||
1072 | * @param string $method |
||
1073 | * @return bool |
||
1074 | */ |
||
1075 | public function unlockTables( $method ) { |
||
1076 | $this->query( "UNLOCK TABLES", $method ); |
||
1077 | |||
1078 | return true; |
||
1079 | } |
||
1080 | |||
1081 | /** |
||
1082 | * @param bool $value |
||
1083 | */ |
||
1084 | public function setBigSelects( $value = true ) { |
||
1085 | if ( $value === 'default' ) { |
||
1086 | if ( $this->mDefaultBigSelects === null ) { |
||
1087 | # Function hasn't been called before so it must already be set to the default |
||
1088 | return; |
||
1089 | } else { |
||
1090 | $value = $this->mDefaultBigSelects; |
||
1091 | } |
||
1092 | } elseif ( $this->mDefaultBigSelects === null ) { |
||
1093 | $this->mDefaultBigSelects = |
||
1094 | (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ ); |
||
1095 | } |
||
1096 | $encValue = $value ? '1' : '0'; |
||
1097 | $this->query( "SET sql_big_selects=$encValue", __METHOD__ ); |
||
1098 | } |
||
1099 | |||
1100 | /** |
||
1101 | * DELETE where the condition is a join. MySql uses multi-table deletes. |
||
1102 | * @param string $delTable |
||
1103 | * @param string $joinTable |
||
1104 | * @param string $delVar |
||
1105 | * @param string $joinVar |
||
1106 | * @param array|string $conds |
||
1107 | * @param bool|string $fname |
||
1108 | * @throws DBUnexpectedError |
||
1109 | * @return bool|ResultWrapper |
||
1110 | */ |
||
1111 | View Code Duplication | public function deleteJoin( |
|
1112 | $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ |
||
1113 | ) { |
||
1114 | if ( !$conds ) { |
||
1115 | throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' ); |
||
1116 | } |
||
1117 | |||
1118 | $delTable = $this->tableName( $delTable ); |
||
1119 | $joinTable = $this->tableName( $joinTable ); |
||
1120 | $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar "; |
||
1121 | |||
1122 | if ( $conds != '*' ) { |
||
1123 | $sql .= ' AND ' . $this->makeList( $conds, self::LIST_AND ); |
||
0 ignored issues
–
show
It seems like
$conds defined by parameter $conds on line 1112 can also be of type string ; however, Database::makeList() does only seem to accept array , maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble.
Loading history...
|
|||
1124 | } |
||
1125 | |||
1126 | return $this->query( $sql, $fname ); |
||
0 ignored issues
–
show
It seems like
$fname defined by parameter $fname on line 1112 can also be of type boolean ; however, Database::query() does only seem to accept string , maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble.
Loading history...
|
|||
1127 | } |
||
1128 | |||
1129 | /** |
||
1130 | * @param string $table |
||
1131 | * @param array $rows |
||
1132 | * @param array $uniqueIndexes |
||
1133 | * @param array $set |
||
1134 | * @param string $fname |
||
1135 | * @return bool |
||
1136 | */ |
||
1137 | public function upsert( $table, array $rows, array $uniqueIndexes, |
||
1138 | array $set, $fname = __METHOD__ |
||
1139 | ) { |
||
1140 | if ( !count( $rows ) ) { |
||
1141 | return true; // nothing to do |
||
1142 | } |
||
1143 | |||
1144 | if ( !is_array( reset( $rows ) ) ) { |
||
1145 | $rows = [ $rows ]; |
||
1146 | } |
||
1147 | |||
1148 | $table = $this->tableName( $table ); |
||
1149 | $columns = array_keys( $rows[0] ); |
||
1150 | |||
1151 | $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES '; |
||
1152 | $rowTuples = []; |
||
1153 | foreach ( $rows as $row ) { |
||
1154 | $rowTuples[] = '(' . $this->makeList( $row ) . ')'; |
||
1155 | } |
||
1156 | $sql .= implode( ',', $rowTuples ); |
||
1157 | $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, self::LIST_SET ); |
||
1158 | |||
1159 | return (bool)$this->query( $sql, $fname ); |
||
1160 | } |
||
1161 | |||
1162 | /** |
||
1163 | * Determines how long the server has been up |
||
1164 | * |
||
1165 | * @return int |
||
1166 | */ |
||
1167 | public function getServerUptime() { |
||
1168 | $vars = $this->getMysqlStatus( 'Uptime' ); |
||
1169 | |||
1170 | return (int)$vars['Uptime']; |
||
1171 | } |
||
1172 | |||
1173 | /** |
||
1174 | * Determines if the last failure was due to a deadlock |
||
1175 | * |
||
1176 | * @return bool |
||
1177 | */ |
||
1178 | public function wasDeadlock() { |
||
1179 | return $this->lastErrno() == 1213; |
||
1180 | } |
||
1181 | |||
1182 | /** |
||
1183 | * Determines if the last failure was due to a lock timeout |
||
1184 | * |
||
1185 | * @return bool |
||
1186 | */ |
||
1187 | public function wasLockTimeout() { |
||
1188 | return $this->lastErrno() == 1205; |
||
1189 | } |
||
1190 | |||
1191 | public function wasErrorReissuable() { |
||
1192 | return $this->lastErrno() == 2013 || $this->lastErrno() == 2006; |
||
1193 | } |
||
1194 | |||
1195 | /** |
||
1196 | * Determines if the last failure was due to the database being read-only. |
||
1197 | * |
||
1198 | * @return bool |
||
1199 | */ |
||
1200 | public function wasReadOnlyError() { |
||
1201 | return $this->lastErrno() == 1223 || |
||
1202 | ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false ); |
||
1203 | } |
||
1204 | |||
1205 | public function wasConnectionError( $errno ) { |
||
1206 | return $errno == 2013 || $errno == 2006; |
||
1207 | } |
||
1208 | |||
1209 | /** |
||
1210 | * @param string $oldName |
||
1211 | * @param string $newName |
||
1212 | * @param bool $temporary |
||
1213 | * @param string $fname |
||
1214 | * @return bool |
||
1215 | */ |
||
1216 | public function duplicateTableStructure( |
||
1217 | $oldName, $newName, $temporary = false, $fname = __METHOD__ |
||
1218 | ) { |
||
1219 | $tmp = $temporary ? 'TEMPORARY ' : ''; |
||
1220 | $newName = $this->addIdentifierQuotes( $newName ); |
||
1221 | $oldName = $this->addIdentifierQuotes( $oldName ); |
||
1222 | $query = "CREATE $tmp TABLE $newName (LIKE $oldName)"; |
||
1223 | |||
1224 | return $this->query( $query, $fname ); |
||
1225 | } |
||
1226 | |||
1227 | /** |
||
1228 | * List all tables on the database |
||
1229 | * |
||
1230 | * @param string $prefix Only show tables with this prefix, e.g. mw_ |
||
1231 | * @param string $fname Calling function name |
||
1232 | * @return array |
||
1233 | */ |
||
1234 | public function listTables( $prefix = null, $fname = __METHOD__ ) { |
||
1235 | $result = $this->query( "SHOW TABLES", $fname ); |
||
1236 | |||
1237 | $endArray = []; |
||
1238 | |||
1239 | View Code Duplication | foreach ( $result as $table ) { |
|
0 ignored issues
–
show
The expression
$result of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
1240 | $vars = get_object_vars( $table ); |
||
1241 | $table = array_pop( $vars ); |
||
1242 | |||
1243 | if ( !$prefix || strpos( $table, $prefix ) === 0 ) { |
||
0 ignored issues
–
show
The expression
$prefix of type string|null is loosely compared to false ; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
Loading history...
|
|||
1244 | $endArray[] = $table; |
||
1245 | } |
||
1246 | } |
||
1247 | |||
1248 | return $endArray; |
||
1249 | } |
||
1250 | |||
1251 | /** |
||
1252 | * @param string $tableName |
||
1253 | * @param string $fName |
||
1254 | * @return bool|ResultWrapper |
||
1255 | */ |
||
1256 | View Code Duplication | public function dropTable( $tableName, $fName = __METHOD__ ) { |
|
1257 | if ( !$this->tableExists( $tableName, $fName ) ) { |
||
1258 | return false; |
||
1259 | } |
||
1260 | |||
1261 | return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName ); |
||
1262 | } |
||
1263 | |||
1264 | /** |
||
1265 | * Get status information from SHOW STATUS in an associative array |
||
1266 | * |
||
1267 | * @param string $which |
||
1268 | * @return array |
||
1269 | */ |
||
1270 | private function getMysqlStatus( $which = "%" ) { |
||
1271 | $res = $this->query( "SHOW STATUS LIKE '{$which}'" ); |
||
1272 | $status = []; |
||
1273 | |||
1274 | foreach ( $res as $row ) { |
||
0 ignored issues
–
show
The expression
$res of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
1275 | $status[$row->Variable_name] = $row->Value; |
||
1276 | } |
||
1277 | |||
1278 | return $status; |
||
1279 | } |
||
1280 | |||
1281 | /** |
||
1282 | * Lists VIEWs in the database |
||
1283 | * |
||
1284 | * @param string $prefix Only show VIEWs with this prefix, eg. |
||
1285 | * unit_test_, or $wgDBprefix. Default: null, would return all views. |
||
1286 | * @param string $fname Name of calling function |
||
1287 | * @return array |
||
1288 | * @since 1.22 |
||
1289 | */ |
||
1290 | public function listViews( $prefix = null, $fname = __METHOD__ ) { |
||
1291 | // The name of the column containing the name of the VIEW |
||
1292 | $propertyName = 'Tables_in_' . $this->mDBname; |
||
1293 | |||
1294 | // Query for the VIEWS |
||
1295 | $res = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' ); |
||
1296 | $allViews = []; |
||
1297 | foreach ( $res as $row ) { |
||
0 ignored issues
–
show
The expression
$res of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
1298 | array_push( $allViews, $row->$propertyName ); |
||
1299 | } |
||
1300 | |||
1301 | if ( is_null( $prefix ) || $prefix === '' ) { |
||
1302 | return $allViews; |
||
1303 | } |
||
1304 | |||
1305 | $filteredViews = []; |
||
1306 | foreach ( $allViews as $viewName ) { |
||
1307 | // Does the name of this VIEW start with the table-prefix? |
||
1308 | if ( strpos( $viewName, $prefix ) === 0 ) { |
||
1309 | array_push( $filteredViews, $viewName ); |
||
1310 | } |
||
1311 | } |
||
1312 | |||
1313 | return $filteredViews; |
||
1314 | } |
||
1315 | |||
1316 | /** |
||
1317 | * Differentiates between a TABLE and a VIEW. |
||
1318 | * |
||
1319 | * @param string $name Name of the TABLE/VIEW to test |
||
1320 | * @param string $prefix |
||
1321 | * @return bool |
||
1322 | * @since 1.22 |
||
1323 | */ |
||
1324 | public function isView( $name, $prefix = null ) { |
||
1325 | return in_array( $name, $this->listViews( $prefix ) ); |
||
1326 | } |
||
1327 | } |
||
1328 | |||
1329 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: