Initialize transaction/rwtransaction.

pull/34/head
Ben Johnson 2014-01-26 15:29:06 -07:00
parent 2c6b8e0ebf
commit 1baa6d576a
8 changed files with 91 additions and 103 deletions

10
TODO Normal file
View File

@ -0,0 +1,10 @@
TODO
====
X Open DB.
X Initialize transaction.
- Cursor First, Goto(key), Next
- RWTransaction.insert()
- page split
- rebalance
- adjust cursors
- RWTransaction Commmit

View File

@ -5,6 +5,8 @@ type bucketid uint32
type Bucket struct { type Bucket struct {
*bucket *bucket
name string name string
transaction Transaction,
cursors []*Cursor,
} }
type bucket struct { type bucket struct {
@ -15,3 +17,21 @@ type bucket struct {
leafs pgid leafs pgid
entries uint64 entries uint64
} }
func (b *Bucket) Close() error {
// TODO: Close cursors.
return nil
}
func (b *Bucket) Cursor() (*Cursor, error) {
if b.transaction == nil {
return nil, InvalidBucketError
}
c := &Cursor{
bucket: b,
stack: make([]elem, 0),
}
return nil
}

View File

@ -1,57 +1,37 @@
package bolt package bolt
type Cursor struct { type Cursor struct {
transaction *Transaction
bucket *Bucket bucket *Bucket
stack []stackelem stack []elem
} }
type stackelem struct { // elem represents a node on a page that's on the cursor's stack.
type elem struct {
page *page page *page
index int index int
} }
func (c *Cursor) Transaction() *Transaction {
return c.transaction
}
func (c *Cursor) Bucket() *Bucket { func (c *Cursor) Bucket() *Bucket {
return c.bucket return c.bucket
} }
func (c *Cursor) Get(key []byte) ([]byte, error) {
// TODO: Move to key
// TODO: If it doesn't exist, return nil, nil
// TODO: Otherwise return node key+data.
return nil, nil
}
// Move the cursor to the next key/value.
func (c *Cursor) Next() ([]byte, []byte, error) {
return nil, nil, nil
}
// First moves the cursor to the first item in the bucket and returns its key and data. // First moves the cursor to the first item in the bucket and returns its key and data.
func (c *Cursor) First() ([]byte, []byte, error) { func (c *Cursor) First() ([]byte, []byte, error) {
// TODO: Traverse to the first key. // TODO: Traverse to the first key.
return nil, nil, nil return nil, nil, nil
} }
// Set the cursor on a specific data item. // Move the cursor to the next key/value.
// (bool return is whether it is exact). func (c *Cursor) Next() ([]byte, []byte, error) {
func (c *Cursor) set(key []byte, data []byte, op int) (error, bool) { return nil, nil, nil
}
// Goto positions the cursor at a specific key.
func (c *Cursor) Goto(key []byte) ([]byte, error) {
// TODO(benbjohnson): Optimize for specific use cases. // TODO(benbjohnson): Optimize for specific use cases.
// TODO: Check if len(key) > 0. // TODO: Check if len(key) > 0.
// TODO: Start from root page and traverse to correct page. // TODO: Start from root page and traverse to correct page.
return nil, false return nil, nil
}
func (c *Cursor) insert(key []byte, data []byte) error {
// TODO: If there is not enough space on page for key+data then split.
// TODO: Move remaining data on page forward.
// TODO: Write leaf node to current location.
// TODO: Adjust available page size.
return nil
} }

29
db.go
View File

