aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Johnson <benbjohnson@yahoo.com>2014-01-14 13:01:02 -0700
committerBen Johnson <benbjohnson@yahoo.com>2014-01-14 13:01:02 -0700
commitcec7b942e7acd9190255716b37099240d6807e4f (patch)
tree5237583c9a9dde1de72bbc66ecfa81b2c0ff0870
parentBegin Transaction.Cursor(). (diff)
downloaddedo-cec7b942e7acd9190255716b37099240d6807e4f.tar.gz
dedo-cec7b942e7acd9190255716b37099240d6807e4f.tar.xz
Add system buckets.
-rw-r--r--bucket.go11
-rw-r--r--cursor.go51
-rw-r--r--db.go22
-rw-r--r--meta.go4
-rw-r--r--page.go2
-rw-r--r--transaction.go270
-rw-r--r--transaction_test.go22
7 files changed, 170 insertions, 212 deletions
diff --git a/bucket.go b/bucket.go
index d0b2ac2..ece92b5 100644
--- a/bucket.go
+++ b/bucket.go
@@ -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
diff --git a/cursor.go b/cursor.go
index e5b3444..9748972 100644
--- a/cursor.go
+++ b/cursor.go
@@ -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.
diff --git a/db.go b/db.go
index b13ec5f..af946e3 100644
--- a/db.go
+++ b/db.go
@@ -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
+}
+
// //
// //
// //
diff --git a/meta.go b/meta.go
index cc3f9e5..6fa2df1 100644
--- a/meta.go
+++ b/meta.go
@@ -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
}
diff --git a/page.go b/page.go
index 33fa0c2..bd60743 100644
--- a/page.go
+++ b/page.go
@@ -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)
+ */
+ })
+}