gorqlite

package module
v0.0.0-...-2e5113c Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2022 License: MIT Imports: 9 Imported by: 0

README

gorqlite

build status PkgGoDev

A client library for rqlite, the lightweight, distributed database built on SQLite. This package is designed to provide a Go interface for the RQLite API endpoints.

In Progress

See TODO.md.

Usage

See documentation for usage.

Examples

Connect

Connects to an rqlite cluster and creates a table.

addrs := []string{"node-1:8423", "node-2:2841", "node-3"}
conn := gorqlite.Open(addrs)

// Create a table with a single statement.
execResult, err := conn.ExecuteOne(
  "CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT, age INTEGER)",
)
if err != nil {
  panic(err)
}
if execResult.Error != "" {
  panic(execResult.Error)
}
Execute Then Query

Inserts a row then selects the row.

// Insert multiple entries in one call.
execResults, err := conn.Execute([]string{
  `INSERT INTO foo(name, age) VALUES(\"fiona\", 20)`,
  `INSERT INTO foo(name, age) VALUES(\"sinead\", 24)`,
})
if err != nil {
  panic(err)
}
if execResults.HasError() {
  panic(execResults.GetFirstError())
}
for _, r := range execResults {
  fmt.Println("id of the inserted row:", r.LastInsertId)
  fmt.Println("rows affected:", r.RowsAffected)
}

// Query the results.
queryResult, err := conn.QueryOne("SELECT * FROM foo")
if err != nil {
  panic(err)
}
if queryResult.Error != "" {
  panic(queryResult.Error)
}

// Scan the results into variables.
for {
  row, ok := queryResult.Next()
  if !ok {
    break
  }

  var id int
  var name string
  if err = row.Scan(&id, &name); err != nil {
    panic(err)
  }
  fmt.Println("ID:", id, "Name:", name)
}
Custom Options

Add default and method override options.

conn := gorqlite.Open(cluster.Addrs(), gorqlite.WithActiveHostRoundRobin(false))

execResult, err := conn.ExecuteOne(
  "CREATE TABLE foo (id integer not null primary key, name text)",
)
if err != nil {
  log.Fatal(err)
}
if execResult.Error != "" {
  log.Fatal(execResult.Error)
}

// Execute the statements within a transaction.
execResults, err := conn.Execute([]string{
  `INSERT INTO foo(name) VALUES("foo")`,
  `INSERT INTO foo(name) VALUES("bar")`,
}, gorqlite.WithTransaction(true))
if err != nil {
  log.Fatal(err)
}
if execResults.HasError() {
  log.Fatal(execResults.GetFirstError())
}

// Query the table with strong consistency.
sql := []string{
  `SELECT * FROM foo WHERE id="1"`,
  `SELECT * FROM foo WHERE name="bar"`,
}
queryResult, err := conn.Query(sql, gorqlite.WithConsistency("strong"))
if err != nil {
  log.Fatal(err)
}
if queryResult.HasError() {
  log.Fatal(queryResult.GetFirstError())
}
for _, result := range queryResult {
  for {
    row, ok := result.Next()
    if !ok {
      break
    }

    var id int
    var name string
    if err = row.Scan(&id, &name); err != nil {
      log.Fatal(err)
    }
    log.Info("id:", id)
    log.Info("name:", name)
  }
}

Testing

Tests are split into unit tests and system tests.

Unit Tests

Unit tests are covered in the packages themselves. These are restricted to running in a single thread, with no sleeps and no external accesses (network, files, ...). These can be run quickly with

make test
System Tests

System tests run gorqlite against real rqlite nodes. These nodes are spun up and down as a cluster within the test itself (see tests/cluster). A new cluster is created per test to avoid any side affects (such as some tests for verifying node failover will terminate random nodes in the cluster).

These are disabled by default using the system build tag. For additional logging the environment variable DEBUG=true can also be used (along with -v flag to see the log output).

[DEBUG=true] go test ./... -tags system [-v]
# or just `make system-test`

For convenience a docker environment is provided that already has Go and rqlite installed.

make env

The logs for each node can be found in tests/log.

Documentation

Overview

Package gorqlite implements an rqlite client.

Example
package main

import (
	"fmt"

	"github.com/dunstall/gorqlite"
)

func main() {
	addrs := []string{"node-1:8423", "node-2:2841", "node-3"}
	conn := gorqlite.Open(addrs)

	// Create a table with a single statement.
	execResult, err := conn.ExecuteOne(
		"CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT, age INTEGER)",
	)
	if err != nil {
		panic(err)
	}
	if execResult.Error != "" {
		panic(execResult.Error)
	}

	// Insert multiple entries in one call.
	execResults, err := conn.Execute([]string{
		`INSERT INTO foo(name, age) VALUES(\"fiona\", 20)`,
		`INSERT INTO foo(name, age) VALUES(\"sinead\", 24)`,
	})
	if err != nil {
		panic(err)
	}
	if execResults.HasError() {
		panic(execResults.GetFirstError())
	}
	for _, r := range execResults {
		fmt.Println("id of the inserted row:", r.LastInsertId)
		fmt.Println("rows affected:", r.RowsAffected)
	}

	// Query the results.
	queryResult, err := conn.QueryOne("SELECT * FROM foo")
	if err != nil {
		panic(err)
	}
	if queryResult.Error != "" {
		panic(queryResult.Error)
	}

	// Scan the results into variables.
	for {
		row, ok := queryResult.Next()
		if !ok {
			break
		}

		var id int
		var name string
		if err = row.Scan(&id, &name); err != nil {
			panic(err)
		}
		fmt.Println("ID:", id, "Name:", name)
	}
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type APIClient

type APIClient interface {
	Get(path string, query url.Values) (*http.Response, error)
	GetWithContext(ctx context.Context, path string, query url.Values) (*http.Response, error)
	Post(path string, query url.Values, body []byte) (*http.Response, error)
	PostWithContext(ctx context.Context, path string, query url.Values, body []byte) (*http.Response, error)
}

type ExecuteOption

type ExecuteOption func(conf *executeConfig)

func WithTransaction

func WithTransaction(transaction bool) ExecuteOption

WithTransaction sets the transaction query parameter when enabled. See https://github.com/rqlite/rqlite/blob/cc74ab0af7c128582b7f0fd380033d43e642a121/DOC/DATA_API.md#transactions.

Disabed by default.

type ExecuteResult

type ExecuteResult struct {
	LastInsertId int64  `json:"last_insert_id,omitempty"`
	RowsAffected int64  `json:"rows_affected,omitempty"`
	Error        string `json:"error,omitempty"`
}

type ExecuteResults

type ExecuteResults []ExecuteResult

func (ExecuteResults) GetFirstError

func (r ExecuteResults) GetFirstError() string

func (ExecuteResults) HasError

func (r ExecuteResults) HasError() bool

type Gorqlite

type Gorqlite struct {
	// contains filtered or unexported fields
}

Gorqlite is a client for the rqlite API endpoints.

func Open

func Open(hosts []string, opts ...Option) *Gorqlite

Open opens the gorqlite client. This will not attempt to connect to the database.

hosts is a list of addresses (in format host[:port]) for the known nodes in the cluster.

opts is a list of opts to apply to every request.

func OpenWithClient

func OpenWithClient(apiClient APIClient, opts ...Option) *Gorqlite

OpenWithClient opens a connection to rqlite using a custom API client.

func (*Gorqlite) Execute

func (g *Gorqlite) Execute(sql []string, opts ...ExecuteOption) (ExecuteResults, error)

Execute writes the sql statements to rqlite and returns the execute results. See https://github.com/rqlite/rqlite/blob/cc74ab0af7c128582b7f0fd380033d43e642a121/DOC/DATA_API.md#writing-data.

To execute the statements within a transaction use WithTransaction(true) option. See https://github.com/rqlite/rqlite/blob/cc74ab0af7c128582b7f0fd380033d43e642a121/DOC/DATA_API.md#transactions.

Example

Demonstrates executing statements within a transaction.

package main

import (
	"github.com/dunstall/gorqlite"
)

var nodeAddrs = []string{"node-1:8423", "node-2:2841", "node-3"}

func main() {
	conn := gorqlite.Open(nodeAddrs)

	// Execute the statements within a transaction.
	execResults, err := conn.Execute([]string{
		`INSERT INTO foo(name) VALUES("fiona")`,
		`INSERT INTO foo(name) VALUES("sinead")`,
	}, gorqlite.WithTransaction(true))
	if err != nil {
		panic(err)
	}
	if execResults.HasError() {
		panic(execResults.GetFirstError())
	}
}

func (*Gorqlite) ExecuteOne

func (g *Gorqlite) ExecuteOne(sql string, opts ...ExecuteOption) (ExecuteResult, error)

func (*Gorqlite) ExecuteOneWithContext

func (g *Gorqlite) ExecuteOneWithContext(ctx context.Context, sql string, opts ...ExecuteOption) (ExecuteResult, error)

func (*Gorqlite) ExecuteWithContext

func (g *Gorqlite) ExecuteWithContext(ctx context.Context, sql []string, opts ...ExecuteOption) (ExecuteResults, error)

func (*Gorqlite) NodesWithContext

func (g *Gorqlite) NodesWithContext(ctx context.Context, opts ...NodesOption) (Nodes, error)

func (*Gorqlite) Query

func (g *Gorqlite) Query(sql []string, opts ...QueryOption) (QueryResults, error)

Query runs the given sql statements and returns the query result. See https://github.com/rqlite/rqlite/blob/cc74ab0af7c128582b7f0fd380033d43e642a121/DOC/DATA_API.md#querying-data.

To execute the statements with a custom consistency level use WithConsistency(level). See https://github.com/rqlite/rqlite/blob/cc74ab0af7c128582b7f0fd380033d43e642a121/DOC/CONSISTENCY.md.

Example

Demonstrates querying the database with strong consistency configured.

package main

import (
	"github.com/dunstall/gorqlite"
)

var nodeAddrs = []string{"node-1:8423", "node-2:2841", "node-3"}

func main() {
	conn := gorqlite.Open(nodeAddrs)

	// Query the table with strong consistency.
	queryResult, err := conn.QueryOne(
		"SELECT * FROM foo",
		gorqlite.WithConsistency("strong"),
	)
	if err != nil {
		panic(err)
	}
	if queryResult.Error != "" {
		panic(queryResult.Error)
	}
}

func (*Gorqlite) QueryOne

func (g *Gorqlite) QueryOne(sql string, opts ...QueryOption) (QueryResult, error)

func (*Gorqlite) QueryOneWithContext

func (g *Gorqlite) QueryOneWithContext(ctx context.Context, sql string, opts ...QueryOption) (QueryResult, error)

func (*Gorqlite) QueryWithContext

func (g *Gorqlite) QueryWithContext(ctx context.Context, sql []string, opts ...QueryOption) (QueryResults, error)

func (*Gorqlite) Status

func (g *Gorqlite) Status() (Status, error)

Status queries the rqlite status API. See https://github.com/rqlite/rqlite/blob/cc74ab0af7c128582b7f0fd380033d43e642a121/DOC/DIAGNOSTICS.md#status-and-diagnostics-api.

Example
package main

import (
	"fmt"

	"github.com/dunstall/gorqlite"
)

var nodeAddrs = []string{"node-1:8423", "node-2:2841", "node-3"}

func main() {
	conn := gorqlite.Open(nodeAddrs)

	status, err := conn.Status()
	if err != nil {
		panic(err)
	}
	fmt.Println("leader", status.Store.Leader.Addr)
}

func (*Gorqlite) StatusWithContext

func (g *Gorqlite) StatusWithContext(ctx context.Context) (Status, error)

type LeaderInfo

type LeaderInfo struct {
	Addr   string `json:"addr,omitempty"`
	NodeID string `json:"node_id,omitempty"`
}

type NodeInfo

type NodeInfo struct {
	Addr     string `json:"addr,omitempty"`
	ID       string `json:"id,omitempty"`
	Suffrage string `json:"suffrage,omitempty"`
}

type Nodes

type Nodes map[string]struct {
	APIAddr   string  `json:"api_addr,omitempty"`
	Addr      string  `json:"addr,omitempty"`
	Reachable bool    `json:"reachable"`
	Leader    bool    `json:"leader"`
	Time      float64 `json:"time,omitempty"`
	Error     string  `json:"error,omitempty"`
}

type NodesOption

type NodesOption func(conf *nodesConfig)

func WithNonVoters

func WithNonVoters(nonVoters bool) NodesOption

WithTransaction sets the nonvoters query parameter when enabled. See https://github.com/rqlite/rqlite/blob/cc74ab0af7c128582b7f0fd380033d43e642a121/DOC/DIAGNOSTICS.md#nodes-api.

Disabed by default.

type Option

type Option func(conf *config)

Options overrides the default configuration used for each request.

A set of default options can be set in gorqlite.Open, and each method accepts options to override the defaults for that request only.

func WithActiveHostRoundRobin

func WithActiveHostRoundRobin(enabled bool) Option

WithActiveHostRoundRobin load balances requests among all known nodes in a round robin strategy if enabled. Otherwise will always try nodes in order until one works.

Enabled by default.

type QueryOption

type QueryOption func(conf *queryConfig)

func WithConsistency

func WithConsistency(consistency string) QueryOption

WithConsistency sets the level query parameter if set, otherwise it is not set (so rqlite will default to weak consistency). See https://github.com/rqlite/rqlite/blob/cc74ab0af7c128582b7f0fd380033d43e642a121/DOC/CONSISTENCY.md.

type QueryResult

type QueryResult struct {
	Columns []string        `json:"columns,omitempty"`
	Values  [][]interface{} `json:"values,omitempty"`
	Error   string          `json:"error,omitempty"`
	// contains filtered or unexported fields
}

func (*QueryResult) Next

func (r *QueryResult) Next() (*QueryRow, bool)

func (*QueryResult) Rows

func (r *QueryResult) Rows() int

type QueryResults

type QueryResults []QueryResult

func (QueryResults) GetFirstError

func (r QueryResults) GetFirstError() string

func (QueryResults) HasError

func (r QueryResults) HasError() bool

type QueryRow

type QueryRow struct {
	Columns []string
	Values  []interface{}
}

type Status

type Status struct {
	Build   StatusBuild   `json:"build,omitempty"`
	Cluster StatusCluster `json:"cluster,omitempty"`
	HTTP    StatusHTTP    `json:"http,omitempty"`
	Node    StatusNode    `json:"node,omitempty"`
	OS      StatusOS      `json:"os,omitempty"`
	Runtime StatusRuntime `json:"runtime,omitempty"`
	Store   StatusStore   `json:"store,omitempty"`
}

type StatusBuild

type StatusBuild struct {
	Branch    string `json:"branch,omitempty"`
	BuildTime string `json:"build_time,omitempty"`
	Commit    string `json:"commit,omitempty"`
	Compiler  string `json:"compiler,omitempty"`
	Version   string `json:"version,omitempty"`
}

type StatusCluster

type StatusCluster struct {
	Addr    string `json:"addr,omitempty"`
	APIAddr string `json:"api_addr,omitempty"`
	HTTPS   string `json:"https,omitempty"`
}

type StatusHTTP

type StatusHTTP struct {
	Auth     string `json:"auth,omitempty"`
	BindAddr string `json:"bind_addr,omitempty"`
}

type StatusNode

type StatusNode struct {
	StartTime string `json:"start_time,omitempty"`
	Uptime    string `json:"uptime,omitempty"`
}

type StatusOS

type StatusOS struct {
	Executable string `json:"executable,omitempty"`
	Hostname   string `json:"hostname,omitempty"`
	PageSize   int    `json:"page_size,omitempty"`
	Pid        int    `json:"pid,omitempty"`
	Ppid       int    `json:"ppid,omitempty"`
}

type StatusRuntime

type StatusRuntime struct {
	GoArch       string `json:"GOARCH,omitempty"`
	GoMaxProcs   int    `json:"GOMAXPROCS,omitempty"`
	GoOS         string `json:"GOOS,omitempty"`
	NumCPU       int    `json:"num_cpu,omitempty"`
	NumGoroutine int    `json:"num_goroutine,omitempty"`
	Version      string `json:"version,omitempty"`
}

type StatusStore

type StatusStore struct {
	Addr             string     `json:"addr,omitempty"`
	ApplyTimeout     string     `json:"apply_timeout,omitempty"`
	DBAppliedIndex   int        `json:"db_applied_index,omitempty"`
	Dir              string     `json:"dir,omitempty"`
	DirSize          int        `json:"dir_size,omitempty"`
	ElectionTimeout  string     `json:"election_timeout,omitempty"`
	FSMIndex         int        `json:"fsm_index,omitempty"`
	HeartbeatTimeout string     `json:"heartbeat_timeout,omitempty"`
	Leader           LeaderInfo `json:"leader,omitempty"`
	NodeID           string     `json:"node_id,omitempty"`
	Nodes            []NodeInfo `json:"nodes,omitempty"`
}

Directories

Path Synopsis
Package cluster handles creating a cluster of local rqlite nodes and injecting faults into the cluster.
Package cluster handles creating a cluster of local rqlite nodes and injecting faults into the cluster.
mocks
api
Package mock_gorqlite is a generated GoMock package.
Package mock_gorqlite is a generated GoMock package.
http_api
Package mock_gorqlite is a generated GoMock package.
Package mock_gorqlite is a generated GoMock package.

Jump to

Keyboard shortcuts

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