Skip to content

Commit

Permalink
create a common package
Browse files Browse the repository at this point in the history
Points:
1. There are lots of duplicated definitions between bolt and
   guts_cli, which is definitely not good.
2. The implementation in guts_cli also has issue, please
   refer to etcd-io#391.
   This refactoring can fix the issue.

Signed-off-by: Benjamin Wang <wachao@vmware.com>
  • Loading branch information
ahrtr authored and missinglink committed Dec 5, 2023
1 parent f87db0e commit 4910a49
Show file tree
Hide file tree
Showing 8 changed files with 839 additions and 0 deletions.
54 changes: 54 additions & 0 deletions internal/common/bucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package common

import (
"fmt"
"unsafe"
)

const BucketHeaderSize = int(unsafe.Sizeof(InBucket{}))

// InBucket represents the on-file representation of a bucket.
// This is stored as the "value" of a bucket key. If the bucket is small enough,
// then its root page can be stored inline in the "value", after the bucket
// header. In the case of inline buckets, the "root" will be 0.
type InBucket struct {
root Pgid // page id of the bucket's root-level page
sequence uint64 // monotonically incrementing, used by NextSequence()
}

func NewInBucket(root Pgid, seq uint64) InBucket {
return InBucket{
root: root,
sequence: seq,
}
}

func (b *InBucket) RootPage() Pgid {
return b.root
}

func (b *InBucket) SetRootPage(id Pgid) {
b.root = id
}

// InSequence returns the sequence. The reason why not naming it `Sequence`
// is to avoid duplicated name as `(*Bucket) Sequence()`
func (b *InBucket) InSequence() uint64 {
return b.sequence
}

func (b *InBucket) SetInSequence(v uint64) {
b.sequence = v
}

func (b *InBucket) IncSequence() {
b.sequence++
}

func (b *InBucket) InlinePage(v []byte) *Page {
return (*Page)(unsafe.Pointer(&v[BucketHeaderSize]))
}

func (b *InBucket) String() string {
return fmt.Sprintf("<pgid=%d,seq=%d>", b.root, b.sequence)
}
78 changes: 78 additions & 0 deletions internal/common/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package common

import "errors"

// These errors can be returned when opening or calling methods on a DB.
var (
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
// is opened or after it is closed.
ErrDatabaseNotOpen = errors.New("database not open")

// ErrDatabaseOpen is returned when opening a database that is
// already open.
ErrDatabaseOpen = errors.New("database already open")

// ErrInvalid is returned when both meta pages on a database are invalid.
// This typically occurs when a file is not a bolt database.
ErrInvalid = errors.New("invalid database")

// ErrInvalidMapping is returned when the database file fails to get mapped.
ErrInvalidMapping = errors.New("database isn't correctly mapped")

// ErrVersionMismatch is returned when the data file was created with a
// different version of Bolt.
ErrVersionMismatch = errors.New("version mismatch")

// ErrChecksum is returned when either meta page checksum does not match.
ErrChecksum = errors.New("checksum error")

// ErrTimeout is returned when a database cannot obtain an exclusive lock
// on the data file after the timeout passed to Open().
ErrTimeout = errors.New("timeout")
)

// These errors can occur when beginning or committing a Tx.
var (
// ErrTxNotWritable is returned when performing a write operation on a
// read-only transaction.
ErrTxNotWritable = errors.New("tx not writable")

// ErrTxClosed is returned when committing or rolling back a transaction
// that has already been committed or rolled back.
ErrTxClosed = errors.New("tx closed")

// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
// read-only database.
ErrDatabaseReadOnly = errors.New("database is in read-only mode")

// ErrFreePagesNotLoaded is returned when a readonly transaction without
// preloading the free pages is trying to access the free pages.
ErrFreePagesNotLoaded = errors.New("free pages are not pre-loaded")
)

// These errors can occur when putting or deleting a value or a bucket.
var (
// ErrBucketNotFound is returned when trying to access a bucket that has
// not been created yet.
ErrBucketNotFound = errors.New("bucket not found")

// ErrBucketExists is returned when creating a bucket that already exists.
ErrBucketExists = errors.New("bucket already exists")

// ErrBucketNameRequired is returned when creating a bucket with a blank name.
ErrBucketNameRequired = errors.New("bucket name required")

// ErrKeyRequired is returned when inserting a zero-length key.
ErrKeyRequired = errors.New("key required")

// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
ErrKeyTooLarge = errors.New("key too large")

// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
ErrValueTooLarge = errors.New("value too large")

// ErrIncompatibleValue is returned when trying create or delete a bucket
// on an existing non-bucket key or when trying to create or delete a
// non-bucket key on an existing bucket key.
ErrIncompatibleValue = errors.New("incompatible value")
)
147 changes: 147 additions & 0 deletions internal/common/meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package common

import (
"fmt"
"hash/fnv"
"io"
"unsafe"
)

type Meta struct {
magic uint32
version uint32
pageSize uint32
flags uint32
root InBucket
freelist Pgid
pgid Pgid
txid Txid
checksum uint64
}

// Validate checks the marker bytes and version of the meta page to ensure it matches this binary.
func (m *Meta) Validate() error {
if m.magic != Magic {
return ErrInvalid
} else if m.version != Version {
return ErrVersionMismatch
} else if m.checksum != m.Sum64() {
return ErrChecksum
}
return nil
}

// Copy copies one meta object to another.
func (m *Meta) Copy(dest *Meta) {
*dest = *m
}

// Write writes the meta onto a page.
func (m *Meta) Write(p *Page) {
if m.root.root >= m.pgid {
panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid))
} else if m.freelist >= m.pgid && m.freelist != PgidNoFreelist {
// TODO: reject pgidNoFreeList if !NoFreelistSync
panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
}

// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
p.id = Pgid(m.txid % 2)
p.flags |= MetaPageFlag

// Calculate the checksum.
m.checksum = m.Sum64()

m.Copy(p.Meta())
}

// Sum64 generates the checksum for the meta.
func (m *Meta) Sum64() uint64 {
var h = fnv.New64a()
_, _ = h.Write((*[unsafe.Offsetof(Meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
return h.Sum64()
}

func (m *Meta) Magic() uint32 {
return m.magic
}

func (m *Meta) SetMagic(v uint32) {
m.magic = v
}

func (m *Meta) SetVersion(v uint32) {
m.version = v
}

func (m *Meta) PageSize() uint32 {
return m.pageSize
}

func (m *Meta) SetPageSize(v uint32) {
m.pageSize = v
}

func (m *Meta) Flags() uint32 {
return m.flags
}

func (m *Meta) SetFlags(v uint32) {
m.flags = v
}

func (m *Meta) SetRootBucket(b InBucket) {
m.root = b
}

func (m *Meta) RootBucket() *InBucket {
return &m.root
}

func (m *Meta) Freelist() Pgid {
return m.freelist
}

func (m *Meta) SetFreelist(v Pgid) {
m.freelist = v
}

func (m *Meta) Pgid() Pgid {
return m.pgid
}

func (m *Meta) SetPgid(id Pgid) {
m.pgid = id
}

func (m *Meta) Txid() Txid {
return m.txid
}

func (m *Meta) SetTxid(id Txid) {
m.txid = id
}

func (m *Meta) IncTxid() {
m.txid += 1
}

func (m *Meta) DecTxid() {
m.txid -= 1
}

func (m *Meta) SetChecksum(v uint64) {
m.checksum = v
}

func (m *Meta) Print(w io.Writer) {
fmt.Fprintf(w, "Version: %d\n", m.version)
fmt.Fprintf(w, "Page Size: %d bytes\n", m.pageSize)
fmt.Fprintf(w, "Flags: %08x\n", m.flags)
fmt.Fprintf(w, "Root: <pgid=%d>\n", m.root.root)
fmt.Fprintf(w, "Freelist: <pgid=%d>\n", m.freelist)
fmt.Fprintf(w, "HWM: <pgid=%d>\n", m.pgid)
fmt.Fprintf(w, "Txn ID: %d\n", m.txid)
fmt.Fprintf(w, "Checksum: %016x\n", m.checksum)
fmt.Fprintf(w, "\n")
}
Loading

0 comments on commit 4910a49

Please sign in to comment.