bolt

package
v3.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 1, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package bolt implements a bbolt (go.etcd.io/bbolt) backed implementation of the storage interface: an embedded, persistent, pure-Go key/value store that fills the gap between the volatile `memory` adapter and the one-file-per-document `file` adapter. Every `target` maps to a bucket inside a single on-disk database file.

Example (Count)

Example_count reports how many keys in a bucket match a Search glob.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/thalesfsp/dal/v3/bolt"
	"github.com/thalesfsp/params/v2/count"
	"github.com/thalesfsp/params/v2/create"
)

// user is the example payload stored and retrieved below.
type user struct {
	Name string `json:"name"`
}

// newExampleStorage opens a throwaway bbolt database under a fresh temp
// directory and returns it along with a cleanup func. Example functions
// can't take a *testing.T, so setup failures — which would never happen in
// a working environment — panic instead of calling t.Fatal.
func newExampleStorage() (*bolt.Bolt, func()) {
	dir, err := os.MkdirTemp("", "dal-bolt-example-*")
	if err != nil {
		panic(err)
	}

	str, err := bolt.New(context.Background(), filepath.Join(dir, "example.db"), nil)
	if err != nil {
		if rmErr := os.RemoveAll(dir); rmErr != nil {
			panic(rmErr)
		}

		panic(err)
	}

	return str, func() {
		if err := str.Close(); err != nil {
			panic(err)
		}

		if err := os.RemoveAll(dir); err != nil {
			panic(err)
		}
	}
}

func main() {
	ctx := context.Background()

	str, cleanup := newExampleStorage()
	defer cleanup()

	for i := range 3 {
		id := fmt.Sprintf("item-%d", i)

		if _, err := str.Create(ctx, id, "items", &user{Name: id}, &create.Create{}); err != nil {
			panic(err)
		}
	}

	n, err := str.Count(ctx, "items", &count.Count{Search: "*"})
	if err != nil {
		panic(err)
	}

	fmt.Println(n)
}
Output:
3
Example (CreateAndRetrieve)

Example_createAndRetrieve stores a document under an id and reads it back — the "Set" then "Get" round-trip.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/thalesfsp/dal/v3/bolt"
	"github.com/thalesfsp/params/v2/create"
	"github.com/thalesfsp/params/v2/retrieve"
)

// user is the example payload stored and retrieved below.
type user struct {
	Name string `json:"name"`
}

// newExampleStorage opens a throwaway bbolt database under a fresh temp
// directory and returns it along with a cleanup func. Example functions
// can't take a *testing.T, so setup failures — which would never happen in
// a working environment — panic instead of calling t.Fatal.
func newExampleStorage() (*bolt.Bolt, func()) {
	dir, err := os.MkdirTemp("", "dal-bolt-example-*")
	if err != nil {
		panic(err)
	}

	str, err := bolt.New(context.Background(), filepath.Join(dir, "example.db"), nil)
	if err != nil {
		if rmErr := os.RemoveAll(dir); rmErr != nil {
			panic(rmErr)
		}

		panic(err)
	}

	return str, func() {
		if err := str.Close(); err != nil {
			panic(err)
		}

		if err := os.RemoveAll(dir); err != nil {
			panic(err)
		}
	}
}

func main() {
	ctx := context.Background()

	str, cleanup := newExampleStorage()
	defer cleanup()

	if _, err := str.Create(ctx, "user-1", "users", &user{Name: "Ada"}, &create.Create{}); err != nil {
		panic(err)
	}

	var got user
	if err := str.Retrieve(ctx, "user-1", "users", &got, &retrieve.Retrieve{}); err != nil {
		panic(err)
	}

	fmt.Println(got.Name)
}
Output:
Ada
Example (Delete)

Example_delete removes a document by id; retrieving it afterward fails.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/thalesfsp/dal/v3/bolt"
	"github.com/thalesfsp/params/v2/create"
	"github.com/thalesfsp/params/v2/delete"
	"github.com/thalesfsp/params/v2/retrieve"
)

// user is the example payload stored and retrieved below.
type user struct {
	Name string `json:"name"`
}

