~mkindahl/mysql-replicant-python/trunk

« back to all changes in this revision

Viewing changes to lib/mysql/replicant/server.py

  • Committer: Mats Kindahl
  • Date: 2013-10-19 21:21:16 UTC
  • Revision ID: mats.kindahl@oracle.com-20131019212116-0gd2e9i0f5bgc5gp
Adding GTIDSet type.

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
        """
34
34
        return cmp((self.file, self.pos), (other.file, other.pos))
35
35
 
36
 
#pylint: disable-msg=C0103
37
 
User = collections.namedtuple('User', ['name', 'passwd'])
 
36
User = collections.namedtuple('User', 'name,passwd')
 
37
 
 
38
GTID = collections.namedtuple('GTID', 'uuid,gid')
 
39
 
 
40
def _normalize(rngs):
 
41
    """Normalize a list of ranges by merging ranges, if possible, and
 
42
    turning single-position ranges into tuples.
 
43
 
 
44
    The normalization is sort the ranges first on the tuples, which
 
45
    makes comparisons easy when merging range sets.
 
46
    """
 
47
 
 
48
    result = []
 
49
    last = None
 
50
    for rng in sorted(rngs):
 
51
        if len(rng) == 1:
 
52
            rng = (rng[0], rng[0])
 
53
        if last is None:
 
54
            last = rng
 
55
        elif rng[1] <= last[1]:
 
56
            pass
 
57
        elif rng[0] <= last[1] or last[1] + 1 >= rng[0]:
 
58
            last = (last[0], max(rng[1], last[1]))
 
59
        else:
 
60
            result.append(last)
 
61
            last = rng
 
62
    result.append(last)
 
63
    return result
 
64
 
 
65
    
 
66
def _compare_sets(lhs, rhs):
 
67
    """Compare two GTID sets.
 
68
 
 
69
    Return a tuple (lhs, rhs) where lhs is a boolean indicating that
 
70
    the left hand side had at least one more item than the right hand
 
71
    side, and vice verse.
 
72
    """
 
73
 
 
74
    lcheck, rcheck = False, False
 
75
 
 
76
    # Create a union of the lhs and rhs for comparison
 
77
    both = copy.deepcopy(lhs)
 
78
    both.union(rhs)
 
79
 
 
80
    for uuid, rngs in both._GTIDSet__gtids.items():
 
81
        if lcheck and rcheck:
 
82
            return lcheck, rcheck     # They are incomparable, just return
 
83
 
 
84
        def _inner_compare(gtid_set):
 
85
            if uuid not in gtid_set._GTIDSet__gtids:
 
86
                return True # UUID not in lhs ==> right hand side has more
 
87
            else:
 
88
                for rng1, rng2 in zip(rngs, gtid_set._GTIDSet__gtids[uuid]):
 
89
                    if rng1 != rng2:
 
90
                        return True
 
91
            return False
 
92
 
 
93
        if _inner_compare(lhs):
 
94
            rcheck = True
 
95
        if _inner_compare(rhs):
 
96
            lcheck = True
 
97
 
 
98
    return lcheck, rcheck
 
99
 
 
100
class GTIDSet(object):
 
101
    def __init__(self, obj):
 
102
        gtids = {}
 
103
        if not isinstance(obj, basestring):
 
104
            obj = str(obj)      # Try to make it into a string that we parse
 
105
 
 
106
        # Parse the string and construct a GTID set
 
107
        for uuid_set in obj.split(','):
 
108
            parts = uuid_set.split(':')
 
109
 
 
110
            # This fandango is done to handle other forms of UUID that
 
111
            # the UUID class can handle. We, however, use the standard
 
112
            # form for our UUIDs.
 
113
            uuid = str(UUID(parts.pop(0)))
 
114
 
 
115
            if len(parts) == 0 or not parts[0]:
 
116
                raise ValueError("At least one range have to be provided")
 
117
            rngs = [ tuple(int(x) for x in part.split('-')) for part in parts ]
 
118
            for rng in rngs:
 
119
                if len(rng) > 2 or len(rng) == 2 and int(rng[0]) > int(rng[1]):
 
120
                    raise ValueError("Range %s in '%s' is not a valid range" % (
 
121
                            '-'.join(str(i) for i in rng), rng
 
122
                            ))
 
123
            gtids[uuid] = _normalize(rngs)
 
124
        self.__gtids = gtids
 
125
 
 
126
    def __str__(self):
 
127
        sets = []
 
128
        for uuid, rngs in sorted(self.__gtids.items()):
 
129
            uuid_set = ':'.join(
 
130
                [str(uuid)] + [ '-'.join(str(i) for i in rng) for rng in rngs ]
 
131
                )
 
132
            sets.append(uuid_set)
 
133
        return ','.join(sets)
 
134
 
 
135
    def union(self, other):
 
136
        """Compute the union of this GTID set and the GTID set in
 
137
        other.
 
138
 
 
139
        The update of the GTID set is done in-place, so if you want to
 
140
        compute the union of two sets 'lhs' and 'rhs' you have to do
 
141
        something like::
 
142
 
 
143
           result = copy.deepcopy(lhs)
 
144
           result.union(rhs)
 
145
           
 
146
        """
 
147
 
 
148
        # If it wasn't already a GTIDSet, try to make it one.
 
149
        if not isinstance(other, GTIDSet):
 
150
            other = GTIDSet(other)
 
151
 
 
152
        gtids = self.__gtids
 
153
        for uuid, rngs in other.__gtids.items():
 
154
            if uuid not in gtids:
 
155
                gtids[uuid] = rngs
 
156
            else:
 
157
                gtids[uuid] = _normalize(gtids[uuid] + rngs)
 
158
        self.__gtids = gtids
 
159
 
 
160
    def __lt__(self, other):
 
161
        lhs, rhs = _compare_sets(self, other)
 
162
        return not lhs and rhs 
 
163
 
 
164
    def __le__(self, other):
 
165
        lhs, _ = _compare_sets(self, other)
 
166
        return not lhs
 
167
 
 
168
    def __eq__(self, other):
 
169
        lhs, rhs = _compare_sets(self, other)
 
170
        return not (lhs or rhs)
 
171
 
 
172
    def __ne__(self, other):
 
173
        return not self.__eq__(other)
 
174
 
 
175
    def __ge__(self, other):
 
176
        return other.__le__(self)
 
177
 
 
178
    def __gt__(self, other):
 
179
        return other.__lt__(self)
 
180
 
 
181
    def __or__(self, other):
 
182
        result = copy.deepcopy(self)
 
183
        result.union(other)
38
184
 
39
185
class Server(object):
40
186
    """A representation of a MySQL server.