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/v2/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/v2/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/v2/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. Matches come back in bbolt's cursor order (lexicographic by key); they're sorted here anyway so the example stays correct even if that ever changes.
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"github.com/thalesfsp/dal/v2/bolt"
"github.com/thalesfsp/params/v2/create"
"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-*"}); err != nil {
panic(err)
}
names := make([]string, 0, len(out.Items))
for _, item := range out.Items {
names = append(names, item.Name)
}
sort.Strings(names)
fmt.Println(names)
}
Output: [user-1 user-2]
Index ¶
- Constants
- func Get() storage.IStorage
- func Set(s storage.IStorage)
- type Bolt
- func (s *Bolt) Close() error
- func (s *Bolt) Count(ctx context.Context, target string, prm *count.Count, ...) (int64, error)
- func (s *Bolt) Create(ctx context.Context, id, target string, v any, prm *create.Create, ...) (string, error)
- func (s *Bolt) Delete(ctx context.Context, id, target string, prm *delete.Delete, ...) error
- func (s *Bolt) GetClient() any
- func (s *Bolt) List(ctx context.Context, target string, v any, prm *list.List, ...) error
- func (s *Bolt) Retrieve(ctx context.Context, id, target string, v any, prm *retrieve.Retrieve, ...) error
- func (s *Bolt) Update(ctx context.Context, id, target string, v any, prm *update.Update, ...) error
- type Config
- type ResponseList
Examples ¶
Constants ¶
const Name = "bolt"
Name of the storage.
Variables ¶
This section is empty.
Functions ¶
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"`
}
Bolt storage definition.
func New ¶
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) Close ¶
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) 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, and honors params.List.Limit (when > 0) to cap how many matches are returned. Results are ordered by key, since that's the order bbolt's cursor walks a bucket in.
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.