Repository: genjidb/genji
Pull request #117 (2020-07-12 09:36:15)

Encode documents using MessagePack
This PR replaces the current custom encoding system with MessagePack. It introduces a new `msgpack` package that takes care of encoding documents.



Patch:
diff --git a/cmd/genji/go.sum b/cmd/genji/go.sum
index b87dfa81..05c8e8a4 100644
--- a/cmd/genji/go.sum
+++ b/cmd/genji/go.sum
@@ -27,10 +27,10 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/genjidb/genji v0.6.0 h1:Ksz9f6FRciZfkocfPAbD8ZSh1GKkuQPLGeGEeH9l6Es=
-github.com/genjidb/genji v0.6.0/go.mod h1:bwsAzPOtKRos+Vblm0pPswIYRmc8aFlCzQlG/n5Fxiw=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@@ -86,13 +86,21 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
+github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1 h1:d71/KA0LhvkrJ/Ok+Wx9qK7bU8meKA1Hk0jpVI5kJjk=
+github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1/go.mod h1:xlngVLeyQ/Qi05oQxhQ+oTuqa03RjMwMfk/7/TCs+QI=
+github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY=
+github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
 go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -105,7 +113,12 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
diff --git a/database/config.go b/database/config.go
index aba1ed95..bd3c561e 100644
--- a/database/config.go
+++ b/database/config.go
@@ -7,7 +7,7 @@ import (
 	"time"
 
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/index"
 )
@@ -173,7 +173,7 @@ func (t *tableInfoStore) Insert(tableName string, info *TableInfo) ([]byte, erro
 	}
 	info.storeID = id
 
-	v, err := encoding.EncodeDocument(info.ToDocument())
+	v, err := msgpack.EncodeDocument(info.ToDocument())
 	if err != nil {
 		return nil, err
 	}
@@ -196,7 +196,7 @@ func (t *tableInfoStore) Get(tableName string) (*TableInfo, error) {
 	}
 
 	var ti TableInfo
-	err = ti.ScanDocument(encoding.EncodedDocument(v))
+	err = ti.ScanDocument(msgpack.EncodedDocument(v))
 	if err != nil {
 		return nil, err
 	}
@@ -318,7 +318,7 @@ func (t *indexStore) Insert(cfg IndexConfig) error {
 		return err
 	}
 
-	v, err := encoding.EncodeDocument(cfg.ToDocument())
+	v, err := msgpack.EncodeDocument(cfg.ToDocument())
 	if err != nil {
 		return err
 	}
@@ -337,7 +337,7 @@ func (t *indexStore) Get(indexName string) (*IndexConfig, error) {
 	}
 
 	var idxopts IndexConfig
-	err = idxopts.ScanDocument(encoding.EncodedDocument(v))
+	err = idxopts.ScanDocument(msgpack.EncodedDocument(v))
 	if err != nil {
 		return nil, err
 	}
@@ -358,7 +358,7 @@ func (t *indexStore) ListAll() ([]*IndexConfig, error) {
 	var idxList []*IndexConfig
 	it := t.st.NewIterator(engine.IteratorConfig{})
 
-	var buf encoding.EncodedDocument
+	var buf msgpack.EncodedDocument
 	var err error
 	for it.Seek(nil); it.Valid(); it.Next() {
 		item := it.Item()
diff --git a/database/table.go b/database/table.go
index 1b6bd6e5..268b2461 100644
--- a/database/table.go
+++ b/database/table.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/genjidb/genji/document"
 	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/index"
 )
@@ -32,7 +33,7 @@ func (t *Table) Info() (*TableInfo, error) {
 }
 
 type encodedDocumentWithKey struct {
-	encoding.EncodedDocument
+	msgpack.EncodedDocument
 
 	key []byte
 }
@@ -85,7 +86,7 @@ func (t *Table) GetDocument(key []byte) (document.Document, error) {
 	}
 
 	var d encodedDocumentWithKey
-	d.EncodedDocument = encoding.EncodedDocument(v)
+	d.EncodedDocument = msgpack.EncodedDocument(v)
 	d.key = key
 	return &d, err
 }
@@ -357,7 +358,7 @@ func (t *Table) Insert(d document.Document) ([]byte, error) {
 		return nil, ErrDuplicateDocument
 	}
 
-	v, err := encoding.EncodeDocument(d)
+	v, err := msgpack.EncodeDocument(d)
 	if err != nil {
 		return nil, fmt.Errorf("failed to encode document: %w", err)
 	}
@@ -457,7 +458,7 @@ func (t *Table) replace(indexes map[string]Index, key []byte, d document.Documen
 	}
 
 	// encode new document
-	v, err := encoding.EncodeDocument(d)
+	v, err := msgpack.EncodeDocument(d)
 	if err != nil {
 		return fmt.Errorf("failed to encode document: %w", err)
 	}
diff --git a/database/transaction.go b/database/transaction.go
index 578c3813..e684de46 100644
--- a/database/transaction.go
+++ b/database/transaction.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/index"
 )