// newExampleStorage opens a throwaway bbolt database under a fresh temp
// directory and returns it along with a cleanup func. Example functions
// can't take a *testing.T, so setup failures — which would never happen in
// a working environment — panic instead of calling t.Fatal.
func newExampleStorage() (*bolt.Bolt, func()) {
	dir, err := os.MkdirTemp("", "dal-bolt-example-*")
	if err != nil {
		panic(err)
	}

	str, err := bolt.New(context.Background(), filepath.Join(dir, "example.db"), nil)
	if err != nil {
		if rmErr := os.RemoveAll(dir); rmErr != nil {
			panic(rmErr)
		}

		panic(err)
	}

	return str, func() {
		if err := str.Close(); err != nil {
			panic(err)
		}

		if err := os.RemoveAll(dir); err != nil {
			panic(err)
		}
	}
}

func main() {
	ctx := context.Background()

	str, cleanup := newExampleStorage()
	defer cleanup()

	if _, err := str.Create(ctx, "temp-1", "items", &user{Name: "temp"}, &create.Create{}); err != nil {
		panic(err)
	}

	if err := str.Delete(ctx, "temp-1", "items", &delete.Delete{}); err != nil {
		panic(err)
	}

	err := str.Retrieve(ctx, "temp-1", "items", &user{}, &retrieve.Retrieve{})

	fmt.Println(err != nil)
}
Output:
true
Example (List)

Example_list filters a bucket's keys with a glob Search pattern and uses Sort to make the result order explicit.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/thalesfsp/dal/v3/bolt"
	"github.com/thalesfsp/params/v2/create"
	"github.com/thalesfsp/params/v2/customsort"
	"github.com/thalesfsp/params/v2/list"
)

// user is the example payload stored and retrieved below.
type user struct {
	Name string `json:"name"`
}

// newExampleStorage opens a throwaway bbolt database under a fresh temp
// directory and returns it along with a cleanup func. Example functions
// can't take a *testing.T, so setup failures — which would never happen in
// a working environment — panic instead of calling t.Fatal.
func newExampleStorage() (*bolt.Bolt, func()) {
	dir, err := os.MkdirTemp("", "dal-bolt-example-*")
	if err != nil {
		panic(err)
	}

	str, err := bolt.New(context.Background(), filepath.Join(dir, "example.db"), nil)
	if err != nil {
		if rmErr := os.RemoveAll(dir); rmErr != nil {
			panic(rmErr)
		}

		panic(err)
	}

	return str, func() {
		if err := str.Close(); err != nil {
			panic(err)
		}

		if err := os.RemoveAll(dir); err != nil {
			panic(err)
		}
	}
}

func main() {
	ctx := context.Background()

	str, cleanup := newExampleStorage()
	defer cleanup()

	for _, id := range []string{"user-1", "user-2", "order-1"} {
		if _, err := str.Create(ctx, id, "records", &user{Name: id}, &create.Create{}); err != nil {
			panic(err)
		}
	}

	var out bolt.ResponseList[user]
	if err := str.List(ctx, "records", &out, &list.List{
		Search: "user-*",
		Sort:   customsort.SortSlice{{"name", customsort.Asc}},
	}); err != nil {
		panic(err)
	}

	names := make([]string, 0, len(out.Items))
	for _, item := range out.Items {
		names = append(names, item.Name)
	}

	fmt.Println(names)
}
Output:
[user-1 user-2]

Index

Examples

Constants

View Source
const Name = "bolt"

Name of the storage.

Variables

This section is empty.

Functions

func Get

func Get() storage.IStorage

Get returns a setup storage, or set it up.

func Set

func Set(s storage.IStorage)

Set sets the storage, primarily used for testing.

Types

type Bolt

type Bolt struct {
	*storage.Storage

	// Client is the live bbolt handle. It's tagged `structonly` so
	// validation confirms it's non-nil without descending into it: bbolt
	// runs a background freelist/mmap and its own internal locking, and
	// reflectively walking a live driver's fields while it mutates them is a
	// data race, not just wasted work.
	Client *bbolt.DB `json:"-" validate:"required,structonly"`

	// Config is the configuration Client was opened with.
	Config *Config `json:"-" validate:"required"`

	// Target allows to set a static target. If it is empty, the target will be
	// dynamic - the one set at the operation (count, create, delete, etc) time.
	// Depending on the storage, target is a collection, a table, a bucket, etc.
	// For ElasticSearch, for example it doesn't have a concept of a database -
	// the target then is the index. Due to different cases of ElasticSearch
	// usage, the target can be static or dynamic - defined at the index time,
	// for example: log-{YYYY}-{MM}. For Bolt, a target maps to a bbolt bucket.
	Target string `json:"-" validate:"omitempty,gt=0"`
	// contains filtered or unexported fields
}

Bolt storage definition.

func New

func New(ctx context.Context, path string, opts *bbolt.Options) (*Bolt, error)

New creates a new Bolt storage. `path` is the file the database is stored in — its parent directory must already exist, bbolt won't create it. `opts` may be nil to use bbolt's defaults.

func (*Bolt) BulkCreate

func (s *Bolt) BulkCreate(
	ctx context.Context,
	target string,
	items []storage.BulkItem,
	prm *create.Create,
	options ...storage.Func[*create.Create],
) (storage.BulkResult, error)

BulkCreate creates data in transactions of at most 25 writes. Each transaction is committed independently, so the overall operation remains deliberately non-atomic while avoiding a transaction per item.

func (*Bolt) Close

func (s *Bolt) Close() error

Close releases the bbolt handle and the exclusive file lock it holds on the database file for as long as it's open. Leaking it blocks every future Open() against the same path — worse than leaking a socket, since it doesn't get cleaned up by the OS closing an fd on process exit if the process itself keeps running.

func (*Bolt) Count

func (s *Bolt) Count(ctx context.Context, target string, prm *count.Count, options ...storage.Func[*count.Count]) (int64, error)

Count returns the number of items in the storage.

func (*Bolt) Create

func (s *Bolt) Create(ctx context.Context, id, target string, v any, prm *create.Create, options ...storage.Func[*create.Create]) (string, error)

Create data.

NOTE: Not all storages returns the ID, neither all storages requires `id` to be set. You are better off setting the ID yourself.

NOTE: bbolt has no per-key expiry mechanism, so `finalParam.TTL` is accepted for interface parity but has no effect — same as `memory`.

func (*Bolt) Delete

func (s *Bolt) Delete(ctx context.Context, id, target string, prm *delete.Delete, options ...storage.Func[*delete.Delete]) error

Delete removes data.

func (*Bolt) GetClient

func (s *Bolt) GetClient() any

GetClient returns the client.

func (*Bolt) List

func (s *Bolt) List(ctx context.Context, target string, v any, prm *list.List, options ...storage.Func[*list.List]) error

List data.

NOTE: It uses params.List.Search (glob) to filter keys.

func (*Bolt) Retrieve

func (s *Bolt) Retrieve(ctx context.Context, id, target string, v any, prm *retrieve.Retrieve, options ...storage.Func[*retrieve.Retrieve]) error

Retrieve data.

func (*Bolt) Update

func (s *Bolt) Update(ctx context.Context, id, target string, v any, prm *update.Update, options ...storage.Func[*update.Update]) error

Update data.

NOTE: Not truly an update, it's an insert.

type Config

type Config struct {
	// Path is the file path where the bbolt database is stored on disk. Its
	// parent directory must already exist — bbolt does not create it.
	Path string `json:"path" validate:"required"`

	// Options are the driver options bbolt opens the database file with
	// (open timeout, read-only mode, etc). Nil applies bbolt's own defaults.
	//
	// It's skipped entirely by validation ("-"): it's third-party
	// configuration data, not something this package's rules apply to.
	Options *bbolt.Options `json:"-" validate:"-"`
}

Config is the bbolt configuration Client is opened with.

type ResponseList

type ResponseList[T any] struct {
	Items []T `json:"items"`
}

ResponseList is the list response.

Jump to

Keyboard shortcuts

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