sequence

package module
v0.0.0-...-f108622 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 24, 2017 License: Apache-2.0 Imports: 4 Imported by: 1

README

Go-Sequence

Build Status Coverage Status Go Report Card GoDoc

Implements an AutoIncrement counter class similar to PostgreSQL's sequence.

This library provides monotonically increasing and decreasing sequences similar to the autoincrement sequence objects that are available in databases like PostgreSQL. Unlike simple counter objects, Sequence objects are bred for safety, that is they expose the largest possible range of positive integers using the uint64 data type and raise exceptions when that number overflows or when the increment function does something unexpected. Moreover, the internal state of a Sequence is not accessible by external libraries and therefore cannot be modified (except for a straight-up reset), giving developers confidence to use these objects in sequence-critical usage such as automatically incrementing IDs.

Getting Started

To install the sequence library, simply go get it from GitHub:

$ go get github.com/bbengfort/sequence

For more specifics, please read the API documentation.

Basic Usage

The basic usage is to create a default, monotonically incrementing by 1 sequence that starts at 1 and goes until 18,446,744,073,709,551,614 (the largest possible uint64 value).

import github.com/bbengfort/sequence

seq := sequence.New()
idx, err := seq.Next()

A range can be specified using different arguments to New. For example, to specify a different maximal value:

seq := sequence.New(1000)

Will produce a sequence from 1 to 1000 (inclusive) and will return errors when the state goes beyond 1000. A specifically bounded range:

seq := sequence.New(10, 100)

Will provide a sequence on the integers from 10 to 100 (inclusive). Finally a step can be provided to determine how the sequence is incremented:

seq := sequence.New(2, 500, 2)

Which will return all the even numbers from 2 to 500 (inclusive). Sequences can be reset, returning them to their original state as follows:

err := seq.Reset()

Sequences can also be updated to a specific value, so long as the value doesn't violate the monotonically increasing or decreasing rule. If it does, the Update() function will return an error:

err := seq.Update(42) // err == nil
seq.Next()
err = seq.Update(42) // err != nil

Note that the Reset() method is the only function that could violate the monotonicity of the Sequence object.

Sequence State

To get the state of a sequence, you can use the following methods:

seq.IsStarted() // Returns a boolean value if the Sequence is started.

// Get the current value of the sequence
idx, err := seq.Current()

// Print a string representation of the sequence state.
fmt.Println(seq.String())

You can also serialize and deserialize the Sequence to pass it across processes as follows:

data, err := seq.Dump()
seq2 := &sequence.Sequence{}
err := seq2.Load(data)

This snippet of code will result in seq2 having an identical state to seq at the moment that it was dumped.

Development

Pull requests are more than welcome to help develop this project!

Testing

To execute tests for this library:

$ make test

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

Examples

Constants

View Source
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.

View Source
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

func New(params ...uint64) (*Sequence, error)

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

func (s *Sequence) Current() (uint64, error)

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

func (s *Sequence) Dump() ([]byte, error)

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

func (s *Sequence) 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.

func (*Sequence) IsStarted

func (s *Sequence) IsStarted() bool

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

func (s *Sequence) Load(data []byte) error

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

func (s *Sequence) 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.

func (*Sequence) Restart

func (s *Sequence) 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.

func (*Sequence) String

func (s *Sequence) String() 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

func (s *Sequence) 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.

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL