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