Repository: genjidb/genji
Pull request #177 (2020-09-04 18:09:52)

Codec system
This PR decouples the `msgpack` package from the rest of the code by introducing a new generic `Codec` type. This codec is now used everywhere and can be changed, defaults to MessagePack and can be changes after a call to `genji.Open`.
This prepares the introduction of other encoding systems.


Patch:
diff --git a/database/config.go b/database/config.go
index c3ecee9a..92c71a13 100644
--- a/database/config.go
+++ b/database/config.go
@@ -1,10 +1,10 @@
 package database
 
 import (
+	"bytes"
 	"sync"
 
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/index"
 	"github.com/genjidb/genji/key"
@@ -155,14 +155,17 @@ func (ti *TableInfo) ScanDocument(d document.Document) error {
 // It loads table information during database startup
 // and holds it in memory.
 type tableInfoStore struct {
+	db *Database
 	// tableInfos contains information about all the tables
 	tableInfos map[string]TableInfo
 
 	mu sync.RWMutex
 }
 
-func newTableInfoStore(tx engine.Transaction) (*tableInfoStore, error) {
-	var ts tableInfoStore
+func newTableInfoStore(db *Database, tx engine.Transaction) (*tableInfoStore, error) {
+	ts := tableInfoStore{
+		db: db,
+	}
 
 	err := ts.loadAllTableInfo(tx)
 	if err != nil {
@@ -189,7 +192,7 @@ func (t *tableInfoStore) Insert(tx *Transaction, tableName string, info *TableIn
 		return ErrTableAlreadyExists
 	}
 
-	st, err := tx.Tx.GetStore([]byte(tableInfoStoreName))
+	st, err := tx.tx.GetStore([]byte(tableInfoStoreName))
 	if err != nil {
 		return err
 	}
@@ -202,12 +205,13 @@ func (t *tableInfoStore) Insert(tx *Transaction, tableName string, info *TableIn
 		info.storeName = key.AppendInt64([]byte{storePrefix}, int64(seq))
 	}
 
-	v, err := msgpack.EncodeDocument(info.ToDocument())
+	var buf bytes.Buffer
+	err = t.db.Codec.NewEncoder(&buf).EncodeDocument(info.ToDocument())
 	if err != nil {
 		return err
 	}
 
-	err = st.Put([]byte(tableName), v)
+	err = st.Put([]byte(tableName), buf.Bytes())
 	if err != nil {
 		return err
 	}
@@ -246,7 +250,7 @@ func (t *tableInfoStore) Delete(tx *Transaction, tableName string) error {
 		return ErrTableNotFound
 	}
 
-	st, err := tx.Tx.GetStore([]byte(tableInfoStoreName))
+	st, err := tx.tx.GetStore([]byte(tableInfoStoreName))
 	if err != nil {
 		return err
 	}
@@ -287,7 +291,7 @@ func (t *tableInfoStore) loadAllTableInfo(tx engine.Transaction) error {
 		}
 
 		var ti TableInfo
-		err = ti.ScanDocument(msgpack.EncodedDocument(b))
+		err = ti.ScanDocument(t.db.Codec.NewDocument(b))
 		if err != nil {
 			return err
 		}
@@ -410,6 +414,7 @@ type Index struct {
 }
 
 type indexStore struct {
+	db *Database
 	st engine.Store
 }
 
@@ -423,12 +428,13 @@ func (t *indexStore) Insert(cfg IndexConfig) error {
 		return err
 	}
 
-	v, err := msgpack.EncodeDocument(cfg.ToDocument())
+	var buf bytes.Buffer
+	err = t.db.Codec.NewEncoder(&buf).EncodeDocument(cfg.ToDocument())
 	if err != nil {
 		return err
 	}
 
-	return t.st.Put(key, v)
+	return t.st.Put(key, buf.Bytes())
 }
 
 func (t *indexStore) Get(indexName string) (*IndexConfig, error) {
@@ -442,7 +448,7 @@ func (t *indexStore) Get(indexName string) (*IndexConfig, error) {
 	}
 
 	var idxopts IndexConfig
-	err = idxopts.ScanDocument(msgpack.EncodedDocument(v))
+	err = idxopts.ScanDocument(t.db.Codec.NewDocument(v))
 	if err != nil {
 		return nil, err
 	}
@@ -451,12 +457,13 @@ func (t *indexStore) Get(indexName string) (*IndexConfig, error) {
 }
 
 func (t *indexStore) Replace(indexName string, cfg IndexConfig) error {
-	v, err := msgpack.EncodeDocument(cfg.ToDocument())
+	var buf bytes.Buffer
+	err := t.db.Codec.NewEncoder(&buf).EncodeDocument(cfg.ToDocument())
 	if err != nil {
 		return err
 	}
 
-	return t.st.Put([]byte(indexName), v)
+	return t.st.Put([]byte(indexName), buf.Bytes())
 }
 
 func (t *indexStore) Delete(indexName string) error {
@@ -472,7 +479,7 @@ func (t *indexStore) ListAll() ([]*IndexConfig, error) {
 	var idxList []*IndexConfig
 	it := t.st.NewIterator(engine.IteratorConfig{})
 
-	var buf msgpack.EncodedDocument
+	var buf []byte
 	var err error
 	for it.Seek(nil); it.Valid(); it.Next() {
 		item := it.Item()
@@ -483,7 +490,7 @@ func (t *indexStore) ListAll() ([]*IndexConfig, error) {
 			return nil, err
 		}
 
-		err = opts.ScanDocument(&buf)
+		err = opts.ScanDocument(t.db.Codec.NewDocument(buf))
 		if err != nil {
 			it.Close()
 			return nil, err
diff --git a/database/database.go b/database/database.go
index 2d271a65..461c98be 100644
--- a/database/database.go
+++ b/database/database.go
@@ -6,6 +6,8 @@ import (
 	"sync"
 	"sync/atomic"
 
+	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 )
 
@@ -27,12 +29,16 @@ type Database struct {
 	// will cause an error until that transaction is rolled back or commited.
 	attachedTransaction *Transaction
 	attachedTxMu        sync.Mutex
+
+	// Codec used to encode documents. Defaults to MessagePack.
+	Codec encoding.Codec
 }
 
 // New initializes the DB using the given engine.
 func New(ng engine.Engine) (*Database, error) {
 	db := Database{
-		ng: ng,
+		ng:    ng,
+		Codec: msgpack.NewCodec(),
 	}
 
 	ntx, err := db.ng.Begin(true)
@@ -46,7 +52,7 @@ func New(ng engine.Engine) (*Database, error) {
 		return nil, err
 	}
 
-	db.tableInfoStore, err = newTableInfoStore(ntx)
+	db.tableInfoStore, err = newTableInfoStore(&db, ntx)
 	if err != nil {
 		return nil, err
 	}
@@ -112,7 +118,7 @@ func (db *Database) BeginTx(opts *TxOptions) (*Transaction, error) {
 	tx := Transaction{
 		id:             atomic.AddInt64(&db.lastTransactionID, 1),
 		db:             db,
-		Tx:             ntx,
+		tx:             ntx,
 		writable:       !opts.ReadOnly,
 		tableInfoStore: db.tableInfoStore,
 	}
diff --git a/database/table.go b/database/table.go
index da94e8ba..86d86bab 100644
--- a/database/table.go
+++ b/database/table.go
@@ -1,11 +1,12 @@
 package database
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding/msgpack"
+	"github.com/genjidb/genji/document/encoding"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/index"
 	"github.com/genjidb/genji/key"
@@ -19,6 +20,11 @@ type Table struct {
 	infoStore *tableInfoStore
 }
 
+// Tx returns the current transaction.
+func (t *Table) Tx() *Transaction {
+	return t.tx
+}
+
 // Info of the table.
 func (t *Table) Info() (*TableInfo, error) {
 	return t.infoStore.Get(t.tx, t.name)
@@ -63,12 +69,13 @@ func (t *Table) Insert(d document.Document) ([]byte, error) {
 		return nil, ErrDuplicateDocument
 	}
 
-	v, err := msgpack.EncodeDocument(d)
+	var buf bytes.Buffer
+	err = t.tx.db.Codec.NewEncoder(&buf).EncodeDocument(d)
 	if err != nil {
 		return nil, fmt.Errorf("failed to encode document: %w", err)
 	}
 
-	err = t.Store.Put(key, v)
+	err = t.Store.Put(key, buf.Bytes())
 	if err != nil {
 		return nil, err
 	}
@@ -186,13 +193,14 @@ func (t *Table) replace(indexes map[string]Index, key []byte, d document.Documen
 	}
 
 	// encode new document
-	v, err := msgpack.EncodeDocument(d)
+	var buf bytes.Buffer
+	err = t.tx.db.Codec.NewEncoder(&buf).EncodeDocument(d)
 	if err != nil {
 		return fmt.Errorf("failed to encode document: %w", err)
 	}
 
 	// replace old document with new document
-	err = t.Store.Put(key, v)
+	err = t.Store.Put(key, buf.Bytes())
 	if err != nil {
 		return err
 	}
@@ -215,7 +223,7 @@ func (t *Table) replace(indexes map[string]Index, key []byte, d document.Documen
 
 // Indexes returns a map of all the indexes of a table.
 func (t *Table) Indexes() (map[string]Index, error) {
-	s, err := t.tx.Tx.GetStore([]byte(indexStoreName))
+	s, err := t.tx.tx.GetStore([]byte(indexStoreName))
 	if err != nil {
 		return nil, err
 	}
@@ -246,9 +254,9 @@ func (t *Table) Indexes() (map[string]Index, error) {
 
 			var idx index.Index
 			if opts.Unique {
-				idx = index.NewUniqueIndex(t.tx.Tx, opts.IndexName)
+				idx = index.NewUniqueIndex(t.tx.tx, opts.IndexName)
 			} else {
-				idx = index.NewListIndex(t.tx.Tx, opts.IndexName)
+				idx = index.NewListIndex(t.tx.tx, opts.IndexName)
 			}
 
 			indexes[opts.Path.String()] = Index{
@@ -266,7 +274,7 @@ func (t *Table) Indexes() (map[string]Index, error) {
 }
 
 type encodedDocumentWithKey struct {
-	msgpack.EncodedDocument
+	document.Document
 
 	key []byte
 }
@@ -282,8 +290,9 @@ func (e encodedDocumentWithKey) Key() []byte {
 // from store on documents that don't need to be
 // decoded.
 type lazilyDecodedDocument struct {
-	item engine.Item
-	buf  msgpack.EncodedDocument
+	item  engine.Item
+	buf   []byte
+	codec encoding.Codec
 }
 
 func (d *lazilyDecodedDocument) GetByField(field string) (v document.Value, err error) {
@@ -291,7 +300,7 @@ func (d *lazilyDecodedDocument) GetByField(field string) (v document.Value, err
 		d.copyFromItem()
 	}
 
-	return d.buf.GetByField(field)
+	return d.codec.NewDocument(d.buf).GetByField(field)
 }
 
 func (d *lazilyDecodedDocument) Iterate(fn func(field string, value document.Value) error) error {
@@ -299,7 +308,7 @@ func (d *lazilyDecodedDocument) Iterate(fn func(field string, value document.Val
 		d.copyFromItem()
 	}
 
-	return d.buf.Iterate(fn)
+	return d.codec.NewDocument(d.buf).Iterate(fn)
 }
 
 func (d *lazilyDecodedDocument) Key() []byte {
@@ -313,7 +322,7 @@ func (d *lazilyDecodedDocument) Reset() {
 
 func (d *lazilyDecodedDocument) copyFromItem() error {
 	var err error
-	d.buf, err = d.item.ValueCopy(d.buf[:0])
+	d.buf, err = d.item.ValueCopy(d.buf)
 
 	return err
 }
@@ -323,7 +332,9 @@ func (d *lazilyDecodedDocument) copyFromItem() error {
 func (t *Table) Iterate(fn func(d document.Document) error) error {
 	// To avoid unnecessary allocations, we create the struct once and reuse
 	// it during each iteration.
-	var d lazilyDecodedDocument
+	d := lazilyDecodedDocument{
+		codec: t.tx.db.Codec,
+	}
 
 	it := t.Store.NewIterator(engine.IteratorConfig{})
 	defer it.Close()
@@ -355,7 +366,7 @@ func (t *Table) GetDocument(key []byte) (document.Document, error) {
 	}
 
 	var d encodedDocumentWithKey
-	d.EncodedDocument = msgpack.EncodedDocument(v)
+	d.Document = t.tx.db.Codec.NewDocument(v)
 	d.key = key
 	return &d, err
 }
diff --git a/database/transaction.go b/database/transaction.go
index e8d325fd..32889993 100644
--- a/database/transaction.go
+++ b/database/transaction.go
@@ -6,7 +6,6 @@ import (
 	"strings"
 
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/index"
 )
@@ -24,13 +23,18 @@ var (
 type Transaction struct {
 	id       int64
 	db       *Database
-	Tx       engine.Transaction
+	tx       engine.Transaction
 	writable bool
 
 	tableInfoStore *tableInfoStore
 	indexStore     *indexStore
 }
 
+// DB returns the underlying database that created the transaction.
+func (tx *Transaction) DB() *Database {
+	return tx.db
+}
+
 // Rollback the transaction. Can be used safely after commit.
 func (tx *Transaction) Rollback() error {
 	tx.db.attachedTxMu.Lock()
@@ -40,7 +44,7 @@ func (tx *Transaction) Rollback() error {
 		tx.tableInfoStore.rollback(tx)
 	}
 
-	err := tx.Tx.Rollback()
+	err := tx.tx.Rollback()
 	if err != nil {
 		return err
 	}
@@ -61,7 +65,7 @@ func (tx *Transaction) Commit() error {
 		tx.tableInfoStore.commit(tx)
 	}
 
-	err := tx.Tx.Commit()
+	err := tx.tx.Commit()
 	if err != nil {
 		return err
 	}
@@ -96,7 +100,7 @@ func (tx *Transaction) CreateTable(name string, info *TableInfo) error {
 		return err
 	}
 
-	err = tx.Tx.CreateStore(info.storeName)
+	err = tx.tx.CreateStore(info.storeName)
 	if err != nil {
 		return fmt.Errorf("failed to create table %q: %w", name, err)
 	}
@@ -111,7 +115,7 @@ func (tx *Transaction) GetTable(name string) (*Table, error) {
 		return nil, err
 	}
 
-	s, err := tx.Tx.GetStore(ti.storeName)
+	s, err := tx.tx.GetStore(ti.storeName)
 	if err != nil {
 		return nil, err
 	}
@@ -175,7 +179,7 @@ func (tx *Transaction) DropTable(name string) error {
 
 	it := tx.indexStore.st.NewIterator(engine.IteratorConfig{})
 
-	var buf msgpack.EncodedDocument
+	var buf []byte
 	for it.Seek(nil); it.Valid(); it.Next() {
 		item := it.Item()
 		var opts IndexConfig
@@ -185,7 +189,7 @@ func (tx *Transaction) DropTable(name string) error {
 			return err
 		}
 
-		err = opts.ScanDocument(&buf)
+		err = opts.ScanDocument(tx.db.Codec.NewDocument(buf))
 		if err != nil {
 			it.Close()
 			return err
@@ -212,7 +216,7 @@ func (tx *Transaction) DropTable(name string) error {
 		return err
 	}
 
-	return tx.Tx.DropStore(ti.storeName)
+	return tx.tx.DropStore(ti.storeName)
 }
 
 // CreateIndex creates an index with the given name.
@@ -235,9 +239,9 @@ func (tx *Transaction) GetIndex(name string) (*Index, error) {
 
 	var idx index.Index
 	if opts.Unique {
-		idx = index.NewUniqueIndex(tx.Tx, opts.IndexName)
+		idx = index.NewUniqueIndex(tx.tx, opts.IndexName)
 	} else {
-		idx = index.NewListIndex(tx.Tx, opts.IndexName)
+		idx = index.NewListIndex(tx.tx, opts.IndexName)
 	}
 
 	return &Index{
@@ -259,9 +263,9 @@ func (tx *Transaction) DropIndex(name string) error {
 
 	var idx index.Index
 	if opts.Unique {
-		idx = index.NewUniqueIndex(tx.Tx, opts.IndexName)
+		idx = index.NewUniqueIndex(tx.tx, opts.IndexName)
 	} else {
-		idx = index.NewListIndex(tx.Tx, opts.IndexName)
+		idx = index.NewListIndex(tx.tx, opts.IndexName)
 	}
 
 	return idx.Truncate()
@@ -323,11 +327,12 @@ func (tx *Transaction) ReIndexAll() error {
 }
 
 func (tx *Transaction) getIndexStore() (*indexStore, error) {
-	st, err := tx.Tx.GetStore([]byte(indexStoreName))
+	st, err := tx.tx.GetStore([]byte(indexStoreName))
 	if err != nil {
 		return nil, err
 	}
 	return &indexStore{
 		st: st,
+		db: tx.db,
 	}, nil
 }
diff --git a/document/encoding/codec.go b/document/encoding/codec.go
new file mode 100644
index 00000000..33981415
--- /dev/null
+++ b/document/encoding/codec.go
@@ -0,0 +1,25 @@
+// Package encoding defines types that deal with document encoding.
+// Genji codecs are designed to support buffer reuse during encoding
+// and optional random-access, i.e. decoding one path without decoding the entire document,
+// during decoding.
+package encoding
+
+import (
+	"io"
+
+	"github.com/genjidb/genji/document"
+)
+
+// A Codec is able to create encoders and decoders for a specific encoding format.
+type Codec interface {
+	NewEncoder(io.Writer) Encoder
+	// NewDocument returns a document without decoding its given binary representation.
+	// The returned document should ideally support random-access, i.e. decoding one path
+	// without decoding the entire document. If not, the document must be lazily decoded.
+	NewDocument([]byte) document.Document
+}
+
+// An Encoder encodes one document to the underlying writer.
+type Encoder interface {
+	EncodeDocument(d document.Document) error
+}
diff --git a/document/encoding/msgpack/codec.go b/document/encoding/msgpack/codec.go
index 1f0e741d..1ef659f8 100644
--- a/document/encoding/msgpack/codec.go
+++ b/document/encoding/msgpack/codec.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/document/encoding"
 	"github.com/vmihailenco/msgpack/v5"
 	"github.com/vmihailenco/msgpack/v5/codes"
 )
@@ -15,6 +16,24 @@ const (
 	DurationType int8 = 0x1
 )
 
+// A Codec is a MessagePack implementation of an encoding.Codec.
+type Codec struct{}
+
+// NewCodec creates a MessagePack codec.
+func NewCodec() Codec {
+	return Codec{}
+}
+
+// NewEncoder implements the encoding.Codec interface.
+func (c Codec) NewEncoder(w io.Writer) encoding.Encoder {
+	return NewEncoder(w)
+}
+
+// NewDocument implements the encoding.Codec interface.
+func (c Codec) NewDocument(data []byte) document.Document {
+	return EncodedDocument(data)
+}
+
 // Encoder encodes Genji documents and values
 // in MessagePack.
 type Encoder struct {
diff --git a/engine/memoryengine/store.go b/engine/memoryengine/store.go
index a8bb742e..88921486 100644
--- a/engine/memoryengine/store.go
+++ b/engine/memoryengine/store.go
@@ -27,8 +27,8 @@ func (i *item) ValueCopy(buf []byte) ([]byte, error) {
 	if len(buf) < len(i.v) {
 		buf = make([]byte, len(i.v))
 	}
-	copy(buf, i.v)
-	return buf, nil
+	n := copy(buf, i.v)
+	return buf[:n], nil
 }
 
 func (i *item) Less(than btree.Item) bool {
diff --git a/sql/planner/replacement.go b/sql/planner/replacement.go
index b0d0b178..25027778 100644
--- a/sql/planner/replacement.go
+++ b/sql/planner/replacement.go
@@ -6,7 +6,7 @@ import (
 
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding/msgpack"
+	"github.com/genjidb/genji/document/encoding"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/sql/query"
 	"github.com/genjidb/genji/sql/query/expr"
@@ -20,6 +20,7 @@ type replacementNode struct {
 
 	tableName string
 	table     *database.Table
+	codec     encoding.Codec
 }
 
 var _ outputNode = (*replacementNode)(nil)
@@ -49,7 +50,10 @@ func (n *replacementNode) Bind(tx *database.Transaction, params []expr.Param) (e
 // Increasing replaceBufferSize will occasionate less key searches (O(log n) for most engines) but will take more memory.
 func (n *replacementNode) toResult(st document.Stream) (res query.Result, err error) {
 	// replace store implementation by a resumable store, temporarily.
-	rit := resumableIterator{store: n.table.Store}
+	rit := resumableIterator{
+		store: n.table.Store,
+		codec: n.codec,
+	}
 
 	st = st.Limit(replaceBufferSize)
 
@@ -103,6 +107,7 @@ func (n *replacementNode) String() string {
 // it is used to resume iteration.
 type resumableIterator struct {
 	store engine.Store
+	codec encoding.Codec
 
 	curKey []byte
 }
@@ -115,15 +120,17 @@ func (u *resumableIterator) Iterate(fn func(d document.Document) error) error {
 	it := u.store.NewIterator(engine.IteratorConfig{})
 	defer it.Close()
 
+	var buf []byte
 	for it.Seek(u.curKey); it.Valid(); it.Next() {
 		item := it.Item()
 
 		d.key = item.Key()
-		d.EncodedDocument, err = item.ValueCopy(d.EncodedDocument)
+		buf, err = item.ValueCopy(buf)
 		if err != nil {
 			return err
 		}
 
+		d.Document = u.codec.NewDocument(buf)
 		err = fn(&d)
 		if err != nil {
 			return err
@@ -134,7 +141,7 @@ func (u *resumableIterator) Iterate(fn func(d document.Document) error) error {
 }
 
 type encodedDocumentWithKey struct {
-	msgpack.EncodedDocument
+	document.Document
 
 	key []byte
 }
diff --git a/sql/query/expr/comparison.go b/sql/query/expr/comparison.go
index 665909e8..eff386e7 100644
--- a/sql/query/expr/comparison.go
+++ b/sql/query/expr/comparison.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/index"
 	"github.com/genjidb/genji/key"
@@ -72,7 +71,7 @@ func (op eqOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 
 		return err
 	}
-	return fn(msgpack.EncodedDocument(val))
+	return fn(tb.Tx().DB().Codec.NewDocument(val))
 }
 
 func (op eqOp) String() string {
@@ -128,23 +127,22 @@ func (op gtOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 		return err
 	}
 
-	var d msgpack.EncodedDocument
-
 	data := key.AppendValue(nil, v)
 
 	it := tb.Store.NewIterator(engine.IteratorConfig{})
 	defer it.Close()
 
+	var buf []byte
 	for it.Seek(data); it.Valid(); it.Next() {
-		d, err = it.Item().ValueCopy(d)
+		buf, err = it.Item().ValueCopy(buf)
 		if err != nil {
 			return err
 		}
-		if bytes.Equal(data, d) {
+		if bytes.Equal(data, buf) {
 			return nil
 		}
 
-		err = fn(&d)
+		err = fn(tb.Tx().DB().Codec.NewDocument(buf))
 		if err != nil {
 			return err
 		}
@@ -189,20 +187,19 @@ func (op gteOp) IteratePK(tb *database.Table, v document.Value, pkType document.
 		return err
 	}
 
-	var d msgpack.EncodedDocument
-
 	data := key.AppendValue(nil, v)
 
 	it := tb.Store.NewIterator(engine.IteratorConfig{})
 	defer it.Close()
 
+	var buf []byte
 	for it.Seek(data); it.Valid(); it.Next() {
-		d, err = it.Item().ValueCopy(d)
+		buf, err = it.Item().ValueCopy(buf)
 		if err != nil {
 			return err
 		}
 
-		err = fn(&d)
+		err = fn(tb.Tx().DB().Codec.NewDocument(buf))
 		if err != nil {
 			return err
 		}
@@ -261,23 +258,22 @@ func (op ltOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 		return err
 	}
 
-	var d msgpack.EncodedDocument
-
 	data := key.AppendValue(nil, v)
 
 	it := tb.Store.NewIterator(engine.IteratorConfig{})
 	defer it.Close()
 
+	var buf []byte
 	for it.Seek(nil); it.Valid(); it.Next() {
-		d, err = it.Item().ValueCopy(d)
+		buf, err = it.Item().ValueCopy(buf)
 		if err != nil {
 			return err
 		}
-		if bytes.Compare(data, d) <= 0 {
+		if bytes.Compare(data, buf) <= 0 {
 			break
 		}
 
-		err = fn(&d)
+		err = fn(tb.Tx().DB().Codec.NewDocument(buf))
 		if err != nil {
 			return err
 		}
@@ -336,23 +332,22 @@ func (op lteOp) IteratePK(tb *database.Table, v document.Value, pkType document.
 		return err
 	}
 
-	var d msgpack.EncodedDocument
-
 	data := key.AppendValue(nil, v)
 
 	it := tb.Store.NewIterator(engine.IteratorConfig{})
 	defer it.Close()
 
+	var buf []byte
 	for it.Seek(nil); it.Valid(); it.Next() {
-		d, err = it.Item().ValueCopy(d)
+		buf, err = it.Item().ValueCopy(buf)
 		if err != nil {
 			return err
 		}
-		if bytes.Compare(data, d) < 0 {
+		if bytes.Compare(data, buf) < 0 {
 			break
 		}
 
-		err = fn(&d)
+		err = fn(tb.Tx().DB().Codec.NewDocument(buf))
 		if err != nil {
 			return err
 		}
@@ -487,8 +482,6 @@ func (op inOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 		return errors.New("IN operator takes an array")
 	}
 
-	var d msgpack.EncodedDocument
-
 	return v.V.(document.Array).Iterate(func(i int, value document.Value) error {
 		val, err := value.CastAs(pkType)
 		if err != nil {
@@ -497,7 +490,7 @@ func (op inOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 
 		data := key.AppendValue(nil, val)
 
-		d, err = tb.Store.Get(data)
+		v, err := tb.Store.Get(data)
 		if err != nil {
 			if err == engine.ErrKeyNotFound {
 				return nil
@@ -505,7 +498,7 @@ func (op inOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 
 			return err
 		}
-		return fn(d)
+		return fn(tb.Tx().DB().Codec.NewDocument(v))
 	})
 }
 


Test patch:
diff --git a/database/config_test.go b/database/config_test.go
index 45ec1990..b5b62705 100644
--- a/database/config_test.go
+++ b/database/config_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine/memoryengine"
 	"github.com/stretchr/testify/require"
 )
@@ -145,7 +146,7 @@ func TestIndexStore(t *testing.T) {
 	st, err := tx.GetStore([]byte("test"))
 	require.NoError(t, err)
 
-	idxs := indexStore{st}
+	idxs := indexStore{db: &Database{Codec: msgpack.NewCodec()}, st: st}
 
 	t.Run("Basic operations", func(t *testing.T) {
 		cfg := IndexConfig{
diff --git a/document/document_test.go b/document/document_test.go
index 2aa48b78..4bd49ad6 100644
--- a/document/document_test.go
+++ b/document/document_test.go
@@ -6,7 +6,6 @@ import (
 	"testing"
 
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/sql/parser"
 	"github.com/stretchr/testify/require"
 )
@@ -579,23 +578,4 @@ func BenchmarkDocumentIterate(b *testing.B) {
 			})
 		}
 	})
-
-	b.Run("Encoding/Implementation", func(b *testing.B) {
-		for i := 0; i < b.N; i++ {
-			msgpack.EncodeDocument(&f)
-		}
-	})
-
-	b.Run("Encoding/Reflection", func(b *testing.B) {
-		for i := 0; i < b.N; i++ {
-			refd, _ := document.NewFromStruct(&f)
-			msgpack.EncodeDocument(refd)
-		}
-	})
-
-	b.Run("Encoding/JSON", func(b *testing.B) {
-		for i := 0; i < b.N; i++ {
-			json.Marshal(&f)
-		}
-	})
 }
diff --git a/document/encoding/encodingtest/benchmark.go b/document/encoding/encodingtest/benchmark.go
new file mode 100644
index 00000000..fbaccc90
--- /dev/null
+++ b/document/encoding/encodingtest/benchmark.go
@@ -0,0 +1,140 @@
+package encodingtest
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/document/encoding"
+	"github.com/stretchr/testify/require"
+)
+
+// BenchmarkCodec runs benchmarks against the given codec.
+func BenchmarkCodec(b *testing.B, codecBuilder func() encoding.Codec) {
+	benchmarks := []struct {
+		name string
+		test func(*testing.B, func() encoding.Codec)
+	}{
+		{"Codec/Encode", benchmarkEncodeDocument},
+		{"Codec/Decode", benchmarkDecodeDocument},
+		{"Codec/Document/GetByField", benchmarkDocumentGetByField},
+		{"Codec/Document/Iterate", benchmarkDocumentIterate},
+		{"ComparedWithJSON/Encode", benchmarkEncodeDocumentJSON},
+		{"ComparedWithJSON/Decode", benchmarkDecodeDocumentJSON},
+	}
+
+	for _, test := range benchmarks {
+		b.Run(test.name, func(b *testing.B) {
+			test.test(b, codecBuilder)
+		})
+	}
+}
+
+func benchmarkEncodeDocument(b *testing.B, codecBuilder func() encoding.Codec) {
+	var fb document.FieldBuffer
+
+	for i := int64(0); i < 100; i++ {
+		fb.Add(fmt.Sprintf("name-%d", i), document.NewIntegerValue(i))
+	}
+
+	codec := codecBuilder()
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		var buf bytes.Buffer
+		codec.NewEncoder(&buf).EncodeDocument(&fb)
+	}
+}
+
+func benchmarkDocumentGetByField(b *testing.B, codecBuilder func() encoding.Codec) {
+	var fb document.FieldBuffer
+
+	for i := int64(0); i < 100; i++ {
+		fb.Add(fmt.Sprintf("name-%d", i), document.NewIntegerValue(i))
+	}
+
+	codec := codecBuilder()
+	var buf bytes.Buffer
+	err := codec.NewEncoder(&buf).EncodeDocument(&fb)
+	require.NoError(b, err)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		codec.NewDocument(buf.Bytes()).GetByField("name-99")
+	}
+}
+
+func benchmarkDocumentIterate(b *testing.B, codecBuilder func() encoding.Codec) {
+	var fb document.FieldBuffer
+
+	for i := int64(0); i < 100; i++ {
+		fb.Add(fmt.Sprintf("name-%d", i), document.NewIntegerValue(i))
+	}
+
+	codec := codecBuilder()
+	var buf bytes.Buffer
+	err := codec.NewEncoder(&buf).EncodeDocument(&fb)
+	require.NoError(b, err)
+
+	doc := codec.NewDocument(buf.Bytes())
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		doc.Iterate(func(string, document.Value) error {
+			return nil
+		})
+	}
+}
+
+func benchmarkDecodeDocument(b *testing.B, codecBuilder func() encoding.Codec) {
+	var fb document.FieldBuffer
+
+	for i := int64(0); i < 100; i++ {
+		fb.Add(fmt.Sprintf("name-%d", i), document.NewIntegerValue(i))
+	}
+
+	codec := codecBuilder()
+	var buf bytes.Buffer
+	err := codec.NewEncoder(&buf).EncodeDocument(&fb)
+	require.NoError(b, err)
+
+	doc := codec.NewDocument(buf.Bytes())
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		var buf document.FieldBuffer
+		buf.Copy(doc)
+	}
+}
+
+func benchmarkEncodeDocumentJSON(b *testing.B, codecBuilder func() encoding.Codec) {
+	m := make(map[string]int64)
+
+	for i := int64(0); i < 100; i++ {
+		m[fmt.Sprintf("name-%d", i)] = i
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		json.Marshal(m)
+	}
+}
+
+func benchmarkDecodeDocumentJSON(b *testing.B, codecBuilder func() encoding.Codec) {
+	m := make(map[string]int64)
+
+	for i := int64(0); i < 100; i++ {
+		m[fmt.Sprintf("name-%d", i)] = i
+	}
+
+	d, err := json.Marshal(m)
+	require.NoError(b, err)
+
+	mm := make(map[string]int64)
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		json.Unmarshal(d, &mm)
+	}
+}
diff --git a/document/encoding/encodingtest/testing.go b/document/encoding/encodingtest/testing.go
new file mode 100644
index 00000000..c1e877e2
--- /dev/null
+++ b/document/encoding/encodingtest/testing.go
@@ -0,0 +1,184 @@
+// Package encodingtest provides a test suite for testing codec implementations.
+package encodingtest
+
+import (
+	"bytes"
+	"testing"
+	"time"
+
+	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/document/encoding"
+	"github.com/stretchr/testify/require"
+)
+
+// TestCodec runs a list of tests on the given codec.
+func TestCodec(t *testing.T, codecBuilder func() encoding.Codec) {
+	tests := []struct {
+		name string
+		test func(*testing.T, func() encoding.Codec)
+	}{
+		{"EncodeDecode", testEncodeDecode},
+		{"NewDocument", testDecodeDocument},
+		{"Array/GetByIndex", testArrayGetByIndex},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			test.test(t, codecBuilder)
+		})
+	}
+}
+
+func testEncodeDecode(t *testing.T, codecBuilder func() encoding.Codec) {
+	userMapDoc, err := document.NewFromMap(map[string]interface{}{
+		"age":  10,
+		"name": "john",
+	})
+	require.NoError(t, err)
+
+	addressMapDoc, err := document.NewFromMap(map[string]string{
+		"city":    "Ajaccio",
+		"country": "France",
+	})
+	require.NoError(t, err)
+
+	complexArray := document.NewValueBuffer().
+		Append(document.NewIntegerValue(-40)).
+		Append(document.NewBoolValue(true)).
+		Append(document.NewTextValue("hello")).
+		Append(document.NewDocumentValue(addressMapDoc)).
+		Append(document.NewArrayValue(document.NewValueBuffer().Append(document.NewIntegerValue(11))))
+
+	tests := []struct {
+		name     string
+		d        document.Document
+		expected string
+	}{
+		{
+			"document.FieldBuffer",
+			document.NewFieldBuffer().
+				Add("age", document.NewIntegerValue(10)).
+				Add("name", document.NewTextValue("john")),
+			`{"age": 10, "name": "john"}`,
+		},
+		{
+			"Map",
+			userMapDoc,
+			`{"age": 10, "name": "john"}`,
+		},
+		{
+			"Nested Document",
+			document.NewFieldBuffer().
+				Add("age", document.NewIntegerValue(10)).
+				Add("name", document.NewTextValue("john")).
+				Add("address", document.NewDocumentValue(addressMapDoc)).
+				Add("array", document.NewArrayValue(complexArray)),
+			`{"age": 10, "name": "john", "address": {"city": "Ajaccio", "country": "France"}, "array": [-40, true, "hello", {"city": "Ajaccio", "country": "France"}, [11]]}`,
+		},
+	}
+
+	var buf bytes.Buffer
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			buf.Reset()
+			codec := codecBuilder()
+			err := codec.NewEncoder(&buf).EncodeDocument(test.d)
+			require.NoError(t, err)
+			var jsonBuf bytes.Buffer
+			err = document.ToJSON(&jsonBuf, codec.NewDocument(buf.Bytes()))
+			require.NoError(t, err)
+			require.JSONEq(t, test.expected, jsonBuf.String())
+		})
+	}
+}
+
+func testArrayGetByIndex(t *testing.T, codecBuilder func() encoding.Codec) {
+	codec := codecBuilder()
+
+	arr := document.NewValueBuffer().
+		Append(document.NewIntegerValue(10)).
+		Append(document.NewNullValue()).
+		Append(document.NewTextValue("john")).
+		Append(document.NewDurationValue(10 * time.Nanosecond))
+
+	var buf bytes.Buffer
+
+	err := codec.NewEncoder(&buf).EncodeDocument(document.NewFieldBuffer().Add("a", document.NewArrayValue(arr)))
+	require.NoError(t, err)
+
+	d := codec.NewDocument(buf.Bytes())
+	v, err := d.GetByField("a")
+	require.NoError(t, err)
+
+	require.Equal(t, document.ArrayValue, v.Type)
+	a := v.V.(document.Array)
+	v, err = a.GetByIndex(0)
+	require.NoError(t, err)
+
+	require.Equal(t, document.NewIntegerValue(10), v)
+
+	v, err = a.GetByIndex(1)
+	require.NoError(t, err)
+	require.Equal(t, document.NewNullValue(), v)
+
+	v, err = a.GetByIndex(2)
+	require.NoError(t, err)
+	require.Equal(t, document.NewTextValue("john"), v)
+
+	v, err = a.GetByIndex(1000)
+	require.Equal(t, err, document.ErrValueNotFound)
+}
+
+func testDecodeDocument(t *testing.T, codecBuilder func() encoding.Codec) {
+	codec := codecBuilder()
+
+	mapDoc, err := document.NewFromMap(map[string]string{
+		"city":    "Ajaccio",
+		"country": "France",
+	})
+	require.NoError(t, err)
+
+	doc := document.NewFieldBuffer().
+		Add("age", document.NewIntegerValue(10)).
+		Add("name", document.NewTextValue("john")).
+		Add("address", document.NewDocumentValue(mapDoc))
+
+	var buf bytes.Buffer
+
+	err = codec.NewEncoder(&buf).EncodeDocument(doc)
+	require.NoError(t, err)
+
+	ec := codec.NewDocument(buf.Bytes())
+	v, err := ec.GetByField("age")
+	require.NoError(t, err)
+	require.Equal(t, document.NewIntegerValue(10), v)
+	v, err = ec.GetByField("address")
+	require.NoError(t, err)
+	var expected, actual bytes.Buffer
+	err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc)))
+	require.NoError(t, err)
+	err = document.ToJSON(&actual, document.NewFieldBuffer().Add("address", v))
+	require.NoError(t, err)
+	require.JSONEq(t, expected.String(), actual.String())
+
+	var i int
+	err = ec.Iterate(func(f string, v document.Value) error {
+		switch f {
+		case "age":
+			require.Equal(t, document.NewIntegerValue(10), v)
+		case "address":
+			var expected, actual bytes.Buffer
+			err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc)))
+			require.NoError(t, err)
+			err = document.ToJSON(&actual, document.NewFieldBuffer().Add(f, v))
+			require.NoError(t, err)
+			require.JSONEq(t, expected.String(), actual.String())
+		case "name":
+			require.Equal(t, document.NewTextValue("john"), v)
+		}
+		i++
+		return nil
+	})
+	require.NoError(t, err)
+	require.Equal(t, 3, i)
+}
diff --git a/document/encoding/msgpack/encoding_test.go b/document/encoding/msgpack/encoding_test.go
index d2d17f70..dd7350cb 100644
--- a/document/encoding/msgpack/encoding_test.go
+++ b/document/encoding/msgpack/encoding_test.go
@@ -1,300 +1,20 @@
 package msgpack
 
 import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"strings"
 	"testing"
-	"time"
 
-	"github.com/genjidb/genji/document"
-	"github.com/stretchr/testify/require"
+	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/encodingtest"
 )
 
-func TestGetByField(t *testing.T) {
-	doc := document.NewFieldBuffer().
-		Add("age", document.NewIntegerValue(10)).
-		Add("address", document.NewNullValue()).
-		Add("name", document.NewTextValue("john")).
-		Add("d", document.NewDurationValue(10*time.Nanosecond)).
-		Add(strings.Repeat("a", 2<<5), document.NewBoolValue(true)).
-		Add(strings.Repeat("a", 2<<10), document.NewBoolValue(true)).
-		Add(strings.Repeat("a", 2<<20), document.NewBoolValue(true))
-
-	data, err := EncodeDocument(doc)
-	require.NoError(t, err)
-
-	d := EncodedDocument(data)
-
-	v, err := d.GetByField("age")
-	require.NoError(t, err)
-	require.Equal(t, document.NewIntegerValue(10), v)
-
-	v, err = d.GetByField("address")
-	require.NoError(t, err)
-	require.Equal(t, document.NewNullValue(), v)
-
-	v, err = d.GetByField("name")
-	require.NoError(t, err)
-	require.Equal(t, document.NewTextValue("john"), v)
-
-	v, err = d.GetByField("doesnexists")
-	require.Equal(t, err, document.ErrFieldNotFound)
-
-	v, err = d.GetByField("d")
-	require.NoError(t, err)
-	require.Equal(t, document.NewDurationValue(10*time.Nanosecond), v)
-
-	v, err = d.GetByField(strings.Repeat("a", 2<<20))
-	require.NoError(t, err)
-	require.Equal(t, document.NewBoolValue(true), v)
-}
-
-func TestGetByIndex(t *testing.T) {
-	arr := document.NewValueBuffer().
-		Append(document.NewIntegerValue(10)).
-		Append(document.NewNullValue()).
-		Append(document.NewTextValue("john")).
-		Append(document.NewDurationValue(10 * time.Nanosecond))
-
-	data, err := EncodeArray(arr)
-	require.NoError(t, err)
-
-	a := EncodedArray(data)
-
-	v, err := a.GetByIndex(0)
-	require.NoError(t, err)
-	require.Equal(t, document.NewIntegerValue(10), v)
-
-	v, err = a.GetByIndex(1)
-	require.NoError(t, err)
-	require.Equal(t, document.NewNullValue(), v)
-
-	v, err = a.GetByIndex(2)
-	require.NoError(t, err)
-	require.Equal(t, document.NewTextValue("john"), v)
-
-	v, err = a.GetByIndex(1000)
-	require.Equal(t, err, document.ErrValueNotFound)
-}
-
-func TestEncodeDecode(t *testing.T) {
-	userMapDoc, err := document.NewFromMap(map[string]interface{}{
-		"age":  10,
-		"name": "john",
-	})
-	require.NoError(t, err)
-
-	addressMapDoc, err := document.NewFromMap(map[string]string{
-		"city":    "Ajaccio",
-		"country": "France",
-	})
-	require.NoError(t, err)
-
-	tests := []struct {
-		name     string
-		d        document.Document
-		expected string
-	}{
-		{
-			"document.FieldBuffer",
-			document.NewFieldBuffer().
-				Add("age", document.NewIntegerValue(10)).
-				Add("name", document.NewTextValue("john")),
-			`{"age": 10, "name": "john"}`,
-		},
-		{
-			"Map",
-			userMapDoc,
-			`{"age": 10, "name": "john"}`,
-		},
-		{
-			"Nested Document",
-			document.NewFieldBuffer().
-				Add("age", document.NewIntegerValue(10)).
-				Add("name", document.NewTextValue("john")).
-				Add("address", document.NewDocumentValue(addressMapDoc)),
-			`{"age": 10, "name": "john", "address": {"city": "Ajaccio", "country": "France"}}`,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			enc, err := EncodeDocument(test.d)
-			require.NoError(t, err)
-			var buf bytes.Buffer
-			err = document.ToJSON(&buf, DecodeDocument(enc))
-			require.NoError(t, err)
-			require.JSONEq(t, test.expected, buf.String())
-		})
-	}
-}
-
-func TestDecodeDocument(t *testing.T) {
-	mapDoc, err := document.NewFromMap(map[string]string{
-		"city":    "Ajaccio",
-		"country": "France",
-	})
-	require.NoError(t, err)
-
-	doc := document.NewFieldBuffer().
-		Add("age", document.NewIntegerValue(10)).
-		Add("name", document.NewTextValue("john")).
-		Add("address", document.NewDocumentValue(mapDoc))
-
-	data, err := EncodeDocument(doc)
-	require.NoError(t, err)
-
-	ec := DecodeDocument(data)
-	v, err := ec.GetByField("age")
-	require.NoError(t, err)
-	require.Equal(t, document.NewIntegerValue(10), v)
-	v, err = ec.GetByField("address")
-	require.NoError(t, err)
-	var expected, actual bytes.Buffer
-	err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc)))
-	require.NoError(t, err)
-	err = document.ToJSON(&actual, document.NewFieldBuffer().Add("address", v))
-	require.NoError(t, err)
-	require.JSONEq(t, expected.String(), actual.String())
-
-	var i int
-	err = ec.Iterate(func(f string, v document.Value) error {
-		switch f {
-		case "age":
-			require.Equal(t, document.NewIntegerValue(10), v)
-		case "address":
-			var expected, actual bytes.Buffer
-			err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc)))
-			require.NoError(t, err)
-			err = document.ToJSON(&actual, document.NewFieldBuffer().Add(f, v))
-			require.NoError(t, err)
-			require.JSONEq(t, expected.String(), actual.String())
-		case "name":
-			require.Equal(t, document.NewTextValue("john"), v)
-		}
-		i++
-		return nil
+func TestCodec(t *testing.T) {
+	encodingtest.TestCodec(t, func() encoding.Codec {
+		return NewCodec()
 	})
-	require.NoError(t, err)
-	require.Equal(t, 3, i)
 }
 
-func TestEncodeArray(t *testing.T) {
-	mapDoc, err := document.NewFromMap(map[string]string{
-		"city":    "Ajaccio",
-		"country": "France",
+func BenchmarkCodec(b *testing.B) {
+	encodingtest.BenchmarkCodec(b, func() encoding.Codec {
+		return NewCodec()
 	})
-	require.NoError(t, err)
-
-	tests := []struct {
-		name     string
-		a        document.Array
-		expected string
-	}{
-		{
-			"Complex array",
-			document.NewValueBuffer().
-				Append(document.NewIntegerValue(10)).
-				Append(document.NewTextValue("john")).
-				Append(document.NewDocumentValue(mapDoc)).
-				Append(document.NewArrayValue(document.NewValueBuffer().Append(document.NewIntegerValue(11)))),
-			`[10, "john", {"city": "Ajaccio", "country": "France"}, [11]]` + "\n",
-		},
-		{
-			"Empty array",
-			document.NewValueBuffer(),
-			"[]\n",
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			data, err := EncodeArray(test.a)
-			require.NoError(t, err)
-			var buf bytes.Buffer
-			document.ArrayToJSON(&buf, DecodeArray(data))
-			require.JSONEq(t, test.expected, buf.String())
-		})
-	}
-}
-
-func BenchmarkEncodeDocument(b *testing.B) {
-	var buf document.FieldBuffer
-
-	for i := int64(0); i < 100; i++ {
-		buf.Add(fmt.Sprintf("name-%d", i), document.NewIntegerValue(i))
-	}
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		EncodeDocument(&buf)
-	}
-}
-
-func BenchmarkEncodeDocumentJSON(b *testing.B) {
-	m := make(map[string]int64)
-
-	for i := int64(0); i < 100; i++ {
-		m[fmt.Sprintf("name-%d", i)] = i
-	}
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		json.Marshal(m)
-	}
-}
-
-func BenchmarkGetByField(b *testing.B) {
-	var buf document.FieldBuffer
-
-	for i := int64(0); i < 100; i++ {
-		buf.Add(fmt.Sprintf("name-%d", i), document.NewIntegerValue(i))
-	}
-
-	data, err := EncodeDocument(&buf)
-	require.NoError(b, err)
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		DecodeDocument(data).GetByField("name-99")
-	}
-}
-
-func BenchmarkDocumentIterate(b *testing.B) {
-	var buf document.FieldBuffer
-
-	for i := int64(0); i < 100; i++ {
-		buf.Add(fmt.Sprintf("name-%d", i), document.NewIntegerValue(i))
-	}
-
-	data, err := EncodeDocument(&buf)
-	require.NoError(b, err)
-
-	ec := DecodeDocument(data)
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		ec.Iterate(func(string, document.Value) error {
-			return nil
-		})
-	}
-}
-
-func BenchmarkDecodeDocumentJSON(b *testing.B) {
-	m := make(map[string]int64)
-
-	for i := int64(0); i < 100; i++ {
-		m[fmt.Sprintf("name-%d", i)] = i
-	}
-
-	d, err := json.Marshal(m)
-	require.NoError(b, err)
-
-	mm := make(map[string]int64)
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		json.Unmarshal(d, &mm)
-	}
 }