diff --git a/TODO b/TODO new file mode 100644 index 0000000..ae1dc9f --- /dev/null +++ b/TODO @@ -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 diff --git a/bucket.go b/bucket.go index f9c3566..39ab551 100644 --- a/bucket.go +++ b/bucket.go @@ -5,6 +5,8 @@ type bucketid uint32 type Bucket struct { *bucket name string + transaction Transaction, + cursors []*Cursor, } type bucket struct { @@ -15,3 +17,21 @@ type bucket struct { leafs pgid 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 +} diff --git a/cursor.go b/cursor.go index d1990dc..b93178e 100644 --- a/cursor.go +++ b/cursor.go @@ -1,57 +1,37 @@ package bolt type Cursor struct { - transaction *Transaction 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 index int } -func (c *Cursor) Transaction() *Transaction { - return c.transaction -} - func (c *Cursor) Bucket() *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. func (c *Cursor) First() ([]byte, []byte, error) { // TODO: Traverse to the first key. return nil, nil, nil } -// Set the cursor on a specific data item. -// (bool return is whether it is exact). -func (c *Cursor) set(key []byte, data []byte, op int) (error, bool) { +// Move the cursor to the next key/value. +func (c *Cursor) Next() ([]byte, []byte, error) { + 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: Check if len(key) > 0. // TODO: Start from root page and traverse to correct page. - return nil, false -} - -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 + return nil, nil } diff --git a/db.go b/db.go index 2a90061..95149a2 100644 --- a/db.go +++ b/db.go @@ -217,18 +217,8 @@ func (db *DB) Transaction() (*Transaction, error) { } // Create a transaction associated with the database. - t := &Transaction{ - db: db, - 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 + t := &Transaction{} + t.init(db, db.meta()) return t, nil } @@ -236,16 +226,21 @@ func (db *DB) Transaction() (*Transaction, error) { // RWTransaction creates a read/write transaction. // Only one read/write transaction is allowed at a time. func (db *DB) RWTransaction() (*RWTransaction, error) { + db.Lock() + defer db.Unlock() + // TODO: db.writerMutex.Lock() // TODO: Add unlock to RWTransaction.Commit() / Abort() - t := &RWTransaction{} - - // Exit if a read-write transaction is currently in progress. - if db.transaction != nil { - return nil, TransactionInProgressError + // Exit if the database is not open yet. + if !db.opened { + return nil, DatabaseNotOpenError } + // Create a transaction associated with the database. + t := &RWTransaction{} + t.init(db, db.meta()) + return t, nil } diff --git a/meta.go b/meta.go index 881417a..be70993 100644 --- a/meta.go +++ b/meta.go @@ -10,7 +10,7 @@ const version uint32 = 1 type meta struct { magic uint32 version uint32 - buckets bucket + sys bucket pageSize uint32 pgid pgid txnid txnid diff --git a/page.go b/page.go index ba39dc5..c26b957 100644 --- a/page.go +++ b/page.go @@ -1,7 +1,6 @@ package bolt import ( - "bytes" "unsafe" ) diff --git a/rwtransaction.go b/rwtransaction.go index 2c0837f..b0123f5 100644 --- a/rwtransaction.go +++ b/rwtransaction.go @@ -5,8 +5,15 @@ package bolt type RWTransaction struct { Transaction - dirtyPages map[int]*page - freelist []pgno + dirtyPages map[pgid]*page + 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. @@ -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. 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 +} diff --git a/transaction.go b/transaction.go index b79f3e7..8371624 100644 --- a/transaction.go +++ b/transaction.go @@ -22,31 +22,21 @@ type txnid uint64 type Transaction struct { id int db *DB - dirty bool - spilled bool - err error meta *meta - sysfree Bucket - sysbuckets Bucket + sys 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. -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 { - // TODO: Close cursors. // TODO: Close buckets. return nil } @@ -57,66 +47,44 @@ func (t *Transaction) DB() *DB { // Bucket retrieves a bucket by name. 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) } func (t *Transaction) bucket(name string) (*Bucket, error) { - // TODO: if ((flags & VALID_FLAGS) != flags) return EINVAL; - // TODO: if (txn->mt_flags & MDB_TXN_ERROR) return MDB_BAD_TXN; - - // Return bucket if it's already been found. + // Return bucket if it's already been looked up. if b := t.buckets[name]; b != nil { return b, nil } - // Open a cursor for the system bucket. - c, err := t.Cursor(&t.sysbuckets) - if err != nil { - return nil, err - } - - // Retrieve bucket data. - data, err := c.Get([]byte(name)) + // Retrieve bucket data from the system bucket. + data, err := c.get(&t.sys, []byte(name)) if err != nil { return nil, err } else if data == nil { return nil, &Error{"bucket not found", nil} } - // TODO: Verify. - // MDB_node *node = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]); - // if (!(node->mn_flags & F_SUBDATA)) - // return MDB_INCOMPATIBLE; + // Create a bucket that overlays the data. + b := &Bucket{ + bucket: (*bucket)(unsafe.Pointer(&data[0])), + name: name, + transaction: t, + } + t.buckets[name] = b - return nil, nil + return b, nil } // Cursor creates a cursor associated with a given bucket. func (t *Transaction) Cursor(b *Bucket) (*Cursor, error) { if b == nil { return nil, &Error{"bucket required", nil} - } else if t.db == nil { - 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 - } + } else // Create a new cursor and associate it with the transaction and bucket. c := &Cursor{ transaction: t, bucket: b, - top: -1, - pages: []*page{}, } // Set the first page if available.