Documentation
¶
Overview ¶
Package bbolt exposes go.etcd.io/bbolt — the pure-Go, embedded, ACID key/value store — as a clean, idiomatic Ruby-style API for the Ruby / rbgo ecosystem.
What it is — and isn't ¶
bbolt is Go-native: there is no canonical Ruby "bbolt" gem to reimplement. This module instead designs the surface a Ruby "Bolt" gem *would* have — buckets, ACID transactions, and cursors — the way a Rubyist expects to use an embedded store, and implements it directly on top of the real bbolt B+tree engine. It sits alongside Ruby's own persistence idioms (the standard library's PStore and GDBM) but, unlike them, offers genuine multi-version concurrency-control ACID transactions: one writer and many concurrent readers over a single memory-mapped file, with commit/rollback semantics.
The storage engine — the copy-on-write B+tree, the page cache, the freelist, the mmap, and the write-ahead crash safety — is bbolt's and is *not* reimplemented here. This package is a thin, faithful, allocation-conscious façade that renames bbolt's Go surface to the shape a Ruby binding wants and maps bbolt's sentinel errors onto a Bolt::Error class tree the host can raise.
The engine is pure Go, so the whole stack links statically with CGO_ENABLED=0 on every 64-bit target the go-* ecosystem supports (amd64, arm64, riscv64, loong64, ppc64le, s390x), and on Linux, macOS and Windows (bbolt's mmap layer supports all three).
Ruby surface ¶
Bolt::DB.open(path, options) -> Open(path, opts)
Bolt.open(path) -> Open(path, nil)
db.close / db.path / db.stats -> DB.Close / DB.Path / DB.Stats
db.update { |tx| ... } -> DB.Update (read-write, auto commit/rollback)
db.view { |tx| ... } -> DB.View (read-only)
db.begin(writable) -> DB.Begin -> Tx.Commit / Tx.Rollback
tx.bucket / create_bucket ... -> Tx.Bucket / CreateBucket / ...
bucket.put / get / delete -> Bucket.Put / Get / Delete
bucket.each { |k, v| ... } -> Bucket.Each
bucket.cursor -> Cursor.First/Next/Prev/Last/Seek/Delete
bucket.next_sequence -> Bucket.NextSequence
Keys and values are []byte throughout. A Ruby host maps them to and from ASCII-8BIT (binary) Strings; using []byte (rather than string) keeps binary safety and preserves bbolt's distinction between a missing key (Get returns a nil slice) and a present, empty value (a non-nil, zero-length slice).
Cross-endian file-portability caveat ¶
bbolt stores a native-endian magic number in its file header and validates it on open. A database file is therefore *not* portable across byte orders: a file written on a little-endian host (amd64, arm64, riscv64, loong64, ppc64le) will fail to open on a big-endian host (s390x), and vice versa, with a "version mismatch" / "invalid database" error. This is a property of the bbolt on-disk format, not of this binding. It is not a problem in practice here: every test writes and reads a fresh temp-file database on the *same* host, so the format round-trips cleanly, and the suite is green on s390x (big-endian) as well as the little-endian arches. If you need a portable dump, serialise the logical key/value contents rather than copying the file.
Index ¶
- Variables
- type Bucket
- func (b *Bucket) Bucket(name []byte) *Bucket
- func (b *Bucket) Buckets() [][]byte
- func (b *Bucket) CreateBucket(name []byte) (*Bucket, error)
- func (b *Bucket) CreateBucketIfNotExists(name []byte) (*Bucket, error)
- func (b *Bucket) Cursor() *Cursor
- func (b *Bucket) Delete(key []byte) error
- func (b *Bucket) DeleteBucket(name []byte) error
- func (b *Bucket) Each(fn func(key, value []byte) error) error
- func (b *Bucket) Get(key []byte) []byte
- func (b *Bucket) NextSequence() (uint64, error)
- func (b *Bucket) Put(key, value []byte) error
- func (b *Bucket) Sequence() uint64
- func (b *Bucket) SetSequence(v uint64) error
- func (b *Bucket) Writable() bool
- type Cursor
- type DB
- type Error
- type Options
- type Stats
- type Tx
- func (tx *Tx) Bucket(name []byte) *Bucket
- func (tx *Tx) Buckets() [][]byte
- func (tx *Tx) Commit() error
- func (tx *Tx) CreateBucket(name []byte) (*Bucket, error)
- func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error)
- func (tx *Tx) DeleteBucket(name []byte) error
- func (tx *Tx) ID() int
- func (tx *Tx) Rollback() error
- func (tx *Tx) Writable() bool
Constants ¶
This section is empty.
Variables ¶
var ( ErrDatabaseNotOpen = &Error{Class: "Bolt::DatabaseNotOpen", err: bolt.ErrDatabaseNotOpen} ErrInvalid = &Error{Class: "Bolt::Invalid", err: bolt.ErrInvalid} ErrInvalidMapping = &Error{Class: "Bolt::InvalidMapping", err: bolt.ErrInvalidMapping} ErrVersionMismatch = &Error{Class: "Bolt::VersionMismatch", err: bolt.ErrVersionMismatch} ErrChecksum = &Error{Class: "Bolt::Checksum", err: bolt.ErrChecksum} ErrTimeout = &Error{Class: "Bolt::Timeout", err: bolt.ErrTimeout} ErrTxNotWritable = &Error{Class: "Bolt::TxNotWritable", err: bolt.ErrTxNotWritable} ErrTxClosed = &Error{Class: "Bolt::TxClosed", err: bolt.ErrTxClosed} ErrDatabaseReadOnly = &Error{Class: "Bolt::DatabaseReadOnly", err: bolt.ErrDatabaseReadOnly} ErrFreePagesNotLoaded = &Error{Class: "Bolt::FreePagesNotLoaded", err: bolt.ErrFreePagesNotLoaded} ErrBucketNotFound = &Error{Class: "Bolt::BucketNotFound", err: bolt.ErrBucketNotFound} ErrBucketExists = &Error{Class: "Bolt::BucketExists", err: bolt.ErrBucketExists} ErrBucketNameRequired = &Error{Class: "Bolt::BucketNameRequired", err: bolt.ErrBucketNameRequired} ErrKeyRequired = &Error{Class: "Bolt::KeyRequired", err: bolt.ErrKeyRequired} ErrKeyTooLarge = &Error{Class: "Bolt::KeyTooLarge", err: bolt.ErrKeyTooLarge} ErrValueTooLarge = &Error{Class: "Bolt::ValueTooLarge", err: bolt.ErrValueTooLarge} ErrIncompatibleValue = &Error{Class: "Bolt::IncompatibleValue", err: bolt.ErrIncompatibleValue} )
The Bolt::Error tree. Each value maps one bbolt sentinel onto the Ruby exception class a host should raise.
Functions ¶
This section is empty.
Types ¶
type Bucket ¶
type Bucket struct {
// contains filtered or unexported fields
}
Bucket is a Bolt bucket — the Ruby Bolt::Bucket — a named, ordered collection of key/value pairs that may also hold nested sub-buckets.
func (*Bucket) Bucket ¶
Bucket returns the nested sub-bucket with the given name, or nil if it does not exist (Bolt::Bucket#bucket).
func (*Bucket) Buckets ¶
Buckets returns the names of the immediate nested sub-buckets, in byte order (Bolt::Bucket#buckets). Each returned slice is a copy owned by the caller.
func (*Bucket) CreateBucket ¶
CreateBucket creates a nested sub-bucket (Bolt::Bucket#create_bucket). It fails with ErrBucketExists if the sub-bucket already exists.
func (*Bucket) CreateBucketIfNotExists ¶
CreateBucketIfNotExists creates a nested sub-bucket if it does not already exist (Bolt::Bucket#create_bucket_if_not_exists).
func (*Bucket) DeleteBucket ¶
DeleteBucket removes a nested sub-bucket (Bolt::Bucket#delete_bucket).
func (*Bucket) Each ¶
Each iterates every key/value pair in byte order (Bolt::Bucket#each). If fn returns an error, iteration stops and that error is returned. Keys that map to nested buckets are yielded with a nil value.
func (*Bucket) Get ¶
Get returns the value for key, or a nil slice if the key is absent (Bolt::Bucket#get). A present but empty value is a non-nil, zero-length slice. The returned slice is only valid for the life of the transaction and must not be mutated.
func (*Bucket) NextSequence ¶
NextSequence returns a new, auto-incrementing sequence value unique within the bucket (Bolt::Bucket#next_sequence).
func (*Bucket) Put ¶
Put stores value under key (Bolt::Bucket#put). An empty key fails with ErrKeyRequired; a read-only transaction fails with ErrTxNotWritable.
func (*Bucket) Sequence ¶
Sequence returns the current monotonic sequence counter (Bolt::Bucket#sequence).
func (*Bucket) SetSequence ¶
SetSequence sets the sequence counter (Bolt::Bucket#sequence=).
type Cursor ¶
type Cursor struct {
// contains filtered or unexported fields
}
Cursor iterates the keys of a bucket in byte order — the Ruby Bolt::Cursor. A nil key returned by any of the movement methods means the cursor has moved past the end (or before the start) of the bucket. When a key names a nested sub-bucket, its value is nil.
func (*Cursor) Delete ¶
Delete removes the key/value pair the cursor currently points at (Bolt::Cursor#delete).
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB is an open Bolt database — the Ruby Bolt::DB. It wraps a single memory-mapped bbolt file and hands out read-write and read-only transactions.
func Open ¶
Open opens (creating if necessary) the Bolt database at path. It is both Bolt::DB.open(path, options) and, with a nil opts, Bolt.open(path).
func (*DB) Begin ¶
Begin starts an explicit transaction (Bolt::DB#begin). Pass writable=true for a read-write transaction. The caller must eventually call Tx.Commit or Tx.Rollback. Only one writable transaction may be open at a time; any number of read-only transactions may run concurrently.
func (*DB) IsReadOnly ¶
IsReadOnly reports whether the database was opened read-only.
type Error ¶
type Error struct {
// Class is the Ruby exception class the host should raise, e.g.
// "Bolt::BucketNotFound".
Class string
// contains filtered or unexported fields
}
Error is a Bolt error. It names the Ruby exception class a host binding should raise (Class) and wraps the underlying bbolt sentinel, so both
errors.Is(err, bbolt.ErrBucketNotFound) // this package's sentinel errors.Is(err, bolt.ErrBucketNotFound) // go.etcd.io/bbolt's sentinel
report true.
type Options ¶
type Options struct {
// Mode is the file mode used when the database file is created. Zero means
// 0600.
Mode os.FileMode
// ReadOnly opens the database in read-only mode, taking a shared lock. Write
// transactions then fail with ErrDatabaseReadOnly.
ReadOnly bool
// Timeout is how long Open waits to obtain the file lock. Zero waits
// indefinitely; on a locked file a non-zero timeout fails with ErrTimeout.
Timeout time.Duration
// NoGrowSync sets bbolt's NoGrowSync flag before memory-mapping the file.
NoGrowSync bool
// NoFreelistSync avoids syncing the freelist to disk, trading recovery cost
// for write throughput.
NoFreelistSync bool
}
Options configures Open, mirroring the option hash a Ruby Bolt::DB.open would accept. The zero value is valid and yields a read-write database created with mode 0600.
type Stats ¶
type Stats struct {
// TxN is the total number of started read transactions.
TxN int
// OpenTxN is the number of currently open read transactions.
OpenTxN int
// FreePageN is the number of free pages on the freelist.
FreePageN int
// PendingPageN is the number of pending pages on the freelist.
PendingPageN int
// FreeAlloc is the bytes allocated in free pages.
FreeAlloc int
// FreelistInuse is the bytes used by the freelist.
FreelistInuse int
}
Stats is a snapshot of database-level counters (Bolt::DB#stats).
type Tx ¶
type Tx struct {
// contains filtered or unexported fields
}
Tx is a Bolt transaction — the Ruby Bolt::Tx. A writable transaction may create, delete and mutate buckets; a read-only one may only read. A Tx is not safe for concurrent use by multiple goroutines.
func (*Tx) Bucket ¶
Bucket returns the top-level bucket with the given name, or nil if it does not exist (Bolt::Tx#bucket).
func (*Tx) Buckets ¶
Buckets returns the names of all top-level buckets, in byte order (Bolt::Tx#buckets). Each returned slice is a copy owned by the caller.
func (*Tx) Commit ¶
Commit writes all changes and closes the transaction (Bolt::Tx#commit). Committing a read-only transaction, or a transaction already finished, fails.
func (*Tx) CreateBucket ¶
CreateBucket creates a new top-level bucket (Bolt::Tx#create_bucket). It fails with ErrBucketExists if the bucket already exists.
func (*Tx) CreateBucketIfNotExists ¶
CreateBucketIfNotExists creates a top-level bucket if it does not already exist (Bolt::Tx#create_bucket_if_not_exists).
func (*Tx) DeleteBucket ¶
DeleteBucket removes a top-level bucket (Bolt::Tx#delete_bucket). It fails with ErrBucketNotFound if the bucket does not exist.
