aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bucket.go4
-rw-r--r--cursor.go221
-rw-r--r--db.go143
-rw-r--r--db_test.go46
-rw-r--r--page.go12
-rw-r--r--transaction.go249
6 files changed, 357 insertions, 318 deletions
diff --git a/bucket.go b/bucket.go
index 6e7b791..d0b2ac2 100644
--- a/bucket.go
+++ b/bucket.go
@@ -1,5 +1,9 @@
package bolt
+const (
+ MDB_DUPSORT = 0x04
+)
+
// TODO: #define MDB_VALID 0x8000 /**< DB handle is valid, for me_dbflags */
// 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)
diff --git a/cursor.go b/cursor.go
index bd85a3b..e5b3444 100644
--- a/cursor.go
+++ b/cursor.go
@@ -2,15 +2,18 @@ package bolt
// TODO: #define CURSOR_STACK 32
-// TODO: #define C_INITIALIZED 0x01 /**< cursor has been initialized and is valid */
-// TODO: #define C_EOF 0x02 /**< No more data */
-// TODO: #define C_SUB 0x04 /**< Cursor is a sub-cursor */
-// TODO: #define C_DEL 0x08 /**< last op was a cursor_del */
-// TODO: #define C_SPLITTING 0x20 /**< Cursor is in page_split */
-// TODO: #define C_UNTRACK 0x40 /**< Un-track cursor when closing */
+const (
+ c_initialized = 0x01 /**< cursor has been initialized and is valid */
+ c_eof = 0x02 /**< No more data */
+ c_sub = 0x04 /**< Cursor is a sub-cursor */
+ c_del = 0x08 /**< last op was a cursor_del */
+ c_splitting = 0x20 /**< Cursor is in page_split */
+ c_untrack = 0x40 /**< Un-track cursor when closing */
+)
// TODO: #define MDB_NOSPILL 0x8000 /** Do not spill pages to disk if txn is getting full, may fail instead */
+/*
type Cursor interface {
First() error
FirstDup() error
@@ -28,37 +31,75 @@ type Cursor interface {
Set() ([]byte, []byte, error)
SetRange() ([]byte, []byte, error)
}
+*/
-type cursor struct {
+type Cursor struct {
flags int
- _next *cursor
- backup *cursor
- xcursor *xcursor
- transaction *transaction
+ next *Cursor
+ backup *Cursor
+ subcursor *Cursor
+ transaction *Transaction
bucketId int
- bucket *Bucket
- // bucketx *bucketx
- bucketFlag int
- snum int
- top int
- page []*page
- ki []int /**< stack of page indices */
+ bucket *txnbucket
+ subbucket *Bucket
+ // subbucketx *bucketx
+ subbucketFlag int
+ snum int
+ top int
+ page []*page
+ ki []int /**< stack of page indices */
}
-type xcursor struct {
- cursor cursor
- bucket *Bucket
- // bucketx *bucketx
- bucketFlag int
+// 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)
+ }
}
+// //
+// //
+// //
+// //
+// //
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CONVERTED ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
+// //
+// //
+// //
+// //
+// //
+
// Set or clear P_KEEP in dirty, non-overflow, non-sub pages watched by txn.
// @param[in] mc A cursor handle for the current operation.
// @param[in] pflags Flags of the pages to update:
// P_DIRTY to set P_KEEP, P_DIRTY|P_KEEP to clear it.
// @param[in] all No shortcuts. Needed except after a full #mdb_page_flush().
// @return 0 on success, non-zero on failure.
-func (c *cursor) xkeep(pflags int, all int) error {
+func (c *Cursor) xkeep(pflags int, all int) error {
/*
enum { Mask = P_SUBP|P_DIRTY|P_KEEP };
MDB_txn *txn = mc->mc_txn;
@@ -149,7 +190,7 @@ func (c *cursor) xkeep(pflags int, all int) error {
// @param[in] key For a put operation, the key being stored.
// @param[in] data For a put operation, the data being stored.
// @return 0 on success, non-zero on failure.
-func (c *cursor) spill(key []byte, data []byte) error {
+func (c *Cursor) spill(key []byte, data []byte) error {
/*
MDB_txn *txn = m0->mc_txn;
MDB_page *dp;
@@ -424,7 +465,7 @@ func (p *page) copyTo(dst *page, size int) {
// Touch a page: make it dirty and re-insert into tree with updated pgno.
// @param[in] mc cursor pointing to the page to be touched
// @return 0 on success, non-zero on failure.
-func (c *cursor) page_touch() int {
+func (c *Cursor) page_touch() int {
/*
MDB_page *mp = mc->mc_pg[mc->mc_top], *np;
MDB_txn *txn = mc->mc_txn;
@@ -532,7 +573,7 @@ func (c *cursor) page_touch() int {
// in *exactp (1 or 0).
// Updates the cursor index with the index of the found entry.
// If no entry larger or equal to the key is found, returns NULL.
-func (c *cursor) search(key []byte) (*node, bool) {
+func (c *Cursor) search(key []byte) (*node, bool) {
/*
unsigned int i = 0, nkeys;
int low, high;
@@ -623,7 +664,7 @@ func (c *cursor) search(key []byte) (*node, bool) {
return nil, false
}
-func (c *cursor) pop() {
+func (c *Cursor) pop() {
/*
if (mc->mc_snum) {
#if MDB_DEBUG
@@ -640,7 +681,7 @@ func (c *cursor) pop() {
}
/** Push a page onto the top of the cursor's stack. */
-func (c *cursor) push(p *page) error {
+func (c *Cursor) push(p *page) error {
/*
DPRINTF(("pushing page %"Z"u on db %d cursor %p", mp->mp_pgno,
DDBI(mc), (void *) mc));
@@ -661,7 +702,7 @@ func (c *cursor) push(p *page) error {
// Finish #mdb_page_search() / #mdb_page_search_lowest().
// The cursor is at the root page, set up the rest of it.
-func (c *cursor) searchRoot(key []byte, flags int) error {
+func (c *Cursor) searchRoot(key []byte, flags int) error {
/*
MDB_page *mp = mc->mc_pg[mc->mc_top];
int rc;
@@ -733,7 +774,7 @@ func (c *cursor) searchRoot(key []byte, flags int) error {
// before calling mdb_page_search_root(), because the callers
// are all in situations where the current page is known to
// be underfilled.
-func (c *cursor) searchLowest() error {
+func (c *Cursor) searchLowest() error {
/*
MDB_page *mp = mc->mc_pg[mc->mc_top];
MDB_node *node = NODEPTR(mp, 0);
@@ -760,7 +801,7 @@ func (c *cursor) searchLowest() error {
// This is used by #mdb_cursor_first() and #mdb_cursor_last().
// If MDB_PS_ROOTONLY set, just fetch root node, no further lookups.
// @return 0 on success, non-zero on failure.
-func (c *cursor) findPage(key []byte, flags int) error {
+func (c *Cursor) findPage(key []byte, flags int) error {
/*
int rc;
pgno_t root;
@@ -831,7 +872,7 @@ func (c *cursor) findPage(key []byte, flags int) error {
return nil
}
-func (c *cursor) freeOverflowPage(p *page) error {
+func (c *Cursor) freeOverflowPage(p *page) error {
/*
MDB_txn *txn = mc->mc_txn;
pgno_t pg = mp->mp_pgno;
@@ -913,7 +954,7 @@ func (c *cursor) freeOverflowPage(p *page) error {
// @param[in] move_right Non-zero if the right sibling is requested,
// otherwise the left sibling.
// @return 0 on success, non-zero on failure.
-func (c *cursor) sibling(moveRight bool) error {
+func (c *Cursor) sibling(moveRight bool) error {
/*
int rc;
MDB_node *indx;
@@ -964,7 +1005,7 @@ func (c *cursor) sibling(moveRight bool) error {
}
// Move the cursor to the next data item.
-func (c *cursor) next(key []byte, data []byte, op int) error {
+func (c *Cursor) Next(key []byte, data []byte, op int) error {
/*
MDB_page *mp;
MDB_node *leaf;
@@ -1046,7 +1087,7 @@ func (c *cursor) next(key []byte, data []byte, op int) error {
}
// Move the cursor to the previous data item.
-func (c *cursor) prev(key []byte, data []byte, op int) error {
+func (c *Cursor) prev(key []byte, data []byte, op int) error {
/*
MDB_page *mp;
MDB_node *leaf;
@@ -1124,7 +1165,7 @@ func (c *cursor) prev(key []byte, data []byte, op int) error {
// 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) {
+func (c *Cursor) set(key []byte, data []byte, op int) (error, bool) {
/*
int rc;
MDB_page *mp;
@@ -1310,7 +1351,7 @@ func (c *cursor) set(key []byte, data []byte, op int) (error, bool) {
}
// Move the cursor to the first item in the database.
-func (c *cursor) first(key []byte, data []byte) error {
+func (c *Cursor) first(key []byte, data []byte) error {
/*
int rc;
MDB_node *leaf;
@@ -1355,7 +1396,7 @@ func (c *cursor) first(key []byte, data []byte) error {
}
// Move the cursor to the last item in the database.
-func (c *cursor) last() ([]byte, []byte) {
+func (c *Cursor) last() ([]byte, []byte) {
/*
int rc;
MDB_node *leaf;
@@ -1401,7 +1442,7 @@ func (c *cursor) last() ([]byte, []byte) {
return nil, nil
}
-func (c *cursor) Get(key []byte, data []byte, op int) ([]byte, []byte, error) {
+func (c *Cursor) Get(key []byte, data []byte, op int) ([]byte, []byte, error) {
/*
int rc;
int exact = 0;
@@ -1569,7 +1610,7 @@ func (c *cursor) Get(key []byte, data []byte, op int) ([]byte, []byte, error) {
// Touch all the pages in the cursor stack. Set mc_top.
// Makes sure all the pages are writable, before attempting a write operation.
// @param[in] mc The cursor to operate on.
-func (c *cursor) touch() error {
+func (c *Cursor) touch() error {
/*
int rc = MDB_SUCCESS;
@@ -2072,7 +2113,7 @@ func (c *cursor) touch() error {
return nil
}
-func (c *cursor) Del(flags int) error {
+func (c *Cursor) Del(flags int) error {
/*
MDB_node *leaf;
MDB_page *mp;
@@ -2152,7 +2193,7 @@ func (c *cursor) Del(flags int) error {
// unless allocating overflow pages for a large record.
// @param[out] mp Address of a page, or NULL on failure.
// @return 0 on success, non-zero on failure.
-func (c *cursor) newPage(flags int, num int) ([]*page, error) {
+func (c *Cursor) newPage(flags int, num int) ([]*page, error) {
/*
MDB_page *np;
int rc;
@@ -2194,7 +2235,7 @@ func (c *cursor) newPage(flags int, num int) ([]*page, error) {
// should never happen since all callers already calculate the
// page's free space before calling this function.
// </ul>
-func (c *cursor) addNode(index int, key []byte, data []byte, pgno int, flags int) error {
+func (c *Cursor) addNode(index int, key []byte, data []byte, pgno int, flags int) error {
/*
unsigned int i;
size_t node_size = NODESIZE;
@@ -2323,7 +2364,7 @@ func (c *cursor) addNode(index int, key []byte, data []byte, pgno int, flags int
// @param[in] indx The index of the node to delete.
// @param[in] ksize The size of a node. Only used if the page is
// part of a #MDB_DUPFIXED database.
-func (c *cursor) deleteNode(ksize int) {
+func (c *Cursor) deleteNode(ksize int) {
/*
MDB_page *mp = mc->mc_pg[mc->mc_top];
indx_t indx = mc->mc_ki[mc->mc_top];
@@ -2375,41 +2416,12 @@ func (c *cursor) deleteNode(ksize int) {
*/
}
-// Initial setup of a sorted-dups cursor.
-// Sorted duplicates are implemented as a sub-database for the given key.
-// The duplicate data items are actually keys of the sub-database.
-// Operations on the duplicate data items are performed using a sub-cursor
-// initialized when the sub-database is first accessed. This function does
-// the preliminary setup of the sub-cursor, filling in the fields that
-// depend only on the parent DB.
-// @param[in] mc The main cursor whose sorted-dups cursor is to be initialized.
-func (c *cursor) xcursor_init0() {
- /*
- MDB_xcursor *mx = mc->mc_xcursor;
-
- mx->mx_cursor.mc_xcursor = NULL;
- mx->mx_cursor.mc_txn = mc->mc_txn;
- mx->mx_cursor.mc_db = &mx->mx_db;
- mx->mx_cursor.mc_dbx = &mx->mx_dbx;
- mx->mx_cursor.mc_dbi = mc->mc_dbi;
- mx->mx_cursor.mc_dbflag = &mx->mx_dbflag;
- mx->mx_cursor.mc_snum = 0;
- mx->mx_cursor.mc_top = 0;
- mx->mx_cursor.mc_flags = C_SUB;
- mx->mx_dbx.md_name.mv_size = 0;
- mx->mx_dbx.md_name.mv_data = NULL;
- mx->mx_dbx.md_cmp = mc->mc_dbx->md_dcmp;
- mx->mx_dbx.md_dcmp = NULL;
- mx->mx_dbx.md_rel = mc->mc_dbx->md_rel;
- */
-}
-
// Final setup of a sorted-dups cursor.
// Sets up the fields that depend on the data from the main cursor.
// @param[in] mc The main cursor whose sorted-dups cursor is to be initialized.
// @param[in] node The data containing the #MDB_db record for the
// sorted-dup database.
-func (c *cursor) xcursor_init1(n *node) {
+func (c *Cursor) xcursor_init1(n *node) {
/*
MDB_xcursor *mx = mc->mc_xcursor;
@@ -2455,35 +2467,8 @@ func (c *cursor) xcursor_init1(n *node) {
*/
}
-// Initialize a cursor for a given transaction and database.
-func (c *cursor) init(t *transaction, bucket *Bucket, mx *xcursor) {
- /*
- mc->mc_next = NULL;
- mc->mc_backup = NULL;
- mc->mc_dbi = dbi;
- mc->mc_txn = txn;
- mc->mc_db = &txn->mt_dbs[dbi];
- mc->mc_dbx = &txn->mt_dbxs[dbi];
- mc->mc_dbflag = &txn->mt_dbflags[dbi];
- mc->mc_snum = 0;
- mc->mc_top = 0;
- mc->mc_pg[0] = 0;
- mc->mc_flags = 0;
- if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) {
- mdb_tassert(txn, mx != NULL);
- mc->mc_xcursor = mx;
- mdb_xcursor_init0(mc);
- } else {
- mc->mc_xcursor = NULL;
- }
- if (*mc->mc_dbflag & DB_STALE) {
- mdb_page_search(mc, NULL, MDB_PS_ROOTONLY);
- }
- */
-}
-
// Return the count of duplicate data items for the current key.
-func (c *cursor) count() (int, error) {
+func (c *Cursor) count() (int, error) {
/*
MDB_node *leaf;
@@ -2507,7 +2492,7 @@ func (c *cursor) count() (int, error) {
return 0, nil
}
-func (c *cursor) Close() {
+func (c *Cursor) Close() {
/*
if (mc && !mc->mc_backup) {
// remove from txn, if tracked
@@ -2522,23 +2507,19 @@ func (c *cursor) Close() {
*/
}
-func (c *cursor) Transaction() Transaction {
- /*
- if (!mc) return NULL;
- return mc->mc_txn;
- */
- return nil
+func (c *Cursor) Transaction() *Transaction {
+ return c.transaction
}
-func (c *cursor) Bucket() *Bucket {
- return c.bucket
+func (c *Cursor) Bucket() *Bucket {
+ return c.bucket.bucket
}
// Replace the key for a branch node with a new key.
// @param[in] mc Cursor pointing to the node to operate on.
// @param[in] key The new key to use.
// @return 0 on success, non-zero on failure.
-func (c *cursor) updateKey(key []byte) error {
+func (c *Cursor) updateKey(key []byte) error {
/*
MDB_page *mp;
MDB_node *node;
@@ -2609,7 +2590,7 @@ func (c *cursor) updateKey(key []byte) error {
}
// Move a node from csrc to cdst.
-func (c *cursor) moveNodeTo(dst *cursor) error {
+func (c *Cursor) moveNodeTo(dst *Cursor) error {
/*
MDB_node *srcnode;
MDB_val key, data;
@@ -2786,7 +2767,7 @@ func (c *cursor) moveNodeTo(dst *cursor) error {
// the \b csrc page will be freed.
// @param[in] csrc Cursor pointing to the source page.
// @param[in] cdst Cursor pointing to the destination page.
-func (c *cursor) mergePage(dst *cursor) error {
+func (c *Cursor) mergePage(dst *Cursor) error {
/*
int rc;
indx_t i, j;
@@ -2901,7 +2882,7 @@ func (c *cursor) mergePage(dst *cursor) error {
// Copy the contents of a cursor.
// @param[in] csrc The cursor to copy from.
// @param[out] cdst The cursor to copy to.
-func (c *cursor) copyTo(dst *cursor) {
+func (c *Cursor) copyTo(dst *Cursor) {
/*
unsigned int i;
@@ -2924,7 +2905,7 @@ func (c *cursor) copyTo(dst *cursor) {
// @param[in] mc Cursor pointing to the page where rebalancing
// should begin.
// @return 0 on success, non-zero on failure.
-func (c *cursor) rebalance() error {
+func (c *Cursor) rebalance() error {
/*
MDB_node *node;
int rc;
@@ -3079,7 +3060,7 @@ func (c *cursor) rebalance() error {
}
// Complete a delete operation started by #mdb_cursor_del().
-func (c *cursor) del0(leaf *node) error {
+func (c *Cursor) del0(leaf *node) error {
/*
int rc;
MDB_page *mp;
@@ -3149,7 +3130,7 @@ func (c *cursor) del0(leaf *node) error {
// @param[in] newpgno The page number, if the new node is a branch node.
// @param[in] nflags The #NODE_ADD_FLAGS for the new node.
// @return 0 on success, non-zero on failure.
-func (c *cursor) splitPage(newKey []byte, newData []byte, newpgno int, nflags int) error {
+func (c *Cursor) splitPage(newKey []byte, newData []byte, newpgno int, nflags int) error {
/*
unsigned int flags;
int rc = MDB_SUCCESS, new_root = 0, did_split = 0;
@@ -3533,7 +3514,7 @@ func (c *cursor) splitPage(newKey []byte, newData []byte, newpgno int, nflags in
// @param[in] mc Cursor on the DB to free.
// @param[in] subs non-Zero to check for sub-DBs in this DB.
// @return 0 on success, non-zero on failure.
-func (c *cursor) drop0(subs int) error {
+func (c *Cursor) drop0(subs int) error {
/*
int rc;
diff --git a/db.go b/db.go
index f5c103d..b13ec5f 100644
--- a/db.go
+++ b/db.go
@@ -15,7 +15,9 @@ const (
IntegerDupKey
)
-var DatabaseAlreadyOpenedError = &Error{"Database already open", nil}
+var DatabaseNotOpenError = &Error{"db is not open", nil}
+var DatabaseAlreadyOpenedError = &Error{"db already open", nil}
+var TransactionInProgressError = &Error{"writable transaction is already in progress", nil}
// TODO: #define MDB_FATAL_ERROR 0x80000000U /** Failed to update the meta page. Probably an I/O error. */
// TODO: #define MDB_ENV_ACTIVE 0x20000000U /** Some fields are initialized. */
@@ -44,9 +46,9 @@ type DB struct {
mmapSize int /**< size of the data memory map */
size int /**< current file size */
pbuf []byte
- transaction *transaction /**< current write transaction */
+ transaction *Transaction /**< current write transaction */
maxPageNumber int /**< me_mapsize / me_psize */
- pageState pageState /**< state of old pages from freeDB */
+ pagestate pagestate /**< state of old pages from freeDB */
dpages []*page /**< list of malloc'd blocks for re-use */
freePages []int /** IDL of pages that became unused in a write txn */
dirtyPages []int /** ID2L of pages written during a write txn. Length MDB_IDL_UM_SIZE. */
@@ -203,12 +205,50 @@ func (db *DB) close() {
// TODO
}
+// Transaction creates a transaction that's associated with this database.
+func (db *DB) Transaction(writable bool) (*Transaction, error) {
+ db.Lock()
+ defer db.Unlock()
+
+ // Exit if the database is not open yet.
+ if !db.opened {
+ return nil, DatabaseNotOpenError
+ }
+ // Exit if a writable transaction is currently in progress.
+ if writable && db.transaction != nil {
+ return nil, TransactionInProgressError
+ }
+
+ // Create a transaction associated with the database.
+ t := &Transaction{
+ db: db,
+ writable: writable,
+ }
+
+ // We only allow one writable transaction at a time so save the reference.
+ if writable {
+ db.transaction = t
+ }
+
+ return t, nil
+}
+
// page retrieves a page reference from a given byte array based on the current page size.
func (db *DB) page(b []byte, id int) *page {
return (*page)(unsafe.Pointer(&b[id*db.pageSize]))
}
+// //
+// //
+// //
+// //
+// //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CONVERTED ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
+// //
+// //
+// //
+// //
+// //
func (db *DB) freePage(p *page) {
/*
@@ -266,103 +306,6 @@ func (db *DB) sync(force bool) error {
return nil
}
-func (db *DB) Transaction(parent *transaction, flags int) (*transaction, error) {
- /*
- MDB_txn *txn;
- MDB_ntxn *ntxn;
- int rc, size, tsize = sizeof(MDB_txn);
-
- if (env->me_flags & MDB_FATAL_ERROR) {
- DPUTS("environment had fatal error, must shutdown!");
- return MDB_PANIC;
- }
- if ((env->me_flags & MDB_RDONLY) && !(flags & MDB_RDONLY))
- return EACCES;
- if (parent) {
- // Nested transactions: Max 1 child, write txns only, no writemap
- if (parent->mt_child ||
- (flags & MDB_RDONLY) ||
- (parent->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_ERROR)) ||
- (env->me_flags & MDB_WRITEMAP))
- {
- return (parent->mt_flags & MDB_TXN_RDONLY) ? EINVAL : MDB_BAD_TXN;
- }
- tsize = sizeof(MDB_ntxn);
- }
- size = tsize + env->me_maxdbs * (sizeof(MDB_db)+1);
- if (!(flags & MDB_RDONLY))
- size += env->me_maxdbs * sizeof(MDB_cursor *);
-
- if ((txn = calloc(1, size)) == NULL) {
- DPRINTF(("calloc: %s", strerror(ErrCode())));
- return ENOMEM;
- }
- txn->mt_dbs = (MDB_db *) ((char *)txn + tsize);
- if (flags & MDB_RDONLY) {
- txn->mt_flags |= MDB_TXN_RDONLY;
- txn->mt_dbflags = (unsigned char *)(txn->mt_dbs + env->me_maxdbs);
- } else {
- txn->mt_cursors = (MDB_cursor **)(txn->mt_dbs + env->me_maxdbs);
- txn->mt_dbflags = (unsigned char *)(txn->mt_cursors + env->me_maxdbs);
- }
- txn->mt_env = env;
-
- if (parent) {
- unsigned int i;
- txn->mt_u.dirty_list = malloc(sizeof(MDB_ID2)*MDB_IDL_UM_SIZE);
- if (!txn->mt_u.dirty_list ||
- !(txn->mt_free_pgs = mdb_midl_alloc(MDB_IDL_UM_MAX)))
- {
- free(txn->mt_u.dirty_list);
- free(txn);
- return ENOMEM;
- }
- txn->mt_txnid = parent->mt_txnid;
- txn->mt_dirty_room = parent->mt_dirty_room;
- txn->mt_u.dirty_list[0].mid = 0;
- txn->mt_spill_pgs = NULL;
- txn->mt_next_pgno = parent->mt_next_pgno;
- parent->mt_child = txn;
- txn->mt_parent = parent;
- txn->mt_numdbs = parent->mt_numdbs;
- txn->mt_flags = parent->mt_flags;
- txn->mt_dbxs = parent->mt_dbxs;
- memcpy(txn->mt_dbs, parent->mt_dbs, txn->mt_numdbs * sizeof(MDB_db));
- // Copy parent's mt_dbflags, but clear DB_NEW
- for (i=0; i<txn->mt_numdbs; i++)
- txn->mt_dbflags[i] = parent->mt_dbflags[i] & ~DB_NEW;
- rc = 0;
- ntxn = (MDB_ntxn *)txn;
- ntxn->mnt_pgstate = env->me_pgstate; // save parent me_pghead & co
- 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;
- }
- if (!rc)
- rc = mdb_cursor_shadow(parent, txn);
- if (rc)
- mdb_txn_reset0(txn, "beginchild-fail");
- } else {
- rc = mdb_txn_renew0(txn);
- }
- if (rc)
- free(txn);
- else {
- *ret = txn;
- DPRINTF(("begin txn %"Z"u%c %p on mdbenv %p, root page %"Z"u",
- txn->mt_txnid, (txn->mt_flags & MDB_TXN_RDONLY) ? 'r' : 'w',
- (void *) txn, (void *) env, txn->mt_dbs[MAIN_DBI].md_root));
- }
-
- return rc;
- */
- return nil, nil
-}
-
// Check both meta pages to see which one is newer.
// @param[in] env the environment handle
// @return meta toggle (0 or 1).
diff --git a/db_test.go b/db_test.go
index cf36c37..b9c4afe 100644
--- a/db_test.go
+++ b/db_test.go
@@ -180,6 +180,41 @@ func TestDBCorruptMeta1(t *testing.T) {
})
}
+//--------------------------------------
+// Transaction()
+//--------------------------------------
+
+// Ensure that a database cannot open a transaction when it's not open.
+func TestDBTransactionDatabaseNotOpenError(t *testing.T) {
+ withDB(func(db *DB, path string) {
+ txn, err := db.Transaction(false)
+ assert.Nil(t, txn)
+ assert.Equal(t, err, DatabaseNotOpenError)
+ })
+}
+
+// Ensure that a database cannot open a writable transaction while one is in progress.
+func TestDBTransactionInProgressError(t *testing.T) {
+ withOpenDB(func(db *DB, path string) {
+ db.Transaction(true)
+ txn, err := db.Transaction(true)
+ assert.Nil(t, txn)
+ assert.Equal(t, err, TransactionInProgressError)
+ })
+}
+
+// Ensure that a database can create a new writable transaction.
+func TestDBTransactionWriter(t *testing.T) {
+ withOpenDB(func(db *DB, path string) {
+ txn, err := db.Transaction(true)
+ if assert.NotNil(t, txn) {
+ assert.Equal(t, txn.db, db)
+ assert.Equal(t, txn.writable, true)
+ }
+ assert.NoError(t, err)
+ })
+}
+
// withDB executes a function with a database reference.
func withDB(fn func(*DB, string)) {
f, _ := ioutil.TempFile("", "bolt-")
@@ -200,3 +235,14 @@ func withMockDB(fn func(*DB, *mockos, *mocksyscall, string)) {
db.syscall = syscall
fn(db, os, syscall, "/mock/db")
}
+
+// withOpenDB executes a function with an already opened database.
+func withOpenDB(fn func(*DB, string)) {
+ withDB(func(db *DB, path string) {
+ if err := db.Open(path, 0666); err != nil {
+ panic("cannot open db: " + err.Error())
+ }
+ defer db.Close()
+ fn(db, path)
+ })
+}
diff --git a/page.go b/page.go
index 5827f96..33fa0c2 100644
--- a/page.go
+++ b/page.go
@@ -38,10 +38,12 @@ const maxWriteByteCount uint = 0x80000000 // TODO: #define MAX_WRITE 0x80000000U
// #define MDB_COMMIT_PAGES IOV_MAX
// #endif
-// TODO: #define MDB_PS_MODIFY 1
-// TODO: #define MDB_PS_ROOTONLY 2
-// TODO: #define MDB_PS_FIRST 4
-// TODO: #define MDB_PS_LAST 8
+const (
+ MDB_PS_MODIFY = 1
+ MDB_PS_ROOTONLY = 2
+ MDB_PS_FIRST = 4
+ MDB_PS_LAST = 8
+)
// TODO: #define MDB_SPLIT_REPLACE MDB_APPENDDUP /**< newkey is not new */
@@ -58,7 +60,7 @@ type page struct {
ptr int
}
-type pageState struct {
+type pagestate struct {
head int /**< Reclaimed freeDB pages, or NULL before use */
last int /**< ID of last used record, or 0 if !mf_pghead */
}
diff --git a/transaction.go b/transaction.go
index 4ca2d35..4525a31 100644
--- a/transaction.go
+++ b/transaction.go
@@ -1,44 +1,143 @@
package bolt
-// TODO: #define DB_DIRTY 0x01 /**< DB was modified or is DUPSORT data */
-// TODO: #define DB_STALE 0x02 /**< Named-DB record is older than txnID */
-// TODO: #define DB_NEW 0x04 /**< Named-DB handle opened in this txn */
-// TODO: #define DB_VALID 0x08 /**< DB handle is valid, see also #MDB_VALID */
-
-// TODO: #define MDB_TXN_RDONLY 0x01 /**< read-only transaction */
-// TODO: #define MDB_TXN_ERROR 0x02 /**< an error has occurred */
-// TODO: #define MDB_TXN_DIRTY 0x04 /**< must write, even if dirty list is empty */
-// TODO: #define MDB_TXN_SPILLS 0x08 /**< txn or a parent has spilled pages */
+var TransactionExistingChildError = &Error{"txn already has a child", nil}
+var TransactionReadOnlyChildError = &Error{"read-only txn cannot create a child", nil}
+
+const (
+ txnb_dirty = 0x01 /**< DB was modified or is DUPSORT data */
+ txnb_stale = 0x02 /**< Named-DB record is older than txnID */
+ txnb_new = 0x04 /**< Named-DB handle opened in this txn */
+ txnb_valid = 0x08 /**< DB handle is valid, see also #MDB_VALID */
+)
+
+const (
+ ps_modify = 1
+ ps_rootonly = 2
+ ps_first = 4
+ ps_last = 8
+)
+
+type Transaction struct {
+ id int
+ db *DB
+ writable bool
+ dirty bool
+ spilled bool
+ err error
+ parent *Transaction
+ child *Transaction
+ buckets []*txnbucket
+
+ 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 Transaction interface {
+type txnbucket struct {
+ bucket *Bucket
+ cursor *Cursor
+ flags int
}
-type transaction struct {
- id int
- flags int
- db *DB
- parent *transaction
- child *transaction
- nextPageNumber int
- freePages []int
- spillPages []int
- dirtyList []int
- reader *reader
- // TODO: bucketxs []*bucketx
- buckets []*Bucket
- bucketFlags []int
- cursors []*cursor
- // Implicit from slices? TODO: MDB_dbi mt_numdbs;
- mt_dirty_room int
+// 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
+ }
+ // Exit if using parent for read-only transaction.
+ if !t.writable {
+ return nil, TransactionReadOnlyChildError
+ }
+ // 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,
+ }
+ 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
}
-// ntxn represents a nested transaction.
-type ntxn struct {
- transaction *transaction /**< the transaction */
- pageState pageState /**< parent transaction's saved freestate */
+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
+
+ // Allow read access to the freelist
+ // TODO: if (!dbi && !F_ISSET(txn->mt_flags, MDB_TXN_RDONLY))
+
+ /*
+ MDB_cursor *mc;
+ size_t size = sizeof(MDB_cursor);
+
+ // Allow read access to the freelist
+ if (!dbi && !F_ISSET(txn->mt_flags, MDB_TXN_RDONLY))
+ return EINVAL;
+
+ if ((mc = malloc(size)) != NULL) {
+ mdb_cursor_init(mc, txn, dbi, (MDB_xcursor *)(mc + 1));
+ if (txn->mt_cursors) {
+ mc->mc_next = txn->mt_cursors[dbi];
+ txn->mt_cursors[dbi] = mc;
+ mc->mc_flags |= C_UNTRACK;
+ }
+ } else {
+ return ENOMEM;
+ }
+
+ *ret = mc;
+
+ return MDB_SUCCESS;
+ */
+ return nil, nil
}
-func (t *transaction) allocPage(num int) *page {
+// //
+// //
+// //
+// //
+// //
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CONVERTED ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
+// //
+// //
+// //
+// //
+// //
+
+func (t *Transaction) allocPage(num int) *page {
/*
MDB_env *env = txn->mt_env;
MDB_page *ret = env->me_dpages;
@@ -74,7 +173,7 @@ func (t *transaction) allocPage(num int) *page {
}
// Find oldest txnid still referenced. Expects txn->mt_txnid > 0.
-func (t *transaction) oldest() int {
+func (t *Transaction) oldest() int {
/*
int i;
txnid_t mr, oldest = txn->mt_txnid - 1;
@@ -94,7 +193,7 @@ func (t *transaction) oldest() int {
}
// Add a page to the txn's dirty list
-func (t *transaction) dirty(p *page) {
+func (t *Transaction) addDirtyPage(p *page) {
/*
MDB_ID2 mid;
int rc, (*insert)(MDB_ID2L, MDB_ID2 *);
@@ -119,7 +218,7 @@ func (t *transaction) dirty(p *page) {
// @param[in] mp the page being referenced. It must not be dirty.
// @param[out] ret the writable page, if any. ret is unchanged if
// mp wasn't spilled.
-func (t *transaction) unspill(p *page) *page {
+func (t *Transaction) unspill(p *page) *page {
/*
MDB_env *env = txn->mt_env;
const MDB_txn *tx2;
@@ -173,7 +272,7 @@ func (t *transaction) unspill(p *page) *page {
}
// Back up parent txn's cursors, then grab the originals for tracking
-func (t *transaction) shadow(dst *transaction) error {
+func (t *Transaction) shadow(dst *Transaction) error {
/*
MDB_cursor *mc, *bk;
MDB_xcursor *mx;
@@ -214,7 +313,7 @@ func (t *transaction) shadow(dst *transaction) error {
// @param[in] txn the transaction handle.
// @param[in] merge true to keep changes to parent cursors, false to revert.
// @return 0 on success, non-zero on failure.
-func (t *transaction) closeCursors(merge bool) {
+func (t *Transaction) closeCursors(merge bool) {
/*
MDB_cursor **cursors = txn->mt_cursors, *mc, *next, *bk;
MDB_xcursor *mx;
@@ -252,7 +351,7 @@ func (t *transaction) closeCursors(merge bool) {
// Common code for #mdb_txn_begin() and #mdb_txn_renew().
// @param[in] txn the transaction handle to initialize
// @return 0 on success, non-zero on failure.
-func (t *transaction) renew() error {
+func (t *Transaction) renew() error {
/*
MDB_env *env = txn->mt_env;
MDB_txninfo *ti = env->me_txns;
@@ -366,7 +465,7 @@ func (t *transaction) renew() error {
return nil
}
-func (t *transaction) Renew() error {
+func (t *Transaction) Renew() error {
/*
int rc;
@@ -389,12 +488,12 @@ func (t *transaction) Renew() error {
return nil
}
-func (t *transaction) DB() *DB {
+func (t *Transaction) DB() *DB {
return t.db
}
// Export or close DBI handles opened in this txn.
-func (t *transaction) updateBuckets(keep bool) {
+func (t *Transaction) updateBuckets(keep bool) {
/*
int i;
MDB_dbi n = txn->mt_numdbs;
@@ -423,7 +522,7 @@ func (t *transaction) updateBuckets(keep bool) {
// May be called twice for readonly txns: First reset it, then abort.
// @param[in] txn the transaction handle to reset
// @param[in] act why the transaction is being reset
-func (t *transaction) reset(act string) {
+func (t *Transaction) reset(act string) {
/*
MDB_env *env = txn->mt_env;
@@ -472,7 +571,7 @@ func (t *transaction) reset(act string) {
*/
}
-func (t *transaction) Reset() {
+func (t *Transaction) Reset() {
/*
if (txn == NULL)
return;
@@ -485,7 +584,7 @@ func (t *transaction) Reset() {
*/
}
-func (t *transaction) Abort() {
+func (t *Transaction) Abort() {
/*
if (txn == NULL)
return;
@@ -504,7 +603,7 @@ func (t *transaction) Abort() {
// Save the freelist as of this transaction to the freeDB.
// This changes the freelist. Keep trying until it stabilizes.
-func (t *transaction) saveFreelist() error {
+func (t *Transaction) saveFreelist() error {
/*
// env->me_pghead[] can grow and shrink during this call.
// env->me_pglast and txn->mt_free_pgs[] can only grow.
@@ -662,7 +761,7 @@ func (t *transaction) saveFreelist() error {
// @param[in] txn the transaction that's being committed
// @param[in] keep number of initial pages in dirty_list to keep dirty.
// @return 0 on success, non-zero on failure.
-func (t *transaction) flush(keep bool) error {
+func (t *Transaction) flush(keep bool) error {
/*
MDB_env *env = txn->mt_env;
MDB_ID2L dl = txn->mt_u.dirty_list;
@@ -1005,7 +1104,7 @@ func (t *transaction) flush(keep bool) error {
// Update the environment info to commit a transaction.
// @param[in] txn the transaction that's being committed
// @return 0 on success, non-zero on failure.
-func (t *transaction) writeMeta() error {
+func (t *Transaction) writeMeta() error {
/*
MDB_env *env;
MDB_meta meta, metab, *mp;
@@ -1129,7 +1228,7 @@ func (t *transaction) writeMeta() error {
// @param[out] ret address of a pointer where the page's address will be stored.
// @param[out] lvl dirty_list inheritance level of found page. 1=current txn, 0=mapped page.
// @return 0 on success, non-zero on failure.
-func (t *transaction) getPage(id int) (*page, int, error) {
+func (t *Transaction) getPage(id int) (*page, int, error) {
/*
MDB_env *env = txn->mt_env;
MDB_page *p = NULL;
@@ -1188,7 +1287,7 @@ func (t *transaction) getPage(id int) (*page, int, error) {
// @param[in] leaf The node being read.
// @param[out] data Updated to point to the node's data.
// @return 0 on success, non-zero on failure.
-func (t *transaction) readNode(leaf *node, data []byte) error {
+func (t *Transaction) readNode(leaf *node, data []byte) error {
/*
MDB_page *omp; // overflow page
pgno_t pgno;
@@ -1214,7 +1313,7 @@ func (t *transaction) readNode(leaf *node, data []byte) error {
return nil
}
-func (t *transaction) Get(bucket Bucket, key []byte) ([]byte, error) {
+func (t *Transaction) Get(bucket Bucket, key []byte) ([]byte, error) {
/*
MDB_cursor mc;
MDB_xcursor mx;
@@ -1238,43 +1337,7 @@ func (t *transaction) Get(bucket Bucket, key []byte) ([]byte, error) {
return nil, nil
}
-func (t *transaction) Cursor(b Bucket) (Cursor, error) {
- /*
- MDB_cursor *mc;
- size_t size = sizeof(MDB_cursor);
-
- if (txn == NULL || ret == NULL || dbi >= txn->mt_numdbs || !(txn->mt_dbflags[dbi] & DB_VALID))
- return EINVAL;
-
- if (txn->mt_flags & MDB_TXN_ERROR)
- return MDB_BAD_TXN;
-
- // Allow read access to the freelist
- if (!dbi && !F_ISSET(txn->mt_flags, MDB_TXN_RDONLY))
- return EINVAL;
-
- if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT)
- size += sizeof(MDB_xcursor);
-
- if ((mc = malloc(size)) != NULL) {
- mdb_cursor_init(mc, txn, dbi, (MDB_xcursor *)(mc + 1));
- if (txn->mt_cursors) {
- mc->mc_next = txn->mt_cursors[dbi];
- txn->mt_cursors[dbi] = mc;
- mc->mc_flags |= C_UNTRACK;
- }
- } else {
- return ENOMEM;
- }
-
- *ret = mc;
-
- return MDB_SUCCESS;
- */
- return nil, nil
-}
-
-func (t *transaction) Renew1(c Cursor) error {
+func (t *Transaction) Renew1(c Cursor) error {
/*
if (txn == NULL || mc == NULL || mc->mc_dbi >= txn->mt_numdbs)
return EINVAL;
@@ -1288,7 +1351,7 @@ func (t *transaction) Renew1(c Cursor) error {
return nil
}
-func (t *transaction) Delete(b *Bucket, key []byte, data []byte) error {
+func (t *Transaction) Delete(b *Bucket, key []byte, data []byte) error {
/*
MDB_cursor mc;
MDB_xcursor mx;
@@ -1343,7 +1406,7 @@ func (t *transaction) Delete(b *Bucket, key []byte, data []byte) error {
return nil
}
-func (t *transaction) Put(b Bucket, key []byte, data []byte, flags int) error {
+func (t *Transaction) Put(b Bucket, key []byte, data []byte, flags int) error {
/*
MDB_cursor mc;
MDB_xcursor mx;
@@ -1363,7 +1426,7 @@ 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) {
+func (t *Transaction) Bucket(name string, flags int) (*Bucket, error) {
/*
MDB_val key, data;
MDB_dbi i;
@@ -1467,7 +1530,7 @@ func (t *transaction) Bucket(name string, flags int) (*Bucket, error) {
return nil, nil
}
-func (t *transaction) Stat(b Bucket) *stat {
+func (t *Transaction) Stat(b Bucket) *stat {
/*
if (txn == NULL || arg == NULL || dbi >= txn->mt_numdbs)
return EINVAL;
@@ -1483,7 +1546,7 @@ func (t *transaction) Stat(b Bucket) *stat {
return nil
}
-func (t *transaction) BucketFlags(b Bucket) (int, error) {
+func (t *Transaction) BucketFlags(b Bucket) (int, error) {
/*
// We could return the flags for the FREE_DBI too but what's the point?
if (txn == NULL || dbi < MAIN_DBI || dbi >= txn->mt_numdbs)
@@ -1494,7 +1557,7 @@ func (t *transaction) BucketFlags(b Bucket) (int, error) {
return 0, nil
}
-func (t *transaction) Drop(b *Bucket, del int) error {
+func (t *Transaction) Drop(b *Bucket, del int) error {
/*
MDB_cursor *mc, *m2;
int rc;