package gtid import ( "errors" "fmt" "sort" "strconv" "strings" ) func Parse(gtidDesc string) (*Gtid, error) { return parse(gtidDesc) } func parse(gtidDesc string) (*Gtid, error) { var gtid Gtid gtid.hashMap = make(map[string]int) gtidDesc = strings.TrimSpace(gtidDesc) if "" == gtidDesc { return >id, nil } return >id, gtid.uniform(gtidDesc) } func (g *Gtid) uniform(gtidDesc string) error { hashMap := make(map[string][]string) gtidlists := strings.Split(gtidDesc, ",") uuidLists := make([]string, 0, len(gtidlists)) for _, v := range gtidlists { if v == "" { continue } numbers := strings.Split(v, ":") if len(numbers) < 2 { return fmt.Errorf("invalid format:%v", v) } uuid, err := g.isValidUUID(numbers[0]) if err != nil { return err } numbers = numbers[1:] existsNums, ok := hashMap[uuid] if ok { for _, v := range existsNums { numbers = append(numbers, v) } hashMap[uuid] = numbers continue } hashMap[uuid] = numbers uuidLists = append(uuidLists, uuid) } sort.Strings(uuidLists) g.uuidNumbers = make([]UUIDNumber, 0, len(uuidLists)) for k, v := range uuidLists { number, err := g.uniformNumber(hashMap[v]) if err != nil { return err } number, err = g.uniformRange(number) if err != nil { return err } g.mu.Lock() g.hashMap[v] = k g.mu.Unlock() g.uuidNumbers = append(g.uuidNumbers, UUIDNumber{ uuid: v, intervals: number, }) } return nil } func (g *Gtid) reform() { sort.Sort(SortUUID(g.uuidNumbers)) g.hashMap = make(map[string]int, len(g.uuidNumbers)) for k, v := range g.uuidNumbers { g.uuidNumbers[k].intervals, _ = g.uniformRange(v.intervals) g.hashMap[v.uuid] = k } } func (g *Gtid) uniformRange(rg []uuidRange) ([]uuidRange, error) { newRg := make([]uuidRange, 0, len(rg)) sort.Sort(SortRange(rg)) var last *uuidRange = nil for _, v := range rg { if last != nil && v.min <= last.max+1 { if last.max <= v.max { last.max = v.max } continue } newRg = append(newRg, v) last = &newRg[len(newRg)-1] } return newRg, nil } func subRange(origin, rg []uuidRange) []uuidRange { newRg := make([]uuidRange, 0) sort.Sort(SortRange(rg)) sort.Sort(SortRange(origin)) for i := 0; i < len(origin); i++ { ori := origin[i] res := make([]uuidRange, 0) shouldAdd := true for _, v := range rg { if v.min <= ori.min && v.max >= ori.max { shouldAdd = false break } if v.max < ori.min { continue } if v.min > ori.max { break } ur1 := uuidRange{ min: ori.min, max: v.min - 1, } ur2 := uuidRange{ min: v.max + 1, max: ori.max, } if ur1.max >= ur1.min { res = append(res, ur1) } if ur2.max >= ur2.min { res = append(res, ur2) } } if len(res) == 0 && shouldAdd { res = append(res, ori) } newRg = append(newRg, res...) } return newRg } func containRange(origin, rg []uuidRange) bool { sort.Sort(SortRange(rg)) sort.Sort(SortRange(origin)) for i := 0; i < len(rg); i++ { ft := rg[i] found := false for _, v := range origin { if v.min <= ft.min && v.max >= ft.max { found = true } } if !found { return false } } return true } func overlapRange(origin, rg []uuidRange) bool { sort.Sort(SortRange(rg)) sort.Sort(SortRange(origin)) for i := 0; i < len(origin); i++ { ori := origin[i] for _, v := range rg { if v.min <= ori.min && v.max >= ori.max { return true } if v.max < ori.min { continue } if v.min > ori.max { break } ur1 := uuidRange{ min: ori.min, max: v.min - 1, } ur2 := uuidRange{ min: v.max + 1, max: ori.max, } if ur1.max >= ur1.min { return true } if ur2.max >= ur2.min { return true } } } return false } func (g *Gtid) uniformNumber(num []string) ([]uuidRange, error) { rg := make([]uuidRange, 0, len(num)) for _, v := range num { ret := uuidRange{} if splitPos := strings.Index(v, "-"); -1 != splitPos { firstPart := string(v[0:splitPos]) if i64, err := strconv.ParseUint(firstPart, 10, 64); nil == err { ret.min = i64 } else { return rg, fmt.Errorf("invalid number %v", firstPart) } secondPart := string(v[splitPos+1:]) if i64, err := strconv.ParseUint(secondPart, 10, 64); nil == err { ret.max = i64 } else { return rg, fmt.Errorf("invalid number %v", secondPart) } } else { if i64, err := strconv.ParseUint(v, 10, 64); nil == err { ret.min = i64 ret.max = i64 } else { return rg, fmt.Errorf("invalid number %v", v) } } rg = append(rg, ret) } return rg, nil } func (g *Gtid) isValidUUID(uuid string) (string, error) { uuid = strings.TrimSpace(strings.ToLower(strings.Replace(uuid, "-", "", -1))) if 32 != len(uuid) { return "", errors.New("invalid uuid" + uuid) } return uuid[0:8] + "-" + uuid[8:12] + "-" + uuid[12:16] + "-" + uuid[16:20] + "-" + uuid[20:], nil } func (g *Gtid) String() (ret string) { if g.changed { g.mu.Lock() g.reform() g.mu.Unlock() g.changed = false } for _, uuidNumber := range g.uuidNumbers { s := uuidNumber.uuid for _, interval := range uuidNumber.intervals { if interval.min == interval.max { s = s + ":" + strconv.FormatUint(interval.min, 10) } else { s = s + ":" + strconv.FormatUint(interval.min, 10) + "-" + strconv.FormatUint(interval.max, 10) } } if "" != ret { ret = ret + "," } ret = ret + s } return ret } func (g *Gtid) AddGtid(uuid string, number uint64) error { if strings.TrimSpace(uuid) == "" { return nil } uuid, err := g.isValidUUID(uuid) if err != nil { return err } g.mu.RLock() id, ok := g.hashMap[uuid] g.mu.RUnlock() g.mu.Lock() defer g.mu.Unlock() if ok { tmp := g.uuidNumbers[id].intervals if len(tmp) > 0 && tmp[len(tmp)-1].max+1 == number { g.uuidNumbers[id].intervals[len(tmp)-1].max = number } else { g.uuidNumbers[id].intervals = append(g.uuidNumbers[id].intervals, uuidRange{ min: number, max: number, }) } } else { g.uuidNumbers = append(g.uuidNumbers, UUIDNumber{ uuid: uuid, intervals: []uuidRange{uuidRange{ min: number, max: number, }}, }) g.hashMap[uuid] = len(g.uuidNumbers) - 1 } if len(g.uuidNumbers[id].intervals) > 5000 { g.reform() } g.changed = true return nil } func (g *Gtid) Add(gtidDesc string) error { return g.add(gtidDesc) } func (g *Gtid) add(gtidDesc string) error { tmpSlice := make([]UUIDNumber, 0) gtidlists := strings.Split(gtidDesc, ",") for _, v := range gtidlists { numbers := strings.Split(v, ":") if len(numbers) < 2 { return fmt.Errorf("invalid format:%v", v) } uuid, err := g.isValidUUID(numbers[0]) if err != nil { return err } num, err := g.uniformNumber(numbers[1:]) if err != nil { return err } tmpSlice = append(tmpSlice, UUIDNumber{ uuid: uuid, intervals: num, }) } g.changed = true g.mu.Lock() defer g.mu.Unlock() for _, v := range tmpSlice { id, ok := g.hashMap[v.uuid] if ok { n := g.uuidNumbers[id].intervals n = append(n, v.intervals...) g.uuidNumbers[id].intervals = n } else { g.uuidNumbers = append(g.uuidNumbers, v) g.hashMap[v.uuid] = len(g.uuidNumbers) - 1 } if len(g.uuidNumbers[id].intervals) > 5000 { g.reform() } } return nil } func (g *Gtid) Sub(gtidDesc string) error { return g.sub(gtidDesc) } func (g *Gtid) sub(gtidDesc string) error { if strings.TrimSpace(gtidDesc) == "" { return nil } tmpSlice := make([]UUIDNumber, 0) gtidlists := strings.Split(gtidDesc, ",") for _, v := range gtidlists { numbers := strings.Split(v, ":") if len(numbers) < 2 { return fmt.Errorf("invalid format:%v", v) } uuid, err := g.isValidUUID(numbers[0]) if err != nil { return err } num, err := g.uniformNumber(numbers[1:]) if err != nil { return err } tmpSlice = append(tmpSlice, UUIDNumber{ uuid: uuid, intervals: num, }) } g.changed = true g.mu.Lock() defer g.mu.Unlock() for _, v := range tmpSlice { id, ok := g.hashMap[v.uuid] if ok { n := subRange(g.uuidNumbers[id].intervals, v.intervals) n, _ = g.uniformRange(n) if len(n) == 0 || (len(n) == 1 && n[0].max == 0) { delete(g.hashMap, v.uuid) g.uuidNumbers = append(g.uuidNumbers[:id], g.uuidNumbers[id+1:]...) for k, v := range g.uuidNumbers { g.hashMap[v.uuid] = k } continue } else { g.uuidNumbers[id].intervals = n } } else { continue } if len(g.uuidNumbers[id].intervals) > 5000 { g.reform() } } return nil } func (g *Gtid) ContainGtid(uuid string, number uint64) (bool, error) { if strings.TrimSpace(uuid) == "" { return true, nil } uuid, err := g.isValidUUID(uuid) if err != nil { return false, err } g.mu.Lock() defer g.mu.Unlock() id, ok := g.hashMap[uuid] if ok { if g.changed { g.reform() } for _, v := range g.uuidNumbers[id].intervals { if v.min <= number && v.max >= number { return true, nil } } } return false, nil } func (g *Gtid) Contain(gtidDesc string) (bool, error) { return g.contain(gtidDesc) } func (g *Gtid) contain(gtidDesc string) (bool, error) { if strings.TrimSpace(gtidDesc) == "" { return true, nil } tmpSlice := make([]UUIDNumber, 0) gtidlists := strings.Split(gtidDesc, ",") for _, v := range gtidlists { numbers := strings.Split(v, ":") if len(numbers) < 2 { return false, fmt.Errorf("invalid format:%v", v) } uuid, err := g.isValidUUID(numbers[0]) if err != nil { return false, err } num, err := g.uniformNumber(numbers[1:]) if err != nil { return false, err } tmpSlice = append(tmpSlice, UUIDNumber{ uuid: uuid, intervals: num, }) } g.mu.Lock() defer g.mu.Unlock() for _, v := range tmpSlice { id, ok := g.hashMap[v.uuid] if ok { if !containRange(g.uuidNumbers[id].intervals, v.intervals) { return false, nil } } else { return false, nil } } return true, nil } func (g *Gtid) Overlap(gtidDesc string) (bool, error) { return g.overlap(gtidDesc) } func (g *Gtid) overlap(gtidDesc string) (bool, error) { if strings.TrimSpace(gtidDesc) == "" { if g.String() == "" { return true, nil } return false, nil } tmpSlice := make([]UUIDNumber, 0) gtidlists := strings.Split(gtidDesc, ",") for _, v := range gtidlists { numbers := strings.Split(v, ":") if len(numbers) < 2 { return false, fmt.Errorf("invalid format:%v", v) } uuid, err := g.isValidUUID(numbers[0]) if err != nil { return false, err } num, err := g.uniformNumber(numbers[1:]) if err != nil { return false, err } tmpSlice = append(tmpSlice, UUIDNumber{ uuid: uuid, intervals: num, }) } g.mu.Lock() defer g.mu.Unlock() for _, v := range tmpSlice { id, ok := g.hashMap[v.uuid] if ok { if overlapRange(g.uuidNumbers[id].intervals, v.intervals) { return true, nil } } else { continue } } return false, nil } func (g *Gtid) Equal(gtidDesc string) (bool, error) { if g.String() == "" && strings.TrimSpace(gtidDesc) == "" { return true, nil } equ, err := parse(gtidDesc) if err != nil { return false, err } return g.String() == equ.String(), nil } func (g *Gtid) Clone() *Gtid { g.mu.Lock() defer g.mu.Unlock() var newGtid = &Gtid{} newGtid.uuidNumbers = make([]UUIDNumber, len(g.uuidNumbers)) copy(newGtid.uuidNumbers, g.uuidNumbers) newGtid.hashMap = make(map[string]int, len(g.hashMap)) for k, v := range g.hashMap { newGtid.hashMap[k] = v } if g.changed { newGtid.reform() } return newGtid } func (g *Gtid) EventCount() uint64 { g.mu.Lock() defer g.mu.Unlock() if g.changed { g.reform() } var ret uint64 = 0 for _, uuidNumber := range g.uuidNumbers { for _, interval := range uuidNumber.intervals { ret += interval.max - interval.min + 1 } } return ret } func (g *Gtid) EventList() []string { g.mu.Lock() defer g.mu.Unlock() if g.changed { g.reform() } eventList := []string{} for _, uuidNumber := range g.uuidNumbers { for _, interval := range uuidNumber.intervals { for i := interval.min; i <= interval.max; i++ { eventList = append(eventList, fmt.Sprintf("%s:%d", uuidNumber.uuid, i)) } } } return eventList }