Begin Transaction.Cursor().

master
Ben Johnson 2014-01-13 10:35:04 -07:00
parent f1d7fe5b08
commit 79d9b6bb5a
6 changed files with 355 additions and 316 deletions

View File

@ -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)

221
cursor.go
View File

@ -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;

143
db.go
View File

@ -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).

View File

@ -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)
})
}

12
page.go
View File

@ -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 */
}

View File

@ -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 */
var TransactionExistingChildError = &Error{"txn already has a child", nil}
var TransactionReadOnlyChildError = &Error{"read-only txn cannot create a child", nil}
// 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 */
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 */
)
type Transaction interface {
}
const (
ps_modify = 1
ps_rootonly = 2
ps_first = 4
ps_last = 8
)
type transaction struct {
id int
flags int
db *DB
parent *transaction
child *transaction
nextPageNumber int
freePages []int
spillPages []int
dirtyList []int
reader *reader
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
buckets []*Bucket
bucketFlags []int
cursors []*cursor
// Implicit from slices? TODO: MDB_dbi mt_numdbs;
mt_dirty_room int
dirty_room int
pagestate pagestate
}
// ntxn represents a nested transaction.
type ntxn struct {
transaction *transaction /**< the transaction */
pageState pageState /**< parent transaction's saved freestate */
type txnbucket struct {
bucket *Bucket
cursor *Cursor
flags int
}
func (t *transaction) allocPage(num int) *page {
// 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
}
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
}
// //
// //
// //
// //
// //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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;