@@ -107,7 +107,7 @@ func (tx Transaction) GetTable(name string) (*Table, error) {
 func (tx Transaction) DropTable(name string) error {
 	it := tx.indexStore.st.NewIterator(engine.IteratorConfig{})
 
-	var buf encoding.EncodedDocument
+	var buf msgpack.EncodedDocument
 	var err error
 	for it.Seek(nil); it.Valid(); it.Next() {
 		item := it.Item()
diff --git a/document/encoding/encoding.go b/document/encoding/encoding.go
index 7d29e60b..5856d0df 100644
--- a/document/encoding/encoding.go
+++ b/document/encoding/encoding.go
@@ -8,34 +8,22 @@
 package encoding
 
 import (
-	"bytes"
 	"encoding/binary"
 	"errors"
 	"math"
 	"time"
 
 	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 )
 
-// EncodeBlob takes a blob and returns it.
-// It is present to ease code generation.
-func EncodeBlob(x []byte) []byte {
-	return x
-}
-
-// DecodeBlob takes a byte slice and returns it.
-// It is present to ease code generation.
-func DecodeBlob(buf []byte) ([]byte, error) {
-	return buf, nil
-}
-
-// EncodeText takes a string and returns its binary representation.
-func EncodeText(x string) []byte {
+// EncodeString takes a string and returns its binary representation.
+func EncodeString(x string) []byte {
 	return []byte(x)
 }
 
-// DecodeText takes a byte slice and decodes it into a string.
-func DecodeText(buf []byte) (string, error) {
+// DecodeString takes a byte slice and decodes it into a string.
+func DecodeString(buf []byte) (string, error) {
 	return string(buf), nil
 }
 
@@ -217,64 +205,6 @@ func DecodeFloat64(buf []byte) (float64, error) {
 	return math.Float64frombits(x), nil
 }
 
-// EncodeDocument takes a document and encodes it using the encoding.Format type.
-func EncodeDocument(d document.Document) ([]byte, error) {
-	if ec, ok := d.(EncodedDocument); ok {
-		return ec, nil
-	}
-
-	var format Format
-
-	var offset uint64
-	var dataList [][]byte
-
-	err := d.Iterate(func(f string, v document.Value) error {
-		data, err := EncodeValue(v)
-		if err != nil {
-			return err
-		}
-
-		format.Header.FieldHeaders = append(format.Header.FieldHeaders, FieldHeader{
-			NameSize:   uint64(len(f)),
-			NameString: f,
-			Type:       uint64(v.Type),
-			Size:       uint64(len(data)),
-			Offset:     offset,
-		})
-
-		offset += uint64(len(data))
-		dataList = append(dataList, data)
-		return nil
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	var buf bytes.Buffer
-	_, err = format.Header.WriteTo(&buf)
-	if err != nil {
-		return nil, err
-	}
-
-	buf.Grow(format.Header.BodySize())
-
-	for _, data := range dataList {
-		_, err = buf.Write(data)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return buf.Bytes(), nil
-}
-
-// DecodeDocument takes a byte slice and returns a lazily decoded document.
-// If buf is malformed, an error will be returned when calling one of the document method.
-func DecodeDocument(buf []byte) document.Document {
-	return EncodedDocument(buf)
-}
-
-// EncodeValue encodes any value to a binary representation.
 func EncodeValue(v document.Value) ([]byte, error) {
 	switch v.Type {
 	case document.DocumentValue:
@@ -282,25 +212,25 @@ func EncodeValue(v document.Value) ([]byte, error) {
 		if err != nil {
 			return nil, err
 		}
-		return EncodeDocument(d)
+		return msgpack.EncodeDocument(d)
 	case document.ArrayValue:
 		a, err := v.ConvertToArray()
 		if err != nil {
 			return nil, err
 		}
-		return EncodeArray(a)
+		return msgpack.EncodeArray(a)
 	case document.BlobValue:
 		x, err := v.ConvertToBlob()
 		if err != nil {
 			return nil, err
 		}
-		return EncodeBlob(x), nil
+		return x, nil
 	case document.TextValue:
 		x, err := v.ConvertToText()
 		if err != nil {
 			return nil, err
 		}
-		return EncodeText(x), nil
+		return EncodeString(x), nil
 	case document.BoolValue:
 		x, err := v.ConvertToBool()
 		if err != nil {
@@ -326,204 +256,17 @@ func EncodeValue(v document.Value) ([]byte, error) {
 	return nil, errors.New("unknown type")
 }
 
-// An EncodedDocument implements the document.Document interface on top of an encoded representation of a
-// document.
-// It is useful to avoid decoding the entire document when only a few fields are needed.
-type EncodedDocument []byte
-
-// GetByField decodes the selected field.
-func (e EncodedDocument) GetByField(field string) (document.Value, error) {
-	return decodeValueFromDocument(e, field)
-}
-
-// Iterate decodes each fields one by one and passes them to fn until the end of the document
-// or until fn returns an error.
-func (e EncodedDocument) Iterate(fn func(field string, value document.Value) error) error {
-	var format Format
-	err := format.Decode(e)
-	if err != nil {
-		return err
-	}
-
-	for _, fh := range format.Header.FieldHeaders {
-		v, err := DecodeValue(document.ValueType(fh.Type), format.Body[fh.Offset:fh.Offset+fh.Size])
-		if err != nil {
-			return err
-		}
-
-		err = fn(string(fh.Name), v)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// MarshalJSON implements the json.Marshaler interface.
-func (e EncodedDocument) MarshalJSON() ([]byte, error) {
-	var buf bytes.Buffer
-
-	err := document.ToJSON(&buf, e)
-	if err != nil {
-		return nil, err
-	}
-
-	return buf.Bytes(), nil
-}
-
-// An EncodedArray implements the document.Array interface on top of an encoded representation of an
-// array.
-// It is useful to avoid decoding the entire array when only a few values are needed.
-type EncodedArray []byte
-
-// Iterate goes through all the values of the array and calls the given function by passing each one of them.
-// If the given function returns an error, the iteration stops.
-func (e EncodedArray) Iterate(fn func(i int, value document.Value) error) error {
-	var format Format
-	err := format.Decode(e)
-	if err != nil {
-		return err
-	}
-
-	for _, fh := range format.Header.FieldHeaders {
-		v, err := DecodeValue(document.ValueType(fh.Type), format.Body[fh.Offset:fh.Offset+fh.Size])
-		if err != nil {
-			return err
-		}
-
-		i, err := DecodeInt64(fh.Name)
-		if err != nil {
-			return err
-		}
-		err = fn(int(i), v)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// GetByIndex returns a value by index of the array.
-func (e EncodedArray) GetByIndex(i int) (document.Value, error) {
-	return decodeValueFromDocument(e, string(EncodeInt64(int64(i))))
-}
-
-// MarshalJSON implements the json.Marshaler interface.
-func (e EncodedArray) MarshalJSON() ([]byte, error) {
-	var buf bytes.Buffer
-
-	err := document.ArrayToJSON(&buf, e)
-	if err != nil {
-		return nil, err
-	}
-
-	return buf.Bytes(), nil
-}
-
-func decodeValueFromDocument(data []byte, field string) (document.Value, error) {
-	hsize, n := binary.Uvarint(data)
-	if n <= 0 {
-		return document.Value{}, errors.New("cannot decode data")
-	}
-
-	hdata := data[n : n+int(hsize)]
-	body := data[n+len(hdata):]
-
-	// skip number of fields
-	_, n = binary.Uvarint(hdata)
-	if n <= 0 {
-		return document.Value{}, errors.New("cannot decode data")
-	}
-	hdata = hdata[n:]
-
-	var fh FieldHeader
-	for len(hdata) > 0 {
-		n, err := fh.Decode(hdata)
-		if err != nil {
-			return document.Value{}, err
-		}
-		hdata = hdata[n:]
-
-		if field == string(fh.Name) {
-			return DecodeValue(document.ValueType(fh.Type), body[fh.Offset:fh.Offset+fh.Size])
-		}
-	}
-
-	return document.Value{}, document.ErrFieldNotFound
-}
-
-// EncodeArray encodes a into its binary representation.
-func EncodeArray(a document.Array) ([]byte, error) {
-	var format Format
-
-	var offset uint64
-	var dataList [][]byte
-
-	err := a.Iterate(func(i int, v document.Value) error {
-		data, err := EncodeValue(v)
-		if err != nil {
-			return err
-		}
-
-		index := EncodeInt64(int64(i))
-
-		format.Header.FieldHeaders = append(format.Header.FieldHeaders, FieldHeader{
-			NameSize: uint64(len(index)),
-			Name:     index,
-			Type:     uint64(v.Type),
-			Size:     uint64(len(data)),
-			Offset:   offset,
-		})
-
-		offset += uint64(len(data))
-		dataList = append(dataList, data)
-		return nil
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	var buf bytes.Buffer
-	_, err = format.Header.WriteTo(&buf)
-	if err != nil {
-		return nil, err
-	}
-
-	buf.Grow(format.Header.BodySize())
-
-	for _, data := range dataList {
-		_, err = buf.Write(data)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return buf.Bytes(), nil
-}
-
-// DecodeArray takes a byte slice and returns a lazily decoded array.
-// If buf is malformed, an error will be returned when calling one of the array method.
-func DecodeArray(buf []byte) document.Array {
-	return EncodedArray(buf)
-}
-
 // DecodeValue takes some encoded data and decodes it to the target type t.
 func DecodeValue(t document.ValueType, data []byte) (document.Value, error) {
 	switch t {
 	case document.DocumentValue:
-		return document.NewDocumentValue(EncodedDocument(data)), nil
+		return document.NewDocumentValue(msgpack.EncodedDocument(data)), nil
 	case document.ArrayValue:
-		return document.NewArrayValue(EncodedArray(data)), nil
+		return document.NewArrayValue(msgpack.EncodedArray(data)), nil
 	case document.BlobValue:
-		x, err := DecodeBlob(data)
-		if err != nil {
-			return document.Value{}, err
-		}
-		return document.NewBlobValue(x), nil
+		return document.NewBlobValue(data), nil
 	case document.TextValue:
-		x, err := DecodeText(data)
+		x, err := DecodeString(data)
 		if err != nil {
 			return document.Value{}, err
 		}
diff --git a/document/encoding/format.go b/document/encoding/format.go
deleted file mode 100644
index fb7be5cf..00000000
--- a/document/encoding/format.go
+++ /dev/null
@@ -1,225 +0,0 @@
-package encoding
-
-import (
-	"bytes"
-	"encoding/binary"
-	"errors"
-	"io"
-)
-
-// Format is an encoding format used to encode and decode documents.
-// It is composed of a header and a body.
-// The header defines a list of fields, offsets and relevant metadata.
-// The body contains each fields data one concatenated one after another.
-type Format struct {
-	Header Header
-	Body   []byte
-}
-
-// Decode the given data into the format.
-func (f *Format) Decode(data []byte) error {
-	n, err := f.Header.Decode(data)
-	if err != nil {
-		return err
-	}
-
-	f.Body = data[n:]
-	return nil
-}
-
-// A Header contains a representation of a document's metadata.
-type Header struct {
-	// Size of the header
-	Size uint64
-	// Number of fields
-	FieldsCount uint64
-	// List of headers for all the fields.
-	FieldHeaders []FieldHeader
-}
-
-// Decode data into the header.
-func (h *Header) Decode(data []byte) (int, error) {
-	var n int
-
-	h.Size, n = binary.Uvarint(data)
-	if n <= 0 {
-		return 0, errors.New("cannot decode data")
-	}
-
-	hdata := data[n : n+int(h.Size)]
-	read := n + int(h.Size)
-
-	h.FieldsCount, n = binary.Uvarint(hdata)
-	if n <= 0 {
-		return 0, errors.New("cannot decode data")
-	}
-	hdata = hdata[n:]
-
-	h.FieldHeaders = make([]FieldHeader, 0, int(h.FieldsCount))
-	for len(hdata) > 0 {
-		var fh FieldHeader
-		n, err := fh.Decode(hdata)
-		if err != nil {
-			return 0, err
-		}
-		hdata = hdata[n:]
-
-		h.FieldHeaders = append(h.FieldHeaders, fh)
-	}
-
-	return read, nil
-}
-
-// BodySize returns the size of the body.
-func (h *Header) BodySize() int {
-	var size uint64
-
-	for _, fh := range h.FieldHeaders {
-		size += fh.Size
-	}
-
-	return int(size)
-}
-
-// WriteTo encodes the header into w.
-func (h *Header) WriteTo(w io.Writer) (int64, error) {
-	intBuf := make([]byte, binary.MaxVarintLen64)
-	var buf bytes.Buffer
-
-	// number of fields
-	h.FieldsCount = uint64(len(h.FieldHeaders))
-	n := binary.PutUvarint(intBuf, h.FieldsCount)
-	_, err := buf.Write(intBuf[:n])
-	if err != nil {
-		return 0, err
-	}
-
-	for _, fh := range h.FieldHeaders {
-		_, err := fh.WriteTo(&buf)
-		if err != nil {
-			return 0, err
-		}
-	}
-
-	// header size
-	h.Size = uint64(buf.Len())
-	n = binary.PutUvarint(intBuf, h.Size)
-	_, err = w.Write(intBuf[:n])
-	if err != nil {
-		return 0, err
-	}
-
-	return io.Copy(w, &buf)
-}
-
-// FieldHeader represents the metadata of a field.
-type FieldHeader struct {
-	// Size of the name of the field
-	NameSize uint64
-	// Name of the field
-	Name []byte
-	// Type of the field, corresponds to the Type
-	Type uint64
-	// Size of the data of the field
-	Size uint64
-	// Offset describing where the field is located, starting
-	// from the end of the format header.
-	Offset uint64
-
-	NameString string // used for encoding
-	buf        [binary.MaxVarintLen64]byte
-}
-
-// Decode the data into the field header.
-func (f *FieldHeader) Decode(data []byte) (int, error) {
-	var n, read int
-
-	// name size
-	f.NameSize, n = binary.Uvarint(data)
-	if n <= 0 {
-		return 0, errors.New("cannot decode data")
-	}
-	data = data[n:]
-	read += n
-
-	// name
-	f.Name = data[:f.NameSize]
-	data = data[f.NameSize:]
-	read += int(f.NameSize)
-
-	// type
-	f.Type, n = binary.Uvarint(data)
-	if n <= 0 {
-		return 0, errors.New("cannot decode data")
-	}
-	data = data[n:]
-	read += n
-
-	// size
-	f.Size, n = binary.Uvarint(data)
-	if n <= 0 {
-		return 0, errors.New("cannot decode data")
-	}
-	data = data[n:]
-	read += n
-
-	// offset
-	f.Offset, n = binary.Uvarint(data)
-	if n <= 0 {
-		return 0, errors.New("cannot decode data")
-	}
-	data = data[n:]
-	read += n
-
-	return read, nil
-}
-
-// WriteTo encodes the field header into w.
-func (f *FieldHeader) WriteTo(w io.Writer) (int64, error) {
-	var written int
-
-	// name size
-	n := binary.PutUvarint(f.buf[:], f.NameSize)
-	_, err := w.Write(f.buf[:n])
-	if err != nil {
-		return 0, err
-	}
-	written += n
-
-	// name
-	if buf, ok := w.(*bytes.Buffer); ok && f.NameString != "" {
-		n, err = buf.WriteString(f.NameString)
-	} else {
-		n, err = w.Write(f.Name)
-	}
-	if err != nil {
-		return 0, err
-	}
-	written += n
-
-	// type
-	n = binary.PutUvarint(f.buf[:], f.Type)
-	_, err = w.Write(f.buf[:n])
-	if err != nil {
-		return 0, err
-	}
-	written += n
-
-	// size
-	n = binary.PutUvarint(f.buf[:], f.Size)
-	_, err = w.Write(f.buf[:n])
-	if err != nil {
-		return 0, err
-	}
-	written += n
-
-	// offset
-	n = binary.PutUvarint(f.buf[:], f.Offset)
-	_, err = w.Write(f.buf[:n])
-	if err != nil {
-		return 0, err
-	}
-	written += n
-
-	return int64(written), nil
-}
diff --git a/document/encoding/msgpack/codec.go b/document/encoding/msgpack/codec.go
new file mode 100644
index 00000000..e6e7a7a6
--- /dev/null
+++ b/document/encoding/msgpack/codec.go
@@ -0,0 +1,355 @@
+package msgpack
+
+import (
+	"fmt"
+	"io"
+	"time"
+
+	"github.com/genjidb/genji/document"
+	"github.com/vmihailenco/msgpack/v5"
+	"github.com/vmihailenco/msgpack/v5/codes"
+)
+
+// List of custom types
+const (
+	DurationType int8 = 0x1
+)
+
+// Encoder encodes Genji documents and values
+// in MessagePack.
+type Encoder struct {
+	enc *msgpack.Encoder
+}
+
+// NewEncoder creates an Encoder that writes in the given writer.
+func NewEncoder(w io.Writer) *Encoder {
+	enc := msgpack.GetEncoder()
+	enc.Reset(w)
+	enc.UseCompactInts(true)
+
+	return &Encoder{
+		enc: enc,
+	}
+}
+
+// EncodeDocument encodes d as a MessagePack map.
+func (e *Encoder) EncodeDocument(d document.Document) error {
+	var dlen int
+	var err error
+
+	fb, ok := d.(*document.FieldBuffer)
+	if ok {
+		dlen = fb.Len()
+	} else {
+		dlen, err = document.Length(d)
+		if err != nil {
+			return err
+		}
+	}
+
+	if err := e.enc.EncodeMapLen(dlen); err != nil {
+		return err
+	}
+
+	return d.Iterate(func(f string, v document.Value) error {
+		if err := e.enc.EncodeString(f); err != nil {
+			return err
+		}
+
+		return e.EncodeValue(v)
+	})
+}
+
+// EncodeArray encodes a as a MessagePack array.
+func (e *Encoder) EncodeArray(a document.Array) error {
+	var alen int
+	var err error
+
+	vb, ok := a.(document.ValueBuffer)
+	if ok {
+		alen = len(vb)
+	} else {
+		alen, err = document.ArrayLength(a)
+		if err != nil {
+			return err
+		}
+	}
+
+	if err := e.enc.EncodeArrayLen(alen); err != nil {
+		return err
+	}
+
+	return a.Iterate(func(i int, v document.Value) error {
+		return e.EncodeValue(v)
+	})
+}
+
+// EncodeValue encodes v based on its type.
+// - document -> map
+// - array -> array
+// - NULL -> nil
+// - text -> string
+// - blob -> bytes
+// - bool -> bool
+// - int8 -> int8
+// - int16 -> int16
+// - int32 -> int32
+// - int64 -> int64
+// - float64 -> float64
+// - duration -> custom type with code 0x1 and size 8
+func (e *Encoder) EncodeValue(v document.Value) error {
+	switch v.Type {
+	case document.DocumentValue:
+		d, err := v.ConvertToDocument()
+		if err != nil {
+			return err
+		}
+		return e.EncodeDocument(d)
+	case document.ArrayValue:
+		a, err := v.ConvertToArray()
+		if err != nil {
+			return err
+		}
+		return e.EncodeArray(a)
+	case document.NullValue:
+		return e.enc.EncodeNil()
+	case document.TextValue:
+		s, err := v.ConvertToText()
+		if err != nil {
+			return err
+		}
+		return e.enc.EncodeString(s)
+	case document.BlobValue:
+		b, err := v.ConvertToBlob()
+		if err != nil {
+			return err
+		}
+		return e.enc.EncodeBytes(b)
+	case document.BoolValue:
+		return e.enc.EncodeBool(v.V.(bool))
+	case document.Int8Value:
+		return e.enc.EncodeInt8(v.V.(int8))
+	case document.Int16Value:
+		return e.enc.EncodeInt16(v.V.(int16))
+	case document.Int32Value:
+		return e.enc.EncodeInt32(v.V.(int32))
+	case document.Int64Value:
+		return e.enc.EncodeInt64(v.V.(int64))
+	case document.Float64Value:
+		return e.enc.EncodeFloat64(v.V.(float64))
+	case document.DurationValue:
+		// because messagepack doesn't have a duration type
+		// vmihailenco/msgpack EncodeDuration method
+		// encodes durations as int64 values.
+		// this means that the duration is lost during
+		// encoding and there is no way of knowing that
+		// an int64 is a duration during decoding.
+		// to avoid that, we create a custom duration type.
+		err := e.enc.EncodeExtHeader(DurationType, 8)
+		if err != nil {
+			return err
+		}
+
+		d := uint64(v.V.(time.Duration))
+		var buf [8]byte
+		buf[0] = byte(d >> 56)
+		buf[1] = byte(d >> 48)
+		buf[2] = byte(d >> 40)
+		buf[3] = byte(d >> 32)
+		buf[4] = byte(d >> 24)
+		buf[5] = byte(d >> 16)
+		buf[6] = byte(d >> 8)
+		buf[7] = byte(d)
+
+		_, err = e.enc.Writer().Write(buf[:])
+		return err
+	}
+
+	return e.enc.Encode(v.V)
+}
+
+// Close puts the encoder into the pool for reuse.
+func (e *Encoder) Close() {
+	msgpack.PutEncoder(e.enc)
+}
+
+// Decoder decodes Genji documents and values
+// from MessagePack.
+type Decoder struct {
+	dec *msgpack.Decoder
+}
+
+// NewDecoder creates a Decoder that reads from the given reader.
+func NewDecoder(r io.Reader) *Decoder {
+	dec := msgpack.GetDecoder()
+	dec.Reset(r)
+
+	return &Decoder{
+		dec: dec,
+	}
+}
+
+// DecodeValue reads one value from the reader and decodes it.
+func (d *Decoder) DecodeValue() (v document.Value, err error) {
+	c, err := d.dec.PeekCode()
+	if err != nil {
+		return
+	}
+
+	// decode array
+	if (codes.IsFixedArray(c)) || (c == codes.Array16) || (c == codes.Array32) {
+		var a document.Array
+		a, err = d.DecodeArray()
+		if err != nil {
+			return
+		}
+
+		v = document.NewArrayValue(a)
+		return
+	}
+
+	// decode document
+	if (codes.IsFixedMap(c)) || (c == codes.Map16) || (c == codes.Map32) {
+		var doc document.Document
+		doc, err = d.DecodeDocument()
+		if err != nil {
+			return
+		}
+
+		v = document.NewDocumentValue(doc)
+		return
+	}
+
+	// decode string
+	if codes.IsString(c) {
+		var s string
+		s, err = d.dec.DecodeString()
+		if err != nil {
+			return
+		}
+		v.V = []byte(s)
+		v.Type = document.TextValue
+		return
+	}
+
+	// decode custom codes
+	if codes.IsExt(c) {
+		var tp int8
+		tp, _, err = d.dec.DecodeExtHeader()
+		if err != nil {
+			return
+		}
+
+		if tp != DurationType {
+			panic(fmt.Sprintf("unknown custom code %d", tp))
+		}
+
+		var buf [8]byte
+		err = d.dec.ReadFull(buf[:])
+		if err != nil {
+			return
+		}
+		n := (uint64(buf[0]) << 56) |
+			(uint64(buf[1]) << 48) |
+			(uint64(buf[2]) << 40) |
+			(uint64(buf[3]) << 32) |
+			(uint64(buf[4]) << 24) |
+			(uint64(buf[5]) << 16) |
+			(uint64(buf[6]) << 8) |
+			uint64(buf[7])
+		v.V = time.Duration(n)
+		v.Type = document.DurationValue
+		return
+	}
+
+	// decode the rest
+	switch c {
+	case codes.Nil:
+		err = d.dec.DecodeNil()
+		if err != nil {
+			return
+		}
+		v.Type = document.NullValue
+		return
+	case codes.Bin8, codes.Bin16, codes.Bin32:
+		v.V, err = d.dec.DecodeBytes()
+		if err != nil {
+			return
+		}
+		v.Type = document.BlobValue
+		return
+	case codes.True, codes.False:
+		v.V, err = d.dec.DecodeBool()
+		if err != nil {
+			return
+		}
+		v.Type = document.BoolValue
+		return
+	case codes.Int8:
+		v.V, err = d.dec.DecodeInt8()
+		if err != nil {
+			return
+		}
+		v.Type = document.Int8Value
+		return
+	case codes.Int16:
+		v.V, err = d.dec.DecodeInt16()
+		if err != nil {
+			return
+		}
+		v.Type = document.Int16Value
+		return
+	case codes.Int32:
+		v.V, err = d.dec.DecodeInt32()
+		if err != nil {
+			return
+		}
+		v.Type = document.Int32Value
+		return
+	case codes.Int64:
+		v.V, err = d.dec.DecodeInt64()
+		if err != nil {
+			return
+		}
+		v.Type = document.Int64Value
+		return
+	case codes.Double:
+		v.V, err = d.dec.DecodeFloat64()
+		if err != nil {
+			return
+		}
+		v.Type = document.Float64Value
+		return
+	}
+
+	panic("unsupported type")
+}
+
+// DecodeDocument decodes one document from the reader.
+// If the document is malformed, it will not return an error.
+// However, calls to Iterate or GetByField will fail.
+func (d *Decoder) DecodeDocument() (document.Document, error) {
+	r, err := d.dec.DecodeRaw()
+	if err != nil {
+		return nil, err
+	}
+
+	return EncodedDocument(r), nil
+}
+
+// DecodeArray decodes one array from the reader.
+// If the array is malformed, this function will not return an error.
+// However, calls to Iterate or GetByIndex will fail.
+func (d *Decoder) DecodeArray() (document.Array, error) {
+	r, err := d.dec.DecodeRaw()
+	if err != nil {
+		return nil, err
+	}
+
+	return EncodedArray(r), nil
+}
+
+// Close puts the decoder into the pool for reuse.
+func (d *Decoder) Close() {
+	msgpack.PutDecoder(d.dec)
+}
diff --git a/document/encoding/msgpack/encoding.go b/document/encoding/msgpack/encoding.go
new file mode 100644
index 00000000..e3d4ebbd
--- /dev/null
+++ b/document/encoding/msgpack/encoding.go
@@ -0,0 +1,306 @@
+package msgpack
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+
+	"github.com/genjidb/genji/document"
+	"github.com/vmihailenco/msgpack/v5"
+	"github.com/vmihailenco/msgpack/v5/codes"
+)
+
+// EncodeDocument takes a document and encodes it
+// in MessagePack.
+func EncodeDocument(d document.Document) ([]byte, error) {
+	if ec, ok := d.(EncodedDocument); ok {
+		return ec, nil
+	}
+
+	var buf bytes.Buffer
+	enc := NewEncoder(&buf)
+	defer enc.Close()
+
+	err := enc.EncodeDocument(d)
+	if err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+// DecodeDocument takes a byte slice and returns a lazily decoded document.
+// If buf is malformed, an error will be returned when calling one of the document method.
+func DecodeDocument(buf []byte) document.Document {
+	return EncodedDocument(buf)
+}
+
+// EncodeValue encodes any value to MessagePack.
+func EncodeValue(w io.Writer, v document.Value) error {
+	enc := NewEncoder(w)
+	defer enc.Close()
+
+	return enc.EncodeValue(v)
+}
+
+// An EncodedDocument implements the document.Document
+// interface on top of an encoded representation of a
+// document.
+// It is useful for avoiding decoding the entire document when
+// only a few fields are needed.
+type EncodedDocument []byte
+
+// bytesLen determines the size of the next string in the decoder
+// based on c.
+// It is originally copied from https://github.com/vmihailenco/msgpack/blob/e7759683b74a27e455669b525427cfd9aec0790e/decode_string.go#L10:19
+// then adapted to our needs.
+func bytesLen(c codes.Code, dec *msgpack.Decoder) (int, error) {
+	if c == codes.Nil {
+		return -1, nil
+	}
+
+	if codes.IsFixedString(c) {
+		return int(c & codes.FixedStrMask), nil
+	}
+
+	switch c {
+	case codes.Str8, codes.Bin8:
+		var b [1]byte
+		err := dec.ReadFull(b[:])
+		return int(b[0]), err
+	case codes.Str16, codes.Bin16:
+		var b [2]byte
+		err := dec.ReadFull(b[:])
+		return int((uint16(b[0]) << 8) | uint16(b[1])), err
+	case codes.Str32, codes.Bin32:
+		var b [4]byte
+		err := dec.ReadFull(b[:])
+		n := (uint32(b[0]) << 24) |
+			(uint32(b[1]) << 16) |
+			(uint32(b[2]) << 8) |
+			uint32(b[3])
+		return int(n), err
+	}
+
+	return 0, fmt.Errorf("msgpack: invalid code=%x decoding bytes length", c)
+}
+
+// GetByField decodes the selected field from the buffer.
+func (e EncodedDocument) GetByField(field string) (v document.Value, err error) {
+	dec := NewDecoder(bytes.NewReader(e))
+	defer dec.Close()
+
+	l, err := dec.dec.DecodeMapLen()
+	if err != nil {
+		return
+	}
+
+	bf := []byte(field)
+
+	buf := make([]byte, 32)
+
+	var c codes.Code
+	var n int
+	for i := 0; i < l; i++ {
+		// this loop does basically two things:
+		// - decode the field name
+		// - decode the value
+		// We don't use dec.dec.DecodeString() here
+		// because it allocates a new string at every call
+		// which is not memory efficient.
+		// Since we only want to compare the field name with
+		// the one received in parameter, we will decode
+		// the field name ourselves and reuse the buffer
+		// everytime.
+
+		// get the type code from the decoder.
+		// PeekCode doesn't move the cursor
+		c, err = dec.dec.PeekCode()
+		if err != nil {
+			return
+		}
+
+		// Move the cursor by one to skip the type code
+		err = dec.dec.ReadFull(buf[:1])
+		if err != nil {
+			return
+		}
+
+		// determine the string length
+		n, err = bytesLen(c, dec.dec)
+		if err != nil {
+			return
+		}
+
+		// ensure the buffer is big enough to hold the string
+		if len(buf) < n {
+			buf = make([]byte, n)
+		}
+
+		// copy the field name into the buffer
+		err = dec.dec.ReadFull(buf[:n])
+		if err != nil {
+			return
+		}
+
+		// if the field name is the one we are
+		// looking for, decode the next value
+		if bytes.Equal(buf[:n], bf) {
+			return dec.DecodeValue()
+		}
+
+		// if not, we skip the next value
+		err = dec.dec.Skip()
+		if err != nil {
+			return
+		}
+	}
+
+	err = document.ErrFieldNotFound
+	return
+}
+
+// Iterate decodes each fields one by one and passes them to fn
+// until the end of the document or until fn returns an error.
+func (e EncodedDocument) Iterate(fn func(field string, value document.Value) error) error {
+	dec := NewDecoder(bytes.NewReader(e))
+	defer dec.Close()
+
+	l, err := dec.dec.DecodeMapLen()
+	if err != nil {
+		return err
+	}
+
+	for i := 0; i < l; i++ {
+		f, err := dec.dec.DecodeString()
+		if err != nil {
+			return err
+		}
+
+		v, err := dec.DecodeValue()
+		if err != nil {
+			return err
+		}
+
+		err = fn(f, v)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (e EncodedDocument) MarshalJSON() ([]byte, error) {
+	var buf bytes.Buffer
+
+	err := document.ToJSON(&buf, e)
+	if err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+// An EncodedArray implements the document.Array interface on top of an
+// encoded representation of an array.
+// It is useful for avoiding decoding the entire array when
+// only a few values are needed.
+type EncodedArray []byte
+
+// Iterate goes through all the values of the array and calls the
+// given function by passing each one of them.
+// If the given function returns an error, the iteration stops.
+func (e EncodedArray) Iterate(fn func(i int, value document.Value) error) error {
+	dec := NewDecoder(bytes.NewReader(e))
+	defer dec.Close()
+
+	l, err := dec.dec.DecodeArrayLen()
+	if err != nil {
+		return err
+	}
+
+	for i := 0; i < l; i++ {
+		v, err := dec.DecodeValue()
+		if err != nil {
+			return err
+		}
+
+		err = fn(i, v)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// GetByIndex returns a value by index of the array.
+func (e EncodedArray) GetByIndex(idx int) (v document.Value, err error) {
+	dec := NewDecoder(bytes.NewReader(e))
+	defer dec.Close()
+
+	l, err := dec.dec.DecodeArrayLen()
+	if err != nil {
+		return
+	}
+
+	for i := 0; i < l; i++ {
+		if i == idx {
+			return dec.DecodeValue()
+		}
+
+		err = dec.dec.Skip()
+		if err != nil {
+			return
+		}
+	}
+
+	err = document.ErrValueNotFound
+	return
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (e EncodedArray) MarshalJSON() ([]byte, error) {
+	var buf bytes.Buffer
+
+	err := document.ArrayToJSON(&buf, e)
+	if err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+// EncodeArray encodes a document.Array into its binary representation.
+func EncodeArray(a document.Array) ([]byte, error) {
+	if ea, ok := a.(EncodedArray); ok {
+		return ea, nil
+	}
+
+	var buf bytes.Buffer
+	enc := NewEncoder(&buf)
+	defer enc.Close()
+
+	err := enc.EncodeArray(a)
+	if err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+// DecodeArray takes a byte slice and returns a lazily decoded array.
+// If buf is malformed, an error will be returned when calling one of the array method.
+func DecodeArray(buf []byte) document.Array {
+	return EncodedArray(buf)
+}
+
+// DecodeValue takes some encoded data and decodes it into its concrete value.
+func DecodeValue(data []byte) (document.Value, error) {
+	dec := NewDecoder(bytes.NewReader(data))
+	defer dec.Close()
+
+	return dec.DecodeValue()
+}
diff --git a/go.mod b/go.mod
index 72c240d9..a60560a2 100644
--- a/go.mod
+++ b/go.mod
@@ -5,5 +5,6 @@ go 1.14
 require (
 	github.com/dgraph-io/badger/v2 v2.0.3
 	github.com/stretchr/testify v1.5.1
+	github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1
 	go.etcd.io/bbolt v1.3.4
 )
diff --git a/go.sum b/go.sum
index d734364e..47c5481f 100644
--- a/go.sum
+++ b/go.sum
@@ -24,6 +24,8 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@@ -57,20 +59,33 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
+github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1 h1:d71/KA0LhvkrJ/Ok+Wx9qK7bU8meKA1Hk0jpVI5kJjk=
+github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1/go.mod h1:xlngVLeyQ/Qi05oQxhQ+oTuqa03RjMwMfk/7/TCs+QI=
+github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY=
+github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
 go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
diff --git a/index/index.go b/index/index.go
index 0d3cc4c8..d2c1a9ef 100644
--- a/index/index.go
+++ b/index/index.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/genjidb/genji/document"
 	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 )
 
@@ -156,9 +157,9 @@ func decodeIndexValueToField(t Type, data []byte) (document.Value, error) {
 		b, err := encoding.DecodeBool(data)
 		return document.NewBoolValue(b), err
 	case Array:
-		return document.NewArrayValue(encoding.DecodeArray(data)), nil
+		return document.NewArrayValue(msgpack.DecodeArray(data)), nil
 	case Document:
-		return document.NewDocumentValue(encoding.DecodeDocument(data)), nil
+		return document.NewDocumentValue(msgpack.DecodeDocument(data)), nil
 	}
 
 	return document.Value{}, fmt.Errorf("unknown index type %d", t)
diff --git a/sql/planner/replacement.go b/sql/planner/replacement.go
index 3409bb1a..b0d0b178 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"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/sql/query"
 	"github.com/genjidb/genji/sql/query/expr"
@@ -134,7 +134,7 @@ func (u *resumableIterator) Iterate(fn func(d document.Document) error) error {
 }
 
 type encodedDocumentWithKey struct {
-	encoding.EncodedDocument
+	msgpack.EncodedDocument
 
 	key []byte
 }
diff --git a/sql/planner/sort.go b/sql/planner/sort.go
index 9b3ba15f..43559700 100644
--- a/sql/planner/sort.go
+++ b/sql/planner/sort.go
@@ -6,7 +6,7 @@ import (
 
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/index"
 	"github.com/genjidb/genji/pkg/bytesutil"
 	"github.com/genjidb/genji/sql/query/expr"
@@ -73,7 +73,7 @@ func (it *sortIterator) Iterate(fn func(d document.Document) error) error {
 	}
 
 	for h.Len() > 0 {
-		err := fn(encoding.EncodedDocument(heap.Pop(h).(heapNode).data))
+		err := fn(msgpack.EncodedDocument(heap.Pop(h).(heapNode).data))
 		if err != nil {
 			return err
 		}
@@ -129,7 +129,7 @@ func (it *sortIterator) sortStream(st document.Stream) (heap.Interface, error) {
 		// representing the type of the value.
 		value = append([]byte{byte(index.NewTypeFromValueType(v.Type))}, value...)
 
-		data, err := encoding.EncodeDocument(d)
+		data, err := msgpack.EncodeDocument(d)
 		if err != nil {
 			return err
 		}
diff --git a/sql/query/expr/comparison.go b/sql/query/expr/comparison.go
index 586135ba..a77070f5 100644
--- a/sql/query/expr/comparison.go
+++ b/sql/query/expr/comparison.go
@@ -8,6 +8,7 @@ import (
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
 	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/index"
 	"github.com/genjidb/genji/pkg/bytesutil"
@@ -80,7 +81,7 @@ func (op eqOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 
 		return err
 	}
-	return fn(encoding.EncodedDocument(val))
+	return fn(msgpack.EncodedDocument(val))
 }
 
 func (op eqOp) String() string {
@@ -141,7 +142,7 @@ func (op gtOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 		return err
 	}
 
-	var d encoding.EncodedDocument
+	var d msgpack.EncodedDocument
 
 	data, err := encoding.EncodeValue(v)
 	if err != nil {
@@ -205,7 +206,7 @@ func (op gteOp) IteratePK(tb *database.Table, v document.Value, pkType document.
 		return err
 	}
 
-	var d encoding.EncodedDocument
+	var d msgpack.EncodedDocument
 
 	data, err := encoding.EncodeValue(v)
 	if err != nil {
@@ -275,7 +276,7 @@ func (op ltOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 		return err
 	}
 
-	var d encoding.EncodedDocument
+	var d msgpack.EncodedDocument
 
 	data, err := encoding.EncodeValue(v)
 	if err != nil {
@@ -348,7 +349,7 @@ func (op lteOp) IteratePK(tb *database.Table, v document.Value, pkType document.
 		return err
 	}
 
-	var d encoding.EncodedDocument
+	var d msgpack.EncodedDocument
 
 	data, err := encoding.EncodeValue(v)
 	if err != nil {
@@ -512,7 +513,7 @@ func (op inOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 		return errors.New("IN operator takes an array")
 	}
 
-	var d encoding.EncodedDocument
+	var d msgpack.EncodedDocument
 	arr, err := v.ConvertToArray()
 	if err != nil {
 		return err
diff --git a/sql/query/plan.go b/sql/query/plan.go
index 3e8af8c9..dd1f4996 100644
--- a/sql/query/plan.go
+++ b/sql/query/plan.go
@@ -6,7 +6,7 @@ import (
 
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/index"
 	"github.com/genjidb/genji/pkg/bytesutil"
@@ -368,7 +368,7 @@ func (it pkIterator) Iterate(fn func(d document.Document) error) error {
 	if it.e == nil {
 		var err error
 
-		var d encoding.EncodedDocument
+		var d msgpack.EncodedDocument
 		it := it.tb.Store.NewIterator(engine.IteratorConfig{Reverse: it.orderByDirection == scanner.DESC})
 		defer func() {
 			it.Close()
@@ -436,7 +436,7 @@ func (qo *queryOptimizer) sortIterator(it document.Iterator) (st document.Stream
 			return err
 		}
 
-		data, err := encoding.EncodeDocument(d)
+		data, err := msgpack.EncodeDocument(d)
 		if err != nil {
 			return err
 		}
@@ -465,7 +465,7 @@ type sortedIterator struct {
 func (s *sortedIterator) Iterate(fn func(d document.Document) error) error {
 	i := 0
 	for s.h.Len() > 0 && (s.k == 0 || i < s.k) {
-		err := fn(encoding.EncodedDocument(heap.Pop(s.h).(heapNode).data))
+		err := fn(msgpack.EncodedDocument(heap.Pop(s.h).(heapNode).data))
 		if err != nil {
 			return err
 		}


Test patch:
diff --git a/database/table_test.go b/database/table_test.go
index 4997e6ad..42291da5 100644
--- a/database/table_test.go
+++ b/database/table_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
 	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine/memoryengine"
 	"github.com/stretchr/testify/require"
 )
@@ -197,7 +198,7 @@ func TestTableInsert(t *testing.T) {
 
 		manualInsert := func(id int64) {
 			docid := encoding.EncodeInt64(id)
-			v, err := encoding.EncodeDocument(newDocument())
+			v, err := msgpack.EncodeDocument(newDocument())
 			require.NoError(t, err)
 			err = tb.Store.Put(docid, v)
 			require.NoError(t, err)
diff --git a/document/document_test.go b/document/document_test.go
index 29991677..627a9b66 100644
--- a/document/document_test.go
+++ b/document/document_test.go
@@ -6,7 +6,7 @@ import (
 	"testing"
 
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/stretchr/testify/require"
 )
 
@@ -587,14 +587,14 @@ func BenchmarkDocumentIterate(b *testing.B) {
 
 	b.Run("Encoding/Implementation", func(b *testing.B) {
 		for i := 0; i < b.N; i++ {
-			encoding.EncodeDocument(&f)
+			msgpack.EncodeDocument(&f)
 		}
 	})
 
 	b.Run("Encoding/Reflection", func(b *testing.B) {
 		for i := 0; i < b.N; i++ {
 			refd, _ := document.NewFromStruct(&f)
-			encoding.EncodeDocument(refd)
+			msgpack.EncodeDocument(refd)
 		}
 	})
 
diff --git a/document/encoding/encoding_test.go b/document/encoding/encoding_test.go
index 1d6d2b5c..880f83de 100644
--- a/document/encoding/encoding_test.go
+++ b/document/encoding/encoding_test.go
@@ -1,250 +1,12 @@
 package encoding
 
 import (
-	"bytes"
-	"fmt"
 	"testing"
-	"time"
 
-	"github.com/genjidb/genji/document"
 	"github.com/genjidb/genji/pkg/bytesutil"
 	"github.com/stretchr/testify/require"
 )
 
-func TestDecodeValueFromDocument(t *testing.T) {
-	doc := document.NewFieldBuffer().
-		Add("age", document.NewInt64Value(10)).
-		Add("address", document.NewNullValue()).
-		Add("name", document.NewTextValue("john")).
-		Add("d", document.NewDurationValue(10*time.Nanosecond))
-
-	data, err := EncodeDocument(doc)
-	require.NoError(t, err)
-
-	v, err := decodeValueFromDocument(data, "age")
-	require.NoError(t, err)
-	require.Equal(t, document.NewInt64Value(10), v)
-
-	v, err = decodeValueFromDocument(data, "address")
-	require.NoError(t, err)
-	require.Equal(t, document.NewNullValue(), v)
-
-	v, err = decodeValueFromDocument(data, "name")
-	require.NoError(t, err)
-	require.Equal(t, document.NewTextValue("john"), v)
-
-	v, err = decodeValueFromDocument(data, "d")
-	require.NoError(t, err)
-	require.Equal(t, document.NewDurationValue(10*time.Nanosecond), v)
-}
-
-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.NewInt64Value(10)).
-				Add("name", document.NewTextValue("john")),
-			`{"age": 10, "name": "john"}`,
-		},
-		{
-			"Map",
-			userMapDoc,
-			`{"age": 10, "name": "john"}`,
-		},
-		{
-			"Nested Document",
-			document.NewFieldBuffer().
-				Add("age", document.NewInt64Value(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.NewInt64Value(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.NewInt64Value(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.NewInt64Value(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)
-}
-
-func TestEncodeArray(t *testing.T) {
-	mapDoc, err := document.NewFromMap(map[string]string{
-		"city":    "Ajaccio",
-		"country": "France",
-	})
-	require.NoError(t, err)
-
-	tests := []struct {
-		name     string
-		a        document.Array
-		expected string
-	}{
-		{
-			"Complex array",
-			document.NewValueBuffer().
-				Append(document.NewInt64Value(10)).
-				Append(document.NewTextValue("john")).
-				Append(document.NewDocumentValue(mapDoc)).
-				Append(document.NewArrayValue(document.NewValueBuffer().Append(document.NewInt64Value(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.NewInt64Value(i))
-	}
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		EncodeDocument(&buf)
-	}
-}
-
-func BenchmarkFormatDecode(b *testing.B) {
-	var buf document.FieldBuffer
-
-	for i := int64(0); i < 100; i++ {
-		buf.Add(fmt.Sprintf("name-%d", i), document.NewInt64Value(i))
-	}
-
-	data, err := EncodeDocument(&buf)
-	require.NoError(b, err)
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		var f Format
-		f.Decode(data)
-	}
-}
-
-func BenchmarkGetByField(b *testing.B) {
-	var buf document.FieldBuffer
-
-	for i := int64(0); i < 100; i++ {
-		buf.Add(fmt.Sprintf("name-%d", i), document.NewInt64Value(i))
-	}
-
-	data, err := EncodeDocument(&buf)
-	require.NoError(b, err)
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		DecodeDocument(data).GetByField("name-99")
-	}
-}
-
-func BenchmarkDecodeDocument(b *testing.B) {
-	var buf document.FieldBuffer
-
-	for i := int64(0); i < 100; i++ {
-		buf.Add(fmt.Sprintf("name-%d", i), document.NewInt64Value(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 TestValueEncodeDecode(t *testing.T) {
 	tests := []struct {
 		name     string
@@ -252,8 +14,7 @@ func TestValueEncodeDecode(t *testing.T) {
 		enc      func() []byte
 		dec      func([]byte) (interface{}, error)
 	}{
-		{"bytes", []byte("foo"), func() []byte { return EncodeBlob([]byte("foo")) }, func(buf []byte) (interface{}, error) { return DecodeBlob(buf) }},
-		{"string", "bar", func() []byte { return EncodeText("bar") }, func(buf []byte) (interface{}, error) { return DecodeText(buf) }},
+		{"string", "bar", func() []byte { return EncodeString("bar") }, func(buf []byte) (interface{}, error) { return DecodeString(buf) }},
 		{"bool", true, func() []byte { return EncodeBool(true) }, func(buf []byte) (interface{}, error) { return DecodeBool(buf) }},
 		{"uint", uint(10), func() []byte { return EncodeUint(10) }, func(buf []byte) (interface{}, error) { return DecodeUint(buf) }},
 		{"uint8", uint8(10), func() []byte { return EncodeUint8(10) }, func(buf []byte) (interface{}, error) { return DecodeUint8(buf) }},
diff --git a/document/encoding/msgpack/encoding_test.go b/document/encoding/msgpack/encoding_test.go
new file mode 100644
index 00000000..efbad9f4
--- /dev/null
+++ b/document/encoding/msgpack/encoding_test.go
@@ -0,0 +1,300 @@
+package msgpack
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/genjidb/genji/document"
+	"github.com/stretchr/testify/require"
+)
+
+func TestGetByField(t *testing.T) {
+	doc := document.NewFieldBuffer().
+		Add("age", document.NewInt64Value(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.NewInt64Value(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.NewInt64Value(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.NewInt64Value(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.NewInt64Value(10)).
+				Add("name", document.NewTextValue("john")),
+			`{"age": 10, "name": "john"}`,
+		},
+		{
+			"Map",
+			userMapDoc,
+			`{"age": 10, "name": "john"}`,
+		},
+		{
+			"Nested Document",
+			document.NewFieldBuffer().
+				Add("age", document.NewInt64Value(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.NewInt64Value(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.NewInt64Value(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.NewInt64Value(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)
+}
+
+func TestEncodeArray(t *testing.T) {
+	mapDoc, err := document.NewFromMap(map[string]string{
+		"city":    "Ajaccio",
+		"country": "France",
+	})
+	require.NoError(t, err)
+
+	tests := []struct {
+		name     string
+		a        document.Array
+		expected string
+	}{
+		{
+			"Complex array",
+			document.NewValueBuffer().
+				Append(document.NewInt64Value(10)).
+				Append(document.NewTextValue("john")).
+				Append(document.NewDocumentValue(mapDoc)).
+				Append(document.NewArrayValue(document.NewValueBuffer().Append(document.NewInt64Value(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.NewInt64Value(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.NewInt64Value(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.NewInt64Value(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)
+	}
+}