mirror of https://github.com/etcd-io/bbolt.git
Initialize transaction/rwtransaction.
parent
2c6b8e0ebf
commit
1baa6d576a
|
@ -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
|
20
bucket.go
20
bucket.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
42
cursor.go
42
cursor.go
|
@ -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
29
db.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
meta.go
2
meta.go
|
@ -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
|
||||||
|
|
1
page.go
1
page.go
|
@ -1,7 +1,6 @@
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue