@@ -24,51 +24,228 @@ import (
2424)
2525
2626var (
27+ // Regex Pattern to match a Singe Value Interval e.g. 1 in uuid:1
2728 singleValueInterval = regexp .MustCompile ("^([0-9]+)$" )
28- multiValueInterval = regexp .MustCompile ("^([0-9]+)[-]([0-9]+)$" )
29+
30+ // Regex Pattern to match a Multi Value Interval e.g. 1-5 in uuid:1-5
31+ multiValueInterval = regexp .MustCompile ("^([0-9]+)[-]([0-9]+)$" )
32+
33+ // Regex Pattern to match GTID tags
34+ // Tag must start with a letter e.g. tag1 in uuid:tag1:1-5
35+ tagRegex = regexp .MustCompile ("^[a-z_][a-z0-9_]{0,31}$" )
2936)
3037
38+ // The below struct represents a tagged interval in GTID set.
39+ // It is private and should not be exported outside this package.
40+ type tagInterval struct {
41+ Tag string // tag name
42+ Interval []string // intervals
43+ }
44+
3145// OracleGtidSetEntry represents an entry in a set of GTID ranges,
32- // for example, the entry: "316d193c-70e5-11e5-adb2-ecf4bb2262ff:1-8935:8984-6124596" (may include gaps)
46+ // for example,
47+ //
48+ // Valid Formats:
49+ // - "316d193c-70e5-11e5-adb2-ecf4bb2262ff:1-8935:8984-6124596" (may include gaps)
50+ // - "321f5c0d-70e5-11e5-adb2-ecf4bb2262ff:1-56457" (no gaps)
51+ // - "230ea8ea-81e3-11e4-972a-e25ec4bd140a:1-10539:tag1:1-2474" (tagged intervals)
52+ // - "230ea8ea-81e3-11e4-972a-e25ec4bd140a:1-5139:5780-6317:tag1:1-2474:3201-4157" (tagged intervals and may have gaps)
3353type OracleGtidSetEntry struct {
34- UUID string
35- Ranges string
54+ UUID string
55+ DefaultIv string // default (untagged) interval
56+ TaggedIv []tagInterval // tagged intervals
3657}
3758
38- // NewOracleGtidSetEntry parses a single entry text
39- func NewOracleGtidSetEntry (gtidRangeString string ) (* OracleGtidSetEntry , error ) {
40- gtidRangeString = strings .TrimSpace (gtidRangeString )
41- tokens := strings .SplitN (gtidRangeString , ":" , 2 )
42- if len (tokens ) != 2 {
59+ func ParseOracleGtidSetEntry (gtidRangeString string ) (* OracleGtidSetEntry , error ) {
60+
61+ // Split the string into two parts: UUID part and the non-UUID part
62+ gtid_str := strings .SplitN (gtidRangeString , ":" , 2 )
63+
64+ // Sanity check
65+ if len (gtid_str ) != 2 {
4366 return nil , fmt .Errorf ("Cannot parse OracleGtidSetEntry from %s" , gtidRangeString )
4467 }
45- if tokens [0 ] == "" {
46- return nil , fmt .Errorf ("Unexpected UUID: %s" , tokens [0 ])
68+
69+ if gtid_str [0 ] == "" {
70+ return nil , fmt .Errorf ("Unexpected UUID: %s" , gtid_str [0 ])
71+ }
72+
73+ if gtid_str [1 ] == "" {
74+ return nil , fmt .Errorf ("Unexpected GTID range: %s" , gtid_str [1 ])
75+ }
76+
77+ // UUID is the first part
78+ uuid := gtid_str [0 ]
79+
80+ // Split the non-UUID parts into multiple blocks
81+ s := strings .SplitN (gtid_str [1 ], ":" , - 1 )
82+
83+ var default_iv string // Default interval
84+ var tag_ivs []tagInterval // Full tagged interval
85+ var tip * tagInterval = nil // Current tag interval
86+
87+ // Tagged intervals always follow untagged ones
88+ // so once tip != nil it will never be nil again
89+ for i := range s {
90+ // If it is a GTID tag
91+ if tagRegex .MatchString (s [i ]) {
92+ if tip != nil && (tip .Tag != "" ) && (len (tip .Interval ) == 0 ) {
93+ // If the tag is already set and we got another tag
94+ return nil , fmt .Errorf ("Invalid format: Found a tag without any intervals" )
95+ } else if tip != nil && (tip .Tag == "" ) && (len (tip .Interval ) != 0 ) {
96+ // Should never happen - just in case
97+ return nil , fmt .Errorf ("Invalid format" )
98+ } else {
99+ // Now process the new tag
100+ ti := tagInterval {
101+ Tag : s [i ],
102+ }
103+ // Append the new tag to tag_ivs
104+ tag_ivs = append (tag_ivs , ti )
105+ tip = & tag_ivs [len (tag_ivs )- 1 ]
106+ }
107+ } else {
108+ // If it is a GTID interval
109+ if singleValueInterval .MatchString (s [i ]) || multiValueInterval .MatchString (s [i ]) {
110+ // If it is an empty tag, add it to default interval
111+ if tip == nil {
112+ default_iv += ":" + s [i ]
113+ } else {
114+ // If tag is already set, add it to the tag interval
115+ tip .Interval = append (tip .Interval , s [i ])
116+ }
117+ } else {
118+ // Regex failed, invalid format
119+ return nil , fmt .Errorf ("Invalid format" )
120+ }
121+ }
47122 }
48- if tokens [1 ] == "" {
49- return nil , fmt .Errorf ("Unexpected GTID range: %s" , tokens [1 ])
123+ // If the interval of the last tag is empty, then it is an invalid format
124+ // eg: "UUID:1-5139::tag1:"
125+ if tip != nil && (tip .Tag != "" ) && (len (tip .Interval ) == 0 ) {
126+ return nil , fmt .Errorf ("Invalid format: Found a tag without any intervals" )
127+ }
128+ // Don't append ':' for the first interval in the default set
129+ default_iv , _ = strings .CutPrefix (default_iv , ":" )
130+ entry := OracleGtidSetEntry {UUID : uuid , DefaultIv : default_iv , TaggedIv : tag_ivs }
131+ return & entry , nil
132+ }
133+
134+ // NewOracleGtidSetEntry parses a single entry text
135+ func NewOracleGtidSetEntry (gtidRangeString string ) (* OracleGtidSetEntry , error ) {
136+ gtidRangeString = strings .TrimSpace (gtidRangeString )
137+
138+ gtidRange , error := ParseOracleGtidSetEntry (gtidRangeString )
139+ if gtidRange == nil {
140+ return nil , error
50141 }
51- gtidRange := & OracleGtidSetEntry { UUID : tokens [ 0 ], Ranges : tokens [ 1 ]}
142+
52143 return gtidRange , nil
53144}
54145
55- // String returns a user-friendly string representation of this entry
146+ // String() returns a user-friendly string representation of this entry
56147func (this * OracleGtidSetEntry ) String () string {
57- return fmt .Sprintf ("%s:%s" , this .UUID , this .Ranges )
148+
149+ var res string
150+
151+ // UUID is always added in the beginning of the Gtid_set
152+ res += this .UUID
153+
154+ // Default ranges are always added immediately after the UUID
155+ if len (this .DefaultIv ) != 0 {
156+ res += ":" + this .DefaultIv
157+ }
158+
159+ // Tagged ranges are added in the end
160+ for _ , v := range this .TaggedIv {
161+ res += ":" + v .Tag
162+ if len (v .Interval ) != 0 {
163+ res += ":" + strings .Join (v .Interval , ":" )
164+ }
165+ }
166+ return res
58167}
59168
60- // String returns a user-friendly string representation of this entry
169+ /*
170+ Explode() returns a list of individual gtids that are represented by this entry.
171+
172+ Example:
173+ Explode of the GTID set "48ebed33-0d12-11ef-a3ec-ac198e4551c8:1-3:7:tag1:1-2:10-12:tag2:74-75:78:81"
174+ shall return the following
175+
176+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:1
177+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:2
178+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:3
179+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:7
180+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:tag1:1
181+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:tag1:2
182+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:tag1:10
183+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:tag1:11
184+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:tag1:12
185+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:tag2:74
186+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:tag2:75
187+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:tag2:78
188+ 48ebed33-0d12-11ef-a3ec-ac198e4551c8:tag2:81
189+ */
61190func (this * OracleGtidSetEntry ) Explode () (result [](* OracleGtidSetEntry )) {
62- intervals := strings .Split (this .Ranges , ":" )
63- for _ , interval := range intervals {
64- if submatch := multiValueInterval .FindStringSubmatch (interval ); submatch != nil {
65- intervalStart , _ := strconv .Atoi (submatch [1 ])
66- intervalEnd , _ := strconv .Atoi (submatch [2 ])
67- for i := intervalStart ; i <= intervalEnd ; i ++ {
68- result = append (result , & OracleGtidSetEntry {UUID : this .UUID , Ranges : fmt .Sprintf ("%d" , i )})
191+
192+ // Appends the default interval to the result
193+ var AppendDefaultInterval = func (this * OracleGtidSetEntry ) {
194+ intervals := strings .Split (this .DefaultIv , ":" )
195+ for _ , interval := range intervals {
196+ // Multi-value interval
197+ if submatch := multiValueInterval .FindStringSubmatch (interval ); submatch != nil {
198+ intervalStart , _ := strconv .Atoi (submatch [1 ])
199+ intervalEnd , _ := strconv .Atoi (submatch [2 ])
200+ for i := intervalStart ; i <= intervalEnd ; i ++ {
201+ result = append (result , & OracleGtidSetEntry {UUID : this .UUID , DefaultIv : fmt .Sprintf ("%d" , i )})
202+ }
203+ } else if submatch := singleValueInterval .FindStringSubmatch (interval ); submatch != nil {
204+ // Single-value interval
205+ result = append (result , & OracleGtidSetEntry {UUID : this .UUID , DefaultIv : interval })
69206 }
70- } else if submatch := singleValueInterval .FindStringSubmatch (interval ); submatch != nil {
71- result = append (result , & OracleGtidSetEntry {UUID : this .UUID , Ranges : interval })
207+ }
208+ }
209+
210+ // Appends tagged intervals to the result
211+ var AppendTaggedInterval = func (tag string , interval string ) {
212+ intervals := strings .Split (interval , ":" )
213+ for _ , interval := range intervals {
214+ // Multi-value interval
215+ if submatch := multiValueInterval .FindStringSubmatch (interval ); submatch != nil {
216+ intervalStart , _ := strconv .Atoi (submatch [1 ])
217+ intervalEnd , _ := strconv .Atoi (submatch [2 ])
218+ for i := intervalStart ; i <= intervalEnd ; i ++ {
219+
220+ ti := tagInterval {
221+ Tag : tag ,
222+ Interval : []string {fmt .Sprintf ("%d" , i )}}
223+ taggedIv := []tagInterval {ti }
224+
225+ entry := OracleGtidSetEntry {UUID : this .UUID , TaggedIv : taggedIv }
226+ result = append (result , & entry )
227+ }
228+ } else if submatch := singleValueInterval .FindStringSubmatch (interval ); submatch != nil {
229+
230+ // Single-value interval
231+ ti := tagInterval {
232+ Tag : tag ,
233+ Interval : []string {interval }}
234+ taggedIv := []tagInterval {ti }
235+
236+ entry := OracleGtidSetEntry {UUID : this .UUID , TaggedIv : taggedIv }
237+ result = append (result , & entry )
238+ }
239+ }
240+ }
241+
242+ // Process default interval first
243+ AppendDefaultInterval (this )
244+
245+ // Process tagged intervals next
246+ for _ , v := range this .TaggedIv {
247+ for _ , iv := range v .Interval {
248+ AppendTaggedInterval (v .Tag , iv )
72249 }
73250 }
74251 return result
0 commit comments