]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/libs/rdbms/database/position/MySQLMasterPos.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / libs / rdbms / database / position / MySQLMasterPos.php
1 <?php
2
3 namespace Wikimedia\Rdbms;
4
5 use InvalidArgumentException;
6
7 /**
8  * DBMasterPos class for MySQL/MariaDB
9  *
10  * Note that master positions and sync logic here make some assumptions:
11  *  - Binlog-based usage assumes single-source replication and non-hierarchical replication.
12  *  - GTID-based usage allows getting/syncing with multi-source replication. It is assumed
13  *    that GTID sets are complete (e.g. include all domains on the server).
14  */
15 class MySQLMasterPos implements DBMasterPos {
16         /** @var string Binlog file */
17         public $file;
18         /** @var int Binglog file position */
19         public $pos;
20         /** @var string[] GTID list */
21         public $gtids = [];
22         /** @var float UNIX timestamp */
23         public $asOfTime = 0.0;
24
25         /**
26          * @param string $file Binlog file name
27          * @param int $pos Binlog position
28          * @param string $gtid Comma separated GTID set [optional]
29          */
30         function __construct( $file, $pos, $gtid = '' ) {
31                 $this->file = $file;
32                 $this->pos = $pos;
33                 $this->gtids = array_map( 'trim', explode( ',', $gtid ) );
34                 $this->asOfTime = microtime( true );
35         }
36
37         /**
38          * @return string <binlog file>/<position>, e.g db1034-bin.000976/843431247
39          */
40         function __toString() {
41                 return "{$this->file}/{$this->pos}";
42         }
43
44         function asOfTime() {
45                 return $this->asOfTime;
46         }
47
48         function hasReached( DBMasterPos $pos ) {
49                 if ( !( $pos instanceof self ) ) {
50                         throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
51                 }
52
53                 // Prefer GTID comparisons, which work with multi-tier replication
54                 $thisPosByDomain = $this->getGtidCoordinates();
55                 $thatPosByDomain = $pos->getGtidCoordinates();
56                 if ( $thisPosByDomain && $thatPosByDomain ) {
57                         $reached = true;
58                         // Check that this has positions GTE all of those in $pos for all domains in $pos
59                         foreach ( $thatPosByDomain as $domain => $thatPos ) {
60                                 $thisPos = isset( $thisPosByDomain[$domain] ) ? $thisPosByDomain[$domain] : -1;
61                                 $reached = $reached && ( $thatPos <= $thisPos );
62                         }
63
64                         return $reached;
65                 }
66
67                 // Fallback to the binlog file comparisons
68                 $thisBinPos = $this->getBinlogCoordinates();
69                 $thatBinPos = $pos->getBinlogCoordinates();
70                 if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
71                         return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
72                 }
73
74                 // Comparing totally different binlogs does not make sense
75                 return false;
76         }
77
78         function channelsMatch( DBMasterPos $pos ) {
79                 if ( !( $pos instanceof self ) ) {
80                         throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
81                 }
82
83                 // Prefer GTID comparisons, which work with multi-tier replication
84                 $thisPosDomains = array_keys( $this->getGtidCoordinates() );
85                 $thatPosDomains = array_keys( $pos->getGtidCoordinates() );
86                 if ( $thisPosDomains && $thatPosDomains ) {
87                         // Check that this has GTIDs for all domains in $pos
88                         return !array_diff( $thatPosDomains, $thisPosDomains );
89                 }
90
91                 // Fallback to the binlog file comparisons
92                 $thisBinPos = $this->getBinlogCoordinates();
93                 $thatBinPos = $pos->getBinlogCoordinates();
94
95                 return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
96         }
97
98         /**
99          * @note: this returns false for multi-source replication GTID sets
100          * @see https://mariadb.com/kb/en/mariadb/gtid
101          * @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
102          * @return array Map of (domain => integer position) or false
103          */
104         protected function getGtidCoordinates() {
105                 $gtidInfos = [];
106                 foreach ( $this->gtids as $gtid ) {
107                         $m = [];
108                         // MariaDB style: <domain>-<server id>-<sequence number>
109                         if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
110                                 $gtidInfos[(int)$m[1]] = (int)$m[2];
111                                 // MySQL style: <UUID domain>:<sequence number>
112                         } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
113                                 $gtidInfos[$m[1]] = (int)$m[2];
114                         } else {
115                                 $gtidInfos = [];
116                                 break; // unrecognized GTID
117                         }
118
119                 }
120
121                 return $gtidInfos;
122         }
123
124         /**
125          * @see https://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
126          * @see https://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
127          * @return array|bool (binlog, (integer file number, integer position)) or false
128          */
129         protected function getBinlogCoordinates() {
130                 $m = [];
131                 if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
132                         return [ 'binlog' => $m[1], 'pos' => [ (int)$m[2], (int)$m[3] ] ];
133                 }
134
135                 return false;
136         }
137 }