@ -217,18 +217,8 @@ func (db *DB) Transaction() (*Transaction, error) {
} }
// Create a transaction associated with the database. // Create a transaction associated with the database.
t := &Transaction{ t := &Transaction{}
db: db, t.init(db, db.meta())
meta: db.meta(),
buckets: make(map[string]*Bucket),
cursors: make(map[uint32]*Cursor),
}
// Save references to the sys•free and sys•buckets buckets.
t.sysfree.transaction = t
t.sysfree.bucket = &t.meta.free
t.sysbuckets.transaction = t
t.sysbuckets.bucket = &t.meta.buckets
return t, nil return t, nil
} }
@ -236,16 +226,21 @@ func (db *DB) Transaction() (*Transaction, error) {
// RWTransaction creates a read/write transaction. // RWTransaction creates a read/write transaction.
// Only one read/write transaction is allowed at a time. // Only one read/write transaction is allowed at a time.
func (db *DB) RWTransaction() (*RWTransaction, error) { func (db *DB) RWTransaction() (*RWTransaction, error) {
db.Lock()
defer db.Unlock()
// TODO: db.writerMutex.Lock() // TODO: db.writerMutex.Lock()
// TODO: Add unlock to RWTransaction.Commit() / Abort() // TODO: Add unlock to RWTransaction.Commit() / Abort()
t := &RWTransaction{} // Exit if the database is not open yet.
if !db.opened {
// Exit if a read-write transaction is currently in progress. return nil, DatabaseNotOpenError
if db.transaction != nil {
return nil, TransactionInProgressError
} }
// Create a transaction associated with the database.
t := &RWTransaction{}
t.init(db, db.meta())
return t, nil return t, nil
} }

View File

@ -10,7 +10,7 @@ const version uint32 = 1
type meta struct { type meta struct {
magic uint32 magic uint32
version uint32 version uint32
buckets bucket sys bucket
pageSize uint32 pageSize uint32
pgid pgid pgid pgid
txnid txnid txnid txnid

View File

@ -1,7 +1,6 @@
package bolt package bolt
import ( import (
"bytes"
"unsafe" "unsafe"
) )

View File

@ -5,8 +5,15 @@ package bolt
type RWTransaction struct { type RWTransaction struct {
Transaction Transaction
dirtyPages map[int]*page dirtyPages map[pgid]*page
freelist []pgno freelist []pgid
}
// init initializes the transaction and associates it with a database.
func (t *RWTransaction) init(db *DB, meta *meta) {
t.dirtyPages = make(map[pgid]*page)
t.freelist = make([]pgid)
t.Transaction.init(db, meta)
} }
// TODO: Allocate scratch meta page. // TODO: Allocate scratch meta page.
@ -232,3 +239,12 @@ func (t *RWTransaction) allocate(count int) (*page, error) {
// TODO: If no free pages are available, resize the mmap to allocate more. // TODO: If no free pages are available, resize the mmap to allocate more.
return nil, nil return nil, nil
} }
func (t *RWTransaction) insert(key []byte, data []byte) error {
// TODO: If there is not enough space on page for key+data then split.
// TODO: Move remaining data on page forward.
// TODO: Write leaf node to current location.
// TODO: Adjust available page size.
return nil
}

View File

@ -22,31 +22,21 @@ type txnid uint64
type Transaction struct { type Transaction struct {
id int id int
db *DB db *DB
dirty bool
spilled bool
err error
meta *meta meta *meta
sysfree Bucket sys Bucket
sysbuckets Bucket
buckets map[string]*Bucket buckets map[string]*Bucket
cursors map[uint32]*Cursor
pgno int
freePages []pgno
spillPages []pgno
dirtyList []pgno
reader *reader
// Implicit from slices? TODO: MDB_dbi mt_numdbs;
dirty_room int
} }
// init initializes the transaction and associates it with a database. // init initializes the transaction and associates it with a database.
func (t *Transaction) init(db *DB, meta *meta) error { func (t *Transaction) init(db *DB, meta *meta) {
t.db = db
t.meta = meta
t.buckets = make(map[string]*Bucket)
t.sys.transaction = t
t.sys.bucket = &t.meta.sys
} }
func (t *Transaction) Close() error { func (t *Transaction) Close() error {
// TODO: Close cursors.
// TODO: Close buckets. // TODO: Close buckets.
return nil return nil
} }
@ -57,66 +47,44 @@ func (t *Transaction) DB() *DB {
// Bucket retrieves a bucket by name. // Bucket retrieves a bucket by name.
func (t *Transaction) Bucket(name string) (*Bucket, error) { func (t *Transaction) Bucket(name string) (*Bucket, error) {
if strings.HasPrefix(name, "sys*") {
return nil, &Error{"system buckets are not available", nil}
}
return t.bucket(name) return t.bucket(name)
} }
func (t *Transaction) bucket(name string) (*Bucket, error) { func (t *Transaction) bucket(name string) (*Bucket, error) {
// TODO: if ((flags & VALID_FLAGS) != flags) return EINVAL; // Return bucket if it's already been looked up.
// TODO: if (txn->mt_flags & MDB_TXN_ERROR) return MDB_BAD_TXN;
// Return bucket if it's already been found.
if b := t.buckets[name]; b != nil { if b := t.buckets[name]; b != nil {
return b, nil return b, nil
} }
// Open a cursor for the system bucket. // Retrieve bucket data from the system bucket.
c, err := t.Cursor(&t.sysbuckets) data, err := c.get(&t.sys, []byte(name))
if err != nil {
return nil, err
}
// Retrieve bucket data.
data, err := c.Get([]byte(name))
if err != nil { if err != nil {
return nil, err return nil, err
} else if data == nil { } else if data == nil {
return nil, &Error{"bucket not found", nil} return nil, &Error{"bucket not found", nil}
} }
// TODO: Verify. // Create a bucket that overlays the data.
// MDB_node *node = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]); b := &Bucket{
// if (!(node->mn_flags & F_SUBDATA)) bucket: (*bucket)(unsafe.Pointer(&data[0])),
// return MDB_INCOMPATIBLE; name: name,
transaction: t,
}
t.buckets[name] = b
return nil, nil return b, nil
} }
// Cursor creates a cursor associated with a given bucket. // Cursor creates a cursor associated with a given bucket.
func (t *Transaction) Cursor(b *Bucket) (*Cursor, error) { func (t *Transaction) Cursor(b *Bucket) (*Cursor, error) {
if b == nil { if b == nil {
return nil, &Error{"bucket required", nil} return nil, &Error{"bucket required", nil}
} else if t.db == nil { } else
return nil, InvalidTransactionError
}
// TODO: if !(txn->mt_dbflags[dbi] & DB_VALID) return InvalidBucketError
// TODO: if (txn->mt_flags & MDB_TXN_ERROR) return BadTransactionError
// Return existing cursor for the bucket if one exists.
if c := t.cursors[b.id]; c != nil {
return c, nil
}
// Create a new cursor and associate it with the transaction and bucket. // Create a new cursor and associate it with the transaction and bucket.
c := &Cursor{ c := &Cursor{
transaction: t, transaction: t,
bucket: b, bucket: b,
top: -1,
pages: []*page{},
} }
// Set the first page if available. // Set the first page if available.