diff options
-rw-r--r-- | bucket.go | 11 | ||||
-rw-r--r-- | cursor.go | 51 | ||||
-rw-r--r-- | db.go | 22 | ||||
-rw-r--r-- | meta.go | 4 | ||||
-rw-r--r-- | page.go | 2 | ||||
-rw-r--r-- | transaction.go | 270 | ||||
-rw-r--r-- | transaction_test.go | 22 |
7 files changed, 170 insertions, 212 deletions
@@ -8,9 +8,18 @@ const ( // TODO: #define PERSISTENT_FLAGS (0xffff & ~(MDB_VALID)) // TODO: #define VALID_FLAGS (MDB_REVERSEKEY|MDB_DUPSORT|MDB_INTEGERKEY|MDB_DUPFIXED|MDB_INTEGERDUP|MDB_REVERSEDUP|MDB_CREATE) // TODO: #define FREE_DBI 0 -// TODO: #define MAIN_DBI 1 type Bucket struct { + *bucket + transaction *Transaction + name string + isNew bool + dirty bool + valid bool +} + +type bucket struct { + id uint32 pad uint32 flags uint16 depth uint16 @@ -39,46 +39,12 @@ type Cursor struct { backup *Cursor subcursor *Cursor transaction *Transaction - bucketId int - bucket *txnbucket + bucket *Bucket subbucket *Bucket - // subbucketx *bucketx - subbucketFlag int - snum int - top int - page []*page - ki []int /**< stack of page indices */ -} - -// Initialize a cursor for a given transaction and database. -func (c *Cursor) init(t *Transaction, b *txnbucket, sub *Cursor) { - c.next = nil - c.backup = nil - c.transaction = t - c.bucket = b - c.snum = 0 - c.top = 0 - // TODO: mc->mc_pg[0] = 0; - c.flags = 0 - - if (b.flags & MDB_DUPSORT) != 0 { - sub.subcursor = nil - sub.transaction = t - sub.bucket = b - sub.snum = 0 - sub.top = 0 - sub.flags = c_sub - // TODO: mx->mx_dbx.md_name.mv_size = 0; - // TODO: mx->mx_dbx.md_name.mv_data = NULL; - c.subcursor = sub - } else { - c.subcursor = nil - } - - // Find the root page if the bucket is stale. - if (c.bucket.flags & txnb_stale) != 0 { - c.findPage(nil, ps_rootonly) - } + snum int + top int + page []*page + ki []int /**< stack of page indices */ } // // @@ -1442,7 +1408,8 @@ func (c *Cursor) last() ([]byte, []byte) { return nil, nil } -func (c *Cursor) Get(key []byte, data []byte, op int) ([]byte, []byte, error) { +// , data []byte, op int +func (c *Cursor) Get(key []byte) ([]byte, error) { /* int rc; int exact = 0; @@ -1604,7 +1571,7 @@ func (c *Cursor) Get(key []byte, data []byte, op int) ([]byte, []byte, error) { return rc; */ - return nil, nil, nil + return nil, nil } // Touch all the pages in the cursor stack. Set mc_top. @@ -2512,7 +2479,7 @@ func (c *Cursor) Transaction() *Transaction { } func (c *Cursor) Bucket() *Bucket { - return c.bucket.bucket + return c.bucket } // Replace the key for a branch node with a new key. @@ -37,10 +37,9 @@ type DB struct { meta0 *meta meta1 *meta - pageSize int - readers []*reader - buckets []*Bucket - // xbuckets []*bucketx /**< array of static DB info */ + pageSize int + readers []*reader + buckets []*Bucket bucketFlags []int /**< array of flags from MDB_db.md_flags */ path string mmapSize int /**< size of the data memory map */ @@ -222,9 +221,16 @@ func (db *DB) Transaction(writable bool) (*Transaction, error) { // Create a transaction associated with the database. t := &Transaction{ db: db, + meta: db.meta(), writable: writable, } + // 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 + // We only allow one writable transaction at a time so save the reference. if writable { db.transaction = t @@ -238,6 +244,14 @@ func (db *DB) page(b []byte, id int) *page { return (*page)(unsafe.Pointer(&b[id*db.pageSize])) } +// meta retrieves the current meta page reference. +func (db *DB) meta() *meta { + if db.meta0.txnid > db.meta1.txnid { + return db.meta0 + } + return db.meta1 +} + // // // // // // @@ -31,8 +31,8 @@ const version uint32 = 1 type meta struct { magic uint32 version uint32 - free Bucket - main Bucket + free bucket + buckets bucket pgno int txnid int } @@ -89,7 +89,7 @@ func (p *page) init(pageSize int) { m.free.pad = uint32(pageSize) m.pgno = 1 m.free.root = p_invalid - m.main.root = p_invalid + m.buckets.root = p_invalid } // nodeCount returns the number of nodes on the page. diff --git a/transaction.go b/transaction.go index 4525a31..384249c 100644 --- a/transaction.go +++ b/transaction.go @@ -1,5 +1,9 @@ package bolt +import ( + "strings" +) + var TransactionExistingChildError = &Error{"txn already has a child", nil} var TransactionReadOnlyChildError = &Error{"read-only txn cannot create a child", nil} @@ -18,87 +22,133 @@ const ( ) type Transaction struct { - id int - db *DB - writable bool - dirty bool - spilled bool - err error - parent *Transaction - child *Transaction - buckets []*txnbucket + id int + db *DB + writable bool + dirty bool + spilled bool + err error + meta *meta + sysfree Bucket + sysbuckets Bucket + buckets map[string]*Bucket + cursors map[uint32]*Cursor pgno int freePages []pgno spillPages []pgno dirtyList []pgno reader *reader - // TODO: bucketxs []*bucketx // Implicit from slices? TODO: MDB_dbi mt_numdbs; dirty_room int pagestate pagestate } -type txnbucket struct { - bucket *Bucket - cursor *Cursor - flags int +// CreateBucket creates a new bucket. +func (t *Transaction) CreateBucket(name string, dupsort bool) (*Bucket, error) { + // TODO: Check if bucket already exists. + // TODO: Put new entry into system bucket. + + /* + MDB_db dummy; + data.mv_size = sizeof(MDB_db); + data.mv_data = &dummy; + memset(&dummy, 0, sizeof(dummy)); + dummy.md_root = P_INVALID; + dummy.md_flags = flags & PERSISTENT_FLAGS; + rc = mdb_cursor_put(&mc, &key, &data, F_SUBDATA); + dbflag |= DB_DIRTY; + */ + return nil, nil +} + +// DropBucket deletes a bucket. +func (t *Transaction) DeleteBucket(b *Bucket) error { + // TODO: Find bucket. + // TODO: Remove entry from system bucket. + return nil } -// Transaction begins a nested child transaction. Child transactions are only -// available to writable transactions and a transaction can only have one child -// at a time. -func (t *Transaction) Transaction() (*Transaction, error) { - // Exit if parent already has a child transaction. - if t.child != nil { - return nil, TransactionExistingChildError +// 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} } - // Exit if using parent for read-only transaction. - if !t.writable { - return nil, TransactionReadOnlyChildError + + 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. + if b := t.buckets[name]; b != nil { + return b, nil } - // TODO: Exit if parent is in an error state. - - // Create the child transaction and attach the parent. - child := &Transaction{ - id: t.id, - db: t.db, - parent: t, - writable: true, - pgno: t.pgno, - pagestate: t.db.pagestate, + + // Open a cursor for the system bucket. + c, err := t.Cursor(&t.sysbuckets) + if err != nil { + return nil, err } - copy(child.buckets, t.buckets) - - // TODO: Remove DB_NEW flag. - t.child = child - - // TODO: wtf? - // if (env->me_pghead) { - // size = MDB_IDL_SIZEOF(env->me_pghead); - // env->me_pghead = mdb_midl_alloc(env->me_pghead[0]); - // if (env->me_pghead) - // memcpy(env->me_pghead, ntxn->mnt_pgstate.mf_pghead, size); - // else - // rc = ENOMEM; - // } - - // TODO: Back up parent transaction's cursors. - // if t.shadow(child); err != nil { - // child.reset0() - // return err - // } - - return child, nil + + // Retrieve bucket data. + data, err := c.Get([]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; + + return nil, nil } +// Cursor creates a cursor associated with a given bucket. func (t *Transaction) Cursor(b *Bucket) (*Cursor, error) { - // TODO: if !(txn->mt_dbflags[dbi] & DB_VALID) return InvalidBucketError - // TODO: if (txn->mt_flags & MDB_TXN_ERROR) return BadTransactionError + if b == nil { + return nil, &Error{"bucket required", nil} + } // Allow read access to the freelist // TODO: if (!dbi && !F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) + return t.cursor(b) +} + +func (t *Transaction) cursor(b *Bucket) (*Cursor, error) { + // 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 b != nil { + if c := t.cursors[b.id]; c != nil { + return c, nil + } + } + + // Create a new cursor and associate it with the transaction and bucket. + c := &Cursor{ + transaction: t, + bucket: b, + } + if (b.flags & MDB_DUPSORT) != 0 { + c.subcursor = &Cursor{ + transaction: t, + bucket: b, + } + } + + // Find the root page if the bucket is stale. + if (c.bucket.flags & txnb_stale) != 0 { + c.findPage(nil, ps_rootonly) + } + /* MDB_cursor *mc; size_t size = sizeof(MDB_cursor); @@ -1426,110 +1476,6 @@ func (t *Transaction) Put(b Bucket, key []byte, data []byte, flags int) error { return nil } -func (t *Transaction) Bucket(name string, flags int) (*Bucket, error) { - /* - MDB_val key, data; - MDB_dbi i; - MDB_cursor mc; - int rc, dbflag, exact; - unsigned int unused = 0; - size_t len; - - if (txn->mt_dbxs[FREE_DBI].md_cmp == NULL) { - mdb_default_cmp(txn, FREE_DBI); - } - - if ((flags & VALID_FLAGS) != flags) - return EINVAL; - if (txn->mt_flags & MDB_TXN_ERROR) - return MDB_BAD_TXN; - - // main DB? - if (!name) { - *dbi = MAIN_DBI; - if (flags & PERSISTENT_FLAGS) { - uint16_t f2 = flags & PERSISTENT_FLAGS; - // make sure flag changes get committed - if ((txn->mt_dbs[MAIN_DBI].md_flags | f2) != txn->mt_dbs[MAIN_DBI].md_flags) { - txn->mt_dbs[MAIN_DBI].md_flags |= f2; - txn->mt_flags |= MDB_TXN_DIRTY; - } - } - mdb_default_cmp(txn, MAIN_DBI); - return MDB_SUCCESS; - } - - if (txn->mt_dbxs[MAIN_DBI].md_cmp == NULL) { - mdb_default_cmp(txn, MAIN_DBI); - } - - // Is the DB already open? - len = strlen(name); - for (i=2; i<txn->mt_numdbs; i++) { - if (!txn->mt_dbxs[i].md_name.mv_size) { - // Remember this free slot - if (!unused) unused = i; - continue; - } - if (len == txn->mt_dbxs[i].md_name.mv_size && - !strncmp(name, txn->mt_dbxs[i].md_name.mv_data, len)) { - *dbi = i; - return MDB_SUCCESS; - } - } - - // If no free slot and max hit, fail - if (!unused && txn->mt_numdbs >= txn->mt_env->me_maxdbs) - return MDB_DBS_FULL; - - // Cannot mix named databases with some mainDB flags - if (txn->mt_dbs[MAIN_DBI].md_flags & (MDB_DUPSORT|MDB_INTEGERKEY)) - return (flags & MDB_CREATE) ? MDB_INCOMPATIBLE : MDB_NOTFOUND; - - // Find the DB info - dbflag = DB_NEW|DB_VALID; - exact = 0; - key.mv_size = len; - key.mv_data = (void *)name; - mdb_cursor_init(&mc, txn, MAIN_DBI, NULL); - rc = mdb_cursor_set(&mc, &key, &data, MDB_SET, &exact); - if (rc == MDB_SUCCESS) { - // make sure this is actually a DB - 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; - } else if (rc == MDB_NOTFOUND && (flags & MDB_CREATE)) { - // Create if requested - MDB_db dummy; - data.mv_size = sizeof(MDB_db); - data.mv_data = &dummy; - memset(&dummy, 0, sizeof(dummy)); - dummy.md_root = P_INVALID; - dummy.md_flags = flags & PERSISTENT_FLAGS; - rc = mdb_cursor_put(&mc, &key, &data, F_SUBDATA); - dbflag |= DB_DIRTY; - } - - // OK, got info, add to table - if (rc == MDB_SUCCESS) { - unsigned int slot = unused ? unused : txn->mt_numdbs; - txn->mt_dbxs[slot].md_name.mv_data = strdup(name); - txn->mt_dbxs[slot].md_name.mv_size = len; - txn->mt_dbxs[slot].md_rel = NULL; - txn->mt_dbflags[slot] = dbflag; - memcpy(&txn->mt_dbs[slot], data.mv_data, sizeof(MDB_db)); - *dbi = slot; - mdb_default_cmp(txn, slot); - if (!unused) { - txn->mt_numdbs++; - } - } - - return rc; - */ - return nil, nil -} - func (t *Transaction) Stat(b Bucket) *stat { /* if (txn == NULL || arg == NULL || dbi >= txn->mt_numdbs) diff --git a/transaction_test.go b/transaction_test.go new file mode 100644 index 0000000..0b03d5e --- /dev/null +++ b/transaction_test.go @@ -0,0 +1,22 @@ +package bolt + +import ( + "testing" + +// "github.com/stretchr/testify/assert" +) + +//-------------------------------------- +// Cursor() +//-------------------------------------- + +// Ensure that a read transaction can get a cursor. +func TestTransactionCursor(t *testing.T) { + withOpenDB(func(db *DB, path string) { + /* + txn, _ := db.Transaction(false) + c := txn.Cursor() + assert.NotNil(t, c) + */ + }) +} |