vitess.io/vitess@v0.16.2/go/mysql/replication_position.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package mysql
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"vitess.io/vitess/go/vt/proto/vtrpc"
    25  	"vitess.io/vitess/go/vt/vterrors"
    26  )
    27  
    28  const (
    29  	// MaximumPositionSize is the maximum size of a
    30  	// replication position. It is used as the maximum column size in the _vt.reparent_journal and
    31  	// other related tables. A row has a maximum size of 65535 bytes. So
    32  	// we want to stay under that. We use VARBINARY so the
    33  	// character set doesn't matter, we only store ascii
    34  	// characters anyway.
    35  	MaximumPositionSize = 64000
    36  )
    37  
    38  // Position represents the information necessary to describe which
    39  // transactions a server has seen, so that it can request a replication stream
    40  // from a new source that picks up where it left off.
    41  //
    42  // This must be a concrete struct because custom Unmarshalers can't be
    43  // registered on an interface.
    44  //
    45  // The == operator should not be used with Position, because the
    46  // underlying GTIDSet might use slices, which are not comparable. Using == in
    47  // those cases will result in a run-time panic.
    48  type Position struct {
    49  	// This is a zero byte compile-time check that no one is trying to
    50  	// use == or != with Position. Without this, we won't know there's
    51  	// a problem until the runtime panic. Note that this must not be
    52  	// the last field of the struct, or else the Go compiler will add
    53  	// padding to prevent pointers to this field from becoming invalid.
    54  	_ [0]struct{ _ []byte }
    55  
    56  	// GTIDSet is the underlying GTID set. It must not be anonymous,
    57  	// or else Position would itself also implement the GTIDSet interface.
    58  	GTIDSet GTIDSet
    59  }
    60  
    61  // Equal returns true if this position is equal to another.
    62  func (rp Position) Equal(other Position) bool {
    63  	if rp.GTIDSet == nil {
    64  		return other.GTIDSet == nil
    65  	}
    66  	return rp.GTIDSet.Equal(other.GTIDSet)
    67  }
    68  
    69  // AtLeast returns true if this position is equal to or after another.
    70  func (rp Position) AtLeast(other Position) bool {
    71  	// The underlying call to the Contains method of the GTIDSet interface
    72  	// does not guarantee handling the nil cases correctly.
    73  	// So, we have to do nil case handling here
    74  	if other.GTIDSet == nil {
    75  		// If the other GTIDSet is nil, then it is contained
    76  		// in all possible GTIDSets, even nil ones.
    77  		return true
    78  	}
    79  	if rp.GTIDSet == nil {
    80  		// Here rp GTIDSet is nil but the other GTIDSet isn't.
    81  		// So it is not contained in the rp GTIDSet.
    82  		return false
    83  	}
    84  	return rp.GTIDSet.Contains(other.GTIDSet)
    85  }
    86  
    87  // String returns a string representation of the underlying GTIDSet.
    88  // If the set is nil, it returns "<nil>" in the style of Sprintf("%v", nil).
    89  func (rp Position) String() string {
    90  	if rp.GTIDSet == nil {
    91  		return "<nil>"
    92  	}
    93  	return rp.GTIDSet.String()
    94  }
    95  
    96  // IsZero returns true if this is the zero value, Position{}.
    97  func (rp Position) IsZero() bool {
    98  	return rp.GTIDSet == nil
    99  }
   100  
   101  // AppendGTID returns a new Position that represents the position
   102  // after the given GTID is replicated.
   103  func AppendGTID(rp Position, gtid GTID) Position {
   104  	if gtid == nil {
   105  		return rp
   106  	}
   107  	if rp.GTIDSet == nil {
   108  		return Position{GTIDSet: gtid.GTIDSet()}
   109  	}
   110  	return Position{GTIDSet: rp.GTIDSet.AddGTID(gtid)}
   111  }
   112  
   113  // MustParsePosition calls ParsePosition and panics
   114  // on error.
   115  func MustParsePosition(flavor, value string) Position {
   116  	rp, err := ParsePosition(flavor, value)
   117  	if err != nil {
   118  		panic(err)
   119  	}
   120  	return rp
   121  }
   122  
   123  // EncodePosition returns a string that contains both the flavor
   124  // and value of the Position, so that the correct parser can be
   125  // selected when that string is passed to DecodePosition.
   126  func EncodePosition(rp Position) string {
   127  	if rp.GTIDSet == nil {
   128  		return ""
   129  	}
   130  	return fmt.Sprintf("%s/%s", rp.GTIDSet.Flavor(), rp.GTIDSet.String())
   131  }
   132  
   133  // DecodePosition converts a string in the format returned by
   134  // EncodePosition back into a Position value with the
   135  // correct underlying flavor.
   136  func DecodePosition(s string) (rp Position, err error) {
   137  	if s == "" {
   138  		return rp, nil
   139  	}
   140  
   141  	flav, gtid, ok := strings.Cut(s, "/")
   142  	if !ok {
   143  		// There is no flavor. Try looking for a default parser.
   144  		return ParsePosition("", s)
   145  	}
   146  	return ParsePosition(flav, gtid)
   147  }
   148  
   149  // ParsePosition calls the parser for the specified flavor.
   150  func ParsePosition(flavor, value string) (rp Position, err error) {
   151  	parser := gtidSetParsers[flavor]
   152  	if parser == nil {
   153  		return rp, vterrors.Errorf(vtrpc.Code_INTERNAL, "parse error: unknown GTIDSet flavor %#v", flavor)
   154  	}
   155  	gtidSet, err := parser(value)
   156  	if err != nil {
   157  		return rp, err
   158  	}
   159  	rp.GTIDSet = gtidSet
   160  	return rp, err
   161  }
   162  
   163  // MarshalJSON implements encoding/json.Marshaler.
   164  func (rp Position) MarshalJSON() ([]byte, error) {
   165  	return json.Marshal(EncodePosition(rp))
   166  }
   167  
   168  // UnmarshalJSON implements encoding/json.Unmarshaler.
   169  func (rp *Position) UnmarshalJSON(buf []byte) error {
   170  	var s string
   171  	err := json.Unmarshal(buf, &s)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	*rp, err = DecodePosition(s)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	return nil
   181  }
   182  
   183  // MatchesFlavor will take a flavor string, and return whether the positions GTIDSet matches the supplied flavor.
   184  // The caller should use the constants Mysql56FlavorID, MariadbFlavorID, or FilePosFlavorID when supplying the flavor string.
   185  func (rp *Position) MatchesFlavor(flavor string) bool {
   186  	switch flavor {
   187  	case Mysql56FlavorID:
   188  		_, matches := rp.GTIDSet.(Mysql56GTIDSet)
   189  		return matches
   190  	case MariadbFlavorID:
   191  		_, matches := rp.GTIDSet.(MariadbGTIDSet)
   192  		return matches
   193  	case FilePosFlavorID:
   194  		_, matches := rp.GTIDSet.(filePosGTID)
   195  		return matches
   196  	}
   197  	return false
   198  }
   199  
   200  // Comparable returns whether the receiver is comparable to the supplied position, based on whether one
   201  // of the two positions contains the other.
   202  func (rp *Position) Comparable(other Position) bool {
   203  	return rp.GTIDSet.Contains(other.GTIDSet) || other.GTIDSet.Contains(rp.GTIDSet)
   204  }
   205  
   206  // AllPositionsComparable returns true if all positions in the supplied list are comparable with one another, and false
   207  // if any are non-comparable.
   208  func AllPositionsComparable(positions []Position) bool {
   209  	for i := 0; i < len(positions); i++ {
   210  		for j := i + 1; j < len(positions); j++ {
   211  			if !positions[i].Comparable(positions[j]) {
   212  				return false
   213  			}
   214  		}
   215  	}
   216  
   217  	return true
   218  }