Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like DatabaseMysqlBase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DatabaseMysqlBase, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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 ) { |
||
93 | |||
94 | /** |
||
95 | * @return string |
||
96 | */ |
||
97 | function getType() { |
||
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 | function open( $server, $user, $password, $dbName ) { |
||
204 | |||
205 | /** |
||
206 | * Set the character set information right after connection |
||
207 | * @return bool |
||
208 | */ |
||
209 | protected function connectInitCharset() { |
||
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 | function freeResult( $res ) { |
|
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 | function fetchObject( $res ) { |
|
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 | function fetchRow( $res ) { |
|
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 ) { |
||
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 | function numFields( $res ) { |
||
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 | function fieldName( $res, $n ) { |
||
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 ) { |
||
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 | function dataSeek( $res, $row ) { |
||
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 | function lastError() { |
||
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 | function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) { |
||
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 = '', |
||
520 | |||
521 | function tableExists( $table, $fname = __METHOD__ ) { |
||
531 | |||
532 | /** |
||
533 | * @param string $table |
||
534 | * @param string $field |
||
535 | * @return bool|MySQLField |
||
536 | */ |
||
537 | function fieldInfo( $table, $field ) { |
||
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 | function indexInfo( $table, $index, $fname = __METHOD__ ) { |
||
596 | |||
597 | /** |
||
598 | * @param string $s |
||
599 | * @return string |
||
600 | */ |
||
601 | function strencode( $s ) { |
||
604 | |||
605 | /** |
||
606 | * @param string $s |
||
607 | * @return mixed |
||
608 | */ |
||
609 | abstract protected function mysqlRealEscapeString( $s ); |
||
610 | |||
611 | public function addQuotes( $s ) { |
||
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 ) { |
||
632 | |||
633 | /** |
||
634 | * @param string $name |
||
635 | * @return bool |
||
636 | */ |
||
637 | public function isQuotedIdentifier( $name ) { |
||
640 | |||
641 | function getLag() { |
||
648 | |||
649 | /** |
||
650 | * @return string |
||
651 | */ |
||
652 | protected function getLagDetectionMethod() { |
||
655 | |||
656 | /** |
||
657 | * @return bool|int |
||
658 | */ |
||
659 | protected function getLagFromSlaveStatus() { |
||
668 | |||
669 | /** |
||
670 | * @return bool|float |
||
671 | */ |
||
672 | protected function getLagFromPtHeartbeat() { |
||
714 | |||
715 | protected function getMasterServerInfo() { |
||
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 ) { |
||
770 | |||
771 | public function getApproximateLagStatus() { |
||
788 | |||
789 | function masterPosWait( DBMasterPos $pos, $timeout ) { |
||
836 | |||
837 | /** |
||
838 | * Get the position of the master from SHOW SLAVE STATUS |
||
839 | * |
||
840 | * @return MySQLMasterPos|bool |
||
841 | */ |
||
842 | function getReplicaPos() { |
||
864 | |||
865 | /** |
||
866 | * Get the position of the master from SHOW MASTER STATUS |
||
867 | * |
||
868 | * @return MySQLMasterPos|bool |
||
869 | */ |
||
870 | function getMasterPos() { |
||
889 | |||
890 | public function serverIsReadOnly() { |
||
896 | |||
897 | /** |
||
898 | * @param string $index |
||
899 | * @return string |
||
900 | */ |
||
901 | function useIndexClause( $index ) { |
||
904 | |||
905 | /** |
||
906 | * @param string $index |
||
907 | * @return string |
||
908 | */ |
||
909 | function ignoreIndexClause( $index ) { |
||
912 | |||
913 | /** |
||
914 | * @return string |
||
915 | */ |
||
916 | function lowPriorityOption() { |
||
919 | |||
920 | /** |
||
921 | * @return string |
||
922 | */ |
||
923 | public function getSoftwareLink() { |
||
937 | |||
938 | /** |
||
939 | * @return string |
||
940 | */ |
||
941 | public function getServerVersion() { |
||
950 | |||
951 | /** |
||
952 | * @param array $options |
||
953 | */ |
||
954 | public function setSessionOptions( array $options ) { |
||
961 | |||
962 | /** |
||
963 | * @param string $sql |
||
964 | * @param string $newLine |
||
965 | * @return bool |
||
966 | */ |
||
967 | public function streamStatementEnd( &$sql, &$newLine ) { |
||
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 ) { |
|
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 ) { |
|
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 ) { |
|
1035 | |||
1036 | private function makeLockName( $lockName ) { |
||
1041 | |||
1042 | public function namedLocksEnqueue() { |
||
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 ) { |
||
1070 | |||
1071 | /** |
||
1072 | * @param string $method |
||
1073 | * @return bool |
||
1074 | */ |
||
1075 | public function unlockTables( $method ) { |
||
1080 | |||
1081 | /** |
||
1082 | * @param bool $value |
||
1083 | */ |
||
1084 | public function setBigSelects( $value = true ) { |
||
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 | function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) { |
|
1126 | |||
1127 | /** |
||
1128 | * @param string $table |
||
1129 | * @param array $rows |
||
1130 | * @param array $uniqueIndexes |
||
1131 | * @param array $set |
||
1132 | * @param string $fname |
||
1133 | * @return bool |
||
1134 | */ |
||
1135 | public function upsert( $table, array $rows, array $uniqueIndexes, |
||
1159 | |||
1160 | /** |
||
1161 | * Determines how long the server has been up |
||
1162 | * |
||
1163 | * @return int |
||
1164 | */ |
||
1165 | function getServerUptime() { |
||
1170 | |||
1171 | /** |
||
1172 | * Determines if the last failure was due to a deadlock |
||
1173 | * |
||
1174 | * @return bool |
||
1175 | */ |
||
1176 | function wasDeadlock() { |
||
1179 | |||
1180 | /** |
||
1181 | * Determines if the last failure was due to a lock timeout |
||
1182 | * |
||
1183 | * @return bool |
||
1184 | */ |
||
1185 | function wasLockTimeout() { |
||
1188 | |||
1189 | function wasErrorReissuable() { |
||
1192 | |||
1193 | /** |
||
1194 | * Determines if the last failure was due to the database being read-only. |
||
1195 | * |
||
1196 | * @return bool |
||
1197 | */ |
||
1198 | function wasReadOnlyError() { |
||
1202 | |||
1203 | function wasConnectionError( $errno ) { |
||
1206 | |||
1207 | /** |
||
1208 | * @param string $oldName |
||
1209 | * @param string $newName |
||
1210 | * @param bool $temporary |
||
1211 | * @param string $fname |
||
1212 | * @return bool |
||
1213 | */ |
||
1214 | function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) { |
||
1222 | |||
1223 | /** |
||
1224 | * List all tables on the database |
||
1225 | * |
||
1226 | * @param string $prefix Only show tables with this prefix, e.g. mw_ |
||
1227 | * @param string $fname Calling function name |
||
1228 | * @return array |
||
1229 | */ |
||
1230 | function listTables( $prefix = null, $fname = __METHOD__ ) { |
||
1246 | |||
1247 | /** |
||
1248 | * @param string $tableName |
||
1249 | * @param string $fName |
||
1250 | * @return bool|ResultWrapper |
||
1251 | */ |
||
1252 | View Code Duplication | public function dropTable( $tableName, $fName = __METHOD__ ) { |
|
1259 | |||
1260 | /** |
||
1261 | * Get status information from SHOW STATUS in an associative array |
||
1262 | * |
||
1263 | * @param string $which |
||
1264 | * @return array |
||
1265 | */ |
||
1266 | function getMysqlStatus( $which = "%" ) { |
||
1276 | |||
1277 | /** |
||
1278 | * Lists VIEWs in the database |
||
1279 | * |
||
1280 | * @param string $prefix Only show VIEWs with this prefix, eg. |
||
1281 | * unit_test_, or $wgDBprefix. Default: null, would return all views. |
||
1282 | * @param string $fname Name of calling function |
||
1283 | * @return array |
||
1284 | * @since 1.22 |
||
1285 | */ |
||
1286 | public function listViews( $prefix = null, $fname = __METHOD__ ) { |
||
1311 | |||
1312 | /** |
||
1313 | * Differentiates between a TABLE and a VIEW. |
||
1314 | * |
||
1315 | * @param string $name Name of the TABLE/VIEW to test |
||
1316 | * @param string $prefix |
||
1317 | * @return bool |
||
1318 | * @since 1.22 |
||
1319 | */ |
||
1320 | public function isView( $name, $prefix = null ) { |
||
1323 | } |
||
1324 | |||
1325 |
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: