Add freelist.

master
Ben Johnson 2014-02-09 15:52:19 -07:00
parent 9ebfc171b3
commit 509e93dff4
19 changed files with 413 additions and 101 deletions

View File

@ -1,4 +1,3 @@
PKG=./...
TEST=.
BENCH=.
COVERPROFILE=/tmp/c.out
@ -7,7 +6,7 @@ bench: benchpreq
go test -v -test.bench=$(BENCH) ./.bench
cover: fmt
go test -coverprofile=$(COVERPROFILE) .
go test -coverprofile=$(COVERPROFILE) -test.run=$(TEST) .
go tool cover -html=$(COVERPROFILE)
rm $(COVERPROFILE)
@ -15,6 +14,6 @@ fmt:
@go fmt ./...
test: fmt
@go test -v -cover -test.run=$(TEST) $(PKG)
@go test -v -cover -test.run=$(TEST)
.PHONY: bench cover fmt test

View File

@ -7,6 +7,6 @@ import "fmt"
// _assert will panic with a given formatted message if the given condition is false.
func _assert(condition bool, msg string, v ...interface{}) {
if !condition {
panic(fmt.Sprintf("assertion failed: " + msg, v...))
panic(fmt.Sprintf("assertion failed: "+msg, v...))
}
}

View File

@ -23,17 +23,3 @@ func (b *Bucket) cursor() *Cursor {
stack: make([]pageElementRef, 0),
}
}
func (b *Bucket) Stat() *Stat {
// TODO: Calculate size, depth, page count (by type), entry count, readers, etc.
return nil
}
type Stat struct {
PageSize int
Depth int
BranchPageCount int
LeafPageCount int
OverflowPageCount int
EntryCount int
}

View File

@ -43,9 +43,7 @@ func (c *Cursor) Get(key []byte) []byte {
}
func (c *Cursor) search(key []byte, p *page) {
if (p.flags & (p_branch | p_leaf)) == 0 {
panic("invalid page type: " + p.typ())
}
_assert((p.flags&(p_branch|p_leaf)) != 0, "invalid page type: "+p.typ())
e := pageElementRef{page: p}
c.stack = append(c.stack, e)
@ -95,11 +93,6 @@ func (c *Cursor) top() (*page, uint16) {
return ptr.page, ptr.index
}
// page returns the page that the cursor is currently pointing at.
func (c *Cursor) page() *page {
return c.stack[len(c.stack)-1].page
}
// element returns the leaf element that the cursor is currently positioned on.
func (c *Cursor) element() *leafPageElement {
ref := c.stack[len(c.stack)-1]
@ -123,4 +116,3 @@ func (c *Cursor) node(t *RWTransaction) *node {
_assert(n.pgid == c.stack[len(c.stack)-1].page.id, "node/page mismatch b: %d != %d", n.pgid, c.stack[len(c.stack)-1].page.id)
return n
}

91
db.go
View File

@ -25,10 +25,14 @@ type DB struct {
meta0 *meta
meta1 *meta
pageSize int
opened bool
mutex sync.Mutex
opened bool
rwtransaction *RWTransaction
transactions []*Transaction
freelist *freelist
rwlock sync.Mutex // Allows only one writer at a time.
metalock sync.Mutex // Protects meta page access.
mmaplock sync.RWMutex // Protects mmap access during remapping.
}
// NewDB creates a new DB instance.
@ -45,8 +49,8 @@ func (db *DB) Path() string {
// If the file does not exist then it will be created automatically.
func (db *DB) Open(path string, mode os.FileMode) error {
var err error
db.mutex.Lock()
defer db.mutex.Unlock()
db.metalock.Lock()
defer db.metalock.Unlock()
// Initialize OS/Syscall references.
// These are overridden by mocks during some tests.
@ -99,6 +103,10 @@ func (db *DB) Open(path string, mode os.FileMode) error {
return err
}
// Read in the freelist.
db.freelist = &freelist{pending: make(map[txnid][]pgid)}
db.freelist.read(db.page(db.meta().freelist))
// Mark the database as opened and return.
db.opened = true
return nil
@ -113,7 +121,7 @@ func (db *DB) mmap() error {
return &Error{"file size too small", err}
}
// TEMP(benbjohnson): Set max size to 1MB.
// TODO(benbjohnson): Determine appropriate mmap size by db size.
size := 2 << 30
// Memory-map the data file as a byte slice.
@ -154,7 +162,7 @@ func (db *DB) init() error {
m.version = version
m.pageSize = uint32(db.pageSize)
m.version = version
m.free = 2
m.freelist = 2
m.buckets = 3
m.pgid = 4
m.txnid = txnid(i)
@ -182,20 +190,21 @@ func (db *DB) init() error {
// Close releases all resources related to the database.
func (db *DB) Close() {
db.mutex.Lock()
defer db.mutex.Unlock()
db.metalock.Lock()
defer db.metalock.Unlock()
db.close()
}
func (db *DB) close() {
// TODO: Undo everything in Open().
db.freelist = nil
}
// Transaction creates a read-only transaction.
// Multiple read-only transactions can be used concurrently.
func (db *DB) Transaction() (*Transaction, error) {
db.mutex.Lock()
defer db.mutex.Unlock()
db.metalock.Lock()
defer db.metalock.Unlock()
// Exit if the database is not open yet.
if !db.opened {
@ -206,30 +215,60 @@ func (db *DB) Transaction() (*Transaction, error) {
t := &Transaction{}
t.init(db)
// Keep track of transaction until it closes.
db.transactions = append(db.transactions, t)
return t, nil
}
// RWTransaction creates a read/write transaction.
// Only one read/write transaction is allowed at a time.
func (db *DB) RWTransaction() (*RWTransaction, error) {
db.mutex.Lock()
defer db.mutex.Unlock()
db.metalock.Lock()
defer db.metalock.Unlock()
// TODO: db.writerMutex.Lock()
// TODO: Add unlock to RWTransaction.Commit() / Abort()
// Obtain writer lock. This is released by the RWTransaction when it closes.
db.rwlock.Lock()
// Exit if the database is not open yet.
if !db.opened {
db.rwlock.Unlock()
return nil, DatabaseNotOpenError
}
// Create a transaction associated with the database.
t := &RWTransaction{nodes: make(map[pgid]*node)}
t.init(db)
db.rwtransaction = t
// Free any pages associated with closed read-only transactions.
var minid txnid = 0xFFFFFFFFFFFFFFFF
for _, t := range db.transactions {
if t.id() < minid {
minid = t.id()
}
}
if minid > 0 {
db.freelist.release(minid - 1)
}
return t, nil
}
// removeTransaction removes a transaction from the database.
func (db *DB) removeTransaction(t *Transaction) {
db.metalock.Lock()
defer db.metalock.Unlock()
// Remove the transaction.
for i, txn := range db.transactions {
if txn == t {
db.transactions = append(db.transactions[:i], db.transactions[i+1:]...)
break
}
}
}
// Bucket retrieves a reference to a bucket.
func (db *DB) Bucket(name string) (*Bucket, error) {
t, err := db.Transaction()
@ -372,8 +411,32 @@ func (db *DB) meta() *meta {
return db.meta1
}
// allocate returns a contiguous block of memory starting at a given page.
func (db *DB) allocate(count int) *page {
// Allocate a temporary buffer for the page.
buf := make([]byte, count*db.pageSize)
p := (*page)(unsafe.Pointer(&buf[0]))
p.overflow = uint32(count - 1)
// Use pages from the freelist if they are available.
if p.id = db.freelist.allocate(count); p.id != 0 {
return p
}
// TODO(benbjohnson): Resize mmap().
// If there are no free pages then allocate from the end of the file.
p.id = db.rwtransaction.meta.pgid
db.rwtransaction.meta.pgid += pgid(count)
return p
}
// sync flushes the file descriptor to disk.
func (db *DB) sync(force bool) error {
if db.opened {
return DatabaseAlreadyOpenedError
}
if err := syscall.Fsync(int(db.file.Fd())); err != nil {
return err
}

View File

@ -167,6 +167,26 @@ func TestDBDelete(t *testing.T) {
})
}
// Ensure that the database can be copied to a writer.
func TestDBCopy(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that the database can be copied to a file path.
func TestDBCopyFile(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that the database can sync to the file system.
func TestDBSync(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that an error is returned when a database write fails.
func TestDBWriteFail(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// withDB executes a function with a database reference.
func withDB(fn func(*DB, string)) {
f, _ := ioutil.TempFile("", "bolt-")

12
error_test.go Normal file
View File

@ -0,0 +1,12 @@
package bolt
import (
"github.com/stretchr/testify/assert"
"testing"
)
// Ensure that nested errors are appropriately formatted.
func TestError(t *testing.T) {
e := &Error{"one error", &Error{"two error", nil}}
assert.Equal(t, e.Error(), "one error: two error")
}

94
freelist.go Normal file
View File

@ -0,0 +1,94 @@
package bolt
import (
"sort"
"unsafe"
)
// freelist represents a list of all pages that are available for allocation.
// It also tracks pages that have been freed but are still in use by open transactions.
type freelist struct {
ids []pgid
pending map[txnid][]pgid
}
// all returns a list of all free ids and all pending ids in one sorted list.
func (f *freelist) all() []pgid {
ids := make([]pgid, len(f.ids))
copy(ids, f.ids)
for _, list := range f.pending {
ids = append(ids, list...)
}
sort.Sort(reverseSortedPgids(ids))
return ids
}
// allocate returns the starting page id of a contiguous list of pages of a given size.
// If a contiguous block cannot be found then 0 is returned.
func (f *freelist) allocate(n int) pgid {
var count int
var previd pgid = 0
for i, id := range f.ids {
// Reset count if this is not contiguous.
if previd == 0 || previd-id != 1 {
count = 1
}
// If we found a contiguous block then remove it and return it.
if count == n {
f.ids = append(f.ids[:i-(n-1)], f.ids[i+1:]...)
_assert(id > 1, "cannot allocate page 0 or 1: %d", id)
return id
}
previd = id
count++
}
return 0
}
// free releases a page and its overflow for a given transaction id.
func (f *freelist) free(txnid txnid, p *page) {
var ids = f.pending[txnid]
_assert(p.id > 1, "cannot free page 0 or 1: %d", p.id)
for i := 0; i < int(p.overflow+1); i++ {
ids = append(ids, p.id+pgid(i))
}
f.pending[txnid] = ids
}
// release moves all page ids for a transaction id (or older) to the freelist.
func (f *freelist) release(txnid txnid) {
for tid, ids := range f.pending {
if tid <= txnid {
f.ids = append(f.ids, ids...)
delete(f.pending, tid)
}
}
sort.Sort(reverseSortedPgids(f.ids))
}
// read initializes the freelist from a freelist page.
func (f *freelist) read(p *page) {
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0:p.count]
f.ids = make([]pgid, len(ids))
copy(f.ids, ids)
}
// write writes the page ids onto a freelist page. All free and pending ids are
// saved to disk since in the event of a program crash, all pending ids will
// become free.
func (f *freelist) write(p *page) {
ids := f.all()
p.flags |= p_freelist
p.count = uint16(len(ids))
copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:], ids)
}
type reverseSortedPgids []pgid
func (s reverseSortedPgids) Len() int { return len(s) }
func (s reverseSortedPgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s reverseSortedPgids) Less(i, j int) bool { return s[i] > s[j] }

95
freelist_test.go Normal file
View File

@ -0,0 +1,95 @@
package bolt
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
)
// Ensure that a page is added to a transaction's freelist.
func TestFreelistFree(t *testing.T) {
f := &freelist{pending: make(map[txnid][]pgid)}
f.free(100, &page{id: 12})
assert.Equal(t, f.pending[100], []pgid{12})
}
// Ensure that a page and its overflow is added to a transaction's freelist.
func TestFreelistFreeOverflow(t *testing.T) {
f := &freelist{pending: make(map[txnid][]pgid)}
f.free(100, &page{id: 12, overflow: 3})
assert.Equal(t, f.pending[100], []pgid{12, 13, 14, 15})
}
// Ensure that a transaction's free pages can be released.
func TestFreelistRelease(t *testing.T) {
f := &freelist{pending: make(map[txnid][]pgid)}
f.free(100, &page{id: 12, overflow: 1})
f.free(100, &page{id: 9})
f.free(102, &page{id: 39})
f.release(100)
f.release(101)
assert.Equal(t, f.ids, []pgid{13, 12, 9})
f.release(102)
assert.Equal(t, f.ids, []pgid{39, 13, 12, 9})
}
// Ensure that a freelist can find contiguous blocks of pages.
func TestFreelistAllocate(t *testing.T) {
f := &freelist{ids: []pgid{18, 13, 12, 9, 7, 6, 5, 4, 3}}
assert.Equal(t, f.allocate(2), pgid(12))
assert.Equal(t, f.allocate(1), pgid(18))
assert.Equal(t, f.allocate(3), pgid(5))
assert.Equal(t, f.allocate(3), pgid(0))
assert.Equal(t, f.allocate(2), pgid(3))
assert.Equal(t, f.allocate(1), pgid(9))
assert.Equal(t, f.allocate(0), pgid(0))
assert.Equal(t, f.ids, []pgid{})
}
// Ensure that a freelist can deserialize from a freelist page.
func TestFreelistRead(t *testing.T) {
// Create a page.
var buf [4096]byte
page := (*page)(unsafe.Pointer(&buf[0]))
page.flags = p_freelist
page.count = 2
// Insert 2 page ids.
ids := (*[3]pgid)(unsafe.Pointer(&page.ptr))
ids[0] = 23
ids[1] = 50
// Deserialize page into a freelist.
f := &freelist{pending: make(map[txnid][]pgid)}
f.read(page)
// Ensure that there are two page ids in the freelist.
assert.Equal(t, len(f.ids), 2)
assert.Equal(t, f.ids[0], pgid(23))
assert.Equal(t, f.ids[1], pgid(50))
}
// Ensure that a freelist can serialize into a freelist page.
func TestFreelistWrite(t *testing.T) {
// Create a freelist and write it to a page.
var buf [4096]byte
f := &freelist{ids: []pgid{12, 39}, pending: make(map[txnid][]pgid)}
f.pending[100] = []pgid{28, 11}
f.pending[101] = []pgid{3}
p := (*page)(unsafe.Pointer(&buf[0]))
f.write(p)
// Read the page back out.
f2 := &freelist{pending: make(map[txnid][]pgid)}
f2.read(p)
// Ensure that the freelist is correct.
// All pages should be present and in reverse order.
assert.Equal(t, len(f2.ids), 5)
assert.Equal(t, f2.ids[0], pgid(39))
assert.Equal(t, f2.ids[1], pgid(28))
assert.Equal(t, f2.ids[2], pgid(12))
assert.Equal(t, f2.ids[3], pgid(11))
assert.Equal(t, f2.ids[4], pgid(3))
}

View File

@ -8,7 +8,7 @@ type meta struct {
pageSize uint32
flags uint32
buckets pgid
free pgid
freelist pgid
pgid pgid
txnid txnid
}
@ -28,10 +28,10 @@ func (m *meta) copy(dest *meta) {
dest.magic = m.magic
dest.version = m.version
dest.pageSize = m.pageSize
dest.pgid = m.pgid
dest.free = m.free
dest.txnid = m.txnid
dest.buckets = m.buckets
dest.freelist = m.freelist
dest.pgid = m.pgid
dest.txnid = m.txnid
}
// write writes the meta onto a page.

18
meta_test.go Normal file
View File

@ -0,0 +1,18 @@
package bolt
import (
"github.com/stretchr/testify/assert"
"testing"
)
// Ensure that meta with bad magic is invalid.
func TestMetaValidateMagic(t *testing.T) {
m := &meta{magic: 0x01234567}
assert.Equal(t, m.validate(), InvalidError)
}
// Ensure that meta with a bad version is invalid.
func TestMetaValidateVersion(t *testing.T) {
m := &meta{magic: magic, version: 200}
assert.Equal(t, m.validate(), VersionMismatchError)
}

20
node.go
View File

@ -9,13 +9,13 @@ import (
// node represents an in-memory, deserialized page.
type node struct {
transaction *RWTransaction
isLeaf bool
unbalanced bool
key []byte
depth int
pgid pgid
parent *node
inodes inodes
isLeaf bool
unbalanced bool
key []byte
depth int
pgid pgid
parent *node
inodes inodes
}
// minKeys returns the minimum number of inodes this node should have.
@ -76,7 +76,7 @@ func (n *node) nextSibling() *node {
return nil
}
index := n.parent.childIndex(n)
if index >= n.parent.numChildren() - 1 {
if index >= n.parent.numChildren()-1 {
return nil
}
return n.parent.childAt(index + 1)
@ -354,7 +354,3 @@ type inode struct {
}
type inodes []inode
func (s inodes) Len() int { return len(s) }
func (s inodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s inodes) Less(i, j int) bool { return bytes.Compare(s[i].key, s[j].key) == -1 }

5
os.go
View File

@ -6,7 +6,6 @@ import (
type _os interface {
OpenFile(name string, flag int, perm os.FileMode) (file file, err error)
Stat(name string) (fi os.FileInfo, err error)
Getpagesize() int
}
@ -23,10 +22,6 @@ func (o *sysos) OpenFile(name string, flag int, perm os.FileMode) (file file, er
return os.OpenFile(name, flag, perm)
}
func (o *sysos) Stat(name string) (fi os.FileInfo, err error) {
return os.Stat(name)
}
func (o *sysos) Getpagesize() int {
return os.Getpagesize()
}

View File

@ -81,11 +81,6 @@ func (p *page) branchPageElements() []branchPageElement {
return ((*[maxNodesPerPage]branchPageElement)(unsafe.Pointer(&p.ptr)))[:]
}
// freelist retrieves a list of page ids from a freelist page.
func (p *page) freelist() []pgid {
return ((*[maxNodesPerPage]pgid)(unsafe.Pointer(&p.ptr)))[0:p.count]
}
// dump writes n bytes of the page to STDERR as hex output.
func (p *page) hexdump(n int) {
buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:n]

21
page_test.go Normal file
View File

@ -0,0 +1,21 @@
package bolt
import (
"github.com/stretchr/testify/assert"
"testing"
)
// Ensure that the page type can be returned in human readable format.
func TestPageTyp(t *testing.T) {
assert.Equal(t, (&page{flags: p_branch}).typ(), "branch")
assert.Equal(t, (&page{flags: p_leaf}).typ(), "leaf")
assert.Equal(t, (&page{flags: p_meta}).typ(), "meta")
assert.Equal(t, (&page{flags: p_buckets}).typ(), "buckets")
assert.Equal(t, (&page{flags: p_freelist}).typ(), "freelist")
assert.Equal(t, (&page{flags: 20000}).typ(), "unknown<4e20>")
}
// Ensure that the hexdump debugging function doesn't blow up.
func TestPageDump(t *testing.T) {
(&page{id: 256}).hexdump(16)
}

View File

@ -49,8 +49,7 @@ func (t *RWTransaction) DeleteBucket(name string) error {
// Remove from buckets page.
t.buckets.del(name)
// TODO: Free all pages.
// TODO: Remove cursor.
// TODO(benbjohnson): Free all pages.
return nil
}
@ -97,8 +96,12 @@ func (t *RWTransaction) Delete(name string, key []byte) error {
// Commit writes all changes to disk.
func (t *RWTransaction) Commit() error {
defer t.close()
// TODO(benbjohnson): Use vectorized I/O to write out dirty pages.
// TODO(benbjohnson): Move rebalancing to occur immediately after deletion (?).
// Rebalance and spill data onto dirty pages.
t.rebalance()
t.spill()
@ -128,26 +131,14 @@ func (t *RWTransaction) Rollback() {
}
func (t *RWTransaction) close() {
// Clear nodes.
t.nodes = nil
// TODO: Release writer lock.
t.db.rwlock.Unlock()
}
// allocate returns a contiguous block of memory starting at a given page.
func (t *RWTransaction) allocate(count int) *page {
// TODO(benbjohnson): Use pages from the freelist.
p := t.db.allocate(count)
// Allocate a set of contiguous pages from the end of the file.
buf := make([]byte, count*t.db.pageSize)
p := (*page)(unsafe.Pointer(&buf[0]))
p.id = t.meta.pgid
p.overflow = uint32(count - 1)
// Increment the last page id.
t.meta.pgid += pgid(count)
// Save it in our page cache.
// Save to our page cache.
t.pages[p.id] = p
return p
@ -196,6 +187,11 @@ func (t *RWTransaction) spill() {
nodes = append(nodes, n.parent)
}
// Add node's page to the freelist.
if n.pgid > 0 {
t.db.freelist.free(t.id(), t.page(n.pgid))
}
// Write nodes to dirty pages.
for i, newNode := range newNodes {
// Allocate contiguous space for the node.
@ -267,10 +263,6 @@ func (t *RWTransaction) writeMeta() error {
return nil
}
// TODO(benbjohnson): Look up node by page id instead of by stack. Determine depth recursively by parent.
// TODO(benbjohnson): prevSibling()
// TODO(benbjohnson): nextSibling()
// node creates a node from a page and associates it with a given parent.
func (t *RWTransaction) node(pgid pgid, parent *node) *node {
// Retrieve node if it has already been fetched.

View File

@ -17,11 +17,12 @@ func TestRWTransaction(t *testing.T) {
txn, err := db.RWTransaction()
assert.NotNil(t, txn)
assert.NoError(t, err)
assert.Equal(t, txn.DB(), db)
})
}
// Ensure that a bucket can be created and retrieved.
func TestTransactionCreateBucket(t *testing.T) {
func TestRWTransactionCreateBucket(t *testing.T) {
withOpenDB(func(db *DB, path string) {
// Create a bucket.
err := db.CreateBucket("widgets")
@ -35,7 +36,7 @@ func TestTransactionCreateBucket(t *testing.T) {
}
// Ensure that a bucket cannot be created twice.
func TestTransactionRecreateBucket(t *testing.T) {
func TestRWTransactionRecreateBucket(t *testing.T) {
withOpenDB(func(db *DB, path string) {
// Create a bucket.
err := db.CreateBucket("widgets")
@ -48,7 +49,7 @@ func TestTransactionRecreateBucket(t *testing.T) {
}
// Ensure that a bucket is created with a non-blank name.
func TestTransactionCreateBucketWithoutName(t *testing.T) {
func TestRWTransactionCreateBucketWithoutName(t *testing.T) {
withOpenDB(func(db *DB, path string) {
err := db.CreateBucket("")
assert.Equal(t, err, &Error{"bucket name cannot be blank", nil})
@ -56,7 +57,7 @@ func TestTransactionCreateBucketWithoutName(t *testing.T) {
}
// Ensure that a bucket name is not too long.
func TestTransactionCreateBucketWithLongName(t *testing.T) {
func TestRWTransactionCreateBucketWithLongName(t *testing.T) {
withOpenDB(func(db *DB, path string) {
err := db.CreateBucket(strings.Repeat("X", 255))
assert.NoError(t, err)
@ -66,6 +67,36 @@ func TestTransactionCreateBucketWithLongName(t *testing.T) {
})
}
// Ensure that a bucket can be deleted.
func TestRWTransactionDeleteBucket(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that an error is returned when inserting into a bucket that doesn't exist.
func TestRWTransactionPutBucketNotFound(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that an error is returned when inserting with an empty key.
func TestRWTransactionPutEmptyKey(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that an error is returned when inserting with a key that's too large.
func TestRWTransactionPutKeyTooLarge(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that an error is returned when inserting with data that's too large.
func TestRWTransactionPutDataTooLarge(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that an error is returned when deleting from a bucket that doesn't exist.
func TestRWTransactionDeleteBucketNotFound(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that a bucket can write random keys and values across multiple txns.
func TestRWTransactionPutSingle(t *testing.T) {
index := 0

View File

@ -10,7 +10,6 @@ const (
type txnid uint64
type Transaction struct {
id int
db *DB
meta *meta
buckets *buckets
@ -27,8 +26,13 @@ func (t *Transaction) init(db *DB) {
t.buckets.read(t.page(t.meta.buckets))
}
// id returns the transaction id.
func (t *Transaction) id() txnid {
return t.meta.txnid
}
func (t *Transaction) Close() {
// TODO: Close buckets.
t.db.removeTransaction(t)
}
func (t *Transaction) DB() *DB {
@ -73,12 +77,6 @@ func (t *Transaction) Get(name string, key []byte) []byte {
return c.Get(key)
}
// stat returns information about a bucket's internal structure.
func (t *Transaction) stat(name string) *Stat {
// TODO
return nil
}
// page returns a reference to the page with a given id.
// If page has been written to then a temporary bufferred page is returned.
func (t *Transaction) page(id pgid) *page {

View File

@ -6,6 +6,11 @@ import (
"github.com/stretchr/testify/assert"
)
// Ensure that the database can retrieve a list of buckets.
func TestTransactionBuckets(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson)
}
// Ensure that a Transaction can retrieve a bucket.
func TestTransactionBucketMissing(t *testing.T) {
withOpenDB(func(db *DB, path string) {
@ -19,7 +24,7 @@ func TestTransactionBucketMissing(t *testing.T) {
}
// Ensure that a Transaction retrieving a non-existent key returns nil.
func TestTransactionGetMising(t *testing.T) {
func TestTransactionGetMissing(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets")
db.Put("widgets", []byte("foo"), []byte("bar"))