blob: 356c6b2b60e3ea1bf1b2b5211536858c6296888a [file] [log] [blame]
/*
Copyright 2016 The Transicator Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package common
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"regexp"
"strconv"
)
var sequenceRe = regexp.MustCompile("([a-fA-F0-9]+)\\.([a-fA-F0-9]+)\\.([a-fA-F0-9]+)")
var networkByteOrder = binary.BigEndian
/*
A Sequence represents a unique position in the list of changes. It consists
of a postgres LSN that represents when a transaction was committed, and a
index into the list of changes in that transaction.
*/
type Sequence struct {
LSN uint64
Index uint32
}
/*
MakeSequence makes a new sequence from an LSN and an index.
*/
func MakeSequence(lsn uint64, index uint32) Sequence {
return Sequence{
LSN: lsn,
Index: index,
}
}
/*
ParseSequence parses a stringified sequence.
*/
func ParseSequence(s string) (Sequence, error) {
parsed := sequenceRe.FindStringSubmatch(s)
if parsed == nil {
return Sequence{}, errors.New("Invalid sequence string")
}
l1, err := strconv.ParseUint(parsed[1], 16, 32)
if err != nil {
return Sequence{}, fmt.Errorf("Invalid sequence: %s", err)
}
l2, err := strconv.ParseUint(parsed[2], 16, 32)
if err != nil {
return Sequence{}, fmt.Errorf("Invalid sequence: %s", err)
}
ix, err := strconv.ParseUint(parsed[3], 16, 32)
if err != nil {
return Sequence{}, fmt.Errorf("Invalid sequence: %s", err)
}
return Sequence{
LSN: (l1 << 32) | l2,
Index: uint32(ix),
}, nil
}
/*
ParseSequenceBytes parses bytes written using Bytes().
As a convenience, an empty sequence parses to the default
value for the Sequence type.
*/
func ParseSequenceBytes(b []byte) (Sequence, error) {
if len(b) == 0 {
return Sequence{}, nil
}
if len(b) != 12 {
return Sequence{}, errors.New("Invalid byte sequence")
}
buf := bytes.NewBuffer(b)
var lsn uint64
err := binary.Read(buf, networkByteOrder, &lsn)
if err != nil {
return Sequence{}, fmt.Errorf("Invalid byte sequence: %s", err)
}
var ix uint32
err = binary.Read(buf, networkByteOrder, &ix)
if err != nil {
return Sequence{}, fmt.Errorf("Invalid byte sequence: %s", err)
}
return Sequence{
LSN: lsn,
Index: ix,
}, nil
}
/*
String turns the sequence into the canonical string form, which looks
like this: "XXX.YYY.ZZZ," where each component is a hexadecimal number.
This is different from the standard Postgres format, which includes a
slash in the first part, in that it doesn't need URL encoding.
*/
func (s Sequence) String() string {
return strconv.FormatUint(s.LSN>>32, 16) + "." +
strconv.FormatUint(s.LSN&0xffffffff, 16) + "." +
strconv.FormatUint(uint64(s.Index), 16)
}
/*
Bytes turns the sequence into an array of 12 bytes, in "network" (aka
big-endian) byte order.
*/
func (s Sequence) Bytes() []byte {
buf := &bytes.Buffer{}
binary.Write(buf, networkByteOrder, s.LSN)
binary.Write(buf, networkByteOrder, s.Index)
return buf.Bytes()
}
/*
Compare returns if the current sequence is less than, greater to, or
equal to the specified sequence.
*/
func (s Sequence) Compare(o Sequence) int {
if s.LSN < o.LSN {
return -1
}
if s.LSN > o.LSN {
return 1
}
if s.Index < o.Index {
return -1
}
if s.Index > o.Index {
return 1
}
return 0
}