From c551e45a4722f58dc4c19f9d1b80b0b8db3ad039 Mon Sep 17 00:00:00 2001
From: Ben Johnson <benbjohnson@yahoo.com>
Date: Sat, 8 Mar 2014 20:25:37 -0700
Subject: [PATCH] Consolidate Tx and RWTx.

---
 bucket.go                 |   7 +-
 bucket_test.go            |  68 +++---
 cursor.go                 |   2 +-
 db.go                     |  46 ++--
 db_test.go                |  46 ++--
 doc.go                    |  11 +-
 error.go                  |   6 +-
 example_test.go           |  28 +--
 functional_test.go        |  14 +-
 meta.go                   |   2 +-
 node.go                   |   2 +-
 rwtransaction_test.go     | 181 ----------------
 transaction.go            | 134 ------------
 transaction_test.go       | 265 -----------------------
 rwtransaction.go => tx.go | 187 +++++++++++++---
 tx_test.go                | 437 ++++++++++++++++++++++++++++++++++++++
 16 files changed, 718 insertions(+), 718 deletions(-)
 delete mode 100644 rwtransaction_test.go
 delete mode 100644 transaction.go
 delete mode 100644 transaction_test.go
 rename rwtransaction.go => tx.go (58%)
 create mode 100644 tx_test.go

diff --git a/bucket.go b/bucket.go
index e406a6e..e1ad2a3 100644
--- a/bucket.go
+++ b/bucket.go
@@ -9,7 +9,6 @@ type Bucket struct {
 	*bucket
 	name string
 	tx   *Tx
-	rwtx *RWTx
 }
 
 // bucket represents the on-file representation of a bucket.
@@ -25,7 +24,7 @@ func (b *Bucket) Name() string {
 
 // Writable returns whether the bucket is writable.
 func (b *Bucket) Writable() bool {
-	return (b.rwtx != nil)
+	return b.tx.writable
 }
 
 // Cursor creates a cursor associated with the bucket.
@@ -74,7 +73,7 @@ func (b *Bucket) Put(key []byte, value []byte) error {
 	c.Seek(key)
 
 	// Insert the key/value.
-	c.node(b.rwtx).put(key, key, value, 0)
+	c.node(b.tx).put(key, key, value, 0)
 
 	return nil
 }
@@ -92,7 +91,7 @@ func (b *Bucket) Delete(key []byte) error {
 	c.Seek(key)
 
 	// Delete the node if we have a matching key.
-	c.node(b.rwtx).del(key)
+	c.node(b.tx).del(key)
 
 	return nil
 }
diff --git a/bucket_test.go b/bucket_test.go
index a599557..9ccc488 100644
--- a/bucket_test.go
+++ b/bucket_test.go
@@ -27,8 +27,8 @@ func TestBucketGetNonExistent(t *testing.T) {
 func TestBucketGetFromNode(t *testing.T) {
 	withOpenDB(func(db *DB, path string) {
 		db.CreateBucket("widgets")
-		db.Do(func(txn *RWTx) error {
-			b := txn.Bucket("widgets")
+		db.Do(func(tx *Tx) error {
+			b := tx.Bucket("widgets")
 			b.Put([]byte("foo"), []byte("bar"))
 			value := b.Get([]byte("foo"))
 			assert.Equal(t, value, []byte("bar"))
@@ -54,8 +54,8 @@ func TestBucketPut(t *testing.T) {
 func TestBucketPutReadOnly(t *testing.T) {
 	withOpenDB(func(db *DB, path string) {
 		db.CreateBucket("widgets")
-		db.With(func(txn *Tx) error {
-			b := txn.Bucket("widgets")
+		db.With(func(tx *Tx) error {
+			b := tx.Bucket("widgets")
 			err := b.Put([]byte("foo"), []byte("bar"))
 			assert.Equal(t, err, ErrBucketNotWritable)
 			return nil
@@ -81,8 +81,8 @@ func TestBucketDelete(t *testing.T) {
 func TestBucketDeleteReadOnly(t *testing.T) {
 	withOpenDB(func(db *DB, path string) {
 		db.CreateBucket("widgets")
-		db.With(func(txn *Tx) error {
-			b := txn.Bucket("widgets")
+		db.With(func(tx *Tx) error {
+			b := tx.Bucket("widgets")
 			err := b.Delete([]byte("foo"))
 			assert.Equal(t, err, ErrBucketNotWritable)
 			return nil
@@ -120,8 +120,8 @@ func TestBucketNextSequence(t *testing.T) {
 func TestBucketNextSequenceReadOnly(t *testing.T) {
 	withOpenDB(func(db *DB, path string) {
 		db.CreateBucket("widgets")
-		db.With(func(txn *Tx) error {
-			b := txn.Bucket("widgets")
+		db.With(func(tx *Tx) error {
+			b := tx.Bucket("widgets")
 			i, err := b.NextSequence()
 			assert.Equal(t, i, 0)
 			assert.Equal(t, err, ErrBucketNotWritable)
@@ -134,8 +134,8 @@ func TestBucketNextSequenceReadOnly(t *testing.T) {
 func TestBucketNextSequenceOverflow(t *testing.T) {
 	withOpenDB(func(db *DB, path string) {
 		db.CreateBucket("widgets")
-		db.Do(func(txn *RWTx) error {
-			b := txn.Bucket("widgets")
+		db.Do(func(tx *Tx) error {
+			b := tx.Bucket("widgets")
 			b.bucket.sequence = uint64(maxInt)
 			seq, err := b.NextSequence()
 			assert.Equal(t, err, ErrSequenceOverflow)
@@ -218,31 +218,31 @@ func TestBucketPutKeyTooLarge(t *testing.T) {
 // Ensure a bucket can calculate stats.
 func TestBucketStat(t *testing.T) {
 	withOpenDB(func(db *DB, path string) {
-		db.Do(func(txn *RWTx) error {
+		db.Do(func(tx *Tx) error {
 			// Add bucket with lots of keys.
-			txn.CreateBucket("widgets")
-			b := txn.Bucket("widgets")
+			tx.CreateBucket("widgets")
+			b := tx.Bucket("widgets")
 			for i := 0; i < 100000; i++ {
 				b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
 			}
 
 			// Add bucket with fewer keys but one big value.
-			txn.CreateBucket("woojits")
-			b = txn.Bucket("woojits")
+			tx.CreateBucket("woojits")
+			b = tx.Bucket("woojits")
 			for i := 0; i < 500; i++ {
 				b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
 			}
 			b.Put([]byte("really-big-value"), []byte(strings.Repeat("*", 10000)))
 
 			// Add a bucket that fits on a single root leaf.
-			txn.CreateBucket("whozawhats")
-			b = txn.Bucket("whozawhats")
+			tx.CreateBucket("whozawhats")
+			b = tx.Bucket("whozawhats")
 			b.Put([]byte("foo"), []byte("bar"))
 
 			return nil
 		})
-		db.With(func(txn *Tx) error {
-			b := txn.Bucket("widgets")
+		db.With(func(tx *Tx) error {
+			b := tx.Bucket("widgets")
 			stat := b.Stat()
 			assert.Equal(t, stat.BranchPageCount, 15)
 			assert.Equal(t, stat.LeafPageCount, 1281)
@@ -250,7 +250,7 @@ func TestBucketStat(t *testing.T) {
 			assert.Equal(t, stat.KeyCount, 100000)
 			assert.Equal(t, stat.MaxDepth, 3)
 
-			b = txn.Bucket("woojits")
+			b = tx.Bucket("woojits")
 			stat = b.Stat()
 			assert.Equal(t, stat.BranchPageCount, 1)
 			assert.Equal(t, stat.LeafPageCount, 6)
@@ -258,7 +258,7 @@ func TestBucketStat(t *testing.T) {
 			assert.Equal(t, stat.KeyCount, 501)
 			assert.Equal(t, stat.MaxDepth, 2)
 
-			b = txn.Bucket("whozawhats")
+			b = tx.Bucket("whozawhats")
 			stat = b.Stat()
 			assert.Equal(t, stat.BranchPageCount, 0)
 			assert.Equal(t, stat.LeafPageCount, 1)
@@ -271,7 +271,7 @@ func TestBucketStat(t *testing.T) {
 	})
 }
 
-// Ensure that a bucket can write random keys and values across multiple txns.
+// Ensure that a bucket can write random keys and values across multiple transactions.
 func TestBucketPutSingle(t *testing.T) {
 	index := 0
 	f := func(items testdata) bool {
@@ -317,16 +317,16 @@ func TestBucketPutMultiple(t *testing.T) {
 		withOpenDB(func(db *DB, path string) {
 			// Bulk insert all values.
 			db.CreateBucket("widgets")
-			rwtxn, _ := db.RWTx()
-			b := rwtxn.Bucket("widgets")
+			tx, _ := db.RWTx()
+			b := tx.Bucket("widgets")
 			for _, item := range items {
 				assert.NoError(t, b.Put(item.Key, item.Value))
 			}
-			assert.NoError(t, rwtxn.Commit())
+			assert.NoError(t, tx.Commit())
 
 			// Verify all items exist.
-			txn, _ := db.Tx()
-			b = txn.Bucket("widgets")
+			tx, _ = db.Tx()
+			b = tx.Bucket("widgets")
 			for _, item := range items {
 				value := b.Get(item.Key)
 				if !assert.Equal(t, item.Value, value) {
@@ -334,7 +334,7 @@ func TestBucketPutMultiple(t *testing.T) {
 					t.FailNow()
 				}
 			}
-			txn.Close()
+			tx.Rollback()
 		})
 		fmt.Fprint(os.Stderr, ".")
 		return true
@@ -351,20 +351,20 @@ func TestBucketDeleteQuick(t *testing.T) {
 		withOpenDB(func(db *DB, path string) {
 			// Bulk insert all values.
 			db.CreateBucket("widgets")
-			rwtxn, _ := db.RWTx()
-			b := rwtxn.Bucket("widgets")
+			tx, _ := db.RWTx()
+			b := tx.Bucket("widgets")
 			for _, item := range items {
 				assert.NoError(t, b.Put(item.Key, item.Value))
 			}
-			assert.NoError(t, rwtxn.Commit())
+			assert.NoError(t, tx.Commit())
 
 			// Remove items one at a time and check consistency.
 			for i, item := range items {
 				assert.NoError(t, db.Delete("widgets", item.Key))
 
 				// Anything before our deletion index should be nil.
-				txn, _ := db.Tx()
-				b := txn.Bucket("widgets")
+				tx, _ := db.Tx()
+				b := tx.Bucket("widgets")
 				for j, exp := range items {
 					if j > i {
 						value := b.Get(exp.Key)
@@ -378,7 +378,7 @@ func TestBucketDeleteQuick(t *testing.T) {
 						}
 					}
 				}
-				txn.Close()
+				tx.Rollback()
 			}
 		})
 		fmt.Fprint(os.Stderr, ".")
diff --git a/cursor.go b/cursor.go
index 2907b84..244c915 100644
--- a/cursor.go
+++ b/cursor.go
@@ -251,7 +251,7 @@ func (c *Cursor) keyValue() ([]byte, []byte) {
 }
 
 // node returns the node that the cursor is currently positioned on.
-func (c *Cursor) node(t *RWTx) *node {
+func (c *Cursor) node(t *Tx) *node {
 	_assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
 
 	// If the top of the stack is a leaf node then just return it.
diff --git a/db.go b/db.go
index bf7be45..6493e1b 100644
--- a/db.go
+++ b/db.go
@@ -29,7 +29,7 @@ type DB struct {
 	meta1    *meta
 	pageSize int
 	opened   bool
-	rwtx     *RWTx
+	rwtx     *Tx
 	txs      []*Tx
 	freelist *freelist
 
@@ -290,11 +290,11 @@ func (db *DB) Tx() (*Tx, error) {
 // RWTx creates a read/write transaction.
 // Only one read/write transaction is allowed at a time.
 // You must call Commit() or Rollback() on the transaction to close it.
-func (db *DB) RWTx() (*RWTx, error) {
+func (db *DB) RWTx() (*Tx, error) {
 	db.metalock.Lock()
 	defer db.metalock.Unlock()
 
-	// Obtain writer lock. This is released by the RWTx when it closes.
+	// Obtain writer lock. This is released by the transaction when it closes.
 	db.rwlock.Lock()
 
 	// Exit if the database is not open yet.
@@ -304,7 +304,7 @@ func (db *DB) RWTx() (*RWTx, error) {
 	}
 
 	// Create a transaction associated with the database.
-	t := &RWTx{}
+	t := &Tx{writable: true}
 	t.init(db)
 	db.rwtx = t
 
@@ -331,20 +331,20 @@ func (db *DB) removeTx(t *Tx) {
 	db.mmaplock.RUnlock()
 
 	// Remove the transaction.
-	for i, txn := range db.txs {
-		if txn == t {
+	for i, tx := range db.txs {
+		if tx == t {
 			db.txs = append(db.txs[:i], db.txs[i+1:]...)
 			break
 		}
 	}
 }
 
-// Do executes a function within the context of a RWTx.
+// Do executes a function within the context of a read-write transaction.
 // If no error is returned from the function then the transaction is committed.
 // If an error is returned then the entire transaction is rolled back.
 // Any error that is returned from the function or returned from the commit is
 // returned from the Do() method.
-func (db *DB) Do(fn func(*RWTx) error) error {
+func (db *DB) Do(fn func(*Tx) error) error {
 	t, err := db.RWTx()
 	if err != nil {
 		return err
@@ -366,7 +366,7 @@ func (db *DB) With(fn func(*Tx) error) error {
 	if err != nil {
 		return err
 	}
-	defer t.Close()
+	defer t.Rollback()
 
 	// If an error is returned from the function then pass it through.
 	return fn(t)
@@ -391,7 +391,7 @@ func (db *DB) Bucket(name string) (*Bucket, error) {
 	if err != nil {
 		return nil, err
 	}
-	defer t.Close()
+	defer t.Rollback()
 	return t.Bucket(name), nil
 }
 
@@ -401,7 +401,7 @@ func (db *DB) Buckets() ([]*Bucket, error) {
 	if err != nil {
 		return nil, err
 	}
-	defer t.Close()
+	defer t.Rollback()
 	return t.Buckets(), nil
 }
 
@@ -409,7 +409,7 @@ func (db *DB) Buckets() ([]*Bucket, error) {
 // This function can return an error if the bucket already exists, if the name
 // is blank, or the bucket name is too long.
 func (db *DB) CreateBucket(name string) error {
-	return db.Do(func(t *RWTx) error {
+	return db.Do(func(t *Tx) error {
 		return t.CreateBucket(name)
 	})
 }
@@ -417,7 +417,7 @@ func (db *DB) CreateBucket(name string) error {
 // CreateBucketIfNotExists creates a new bucket with the given name if it doesn't already exist.
 // This function can return an error if the name is blank, or the bucket name is too long.
 func (db *DB) CreateBucketIfNotExists(name string) error {
-	return db.Do(func(t *RWTx) error {
+	return db.Do(func(t *Tx) error {
 		return t.CreateBucketIfNotExists(name)
 	})
 }
@@ -425,7 +425,7 @@ func (db *DB) CreateBucketIfNotExists(name string) error {
 // DeleteBucket removes a bucket from the database.
 // Returns an error if the bucket does not exist.
 func (db *DB) DeleteBucket(name string) error {
-	return db.Do(func(t *RWTx) error {
+	return db.Do(func(t *Tx) error {
 		return t.DeleteBucket(name)
 	})
 }
@@ -434,7 +434,7 @@ func (db *DB) DeleteBucket(name string) error {
 // This function can return an error if the bucket does not exist.
 func (db *DB) NextSequence(name string) (int, error) {
 	var seq int
-	err := db.Do(func(t *RWTx) error {
+	err := db.Do(func(t *Tx) error {
 		b := t.Bucket(name)
 		if b == nil {
 			return ErrBucketNotFound
@@ -457,17 +457,15 @@ func (db *DB) Get(name string, key []byte) ([]byte, error) {
 	if err != nil {
 		return nil, err
 	}
-	defer t.Close()
+	defer t.Rollback()
 
 	// Open bucket and retrieve value for key.
 	b := t.Bucket(name)
 	if b == nil {
 		return nil, ErrBucketNotFound
 	}
-	value, err := b.Get(key), nil
-	if err != nil {
-		return nil, err
-	} else if value == nil {
+	value := b.Get(key)
+	if value == nil {
 		return nil, nil
 	}
 
@@ -482,7 +480,7 @@ func (db *DB) Get(name string, key []byte) ([]byte, error) {
 // Put sets the value for a key in a bucket.
 // Returns an error if the bucket is not found, if key is blank, if the key is too large, or if the value is too large.
 func (db *DB) Put(name string, key []byte, value []byte) error {
-	return db.Do(func(t *RWTx) error {
+	return db.Do(func(t *Tx) error {
 		b := t.Bucket(name)
 		if b == nil {
 			return ErrBucketNotFound
@@ -494,7 +492,7 @@ func (db *DB) Put(name string, key []byte, value []byte) error {
 // Delete removes a key from a bucket.
 // Returns an error if the bucket cannot be found.
 func (db *DB) Delete(name string, key []byte) error {
-	return db.Do(func(t *RWTx) error {
+	return db.Do(func(t *Tx) error {
 		b := t.Bucket(name)
 		if b == nil {
 			return ErrBucketNotFound
@@ -512,7 +510,7 @@ func (db *DB) Copy(w io.Writer) error {
 	if err != nil {
 		return err
 	}
-	defer t.Close()
+	defer t.Rollback()
 
 	// Open reader on the database.
 	f, err := os.Open(db.path)
@@ -557,7 +555,7 @@ func (db *DB) Stat() (*Stat, error) {
 	db.mmaplock.RUnlock()
 	db.metalock.Unlock()
 
-	err := db.Do(func(t *RWTx) error {
+	err := db.Do(func(t *Tx) error {
 		s.PageCount = int(t.meta.pgid)
 		s.FreePageCount = len(db.freelist.all())
 		s.PageSize = db.pageSize
diff --git a/db_test.go b/db_test.go
index 029d248..e35502c 100644
--- a/db_test.go
+++ b/db_test.go
@@ -158,8 +158,8 @@ func TestDBCorruptMeta0(t *testing.T) {
 // Ensure that a database cannot open a transaction when it's not open.
 func TestDBTxErrDatabaseNotOpen(t *testing.T) {
 	withDB(func(db *DB, path string) {
-		txn, err := db.Tx()
-		assert.Nil(t, txn)
+		tx, err := db.Tx()
+		assert.Nil(t, tx)
 		assert.Equal(t, err, ErrDatabaseNotOpen)
 	})
 }
@@ -172,12 +172,32 @@ func TestDBDeleteFromMissingBucket(t *testing.T) {
 	})
 }
 
+// Ensure that a read-write transaction can be retrieved.
+func TestDBRWTx(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		tx, err := db.RWTx()
+		assert.NotNil(t, tx)
+		assert.NoError(t, err)
+		assert.Equal(t, tx.DB(), db)
+		assert.Equal(t, tx.Writable(), true)
+	})
+}
+
+// Ensure that opening a transaction while the DB is closed returns an error.
+func TestDBRWTxOpenWithClosedDB(t *testing.T) {
+	withDB(func(db *DB, path string) {
+		tx, err := db.RWTx()
+		assert.Equal(t, err, ErrDatabaseNotOpen)
+		assert.Nil(t, tx)
+	})
+}
+
 // Ensure a database can provide a transactional block.
 func TestDBTxBlock(t *testing.T) {
 	withOpenDB(func(db *DB, path string) {
-		err := db.Do(func(txn *RWTx) error {
-			txn.CreateBucket("widgets")
-			b := txn.Bucket("widgets")
+		err := db.Do(func(tx *Tx) error {
+			tx.CreateBucket("widgets")
+			b := tx.Bucket("widgets")
 			b.Put([]byte("foo"), []byte("bar"))
 			b.Put([]byte("baz"), []byte("bat"))
 			b.Delete([]byte("foo"))
@@ -194,8 +214,8 @@ func TestDBTxBlock(t *testing.T) {
 // Ensure a closed database returns an error while running a transaction block
 func TestDBTxBlockWhileClosed(t *testing.T) {
 	withDB(func(db *DB, path string) {
-		err := db.Do(func(txn *RWTx) error {
-			txn.CreateBucket("widgets")
+		err := db.Do(func(tx *Tx) error {
+			tx.CreateBucket("widgets")
 			return nil
 		})
 		assert.Equal(t, err, ErrDatabaseNotOpen)
@@ -276,9 +296,9 @@ func TestDBCopyFile(t *testing.T) {
 // Ensure the database can return stats about itself.
 func TestDBStat(t *testing.T) {
 	withOpenDB(func(db *DB, path string) {
-		db.Do(func(txn *RWTx) error {
-			txn.CreateBucket("widgets")
-			b := txn.Bucket("widgets")
+		db.Do(func(tx *Tx) error {
+			tx.CreateBucket("widgets")
+			b := tx.Bucket("widgets")
 			for i := 0; i < 10000; i++ {
 				b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
 			}
@@ -293,7 +313,7 @@ func TestDBStat(t *testing.T) {
 		t0, _ := db.Tx()
 		t1, _ := db.Tx()
 		t2, _ := db.Tx()
-		t2.Close()
+		t2.Rollback()
 
 		// Obtain stats.
 		stat, err := db.Stat()
@@ -305,8 +325,8 @@ func TestDBStat(t *testing.T) {
 		assert.Equal(t, stat.TxCount, 2)
 
 		// Close readers.
-		t0.Close()
-		t1.Close()
+		t0.Rollback()
+		t1.Rollback()
 	})
 }
 
diff --git a/doc.go b/doc.go
index ec576a2..caf66e9 100644
--- a/doc.go
+++ b/doc.go
@@ -14,14 +14,15 @@ The design of Bolt is based on Howard Chu's LMDB database project.
 
 Basics
 
-There are only a few types in Bolt: DB, Bucket, Tx, RWTx, and Cursor. The DB is
+There are only a few types in Bolt: DB, Bucket, Tx, and Cursor. The DB is
 a collection of buckets and is represented by a single file on disk. A bucket is
 a collection of unique keys that are associated with values.
 
-Txs provide read-only access to data inside the database. They can retrieve
-key/value pairs and can use Cursors to iterate over the entire dataset. RWTxs
-provide read-write access to the database. They can create and delete buckets
-and they can insert and remove keys. Only one RWTx is allowed at a time.
+Transactions provide either read-only or read-write access to the database.
+Read-only transactions can retrieve key/value pairs and can use Cursors to
+iterate over the dataset sequentially. Read-write transactions can create and
+delete buckets and can insert and remove keys. Only one read-write transaction
+is allowed at a time.
 
 
 Caveats
diff --git a/error.go b/error.go
index 7e879a1..7f867b2 100644
--- a/error.go
+++ b/error.go
@@ -4,7 +4,7 @@ var (
 	// ErrInvalid is returned when a data file is not a Bolt-formatted database.
 	ErrInvalid = &Error{"Invalid database", nil}
 
-	// ErrVersionMismatch is returned when the data file was created with a 
+	// ErrVersionMismatch is returned when the data file was created with a
 	// different version of Bolt.
 	ErrVersionMismatch = &Error{"version mismatch", nil}
 
@@ -16,6 +16,10 @@ var (
 	// already open.
 	ErrDatabaseOpen = &Error{"database already open", nil}
 
+	// ErrTxNotWritable is returned when performing a write operation on a
+	// read-only transaction.
+	ErrTxNotWritable = &Error{"tx not writable", nil}
+
 	// ErrBucketNotFound is returned when trying to access a bucket that has
 	// not been created yet.
 	ErrBucketNotFound = &Error{"bucket not found", nil}
diff --git a/example_test.go b/example_test.go
index 82db552..0185bb1 100644
--- a/example_test.go
+++ b/example_test.go
@@ -67,7 +67,7 @@ func ExampleDB_Do() {
 	defer db.Close()
 
 	// Execute several commands within a write transaction.
-	err := db.Do(func(t *RWTx) error {
+	err := db.Do(func(t *Tx) error {
 		if err := t.CreateBucket("widgets"); err != nil {
 			return err
 		}
@@ -134,30 +134,30 @@ func ExampleDB_ForEach() {
 	// A liger is awesome.
 }
 
-func ExampleRWTx() {
+func ExampleTx() {
 	// Open the database.
 	var db DB
-	db.Open("/tmp/bolt/rwtx.db", 0666)
+	db.Open("/tmp/bolt/tx.db", 0666)
 	defer db.Close()
 
 	// Create a bucket.
 	db.CreateBucket("widgets")
 
 	// Create several keys in a transaction.
-	rwtxn, _ := db.RWTx()
-	b := rwtxn.Bucket("widgets")
+	tx, _ := db.RWTx()
+	b := tx.Bucket("widgets")
 	b.Put([]byte("john"), []byte("blue"))
 	b.Put([]byte("abby"), []byte("red"))
 	b.Put([]byte("zephyr"), []byte("purple"))
-	rwtxn.Commit()
+	tx.Commit()
 
 	// Iterate over the values in sorted key order.
-	txn, _ := db.Tx()
-	c := txn.Bucket("widgets").Cursor()
+	tx, _ = db.Tx()
+	c := tx.Bucket("widgets").Cursor()
 	for k, v := c.First(); k != nil; k, v = c.Next() {
 		fmt.Printf("%s likes %s\n", string(k), string(v))
 	}
-	txn.Close()
+	tx.Rollback()
 
 	// Output:
 	// abby likes red
@@ -165,10 +165,10 @@ func ExampleRWTx() {
 	// zephyr likes purple
 }
 
-func ExampleRWTx_rollback() {
+func ExampleTx_rollback() {
 	// Open the database.
 	var db DB
-	db.Open("/tmp/bolt/rwtx_rollback.db", 0666)
+	db.Open("/tmp/bolt/tx_rollback.db", 0666)
 	defer db.Close()
 
 	// Create a bucket.
@@ -178,10 +178,10 @@ func ExampleRWTx_rollback() {
 	db.Put("widgets", []byte("foo"), []byte("bar"))
 
 	// Update the key but rollback the transaction so it never saves.
-	rwtxn, _ := db.RWTx()
-	b := rwtxn.Bucket("widgets")
+	tx, _ := db.RWTx()
+	b := tx.Bucket("widgets")
 	b.Put([]byte("foo"), []byte("baz"))
-	rwtxn.Rollback()
+	tx.Rollback()
 
 	// Ensure that our original value is still set.
 	value, _ := db.Get("widgets", []byte("foo"))
diff --git a/functional_test.go b/functional_test.go
index 20af8fc..a62e6a0 100644
--- a/functional_test.go
+++ b/functional_test.go
@@ -45,7 +45,7 @@ func TestParallelTxs(t *testing.T) {
 					go func() {
 						mutex.RLock()
 						local := current
-						txn, err := db.Tx()
+						tx, err := db.Tx()
 						mutex.RUnlock()
 						if err == ErrDatabaseNotOpen {
 							wg.Done()
@@ -56,15 +56,15 @@ func TestParallelTxs(t *testing.T) {
 
 						// Verify all data is in for local data list.
 						for _, item := range local {
-							value := txn.Bucket("widgets").Get(item.Key)
+							value := tx.Bucket("widgets").Get(item.Key)
 							if !assert.NoError(t, err) || !assert.Equal(t, value, item.Value) {
-								txn.Close()
+								tx.Rollback()
 								wg.Done()
 								t.FailNow()
 							}
 						}
 
-						txn.Close()
+						tx.Rollback()
 						wg.Done()
 						<-readers
 					}()
@@ -83,13 +83,13 @@ func TestParallelTxs(t *testing.T) {
 				pending = pending[currentBatchSize:]
 
 				// Start write transaction.
-				txn, err := db.RWTx()
+				tx, err := db.RWTx()
 				if !assert.NoError(t, err) {
 					t.FailNow()
 				}
 
 				// Insert whole batch.
-				b := txn.Bucket("widgets")
+				b := tx.Bucket("widgets")
 				for _, item := range batchItems {
 					err := b.Put(item.Key, item.Value)
 					if !assert.NoError(t, err) {
@@ -99,7 +99,7 @@ func TestParallelTxs(t *testing.T) {
 
 				// Commit and update the current list.
 				mutex.Lock()
-				err = txn.Commit()
+				err = tx.Commit()
 				current = append(current, batchItems...)
 				mutex.Unlock()
 				if !assert.NoError(t, err) {
diff --git a/meta.go b/meta.go
index cee2d29..0be4e94 100644
--- a/meta.go
+++ b/meta.go
@@ -36,7 +36,7 @@ func (m *meta) copy(dest *meta) {
 
 // write writes the meta onto a page.
 func (m *meta) write(p *page) {
-	// Page id is either going to be 0 or 1 which we can determine by the Txn ID.
+	// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
 	p.id = pgid(m.txid % 2)
 	p.flags |= metaPageFlag
 
diff --git a/node.go b/node.go
index f0929c7..51be690 100644
--- a/node.go
+++ b/node.go
@@ -8,7 +8,7 @@ import (
 
 // node represents an in-memory, deserialized page.
 type node struct {
-	tx         *RWTx
+	tx         *Tx
 	isLeaf     bool
 	unbalanced bool
 	key        []byte
diff --git a/rwtransaction_test.go b/rwtransaction_test.go
deleted file mode 100644
index c94f534..0000000
--- a/rwtransaction_test.go
+++ /dev/null
@@ -1,181 +0,0 @@
-package bolt
-
-import (
-	"math/rand"
-	"strconv"
-	"strings"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-// Ensure that a RWTx can be retrieved.
-func TestRWTx(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		txn, err := db.RWTx()
-		assert.NotNil(t, txn)
-		assert.NoError(t, err)
-		assert.Equal(t, txn.DB(), db)
-	})
-}
-
-// Ensure that opening a RWTx while the DB is closed returns an error.
-func TestRWTxOpenWithClosedDB(t *testing.T) {
-	withDB(func(db *DB, path string) {
-		txn, err := db.RWTx()
-		assert.Equal(t, err, ErrDatabaseNotOpen)
-		assert.Nil(t, txn)
-	})
-}
-
-// Ensure that retrieving all buckets returns writable buckets.
-func TestRWTxBuckets(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("widgets")
-		db.CreateBucket("woojits")
-		db.Do(func(txn *RWTx) error {
-			buckets := txn.Buckets()
-			assert.Equal(t, len(buckets), 2)
-			assert.Equal(t, buckets[0].Name(), "widgets")
-			assert.Equal(t, buckets[1].Name(), "woojits")
-			buckets[0].Put([]byte("foo"), []byte("0000"))
-			buckets[1].Put([]byte("bar"), []byte("0001"))
-			return nil
-		})
-		v, _ := db.Get("widgets", []byte("foo"))
-		assert.Equal(t, v, []byte("0000"))
-		v, _ = db.Get("woojits", []byte("bar"))
-		assert.Equal(t, v, []byte("0001"))
-	})
-}
-
-// Ensure that a bucket can be created and retrieved.
-func TestRWTxCreateBucket(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		// Create a bucket.
-		err := db.CreateBucket("widgets")
-		assert.NoError(t, err)
-
-		// Read the bucket through a separate transaction.
-		b, err := db.Bucket("widgets")
-		assert.NotNil(t, b)
-		assert.NoError(t, err)
-	})
-}
-
-// Ensure that a bucket can be created if it doesn't already exist.
-func TestRWTxCreateBucketIfNotExists(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		assert.NoError(t, db.CreateBucketIfNotExists("widgets"))
-		assert.NoError(t, db.CreateBucketIfNotExists("widgets"))
-		assert.Equal(t, db.CreateBucketIfNotExists(""), ErrBucketNameRequired)
-
-		// Read the bucket through a separate transaction.
-		b, err := db.Bucket("widgets")
-		assert.NotNil(t, b)
-		assert.NoError(t, err)
-	})
-}
-
-// Ensure that a bucket cannot be created twice.
-func TestRWTxRecreateBucket(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		// Create a bucket.
-		err := db.CreateBucket("widgets")
-		assert.NoError(t, err)
-
-		// Create the same bucket again.
-		err = db.CreateBucket("widgets")
-		assert.Equal(t, err, ErrBucketExists)
-	})
-}
-
-// Ensure that a bucket is created with a non-blank name.
-func TestRWTxCreateBucketWithoutName(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		err := db.CreateBucket("")
-		assert.Equal(t, err, ErrBucketNameRequired)
-	})
-}
-
-// Ensure that a bucket name is not too long.
-func TestRWTxCreateBucketWithLongName(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		err := db.CreateBucket(strings.Repeat("X", 255))
-		assert.NoError(t, err)
-
-		err = db.CreateBucket(strings.Repeat("X", 256))
-		assert.Equal(t, err, ErrBucketNameTooLarge)
-	})
-}
-
-// Ensure that a bucket can be deleted.
-func TestRWTxDeleteBucket(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		// Create a bucket and add a value.
-		db.CreateBucket("widgets")
-		db.Put("widgets", []byte("foo"), []byte("bar"))
-
-		b, _ := db.Bucket("widgets")
-
-		// Delete the bucket and make sure we can't get the value.
-		assert.NoError(t, db.DeleteBucket("widgets"))
-		value, err := db.Get("widgets", []byte("foo"))
-		assert.Equal(t, err, ErrBucketNotFound)
-		assert.Nil(t, value)
-
-		// Verify that the bucket's page is free.
-		assert.Equal(t, db.freelist.all(), []pgid{b.root})
-
-		// Create the bucket again and make sure there's not a phantom value.
-		assert.NoError(t, db.CreateBucket("widgets"))
-		value, err = db.Get("widgets", []byte("foo"))
-		assert.NoError(t, err)
-		assert.Nil(t, value)
-	})
-}
-
-// Ensure that an error is returned when deleting from a bucket that doesn't exist.
-func TestRWTxDeleteBucketNotFound(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		err := db.DeleteBucket("widgets")
-		assert.Equal(t, err, ErrBucketNotFound)
-	})
-}
-
-// Benchmark the performance of bulk put transactions in random order.
-func BenchmarkRWTxPutRandom(b *testing.B) {
-	indexes := rand.Perm(b.N)
-	value := []byte(strings.Repeat("0", 64))
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("widgets")
-		var txn *RWTx
-		var bucket *Bucket
-		for i := 0; i < b.N; i++ {
-			if i%1000 == 0 {
-				if txn != nil {
-					txn.Commit()
-				}
-				txn, _ = db.RWTx()
-				bucket = txn.Bucket("widgets")
-			}
-			bucket.Put([]byte(strconv.Itoa(indexes[i])), value)
-		}
-		txn.Commit()
-	})
-}
-
-// Benchmark the performance of bulk put transactions in sequential order.
-func BenchmarkRWTxPutSequential(b *testing.B) {
-	value := []byte(strings.Repeat("0", 64))
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("widgets")
-		db.Do(func(txn *RWTx) error {
-			bucket := txn.Bucket("widgets")
-			for i := 0; i < b.N; i++ {
-				bucket.Put([]byte(strconv.Itoa(i)), value)
-			}
-			return nil
-		})
-	})
-}
diff --git a/transaction.go b/transaction.go
deleted file mode 100644
index 91ab730..0000000
--- a/transaction.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package bolt
-
-// Tx represents a read-only transaction on the database.
-// It can be used for retrieving values for keys as well as creating cursors for
-// iterating over the data.
-//
-// IMPORTANT: You must close transactions when you are done with them. Pages
-// can not be reclaimed by the writer until no more transactions are using them.
-// A long running read transaction can cause the database to quickly grow.
-type Tx struct {
-	db      *DB
-	rwtx    *RWTx
-	meta    *meta
-	buckets *buckets
-	nodes   map[pgid]*node
-	pages   map[pgid]*page
-}
-
-// txid represents the internal transaction identifier.
-type txid uint64
-
-// init initializes the transaction and associates it with a database.
-func (t *Tx) init(db *DB) {
-	t.db = db
-	t.pages = nil
-
-	// Copy the meta page since it can be changed by the writer.
-	t.meta = &meta{}
-	db.meta().copy(t.meta)
-
-	// Read in the buckets page.
-	t.buckets = &buckets{}
-	t.buckets.read(t.page(t.meta.buckets))
-}
-
-// id returns the transaction id.
-func (t *Tx) id() txid {
-	return t.meta.txid
-}
-
-// Close closes the transaction and releases any pages it is using.
-func (t *Tx) Close() {
-	if t.db != nil {
-		if t.rwtx != nil {
-			t.rwtx.Rollback()
-		} else {
-			t.db.removeTx(t)
-			t.db = nil
-		}
-	}
-}
-
-// DB returns a reference to the database that created the transaction.
-func (t *Tx) DB() *DB {
-	return t.db
-}
-
-// Bucket retrieves a bucket by name.
-// Returns nil if the bucket does not exist.
-func (t *Tx) Bucket(name string) *Bucket {
-	b := t.buckets.get(name)
-	if b == nil {
-		return nil
-	}
-
-	return &Bucket{
-		bucket: b,
-		name:   name,
-		tx:     t,
-		rwtx:   t.rwtx,
-	}
-}
-
-// Buckets retrieves a list of all buckets.
-func (t *Tx) Buckets() []*Bucket {
-	buckets := make([]*Bucket, 0, len(t.buckets.items))
-	for name, b := range t.buckets.items {
-		bucket := &Bucket{
-			bucket: b,
-			name:   name,
-			tx:     t,
-			rwtx:   t.rwtx,
-		}
-		buckets = append(buckets, bucket)
-	}
-	return buckets
-}
-
-// page returns a reference to the page with a given id.
-// If page has been written to then a temporary bufferred page is returned.
-func (t *Tx) page(id pgid) *page {
-	// Check the dirty pages first.
-	if t.pages != nil {
-		if p, ok := t.pages[id]; ok {
-			return p
-		}
-	}
-
-	// Otherwise return directly from the mmap.
-	return t.db.page(id)
-}
-
-// node returns a reference to the in-memory node for a given page, if it exists.
-func (t *Tx) node(id pgid) *node {
-	if t.nodes == nil {
-		return nil
-	}
-	return t.nodes[id]
-}
-
-// pageNode returns the in-memory node, if it exists.
-// Otherwise returns the underlying page.
-func (t *Tx) pageNode(id pgid) (*page, *node) {
-	if n := t.node(id); n != nil {
-		return nil, n
-	}
-	return t.page(id), nil
-}
-
-// forEachPage iterates over every page within a given page and executes a function.
-func (t *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) {
-	p := t.page(pgid)
-
-	// Execute function.
-	fn(p, depth)
-
-	// Recursively loop over children.
-	if (p.flags & branchPageFlag) != 0 {
-		for i := 0; i < int(p.count); i++ {
-			elem := p.branchPageElement(uint16(i))
-			t.forEachPage(elem.pgid, depth+1, fn)
-		}
-	}
-}
diff --git a/transaction_test.go b/transaction_test.go
deleted file mode 100644
index 59f4dd5..0000000
--- a/transaction_test.go
+++ /dev/null
@@ -1,265 +0,0 @@
-package bolt
-
-import (
-	"fmt"
-	"math/rand"
-	"os"
-	"sort"
-	"strconv"
-	"strings"
-	"testing"
-	"testing/quick"
-
-	"github.com/stretchr/testify/assert"
-)
-
-// Ensure that the database can retrieve a list of buckets.
-func TestTxBuckets(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("foo")
-		db.CreateBucket("bar")
-		db.CreateBucket("baz")
-		buckets, err := db.Buckets()
-		if assert.NoError(t, err) && assert.Equal(t, len(buckets), 3) {
-			assert.Equal(t, buckets[0].Name(), "bar")
-			assert.Equal(t, buckets[1].Name(), "baz")
-			assert.Equal(t, buckets[2].Name(), "foo")
-		}
-	})
-}
-
-// Ensure that a Tx can retrieve a bucket.
-func TestTxBucketMissing(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("widgets")
-		b, err := db.Bucket("widgets")
-		assert.NoError(t, err)
-		if assert.NotNil(t, b) {
-			assert.Equal(t, "widgets", b.Name())
-		}
-	})
-}
-
-// Ensure that a Tx retrieving a non-existent key returns nil.
-func TestTxGetMissing(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("widgets")
-		db.Put("widgets", []byte("foo"), []byte("bar"))
-		value, err := db.Get("widgets", []byte("no_such_key"))
-		assert.NoError(t, err)
-		assert.Nil(t, value)
-	})
-}
-
-// Ensure that a Tx cursor can iterate over an empty bucket without error.
-func TestTxCursorEmptyBucket(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("widgets")
-		txn, _ := db.Tx()
-		c := txn.Bucket("widgets").Cursor()
-		k, v := c.First()
-		assert.Nil(t, k)
-		assert.Nil(t, v)
-		txn.Close()
-	})
-}
-
-// Ensure that a Tx cursor can iterate over a single root with a couple elements.
-func TestTxCursorLeafRoot(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("widgets")
-		db.Put("widgets", []byte("baz"), []byte{})
-		db.Put("widgets", []byte("foo"), []byte{0})
-		db.Put("widgets", []byte("bar"), []byte{1})
-		txn, _ := db.Tx()
-		c := txn.Bucket("widgets").Cursor()
-
-		k, v := c.First()
-		assert.Equal(t, string(k), "bar")
-		assert.Equal(t, v, []byte{1})
-
-		k, v = c.Next()
-		assert.Equal(t, string(k), "baz")
-		assert.Equal(t, v, []byte{})
-
-		k, v = c.Next()
-		assert.Equal(t, string(k), "foo")
-		assert.Equal(t, v, []byte{0})
-
-		k, v = c.Next()
-		assert.Nil(t, k)
-		assert.Nil(t, v)
-
-		k, v = c.Next()
-		assert.Nil(t, k)
-		assert.Nil(t, v)
-
-		txn.Close()
-	})
-}
-
-// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements.
-func TestTxCursorLeafRootReverse(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("widgets")
-		db.Put("widgets", []byte("baz"), []byte{})
-		db.Put("widgets", []byte("foo"), []byte{0})
-		db.Put("widgets", []byte("bar"), []byte{1})
-		txn, _ := db.Tx()
-		c := txn.Bucket("widgets").Cursor()
-
-		k, v := c.Last()
-		assert.Equal(t, string(k), "foo")
-		assert.Equal(t, v, []byte{0})
-
-		k, v = c.Prev()
-		assert.Equal(t, string(k), "baz")
-		assert.Equal(t, v, []byte{})
-
-		k, v = c.Prev()
-		assert.Equal(t, string(k), "bar")
-		assert.Equal(t, v, []byte{1})
-
-		k, v = c.Prev()
-		assert.Nil(t, k)
-		assert.Nil(t, v)
-
-		k, v = c.Prev()
-		assert.Nil(t, k)
-		assert.Nil(t, v)
-
-		txn.Close()
-	})
-}
-
-// Ensure that a Tx cursor can restart from the beginning.
-func TestTxCursorRestart(t *testing.T) {
-	withOpenDB(func(db *DB, path string) {
-		db.CreateBucket("widgets")
-		db.Put("widgets", []byte("bar"), []byte{})
-		db.Put("widgets", []byte("foo"), []byte{})
-
-		txn, _ := db.Tx()
-		c := txn.Bucket("widgets").Cursor()
-
-		k, _ := c.First()
-		assert.Equal(t, string(k), "bar")
-
-		k, _ = c.Next()
-		assert.Equal(t, string(k), "foo")
-
-		k, _ = c.First()
-		assert.Equal(t, string(k), "bar")
-
-		k, _ = c.Next()
-		assert.Equal(t, string(k), "foo")
-
-		txn.Close()
-	})
-}
-
-// Ensure that a Tx can iterate over all elements in a bucket.
-func TestTxCursorIterate(t *testing.T) {
-	f := func(items testdata) bool {
-		withOpenDB(func(db *DB, path string) {
-			// Bulk insert all values.
-			db.CreateBucket("widgets")
-			rwtxn, _ := db.RWTx()
-			b := rwtxn.Bucket("widgets")
-			for _, item := range items {
-				assert.NoError(t, b.Put(item.Key, item.Value))
-			}
-			assert.NoError(t, rwtxn.Commit())
-
-			// Sort test data.
-			sort.Sort(items)
-
-			// Iterate over all items and check consistency.
-			var index = 0
-			txn, _ := db.Tx()
-			c := txn.Bucket("widgets").Cursor()
-			for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() {
-				assert.Equal(t, k, items[index].Key)
-				assert.Equal(t, v, items[index].Value)
-				index++
-			}
-			assert.Equal(t, len(items), index)
-			txn.Close()
-		})
-		fmt.Fprint(os.Stderr, ".")
-		return true
-	}
-	if err := quick.Check(f, qconfig()); err != nil {
-		t.Error(err)
-	}
-	fmt.Fprint(os.Stderr, "\n")
-}
-
-// Ensure that a transaction can iterate over all elements in a bucket in reverse.
-func TestTxCursorIterateReverse(t *testing.T) {
-	f := func(items testdata) bool {
-		withOpenDB(func(db *DB, path string) {
-			// Bulk insert all values.
-			db.CreateBucket("widgets")
-			rwtxn, _ := db.RWTx()
-			b := rwtxn.Bucket("widgets")
-			for _, item := range items {
-				assert.NoError(t, b.Put(item.Key, item.Value))
-			}
-			assert.NoError(t, rwtxn.Commit())
-
-			// Sort test data.
-			sort.Sort(revtestdata(items))
-
-			// Iterate over all items and check consistency.
-			var index = 0
-			txn, _ := db.Tx()
-			c := txn.Bucket("widgets").Cursor()
-			for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() {
-				assert.Equal(t, k, items[index].Key)
-				assert.Equal(t, v, items[index].Value)
-				index++
-			}
-			assert.Equal(t, len(items), index)
-			txn.Close()
-		})
-		fmt.Fprint(os.Stderr, ".")
-		return true
-	}
-	if err := quick.Check(f, qconfig()); err != nil {
-		t.Error(err)
-	}
-	fmt.Fprint(os.Stderr, "\n")
-}
-
-// Benchmark the performance iterating over a cursor.
-func BenchmarkTxCursor(b *testing.B) {
-	indexes := rand.Perm(b.N)
-	value := []byte(strings.Repeat("0", 64))
-
-	withOpenDB(func(db *DB, path string) {
-		// Write data to bucket.
-		db.CreateBucket("widgets")
-		db.Do(func(txn *RWTx) error {
-			bucket := txn.Bucket("widgets")
-			for i := 0; i < b.N; i++ {
-				bucket.Put([]byte(strconv.Itoa(indexes[i])), value)
-			}
-			return nil
-		})
-		b.ResetTimer()
-
-		// Iterate over bucket using cursor.
-		db.With(func(txn *Tx) error {
-			count := 0
-			c := txn.Bucket("widgets").Cursor()
-			for k, _ := c.First(); k != nil; k, _ = c.Next() {
-				count++
-			}
-			if count != b.N {
-				b.Fatalf("wrong count: %d; expected: %d", count, b.N)
-			}
-			return nil
-		})
-	})
-}
diff --git a/rwtransaction.go b/tx.go
similarity index 58%
rename from rwtransaction.go
rename to tx.go
index 0c670d9..53f0456 100644
--- a/rwtransaction.go
+++ b/tx.go
@@ -5,31 +5,99 @@ import (
 	"unsafe"
 )
 
-// RWTx represents a transaction that can read and write data.
-// Only one read/write transaction can be active for a database at a time.
-// RWTx is composed of a read-only transaction so it can also use
-// functions provided by Tx.
-type RWTx struct {
-	Tx
-	pending []*node
+// txid represents the internal transaction identifier.
+type txid uint64
+
+// Tx represents a read-only or read/write transaction on the database.
+// Read-only transactions can be used for retrieving values for keys and creating cursors.
+// Read/write transactions can create and remove buckets and create and remove keys.
+//
+// IMPORTANT: You must commit or rollback transactions when you are done with
+// them. Pages can not be reclaimed by the writer until no more transactions
+// are using them. A long running read transaction can cause the database to
+// quickly grow.
+type Tx struct {
+	writable bool
+	db       *DB
+	meta     *meta
+	buckets  *buckets
+	nodes    map[pgid]*node
+	pages    map[pgid]*page
+	pending  []*node
 }
 
 // init initializes the transaction.
-func (t *RWTx) init(db *DB) {
-	t.Tx.init(db)
-	t.Tx.rwtx = t
-	t.pages = make(map[pgid]*page)
-	t.nodes = make(map[pgid]*node)
+func (t *Tx) init(db *DB) {
+	t.db = db
+	t.pages = nil
 
-	// Increment the transaction id.
-	t.meta.txid += txid(1)
+	// Copy the meta page since it can be changed by the writer.
+	t.meta = &meta{}
+	db.meta().copy(t.meta)
+
+	// Read in the buckets page.
+	t.buckets = &buckets{}
+	t.buckets.read(t.page(t.meta.buckets))
+
+	if t.writable {
+		t.pages = make(map[pgid]*page)
+		t.nodes = make(map[pgid]*node)
+
+		// Increment the transaction id.
+		t.meta.txid += txid(1)
+	}
+}
+
+// id returns the transaction id.
+func (t *Tx) id() txid {
+	return t.meta.txid
+}
+
+// DB returns a reference to the database that created the transaction.
+func (t *Tx) DB() *DB {
+	return t.db
+}
+
+// Writable returns whether the transaction can perform write operations.
+func (t *Tx) Writable() bool {
+	return t.writable
+}
+
+// Bucket retrieves a bucket by name.
+// Returns nil if the bucket does not exist.
+func (t *Tx) Bucket(name string) *Bucket {
+	b := t.buckets.get(name)
+	if b == nil {
+		return nil
+	}
+
+	return &Bucket{
+		bucket: b,
+		name:   name,
+		tx:     t,
+	}
+}
+
+// Buckets retrieves a list of all buckets.
+func (t *Tx) Buckets() []*Bucket {
+	buckets := make([]*Bucket, 0, len(t.buckets.items))
+	for name, b := range t.buckets.items {
+		bucket := &Bucket{
+			bucket: b,
+			name:   name,
+			tx:     t,
+		}
+		buckets = append(buckets, bucket)
+	}
+	return buckets
 }
 
 // CreateBucket creates a new bucket.
 // Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long.
-func (t *RWTx) CreateBucket(name string) error {
-	// Check if bucket already exists.
-	if b := t.Bucket(name); b != nil {
+func (t *Tx) CreateBucket(name string) error {
+	if !t.writable {
+		return ErrTxNotWritable
+	} else if b := t.Bucket(name); b != nil {
 		return ErrBucketExists
 	} else if len(name) == 0 {
 		return ErrBucketNameRequired
@@ -52,7 +120,7 @@ func (t *RWTx) CreateBucket(name string) error {
 
 // CreateBucketIfNotExists creates a new bucket if it doesn't already exist.
 // Returns an error if the bucket name is blank, or if the bucket name is too long.
-func (t *RWTx) CreateBucketIfNotExists(name string) error {
+func (t *Tx) CreateBucketIfNotExists(name string) error {
 	err := t.CreateBucket(name)
 	if err != nil && err != ErrBucketExists {
 		return err
@@ -62,7 +130,11 @@ func (t *RWTx) CreateBucketIfNotExists(name string) error {
 
 // DeleteBucket deletes a bucket.
 // Returns an error if the bucket cannot be found.
-func (t *RWTx) DeleteBucket(name string) error {
+func (t *Tx) DeleteBucket(name string) error {
+	if !t.writable {
+		return ErrTxNotWritable
+	}
+
 	b := t.Bucket(name)
 	if b == nil {
 		return ErrBucketNotFound
@@ -81,11 +153,13 @@ func (t *RWTx) DeleteBucket(name string) error {
 
 // Commit writes all changes to disk and updates the meta page.
 // Returns an error if a disk write error occurs.
-func (t *RWTx) Commit() error {
+func (t *Tx) Commit() error {
 	if t.db == nil {
 		return nil
+	} else if !t.writable {
+		t.Rollback()
+		return nil
 	}
-
 	defer t.close()
 
 	// TODO(benbjohnson): Use vectorized I/O to write out dirty pages.
@@ -118,19 +192,23 @@ func (t *RWTx) Commit() error {
 }
 
 // Rollback closes the transaction and ignores all previous updates.
-func (t *RWTx) Rollback() {
+func (t *Tx) Rollback() {
 	t.close()
 }
 
-func (t *RWTx) close() {
+func (t *Tx) close() {
 	if t.db != nil {
-		t.db.rwlock.Unlock()
+		if t.writable {
+			t.db.rwlock.Unlock()
+		} else {
+			t.db.removeTx(t)
+		}
 		t.db = nil
 	}
 }
 
 // allocate returns a contiguous block of memory starting at a given page.
-func (t *RWTx) allocate(count int) (*page, error) {
+func (t *Tx) allocate(count int) (*page, error) {
 	p, err := t.db.allocate(count)
 	if err != nil {
 		return nil, err
@@ -143,14 +221,14 @@ func (t *RWTx) allocate(count int) (*page, error) {
 }
 
 // rebalance attempts to balance all nodes.
-func (t *RWTx) rebalance() {
+func (t *Tx) rebalance() {
 	for _, n := range t.nodes {
 		n.rebalance()
 	}
 }
 
 // spill writes all the nodes to dirty pages.
-func (t *RWTx) spill() error {
+func (t *Tx) spill() error {
 	// Keep track of the current root nodes.
 	// We will update this at the end once all nodes are created.
 	type root struct {
@@ -233,7 +311,7 @@ func (t *RWTx) spill() error {
 }
 
 // write writes any dirty pages to disk.
-func (t *RWTx) write() error {
+func (t *Tx) write() error {
 	// Sort pages by id.
 	pages := make(pages, 0, len(t.pages))
 	for _, p := range t.pages {
@@ -258,7 +336,7 @@ func (t *RWTx) write() error {
 }
 
 // writeMeta writes the meta to the disk.
-func (t *RWTx) writeMeta() error {
+func (t *Tx) writeMeta() error {
 	// Create a temporary buffer for the meta page.
 	buf := make([]byte, t.db.pageSize)
 	p := t.db.pageInBuffer(buf, 0)
@@ -271,9 +349,11 @@ func (t *RWTx) writeMeta() error {
 }
 
 // node creates a node from a page and associates it with a given parent.
-func (t *RWTx) node(pgid pgid, parent *node) *node {
-	// Retrieve node if it has already been fetched.
-	if n := t.Tx.node(pgid); n != nil {
+func (t *Tx) node(pgid pgid, parent *node) *node {
+	// Retrieve node if it's already been created.
+	if t.nodes == nil {
+		return nil
+	} else if n := t.nodes[pgid]; n != nil {
 		return n
 	}
 
@@ -289,7 +369,7 @@ func (t *RWTx) node(pgid pgid, parent *node) *node {
 }
 
 // dereference removes all references to the old mmap.
-func (t *RWTx) dereference() {
+func (t *Tx) dereference() {
 	for _, n := range t.nodes {
 		n.dereference()
 	}
@@ -298,3 +378,44 @@ func (t *RWTx) dereference() {
 		n.dereference()
 	}
 }
+
+// page returns a reference to the page with a given id.
+// If page has been written to then a temporary bufferred page is returned.
+func (t *Tx) page(id pgid) *page {
+	// Check the dirty pages first.
+	if t.pages != nil {
+		if p, ok := t.pages[id]; ok {
+			return p
+		}
+	}
+
+	// Otherwise return directly from the mmap.
+	return t.db.page(id)
+}
+
+// pageNode returns the in-memory node, if it exists.
+// Otherwise returns the underlying page.
+func (t *Tx) pageNode(id pgid) (*page, *node) {
+	if t.nodes != nil {
+		if n := t.nodes[id]; n != nil {
+			return nil, n
+		}
+	}
+	return t.page(id), nil
+}
+
+// forEachPage iterates over every page within a given page and executes a function.
+func (t *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) {
+	p := t.page(pgid)
+
+	// Execute function.
+	fn(p, depth)
+
+	// Recursively loop over children.
+	if (p.flags & branchPageFlag) != 0 {
+		for i := 0; i < int(p.count); i++ {
+			elem := p.branchPageElement(uint16(i))
+			t.forEachPage(elem.pgid, depth+1, fn)
+		}
+	}
+}
diff --git a/tx_test.go b/tx_test.go
new file mode 100644
index 0000000..1274476
--- /dev/null
+++ b/tx_test.go
@@ -0,0 +1,437 @@
+package bolt
+
+import (
+	"fmt"
+	"math/rand"
+	"os"
+	"sort"
+	"strconv"
+	"strings"
+	"testing"
+	"testing/quick"
+
+	"github.com/stretchr/testify/assert"
+)
+
+// Ensure that the database can retrieve a list of buckets.
+func TestTxBuckets(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("foo")
+		db.CreateBucket("bar")
+		db.CreateBucket("baz")
+		buckets, err := db.Buckets()
+		if assert.NoError(t, err) && assert.Equal(t, len(buckets), 3) {
+			assert.Equal(t, buckets[0].Name(), "bar")
+			assert.Equal(t, buckets[1].Name(), "baz")
+			assert.Equal(t, buckets[2].Name(), "foo")
+		}
+	})
+}
+
+// Ensure that creating a bucket with a read-only transaction returns an error.
+func TestTxCreateBucketReadOnly(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.With(func(tx *Tx) error {
+			assert.Equal(t, tx.CreateBucket("foo"), ErrTxNotWritable)
+			return nil
+		})
+	})
+}
+
+// Ensure that a Tx can retrieve a bucket.
+func TestTxBucketMissing(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("widgets")
+		b, err := db.Bucket("widgets")
+		assert.NoError(t, err)
+		if assert.NotNil(t, b) {
+			assert.Equal(t, "widgets", b.Name())
+		}
+	})
+}
+
+// Ensure that a Tx retrieving a non-existent key returns nil.
+func TestTxGetMissing(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("widgets")
+		db.Put("widgets", []byte("foo"), []byte("bar"))
+		value, err := db.Get("widgets", []byte("no_such_key"))
+		assert.NoError(t, err)
+		assert.Nil(t, value)
+	})
+}
+
+// Ensure that retrieving all buckets returns writable buckets.
+func TestTxWritableBuckets(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("widgets")
+		db.CreateBucket("woojits")
+		db.Do(func(tx *Tx) error {
+			buckets := tx.Buckets()
+			assert.Equal(t, len(buckets), 2)
+			assert.Equal(t, buckets[0].Name(), "widgets")
+			assert.Equal(t, buckets[1].Name(), "woojits")
+			buckets[0].Put([]byte("foo"), []byte("0000"))
+			buckets[1].Put([]byte("bar"), []byte("0001"))
+			return nil
+		})
+		v, _ := db.Get("widgets", []byte("foo"))
+		assert.Equal(t, v, []byte("0000"))
+		v, _ = db.Get("woojits", []byte("bar"))
+		assert.Equal(t, v, []byte("0001"))
+	})
+}
+
+// Ensure that a bucket can be created and retrieved.
+func TestTxCreateBucket(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		// Create a bucket.
+		err := db.CreateBucket("widgets")
+		assert.NoError(t, err)
+
+		// Read the bucket through a separate transaction.
+		b, err := db.Bucket("widgets")
+		assert.NotNil(t, b)
+		assert.NoError(t, err)
+	})
+}
+
+// Ensure that a bucket can be created if it doesn't already exist.
+func TestTxCreateBucketIfNotExists(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		assert.NoError(t, db.CreateBucketIfNotExists("widgets"))
+		assert.NoError(t, db.CreateBucketIfNotExists("widgets"))
+		assert.Equal(t, db.CreateBucketIfNotExists(""), ErrBucketNameRequired)
+
+		// Read the bucket through a separate transaction.
+		b, err := db.Bucket("widgets")
+		assert.NotNil(t, b)
+		assert.NoError(t, err)
+	})
+}
+
+// Ensure that a bucket cannot be created twice.
+func TestTxRecreateBucket(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		// Create a bucket.
+		err := db.CreateBucket("widgets")
+		assert.NoError(t, err)
+
+		// Create the same bucket again.
+		err = db.CreateBucket("widgets")
+		assert.Equal(t, err, ErrBucketExists)
+	})
+}
+
+// Ensure that a bucket is created with a non-blank name.
+func TestTxCreateBucketWithoutName(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		err := db.CreateBucket("")
+		assert.Equal(t, err, ErrBucketNameRequired)
+	})
+}
+
+// Ensure that a bucket name is not too long.
+func TestTxCreateBucketWithLongName(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		err := db.CreateBucket(strings.Repeat("X", 255))
+		assert.NoError(t, err)
+
+		err = db.CreateBucket(strings.Repeat("X", 256))
+		assert.Equal(t, err, ErrBucketNameTooLarge)
+	})
+}
+
+// Ensure that a bucket can be deleted.
+func TestTxDeleteBucket(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		// Create a bucket and add a value.
+		db.CreateBucket("widgets")
+		db.Put("widgets", []byte("foo"), []byte("bar"))
+
+		b, _ := db.Bucket("widgets")
+
+		// Delete the bucket and make sure we can't get the value.
+		assert.NoError(t, db.DeleteBucket("widgets"))
+		value, err := db.Get("widgets", []byte("foo"))
+		assert.Equal(t, err, ErrBucketNotFound)
+		assert.Nil(t, value)
+
+		// Verify that the bucket's page is free.
+		assert.Equal(t, db.freelist.all(), []pgid{b.root})
+
+		// Create the bucket again and make sure there's not a phantom value.
+		assert.NoError(t, db.CreateBucket("widgets"))
+		value, err = db.Get("widgets", []byte("foo"))
+		assert.NoError(t, err)
+		assert.Nil(t, value)
+	})
+}
+
+// Ensure that deleting a bucket with a read-only transaction returns an error.
+func TestTxDeleteBucketReadOnly(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.With(func(tx *Tx) error {
+			assert.Equal(t, tx.DeleteBucket("foo"), ErrTxNotWritable)
+			return nil
+		})
+	})
+}
+
+// Ensure that an error is returned when deleting from a bucket that doesn't exist.
+func TestTxDeleteBucketNotFound(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		err := db.DeleteBucket("widgets")
+		assert.Equal(t, err, ErrBucketNotFound)
+	})
+}
+
+// Ensure that a Tx cursor can iterate over an empty bucket without error.
+func TestTxCursorEmptyBucket(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("widgets")
+		tx, _ := db.Tx()
+		c := tx.Bucket("widgets").Cursor()
+		k, v := c.First()
+		assert.Nil(t, k)
+		assert.Nil(t, v)
+		tx.Commit()
+	})
+}
+
+// Ensure that a Tx cursor can iterate over a single root with a couple elements.
+func TestTxCursorLeafRoot(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("widgets")
+		db.Put("widgets", []byte("baz"), []byte{})
+		db.Put("widgets", []byte("foo"), []byte{0})
+		db.Put("widgets", []byte("bar"), []byte{1})
+		tx, _ := db.Tx()
+		c := tx.Bucket("widgets").Cursor()
+
+		k, v := c.First()
+		assert.Equal(t, string(k), "bar")
+		assert.Equal(t, v, []byte{1})
+
+		k, v = c.Next()
+		assert.Equal(t, string(k), "baz")
+		assert.Equal(t, v, []byte{})
+
+		k, v = c.Next()
+		assert.Equal(t, string(k), "foo")
+		assert.Equal(t, v, []byte{0})
+
+		k, v = c.Next()
+		assert.Nil(t, k)
+		assert.Nil(t, v)
+
+		k, v = c.Next()
+		assert.Nil(t, k)
+		assert.Nil(t, v)
+
+		tx.Rollback()
+	})
+}
+
+// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements.
+func TestTxCursorLeafRootReverse(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("widgets")
+		db.Put("widgets", []byte("baz"), []byte{})
+		db.Put("widgets", []byte("foo"), []byte{0})
+		db.Put("widgets", []byte("bar"), []byte{1})
+		tx, _ := db.Tx()
+		c := tx.Bucket("widgets").Cursor()
+
+		k, v := c.Last()
+		assert.Equal(t, string(k), "foo")
+		assert.Equal(t, v, []byte{0})
+
+		k, v = c.Prev()
+		assert.Equal(t, string(k), "baz")
+		assert.Equal(t, v, []byte{})
+
+		k, v = c.Prev()
+		assert.Equal(t, string(k), "bar")
+		assert.Equal(t, v, []byte{1})
+
+		k, v = c.Prev()
+		assert.Nil(t, k)
+		assert.Nil(t, v)
+
+		k, v = c.Prev()
+		assert.Nil(t, k)
+		assert.Nil(t, v)
+
+		tx.Rollback()
+	})
+}
+
+// Ensure that a Tx cursor can restart from the beginning.
+func TestTxCursorRestart(t *testing.T) {
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("widgets")
+		db.Put("widgets", []byte("bar"), []byte{})
+		db.Put("widgets", []byte("foo"), []byte{})
+
+		tx, _ := db.Tx()
+		c := tx.Bucket("widgets").Cursor()
+
+		k, _ := c.First()
+		assert.Equal(t, string(k), "bar")
+
+		k, _ = c.Next()
+		assert.Equal(t, string(k), "foo")
+
+		k, _ = c.First()
+		assert.Equal(t, string(k), "bar")
+
+		k, _ = c.Next()
+		assert.Equal(t, string(k), "foo")
+
+		tx.Rollback()
+	})
+}
+
+// Ensure that a Tx can iterate over all elements in a bucket.
+func TestTxCursorIterate(t *testing.T) {
+	f := func(items testdata) bool {
+		withOpenDB(func(db *DB, path string) {
+			// Bulk insert all values.
+			db.CreateBucket("widgets")
+			tx, _ := db.RWTx()
+			b := tx.Bucket("widgets")
+			for _, item := range items {
+				assert.NoError(t, b.Put(item.Key, item.Value))
+			}
+			assert.NoError(t, tx.Commit())
+
+			// Sort test data.
+			sort.Sort(items)
+
+			// Iterate over all items and check consistency.
+			var index = 0
+			tx, _ = db.Tx()
+			c := tx.Bucket("widgets").Cursor()
+			for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() {
+				assert.Equal(t, k, items[index].Key)
+				assert.Equal(t, v, items[index].Value)
+				index++
+			}
+			assert.Equal(t, len(items), index)
+			tx.Rollback()
+		})
+		fmt.Fprint(os.Stderr, ".")
+		return true
+	}
+	if err := quick.Check(f, qconfig()); err != nil {
+		t.Error(err)
+	}
+	fmt.Fprint(os.Stderr, "\n")
+}
+
+// Ensure that a transaction can iterate over all elements in a bucket in reverse.
+func TestTxCursorIterateReverse(t *testing.T) {
+	f := func(items testdata) bool {
+		withOpenDB(func(db *DB, path string) {
+			// Bulk insert all values.
+			db.CreateBucket("widgets")
+			tx, _ := db.RWTx()
+			b := tx.Bucket("widgets")
+			for _, item := range items {
+				assert.NoError(t, b.Put(item.Key, item.Value))
+			}
+			assert.NoError(t, tx.Commit())
+
+			// Sort test data.
+			sort.Sort(revtestdata(items))
+
+			// Iterate over all items and check consistency.
+			var index = 0
+			tx, _ = db.Tx()
+			c := tx.Bucket("widgets").Cursor()
+			for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() {
+				assert.Equal(t, k, items[index].Key)
+				assert.Equal(t, v, items[index].Value)
+				index++
+			}
+			assert.Equal(t, len(items), index)
+			tx.Rollback()
+		})
+		fmt.Fprint(os.Stderr, ".")
+		return true
+	}
+	if err := quick.Check(f, qconfig()); err != nil {
+		t.Error(err)
+	}
+	fmt.Fprint(os.Stderr, "\n")
+}
+
+// Benchmark the performance iterating over a cursor.
+func BenchmarkTxCursor(b *testing.B) {
+	indexes := rand.Perm(b.N)
+	value := []byte(strings.Repeat("0", 64))
+
+	withOpenDB(func(db *DB, path string) {
+		// Write data to bucket.
+		db.CreateBucket("widgets")
+		db.Do(func(tx *Tx) error {
+			bucket := tx.Bucket("widgets")
+			for i := 0; i < b.N; i++ {
+				bucket.Put([]byte(strconv.Itoa(indexes[i])), value)
+			}
+			return nil
+		})
+		b.ResetTimer()
+
+		// Iterate over bucket using cursor.
+		db.With(func(tx *Tx) error {
+			count := 0
+			c := tx.Bucket("widgets").Cursor()
+			for k, _ := c.First(); k != nil; k, _ = c.Next() {
+				count++
+			}
+			if count != b.N {
+				b.Fatalf("wrong count: %d; expected: %d", count, b.N)
+			}
+			return nil
+		})
+	})
+}
+
+// Benchmark the performance of bulk put transactions in random order.
+func BenchmarkTxPutRandom(b *testing.B) {
+	indexes := rand.Perm(b.N)
+	value := []byte(strings.Repeat("0", 64))
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("widgets")
+		var tx *Tx
+		var bucket *Bucket
+		for i := 0; i < b.N; i++ {
+			if i%1000 == 0 {
+				if tx != nil {
+					tx.Commit()
+				}
+				tx, _ = db.RWTx()
+				bucket = tx.Bucket("widgets")
+			}
+			bucket.Put([]byte(strconv.Itoa(indexes[i])), value)
+		}
+		tx.Commit()
+	})
+}
+
+// Benchmark the performance of bulk put transactions in sequential order.
+func BenchmarkTxPutSequential(b *testing.B) {
+	value := []byte(strings.Repeat("0", 64))
+	withOpenDB(func(db *DB, path string) {
+		db.CreateBucket("widgets")
+		db.Do(func(tx *Tx) error {
+			bucket := tx.Bucket("widgets")
+			for i := 0; i < b.N; i++ {
+				bucket.Put([]byte(strconv.Itoa(i)), value)
+			}
+			return nil
+		})
+	})
+}