Documentation
¶
Overview ¶
Package sequence provides monotonically increasing counters similar to the autoincrement sequence objects that are available in databases like PostgreSQL. While this type of object is simple to implement, stateful representations are used in a wide variety of projects and this package is about having a standard API and interface to reuse these objects without fear. In particular, this project does not implement simple counters or iterables, but is designed to maximize the sequence space for constant growth and to raise errors when sequences become too large.
The primary interface is the Sequence object, which essentially wraps a 64-bit unsigned integer, providing the single largest positive integer range. The Sequence object implements the Incrementer interface, which specifies how to interact with the Sequence object.
Simple usage is as follows:
import github.com/bbengfort/sequence seq := sequence.New() idx, err := seq.Next()
By default, this will provide an integer sequence in the positive range 1 - 18,446,744,073,709,551,614. Note that sequences start at one as they are typically used to compute autoincrement positive ids.
The Sequence.Next() method will return an error if it reaches the maximum bound, which by default is the maximal uint64 value, such that incrementing will not start to repeat values. If the increment is negative, then the sequence will return an error if it reaches a minimum bound, which by default is 0 since the Sequence will always return positive values.
The Sequence object provides several helper methods to interact with it during long running processes, including Current(), IsStarted(), and String() methods which return information about the state of the Sequence.
Interacting with sequences across processes is provided through the Sequence.Dump and Sequence.Load methods, which serialize the Sequence to a []byte JSON representation. Note that this does not use the standard json.Marshal and json.Unmarshal interface in order to keep members of the sequence inaccessible outside the library, ensuring that a sequence cannot be modified except to be restarted.
Index ¶
- Constants
- type AtomicSequence
- func (s *AtomicSequence) Current() (uint64, error)
- func (s *AtomicSequence) Dump() ([]byte, error)
- func (s *AtomicSequence) Init(params ...uint64) error
- func (s *AtomicSequence) IsStarted() bool
- func (s *AtomicSequence) Load(data []byte) error
- func (s *AtomicSequence) Next() (uint64, error)
- func (s *AtomicSequence) Restart() error
- func (s *AtomicSequence) String() string
- func (s *AtomicSequence) Update(val uint64) error
- type Incrementer
- type Sequence
- func (s *Sequence) Current() (uint64, error)
- func (s *Sequence) Dump() ([]byte, error)
- func (s *Sequence) Init(params ...uint64) error
- func (s *Sequence) IsStarted() bool
- func (s *Sequence) Load(data []byte) error
- func (s *Sequence) Next() (uint64, error)
- func (s *Sequence) Restart() error
- func (s *Sequence) String() string
- func (s *Sequence) Update(val uint64) error
Examples ¶
Constants ¶
const MaximumBound = maxuint64
MaximumBound specifies the largest integer value of a Sequence object. This bound is currently set to the largest possible unsigned 64-bit integer: 18,446,744,073,709,551,614.
const MinimumBound = 1
MinimumBound specifies the smallest integer value of a Sequence object. Note that the minimum bound is not zero because zero values denote unintialized numeric values, and because counters generally do not index from zero, but rather from 1.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AtomicSequence ¶
type AtomicSequence Sequence
AtomicSequence is a basic Sequence that uses atomic instructions in Sequence methods. Although implementation is very close, it is safe for concurrent use.
func NewAtomic ¶
func NewAtomic(params ...uint64) (*AtomicSequence, error)
NewAtomic creates a NewSequence that implements the Incrementer interface. It is safe for concurrent use.
func (*AtomicSequence) Current ¶
func (s *AtomicSequence) Current() (uint64, error)
Current gives the current value of this sequence atomically.
func (*AtomicSequence) Dump ¶
func (s *AtomicSequence) Dump() ([]byte, error)
Dump uses atomic Loads to Marshal current data from a AtomicSequence into a JSON object
func (*AtomicSequence) Init ¶
func (s *AtomicSequence) Init(params ...uint64) error
Init a sequence with reasonable defaults based on the number and order of the numeric parameters passed into this method. By default, if no arguments are passed into Init, then the Sequence will be initialized as a monotonically increasing counter in the positive space as follows:
seq.Init() // count by 1 from 1 to MaximumBound
If only a single argument is passed in, then it is interpreted as the maximum bound as follows:
seq.Init(100) // count by 1 from 1 until 100.
If two arguments are passed in, then it is interpreted as a discrete range.
seq.Init(10, 100) // count by 1 from 10 until 100.
If three arguments are passed in, then the third is the step.
seq.Init(2, 100, 2) // even numbers from 2 until 100.
Both endpoints of these ranges are inclusive.
Init can return a variety of errors. The most common error is if Init is called twice - that is that an already initialized sequence is attempted to be modified in a way that doesn't reset it. This is part of the safety features that Sequence provides. Other errors include mismatched or non-sensical arguments that won't initialize the Sequence properly. It is done in an atomic way and is safe for concurrent use.
func (*AtomicSequence) IsStarted ¶
func (s *AtomicSequence) IsStarted() bool
IsStarted does atomic checks to see if this sequence has already started.
func (*AtomicSequence) Load ¶
func (s *AtomicSequence) Load(data []byte) error
Load loads data from Dump. If the input is not the same as the output from Dump() then it will return a error.
func (*AtomicSequence) Next ¶
func (s *AtomicSequence) Next() (uint64, error)
Next updates the state of the Sequence and return the next item in the sequence. It will return an error if either the minimum or the maximal value has been reached. It is done in an atomic way.
func (*AtomicSequence) Restart ¶
func (s *AtomicSequence) Restart() error
Restart the sequence by resetting the current value. This is the only method that allows direct manipulation of the sequence state which violates the monotonically increasing or decreasing rule. Use with care and as a fail safe if required. It is done in an atomic way.
func (*AtomicSequence) String ¶
func (s *AtomicSequence) String() string
String returns a human readable representation of this sequence.
func (*AtomicSequence) Update ¶
func (s *AtomicSequence) Update(val uint64) error
Update the sequence to the current value. If the update value violates the monotonically increasing or decreasing rule, an error is returned. It is done in an atomic way.
type Incrementer ¶
type Incrementer interface {
Init(params ...uint64) error // Initialize the Incrementer with values
Next() (uint64, error) // Get the next value in the sequence and update
Restart() error // Restarts the sequence if possible
Update(val uint64) error // Update the state of the Incrementer
Current() (uint64, error) // Returns the current value of the Incrementer
IsStarted() bool // Returns the state of the Incrementer
String() string // Returns a string representation of the state
Load(data []byte) error // Load the sequence from a serialized representation
Dump() ([]byte, error) // Dump the sequence to a serialized representation
}
Incrementer defines the interface for sequence-like objects. The primary diference between an Incrementer and other state-like iterables is that error handling is critical, and as a result many interaction methods like Next() and Restart() return an error. An Incrementer usually keep the state as private as possible to ensure that it can't be tampered with accidentally, and state is only revealed through the Current(), IsStarted(), and String() methods.
type Sequence ¶
type Sequence struct {
// contains filtered or unexported fields
}
Sequence implements an AutoIncrement counter class similar to the PostgreSQL sequence object. Sequence is the primary implementation of the Incrementer interface. Once a Sequence has been constructed either with the New() function that is part of this package or manually, it is expected that the state of the Sequence only changes via the two interaction methods - Next() and Restart(). Therefore, all internal members of Sequence are private to ensure outside libraries that the internal state of the sequence cannot be modified accidentally.
Sequences can be created manually as follows:
seq := new(Sequence) seq.Init()
However it is recommended to create Sequences with the New() function:
import github.com/bbengfort/sequence seq := sequence.New()
Sequence objects are intended to act as monotonically increasing counters, maximizing the positive integer value range. Sequences can be constructed to be monotonically decreasing counters in the positive range or constrained by different bounds by passing different arguments to the New() function or to the Init() method.
Sequences can also be serialized and deserialized to be passed between processes while still maintaining state. The final mechanism to create a Sequence is as follows:
data, err := seq.Dump()
if err == nil {
seq2 := &Sequence{}
seq2.Load(data)
}
This will create a second Sequence (seq2) that is identical to the state of the first Sequence (seq) when it was dumped.
Example ¶
Example of the Basic Usage
seq, _ := New() // Create a new monotonically increasing counter
// Fetch the first 10 sequence ids.
for {
idx, _ := seq.Next()
if idx > 10 {
break
}
fmt.Printf("%d ", idx)
}
Output: 1 2 3 4 5 6 7 8 9 10
func New ¶
New constructs a Sequence object, and is the simplest way to create a new monotonically increasing counter. By default (passing in no arguments), the Sequence counts by 1 from 1 to the MaximumBound value without returning errors. Additional arguments can be supplied, as described by the Init() method. In brief, zero or more of the following arguments can be supplied: maximum value, minimum value, and step. However the number and order of the arguments matters, see Init() for details.
The two most common Sequence constructors are:
seq := sequence.New() // monotonically increasing counter
Which counts from 1 until the max bound and is equivalent to:
seq := sequence.New(1, sequence.MaximumBound, 1)
Because the initialization is somewhat complex, New() can return an error, which is also defined by the Init() method.
func (*Sequence) Current ¶
Current value of the sequence. Normally the Next() method should be used to ensure only a single value is retrieved from the sequence. This method is used to compare the sequence state to another or to verify some external rule. Current will return an error if the sequence has not been started or initialized.
func (*Sequence) Dump ¶
Dump the sequence into a JSON binary representation for the current state. The data that is dumped from this method can be loaded by an uninitialized Sequence to bring it as up to date as the sequence state when it was dumped. This method is intended to allow cross process communication of the sequence state.
Note, however, that the autoincrement invariant is not satisfied during concurrent access. Therefore Dump and Load should be used with locks to ensure that the system does not end up diverging the state of the Sequence. It is up to the calling library to implement these locks.
Example ¶
Write a sequence to disk to be loaded later.
seq := new(Sequence)
seq.Init()
for i := 0; i < 10; i++ {
seq.Next()
}
data, _ := seq.Dump()
fmt.Println(string(data))
Output: {"current":10,"increment":1,"maxvalue":18446744073709551614,"minvalue":1}
func (*Sequence) Init ¶
Init a sequence with reasonable defaults based on the number and order of the numeric parameters passed into this method. By default, if no arguments are passed into Init, then the Sequence will be initialized as a monotonically increasing counter in the positive space as follows:
seq.Init() // count by 1 from 1 to MaximumBound
If only a single argument is passed in, then it is interpreted as the maximum bound as follows:
seq.Init(100) // count by 1 from 1 until 100.
If two arguments are passed in, then it is interpreted as a discrete range.
seq.Init(10, 100) // count by 1 from 10 until 100.
If three arguments are passed in, then the third is the step.
seq.Init(2, 100, 2) // even numbers from 2 until 100.
Both endpoints of these ranges are inclusive.
Init can return a variety of errors. The most common error is if Init is called twice - that is that an already initialized sequence is attempted to be modified in a way that doesn't reset it. This is part of the safety features that Sequence provides. Other errors include mismatched or non-sensical arguments that won't initialize the Sequence properly.
func (*Sequence) IsStarted ¶
IsStarted returns the state of the Sequence (started or stopped). This method returns true if the current value is greater than or equal to the minimum value and if it is less than the maximal value. This method will also return false if the Sequence is not yet initialized.
func (*Sequence) Load ¶
Load an uninitialized sequence from a JSON binary representation of the state of another sequence. The data should be exported from the sequence Dump method. If the data does not match the Sequence specification this method will return an error. Note that different versions of the sequence library could lead to errors.
Example ¶
An example of sequence serialization.
seq := new(Sequence)
seq.Init()
for i := 0; i < 10; i++ {
seq.Next()
}
data, _ := seq.Dump()
sequel := new(Sequence)
sequel.Load(data)
fmt.Println(sequel)
Output: Sequence at 10, incremented by 1 between 1 and 18446744073709551614
func (*Sequence) Next ¶
Next updates the state of the Sequence and return the next item in the sequence. It will return an error if either the minimum or the maximal value has been reached.
func (*Sequence) Restart ¶
Restart the sequence by resetting the current value. This is the only method that allows direct manipulation of the sequence state which violates the monotonically increasing or decreasing rule. Use with care and as a fail safe if required.
func (*Sequence) String ¶
String returns a human readable representation of the sequence.
Example ¶
An example of the human readable state of a sequence.
seq, _ := New() fmt.Println(seq) seq.Next() fmt.Println(seq)
Output: Unstarted Sequence incremented by 1 between 1 and 18446744073709551614 Sequence at 1, incremented by 1 between 1 and 18446744073709551614
func (*Sequence) Update ¶
Update the sequence to the current value. If the update value violates the monotonically increasing or decreasing rule, an error is returned.
Example ¶
An example of updating a sequence.
var idx uint64 seq, _ := New() seq.Next() idx, _ = seq.Current() fmt.Println(idx) seq.Update(42) idx, _ = seq.Current() fmt.Println(idx) seq.Next() idx, _ = seq.Current() fmt.Println(idx) err := seq.Update(42) fmt.Println(err)
Output: 1 42 43 cannot decrease monotonically increasing sequence