Repository: genjidb/genji
Pull request #145 (2020-08-06 21:40:05)

Remove convert methods
This PR removes the document.Convert* methods and replaces them by calls to Cast methods. It also removes the plan.go file which is useless since it was replaced by the `planner` package. 


Patch:
diff --git a/database/config.go b/database/config.go
index 40297b37..8dab1817 100644
--- a/database/config.go
+++ b/database/config.go
@@ -48,27 +48,21 @@ func (f *FieldConstraint) ScanDocument(d document.Document) error {
 	if err != nil {
 		return err
 	}
-	tp, err := v.ConvertToInt64()
-	if err != nil {
-		return err
-	}
+	tp := v.V.(int64)
 	f.Type = document.ValueType(tp)
 
 	v, err = d.GetByField("is_primary_key")
 	if err != nil {
 		return err
 	}
-	f.IsPrimaryKey, err = v.ConvertToBool()
-	if err != nil {
-		return err
-	}
+	f.IsPrimaryKey = v.V.(bool)
 
 	v, err = d.GetByField("is_not_null")
 	if err != nil {
 		return err
 	}
-	f.IsNotNull, err = v.ConvertToBool()
-	return err
+	f.IsNotNull = v.V.(bool)
+	return nil
 }
 
 type TableInfo struct {
@@ -112,20 +106,13 @@ func (ti *TableInfo) ScanDocument(d document.Document) error {
 	if err != nil {
 		return err
 	}
-	b, err := v.ConvertToBytes()
-	if err != nil {
-		return err
-	}
-	copy(ti.storeID[:], b)
+	copy(ti.storeID[:], v.V.([]byte))
 
 	v, err = d.GetByField("field_constraints")
 	if err != nil {
 		return err
 	}
-	ar, err := v.ConvertToArray()
-	if err != nil {
-		return err
-	}
+	ar := v.V.(document.Array)
 
 	l, err := document.ArrayLength(ar)
 	if err != nil {
@@ -135,11 +122,7 @@ func (ti *TableInfo) ScanDocument(d document.Document) error {
 	ti.FieldConstraints = make([]FieldConstraint, l)
 
 	return ar.Iterate(func(i int, value document.Value) error {
-		doc, err := value.ConvertToDocument()
-		if err != nil {
-			return err
-		}
-		return ti.FieldConstraints[i].ScanDocument(doc)
+		return ti.FieldConstraints[i].ScanDocument(v.V.(document.Document))
 	})
 }
 
@@ -335,28 +318,19 @@ func (i *IndexConfig) ScanDocument(d document.Document) error {
 	if err != nil {
 		return err
 	}
-	i.Unique, err = v.ConvertToBool()
-	if err != nil {
-		return err
-	}
+	i.Unique = v.V.(bool)
 
 	v, err = d.GetByField("indexname")
 	if err != nil {
 		return err
 	}
-	i.IndexName, err = v.ConvertToString()
-	if err != nil {
-		return err
-	}
+	i.IndexName = string(v.V.([]byte))
 
 	v, err = d.GetByField("tablename")
 	if err != nil {
 		return err
 	}
-	i.TableName, err = v.ConvertToString()
-	if err != nil {
-		return err
-	}
+	i.TableName = string(v.V.([]byte))
 
 	v, err = d.GetByField("path")
 	if err != nil {
@@ -455,20 +429,10 @@ func (t *indexStore) ListAll() ([]*IndexConfig, error) {
 }
 
 func arrayToValuePath(v document.Value) (document.ValuePath, error) {
-	ar, err := v.ConvertToArray()
-	if err != nil {
-		return nil, err
-	}
-
 	var path document.ValuePath
 
-	err = ar.Iterate(func(_ int, value document.Value) error {
-		p, err := value.ConvertToString()
-		if err != nil {
-			return err
-		}
-
-		path = append(path, p)
+	err := v.V.(document.Array).Iterate(func(_ int, value document.Value) error {
+		path = append(path, string(value.V.([]byte)))
 		return nil
 	})
 
diff --git a/database/table.go b/database/table.go
index 280b9f8b..8b57d92a 100644
--- a/database/table.go
+++ b/database/table.go
@@ -321,7 +321,7 @@ func validateConstraint(d document.Document, c *FieldConstraint) error {
 			return nil
 		}
 
-		v, err = v.ConvertTo(c.Type)
+		v, err = v.CastAs(c.Type)
 		if err != nil {
 			return err
 		}
@@ -361,7 +361,7 @@ func validateConstraint(d document.Document, c *FieldConstraint) error {
 			return nil
 		}
 
-		v, err = v.ConvertTo(c.Type)
+		v, err = v.CastAs(c.Type)
 		if err != nil {
 			return err
 		}
@@ -555,12 +555,7 @@ func (t *Table) Indexes() (map[string]Index, error) {
 				return false, err
 			}
 
-			b, err := v.ConvertToBytes()
-			if err != nil {
-				return false, err
-			}
-
-			return bytes.Equal(b, tableName), nil
+			return bytes.Equal(v.V.([]byte), tableName), nil
 		}).
 		Iterate(func(d document.Document) error {
 			var opts IndexConfig
diff --git a/document/cast.go b/document/cast.go
index 2b136f25..ba843fe6 100644
--- a/document/cast.go
+++ b/document/cast.go
@@ -37,7 +37,7 @@ func (v Value) CastAs(t ValueType) (Value, error) {
 		return v.CastAsDocument()
 	}
 
-	return Value{}, fmt.Errorf("cannot cast %q as %q", v.Type, t)
+	return Value{}, fmt.Errorf("cannot cast %s as %q", v.Type, t)
 }
 
 // CastAsBool casts according to the following rules:
@@ -54,17 +54,18 @@ func (v Value) CastAsBool() (Value, error) {
 	case TextValue:
 		b, err := strconv.ParseBool(string(v.V.([]byte)))
 		if err != nil {
-			return Value{}, err
+			return Value{}, fmt.Errorf(`cannot cast %q as bool: %w`, v.V, err)
 		}
 		return NewBoolValue(b), nil
 	}
 
-	return Value{}, fmt.Errorf("cannot cast %q as bool", v.Type)
+	return Value{}, fmt.Errorf("cannot cast %s as bool", v.Type)
 }
 
 // CastAsInteger casts according to the following rules:
 // Bool: returns 1 if true, 0 if false.
 // Double: cuts off the decimal and remaining numbers.
+// Duration: returns the number of nanoseconds in the duration.
 // Text: uses strconv.ParseFloat to determine the double value,
 // then casts it to an integer. It fails if the text doesn't
 // contain a valid float value.
@@ -80,15 +81,17 @@ func (v Value) CastAsInteger() (Value, error) {
 		return NewIntegerValue(0), nil
 	case DoubleValue:
 		return NewIntegerValue(int64(v.V.(float64))), nil
+	case DurationValue:
+		return NewIntegerValue(int64(v.V.(time.Duration))), nil
 	case TextValue:
 		f, err := strconv.ParseFloat(string(v.V.([]byte)), 64)
 		if err != nil {
-			return Value{}, err
+			return Value{}, fmt.Errorf(`cannot cast %q as integer: %w`, v.V, err)
 		}
 		return NewIntegerValue(int64(f)), nil
 	}
 
-	return Value{}, fmt.Errorf("cannot cast %q as integer", v.Type)
+	return Value{}, fmt.Errorf("cannot cast %s as integer", v.Type)
 }
 
 // CastAsDouble casts according to the following rules:
@@ -105,12 +108,12 @@ func (v Value) CastAsDouble() (Value, error) {
 	case TextValue:
 		f, err := strconv.ParseFloat(string(v.V.([]byte)), 64)
 		if err != nil {
-			return Value{}, err
+			return Value{}, fmt.Errorf(`cannot cast %q as double: %w`, v.V, err)
 		}
 		return NewDoubleValue(f), nil
 	}
 
-	return Value{}, fmt.Errorf("cannot cast %q as double", v.Type)
+	return Value{}, fmt.Errorf("cannot cast %s as double", v.Type)
 }
 
 // CastAsDuration casts according to the following rules:
@@ -124,13 +127,13 @@ func (v Value) CastAsDuration() (Value, error) {
 	if v.Type == TextValue {
 		d, err := time.ParseDuration(string(v.V.([]byte)))
 		if err != nil {
-			return Value{}, err
+			return Value{}, fmt.Errorf(`cannot cast %q as duration: %w`, v.V, err)
 		}
 
 		return NewDurationValue(d), nil
 	}
 
-	return Value{}, fmt.Errorf("cannot cast %q as duration", v.Type)
+	return Value{}, fmt.Errorf("cannot cast %s as duration", v.Type)
 }
 
 // CastAsText returns a JSON representation of v.
@@ -168,13 +171,13 @@ func (v Value) CastAsBlob() (Value, error) {
 	if v.Type == TextValue {
 		b, err := base64.StdEncoding.DecodeString(string(v.V.([]byte)))
 		if err != nil {
-			return Value{}, err
+			return Value{}, fmt.Errorf(`cannot cast %q as blob: %w`, v.V, err)
 		}
 
 		return NewBlobValue(b), nil
 	}
 
-	return Value{}, fmt.Errorf("cannot cast %q as blob", v.Type)
+	return Value{}, fmt.Errorf("cannot cast %s as blob", v.Type)
 }
 
 // CastAsArray casts according to the following rules:
@@ -189,13 +192,13 @@ func (v Value) CastAsArray() (Value, error) {
 		var vb ValueBuffer
 		err := vb.UnmarshalJSON([]byte(string(v.V.([]byte))))
 		if err != nil {
-			return Value{}, err
+			return Value{}, fmt.Errorf(`cannot cast %q as array: %w`, v.V, err)
 		}
 
 		return NewArrayValue(vb), nil
 	}
 
-	return Value{}, fmt.Errorf("cannot cast %q as array", v.Type)
+	return Value{}, fmt.Errorf("cannot cast %s as array", v.Type)
 }
 
 // CastAsDocument casts according to the following rules:
@@ -210,11 +213,11 @@ func (v Value) CastAsDocument() (Value, error) {
 		var fb FieldBuffer
 		err := fb.UnmarshalJSON([]byte(string(v.V.([]byte))))
 		if err != nil {
-			return Value{}, err
+			return Value{}, fmt.Errorf(`cannot cast %q as document: %w`, v.V, err)
 		}
 
 		return NewDocumentValue(&fb), nil
 	}
 
-	return Value{}, fmt.Errorf("cannot cast %q as document", v.Type)
+	return Value{}, fmt.Errorf("cannot cast %s as document", v.Type)
 }
diff --git a/document/compare.go b/document/compare.go
index 4eb4c1f1..882dd357 100644
--- a/document/compare.go
+++ b/document/compare.go
@@ -174,17 +174,19 @@ func compareBytes(op operator, l, r Value) (bool, error) {
 }
 
 func compareIntegers(op operator, l, r Value) (bool, error) {
-	// integer OP integer
-	ai, err := l.ConvertToInt64()
+	l, err := l.CastAsInteger()
 	if err != nil {
 		return false, err
 	}
-
-	bi, err := r.ConvertToInt64()
+	r, err = r.CastAsInteger()
 	if err != nil {
 		return false, err
 	}
 
+	// integer OP integer
+	ai := l.V.(int64)
+	bi := r.V.(int64)
+
 	var ok bool
 
 	switch op {
@@ -204,16 +206,18 @@ func compareIntegers(op operator, l, r Value) (bool, error) {
 }
 
 func compareNumbers(op operator, l, r Value) (bool, error) {
-	af, err := l.ConvertToFloat64()
+	l, err := l.CastAsDouble()
 	if err != nil {
 		return false, err
 	}
-
-	bf, err := r.ConvertToFloat64()
+	r, err = r.CastAsDouble()
 	if err != nil {
 		return false, err
 	}
 
+	af := l.V.(float64)
+	bf := r.V.(float64)
+
 	var ok bool
 
 	switch op {
@@ -239,18 +243,11 @@ func compareDocuments(op operator, l, r Value) (bool, error) {
 		return false, nil
 	}
 
-	ld, err := l.ConvertToDocument()
-	if err != nil {
-		return false, err
-	}
-
-	rd, err := r.ConvertToDocument()
-	if err != nil {
-		return false, err
-	}
+	ld := l.V.(Document)
+	rd := r.V.(Document)
 
 	var lsize, rsize int
-	err = ld.Iterate(func(field string, lv Value) error {
+	err := ld.Iterate(func(field string, lv Value) error {
 		lsize++
 		return nil
 	})
@@ -302,15 +299,8 @@ func compareDocuments(op operator, l, r Value) (bool, error) {
 }
 
 func compareArrays(op operator, l, r Value) (bool, error) {
-	la, err := l.ConvertToArray()
-	if err != nil {
-		return false, err
-	}
-
-	ra, err := r.ConvertToArray()
-	if err != nil {
-		return false, err
-	}
+	la := l.V.(Array)
+	ra := r.V.(Array)
 
 	var i, j int
 
diff --git a/document/document.go b/document/document.go
index 7ecd1820..a85279ab 100644
--- a/document/document.go
+++ b/document/document.go
@@ -244,20 +244,9 @@ func (p ValuePath) getValueFromValue(v Value) (Value, error) {
 			return v, nil
 		}
 
-		d, err := v.ConvertToDocument()
-		if err != nil {
-			return Value{}, err
-		}
-
-		return p[1:].getValueFromDocument(d)
+		return p[1:].getValueFromDocument(v.V.(Document))
 	case ArrayValue:
-
-		a, err := v.ConvertToArray()
-		if err != nil {
-			return Value{}, err
-		}
-
-		return p[1:].getValueFromArray(a)
+		return p[1:].getValueFromArray(v.V.(Array))
 	}
 
 	return Value{}, ErrFieldNotFound
diff --git a/document/encoding/encoding.go b/document/encoding/encoding.go
index 2257e550..ad684dbe 100644
--- a/document/encoding/encoding.go
+++ b/document/encoding/encoding.go
@@ -208,35 +208,15 @@ func DecodeFloat64(buf []byte) (float64, error) {
 func EncodeValue(v document.Value) ([]byte, error) {
 	switch v.Type {
 	case document.DocumentValue:
-		d, err := v.ConvertToDocument()
-		if err != nil {
-			return nil, err
-		}
-		return msgpack.EncodeDocument(d)
+		return msgpack.EncodeDocument(v.V.(document.Document))
 	case document.ArrayValue:
-		a, err := v.ConvertToArray()
-		if err != nil {
-			return nil, err
-		}
-		return msgpack.EncodeArray(a)
+		return msgpack.EncodeArray(v.V.(document.Array))
 	case document.BlobValue:
-		x, err := v.ConvertToBytes()
-		if err != nil {
-			return nil, err
-		}
-		return x, nil
+		return v.V.([]byte), nil
 	case document.TextValue:
-		x, err := v.ConvertToString()
-		if err != nil {
-			return nil, err
-		}
-		return EncodeString(x), nil
+		return EncodeString(string(v.V.([]byte))), nil
 	case document.BoolValue:
-		x, err := v.ConvertToBool()
-		if err != nil {
-			return nil, err
-		}
-		return EncodeBool(x), nil
+		return EncodeBool(v.V.(bool)), nil
 	case document.IntegerValue:
 		return EncodeInt64(v.V.(int64)), nil
 	case document.DoubleValue:
diff --git a/document/json.go b/document/json.go
index bde40b4c..464bf4da 100644
--- a/document/json.go
+++ b/document/json.go
@@ -9,7 +9,7 @@ import (
 	"fmt"
 	"io"
 	"strconv"
-	"strings"
+	"time"
 )
 
 // NewFromJSON creates a document from a JSON object.
@@ -28,29 +28,13 @@ func (v Value) MarshalJSON() ([]byte, error) {
 
 	switch v.Type {
 	case DocumentValue:
-		d, err := v.ConvertToDocument()
-		if err != nil {
-			return nil, err
-		}
-		return jsonDocument{d}.MarshalJSON()
+		return jsonDocument{v.V.(Document)}.MarshalJSON()
 	case ArrayValue:
-		a, err := v.ConvertToArray()
-		if err != nil {
-			return nil, err
-		}
-		return jsonArray{a}.MarshalJSON()
+		return jsonArray{v.V.(Array)}.MarshalJSON()
 	case TextValue:
-		s, err := v.ConvertToString()
-		if err != nil {
-			return nil, err
-		}
-		x = s
+		x = string(v.V.([]byte))
 	case DurationValue:
-		d, err := v.ConvertToDuration()
-		if err != nil {
-			return nil, err
-		}
-		x = d.String()
+		x = v.V.(time.Duration).String()
 	default:
 		x = v.V
 	}
@@ -376,50 +360,3 @@ func IteratorToJSONArray(w io.Writer, s Iterator) error {
 	buf.WriteByte(']')
 	return buf.Flush()
 }
-
-// Compare compares two values performing best-effort comparisons
-// Returns > 0 if this value can be considered bigger
-// Returns < 0 if this value can be considered smaller
-// Returns 0 if values can be considered equal
-func (v Value) Compare(u Value) int {
-	if v.Type == NullValue && u.Type == NullValue {
-		return 0
-	}
-	// Null is always less than non-null
-	if v.Type == NullValue {
-		return -1
-	}
-	if u.Type == NullValue {
-		return 1
-	}
-
-	un := v.Type.IsNumber() || v.Type == BoolValue
-	vn := u.Type.IsNumber() || u.Type == BoolValue
-
-	// if any of the values is a number, perform a best effort numeric comparison
-	if un || vn {
-		var vf float64
-		var uf float64
-		if un {
-			vf, _ = v.ConvertToFloat64()
-		} else {
-			vf, _ = strconv.ParseFloat(v.String(), 64)
-		}
-		if vn {
-			uf, _ = u.ConvertToFloat64()
-		} else {
-			uf, _ = strconv.ParseFloat(u.String(), 64)
-		}
-		return int(vf - uf)
-	}
-
-	// compare byte arrays and strings
-	if (v.Type == TextValue || v.Type == BlobValue) && (u.Type == TextValue || u.Type == BlobValue) {
-		bv, _ := v.ConvertToBytes()
-		bu, _ := u.ConvertToBytes()
-		return bytes.Compare(bv, bu)
-	}
-
-	// if all else fails, compare string representation of values
-	return strings.Compare(v.String(), u.String())
-}
diff --git a/document/scan.go b/document/scan.go
index 95b7189c..30856dbb 100644
--- a/document/scan.go
+++ b/document/scan.go
@@ -245,72 +245,72 @@ func scanValue(v Value, ref reflect.Value) error {
 
 	switch ref.Kind() {
 	case reflect.String:
-		x, err := v.ConvertToString()
+		v, err := v.CastAsText()
 		if err != nil {
 			return err
 		}
-		ref.SetString(x)
+		ref.SetString(string(v.V.([]byte)))
 		return nil
 	case reflect.Bool:
-		x, err := v.ConvertToBool()
+		v, err := v.CastAsBool()
 		if err != nil {
 			return err
 		}
-		ref.SetBool(x)
+		ref.SetBool(v.V.(bool))
 		return nil
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
-		x, err := v.ConvertToInt64()
+		v, err := v.CastAsInteger()
 		if err != nil {
 			return err
 		}
+		x := v.V.(int64)
 		if x < 0 {
 			return fmt.Errorf("cannot convert value %d into Go value of type %s", x, ref.Type().Name())
 		}
 		ref.SetUint(uint64(x))
 		return nil
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		x, err := v.ConvertToInt64()
+		v, err := v.CastAsInteger()
 		if err != nil {
 			return err
 		}
-		ref.SetInt(x)
+		ref.SetInt(v.V.(int64))
 		return nil
 	case reflect.Float32, reflect.Float64:
-		x, err := v.ConvertToFloat64()
+		v, err := v.CastAsDouble()
 		if err != nil {
 			return err
 		}
-		ref.SetFloat(x)
+		ref.SetFloat(v.V.(float64))
 		return nil
 	case reflect.Struct:
-		d, err := v.ConvertToDocument()
+		v, err := v.CastAsDocument()
 		if err != nil {
 			return err
 		}
 
-		return structScan(d, ref)
+		return structScan(v.V.(Document), ref)
 	case reflect.Slice:
 		if ref.Type().Elem().Kind() == reflect.Uint8 {
-			x, err := v.ConvertToBytes()
-			if err != nil {
-				return err
+			if v.Type != TextValue && v.Type != BlobValue {
+				return fmt.Errorf("cannot scan value of type %s to byte slice", v.Type)
 			}
-			ref.SetBytes(x)
+			ref.SetBytes(v.V.([]byte))
 			return nil
 		}
-		a, err := v.ConvertToArray()
+		v, err := v.CastAsArray()
 		if err != nil {
 			return err
 		}
 
-		return sliceScan(a, ref.Addr())
+		return sliceScan(v.V.(Array), ref.Addr())
 	case reflect.Map:
-		d, err := v.ConvertToDocument()
+		v, err := v.CastAsDocument()
 		if err != nil {
 			return err
 		}
 
-		return mapScan(d, ref)
+		return mapScan(v.V.(Document), ref)
 	case reflect.Interface:
 		ref.Set(reflect.ValueOf(v.V))
 		return nil
diff --git a/document/value.go b/document/value.go
index 2b17f064..6455d57f 100644
--- a/document/value.go
+++ b/document/value.go
@@ -215,210 +215,6 @@ func (v Value) IsTruthy() (bool, error) {
 	return !b, err
 }
 
-// ConvertTo decodes v to the selected type when possible.
-func (v Value) ConvertTo(t ValueType) (Value, error) {
-	if v.Type == t {
-		return v, nil
-	}
-
-	// Null values always remain null.
-	if v.Type == NullValue {
-		return v, nil
-	}
-
-	switch t {
-	case BoolValue:
-		x, err := v.ConvertToBool()
-		if err != nil {
-			return Value{}, err
-		}
-		return NewBoolValue(x), nil
-	case IntegerValue:
-		x, err := v.ConvertToInt64()
-		if err != nil {
-			return Value{}, fmt.Errorf(`cannot convert %q to "integer": %w`, v.Type, err)
-		}
-		return NewIntegerValue(x), nil
-	case DoubleValue:
-		x, err := v.ConvertToFloat64()
-		if err != nil {
-			return Value{}, err
-		}
-		return NewDoubleValue(x), nil
-	case DurationValue:
-		x, err := v.ConvertToDuration()
-		if err != nil {
-			return Value{}, err
-		}
-		return NewDurationValue(x), nil
-	case BlobValue:
-		x, err := v.ConvertToBytes()
-		if err != nil {
-			return Value{}, err
-		}
-		return NewBlobValue(x), nil
-	case TextValue:
-		x, err := v.ConvertToString()
-		if err != nil {
-			return Value{}, err
-		}
-		return NewTextValue(x), nil
-	}
-
-	return Value{}, fmt.Errorf("cannot convert %q to %q", v.Type, t)
-}
-
-// ConvertToBytes converts a value of type Text or Blob to a slice of bytes.
-// If fails if it's used with any other type.
-func (v Value) ConvertToBytes() ([]byte, error) {
-	switch v.Type {
-	case TextValue, BlobValue:
-		return v.V.([]byte), nil
-	}
-
-	if v.Type == NullValue {
-		return nil, nil
-	}
-
-	return nil, fmt.Errorf(`cannot convert %q to "bytes"`, v.Type)
-}
-
-// ConvertToString turns a value of type Text or Blob into a string.
-// If fails if it's used with any other type.
-func (v Value) ConvertToString() (string, error) {
-	switch v.Type {
-	case TextValue, BlobValue:
-		return string(v.V.([]byte)), nil
-	}
-
-	if v.Type == NullValue {
-		return "", nil
-	}
-
-	return "", fmt.Errorf(`cannot convert %q to "string"`, v.Type)
-}
-
-// ConvertToBool returns true if v is truthy, otherwise it returns false.
-func (v Value) ConvertToBool() (bool, error) {
-	if v.Type == BoolValue {
-		return v.V.(bool), nil
-	}
-
-	if v.Type == NullValue {
-		return false, nil
-	}
-
-	b, err := v.IsZeroValue()
-	return !b, err
-}
-
-// ConvertToInt64 turns any number into an int64.
-// It doesn't work with other types.
-func (v Value) ConvertToInt64() (int64, error) {
-	if v.Type == IntegerValue {
-		return v.V.(int64), nil
-	}
-
-	if v.Type == NullValue {
-		return 0, nil
-	}
-
-	if v.Type.IsNumber() {
-		return convertNumberToInt64(v)
-	}
-
-	if v.Type == BoolValue {
-		if v.V.(bool) {
-			return 1, nil
-		}
-
-		return 0, nil
-	}
-
-	return 0, fmt.Errorf(`type %q incompatible with "integer"`, v.Type)
-}
-
-// ConvertToFloat64 turns any number into a float64.
-// It doesn't work with other types.
-func (v Value) ConvertToFloat64() (float64, error) {
-	if v.Type == DoubleValue {
-		return v.V.(float64), nil
-	}
-
-	if v.Type == NullValue {
-		return 0, nil
-	}
-
-	if v.Type.IsInteger() {
-		x, err := convertNumberToInt64(v)
-		if err != nil {
-			return 0, err
-		}
-		return float64(x), nil
-	}
-
-	if v.Type == BoolValue {
-		if v.V.(bool) {
-			return 1, nil
-		}
-
-		return 0, nil
-	}
-
-	return 0, fmt.Errorf(`cannot convert %q to "double"`, v.Type)
-}
-
-// ConvertToDocument returns a document from the value.
-// It only works if the type of v is DocumentValue or NullValue.
-func (v Value) ConvertToDocument() (Document, error) {
-	if v.Type == NullValue {
-		return NewFieldBuffer(), nil
-	}
-
-	if v.Type != DocumentValue {
-		return nil, fmt.Errorf(`cannot convert %q to "document"`, v.Type)
-	}
-
-	return v.V.(Document), nil
-}
-
-// ConvertToArray returns an array from the value.
-// It only works if the type of v is ArrayValue or NullValue.
-func (v Value) ConvertToArray() (Array, error) {
-	if v.Type == NullValue {
-		return NewValueBuffer(), nil
-	}
-
-	if v.Type != ArrayValue {
-		return nil, fmt.Errorf(`cannot convert %q to "array"`, v.Type)
-	}
-
-	return v.V.(Array), nil
-}
-
-// ConvertToDuration turns any number into a time.Duration.
-// It doesn't work with other types.
-func (v Value) ConvertToDuration() (time.Duration, error) {
-	if v.Type == DurationValue {
-		return v.V.(time.Duration), nil
-	}
-
-	if v.Type == NullValue {
-		return 0, nil
-	}
-
-	if v.Type == TextValue {
-		d, err := time.ParseDuration(string(v.V.([]byte)))
-		if err != nil {
-			return 0, fmt.Errorf(`cannot convert %q to "duration": %v`, v.V, err)
-		}
-		return d, nil
-	}
-
-	x, err := v.ConvertToInt64()
-	return time.Duration(x), err
-}
-
 // IsZeroValue indicates if the value data is the zero value for the value type.
 // This function doesn't perform any allocation.
 func (v Value) IsZeroValue() (bool, error) {
@@ -523,14 +319,14 @@ func calculateValues(a, b Value, operator byte) (res Value, err error) {
 	}
 
 	if a.Type == BoolValue {
-		a, err = a.ConvertTo(IntegerValue)
+		a, err = a.CastAs(IntegerValue)
 		if err != nil {
 			return
 		}
 	}
 
 	if b.Type == BoolValue {
-		b, err = b.ConvertTo(IntegerValue)
+		b, err = b.CastAs(IntegerValue)
 		if err != nil {
 			return
 		}
@@ -542,18 +338,20 @@ func calculateValues(a, b Value, operator byte) (res Value, err error) {
 			return
 		}
 		if operator != '&' && operator != '|' && operator != '^' {
-			return res.ConvertTo(DurationValue)
+			return NewDurationValue(time.Duration(res.V.(int64))), nil
 		}
 
 		return
 	}
 
-	if a.Type.IsFloat() || b.Type.IsFloat() {
-		return calculateFloats(a, b, operator)
-	}
+	if a.Type.IsNumber() && b.Type.IsNumber() {
+		if a.Type.IsFloat() || b.Type.IsFloat() {
+			return calculateFloats(a, b, operator)
+		}
 
-	if a.Type.IsInteger() || b.Type.IsInteger() {
-		return calculateIntegers(a, b, operator)
+		if a.Type.IsInteger() || b.Type.IsInteger() {
+			return calculateIntegers(a, b, operator)
+		}
 	}
 
 	return NewNullValue(), nil
@@ -584,15 +382,17 @@ func convertNumberToInt64(v Value) (int64, error) {
 func calculateIntegers(a, b Value, operator byte) (res Value, err error) {
 	var xa, xb int64
 
-	xa, err = a.ConvertToInt64()
+	ia, err := a.CastAsInteger()
 	if err != nil {
 		return NewNullValue(), nil
 	}
+	xa = ia.V.(int64)
 
-	xb, err = b.ConvertToInt64()
+	ib, err := b.CastAsInteger()
 	if err != nil {
 		return NewNullValue(), nil
 	}
+	xb = ib.V.(int64)
 
 	var xr int64
 
@@ -649,15 +449,17 @@ func calculateIntegers(a, b Value, operator byte) (res Value, err error) {
 func calculateFloats(a, b Value, operator byte) (res Value, err error) {
 	var xa, xb float64
 
-	xa, err = a.ConvertToFloat64()
+	fa, err := a.CastAsDouble()
 	if err != nil {
 		return NewNullValue(), nil
 	}
+	xa = fa.V.(float64)
 
-	xb, err = b.ConvertToFloat64()
+	fb, err := b.CastAsDouble()
 	if err != nil {
 		return NewNullValue(), nil
 	}
+	xb = fb.V.(float64)
 
 	switch operator {
 	case '+':
diff --git a/index/index.go b/index/index.go
index 5e7c8514..1ed2f95d 100644
--- a/index/index.go
+++ b/index/index.go
@@ -133,12 +133,12 @@ func EmptyPivot(t document.ValueType) *Pivot {
 // a way that can be compared for ordering and indexing
 func EncodeFieldToIndexValue(val document.Value) ([]byte, error) {
 	if val.V != nil && val.Type.IsNumber() && val.Type != document.DoubleValue {
-		x, err := val.ConvertToFloat64()
+		v, err := val.CastAsDouble()
 		if err != nil {
 			return nil, err
 		}
 
-		return encoding.EncodeFloat64(x), nil
+		return encoding.EncodeFloat64(v.V.(float64)), nil
 	}
 
 	return encoding.EncodeValue(val)
diff --git a/sql/parser/select.go b/sql/parser/select.go
index dcf3c62c..0133797d 100644
--- a/sql/parser/select.go
+++ b/sql/parser/select.go
@@ -213,12 +213,12 @@ func (cfg selectConfig) ToTree() (*planner.Tree, error) {
 			return nil, fmt.Errorf("offset expression must evaluate to a number, got %q", v.Type)
 		}
 
-		offset, err := v.ConvertToInt64()
+		v, err = v.CastAsInteger()
 		if err != nil {
 			return nil, err
 		}
 
-		t = planner.NewOffsetNode(t, int(offset))
+		t = planner.NewOffsetNode(t, int(v.V.(int64)))
 	}
 
 	if cfg.LimitExpr != nil {
@@ -231,12 +231,12 @@ func (cfg selectConfig) ToTree() (*planner.Tree, error) {
 			return nil, fmt.Errorf("limit expression must evaluate to a number, got %q", v.Type)
 		}
 
-		limit, err := v.ConvertToInt64()
+		v, err = v.CastAsInteger()
 		if err != nil {
 			return nil, err
 		}
 
-		t = planner.NewLimitNode(t, int(limit))
+		t = planner.NewLimitNode(t, int(v.V.(int64)))
 	}
 
 	t = planner.NewProjectionNode(t, cfg.ProjectionExprs, cfg.TableName)
diff --git a/sql/planner/input.go b/sql/planner/input.go
index e79fdae4..9173789a 100644
--- a/sql/planner/input.go
+++ b/sql/planner/input.go
@@ -168,7 +168,7 @@ func (it indexIterator) Iterate(fn func(d document.Document) error) error {
 	}
 
 	if v.Type.IsNumber() {
-		v, err = v.ConvertTo(document.DoubleValue)
+		v, err = v.CastAsDouble()
 		if err != nil {
 			return err
 		}
diff --git a/sql/query/expr/comparison.go b/sql/query/expr/comparison.go
index 6f161fde..67199be7 100644
--- a/sql/query/expr/comparison.go
+++ b/sql/query/expr/comparison.go
@@ -62,7 +62,7 @@ func (op eqOp) IterateIndex(idx index.Index, tb *database.Table, v document.Valu
 }
 
 func (op eqOp) IteratePK(tb *database.Table, v document.Value, pkType document.ValueType, fn func(d document.Document) error) error {
-	v, err := v.ConvertTo(pkType)
+	v, err := v.CastAs(pkType)
 	if err != nil {
 		return nil
 	}
@@ -136,7 +136,7 @@ func (op gtOp) IterateIndex(idx index.Index, tb *database.Table, v document.Valu
 }
 
 func (op gtOp) IteratePK(tb *database.Table, v document.Value, pkType document.ValueType, fn func(d document.Document) error) error {
-	v, err := v.ConvertTo(pkType)
+	v, err := v.CastAs(pkType)
 	if err != nil {
 		return err
 	}
@@ -200,7 +200,7 @@ func (op gteOp) IterateIndex(idx index.Index, tb *database.Table, v document.Val
 }
 
 func (op gteOp) IteratePK(tb *database.Table, v document.Value, pkType document.ValueType, fn func(d document.Document) error) error {
-	v, err := v.ConvertTo(pkType)
+	v, err := v.CastAs(pkType)
 	if err != nil {
 		return err
 	}
@@ -270,7 +270,7 @@ func (op ltOp) IterateIndex(idx index.Index, tb *database.Table, v document.Valu
 }
 
 func (op ltOp) IteratePK(tb *database.Table, v document.Value, pkType document.ValueType, fn func(d document.Document) error) error {
-	v, err := v.ConvertTo(pkType)
+	v, err := v.CastAs(pkType)
 	if err != nil {
 		return err
 	}
@@ -343,7 +343,7 @@ func (op lteOp) IterateIndex(idx index.Index, tb *database.Table, v document.Val
 }
 
 func (op lteOp) IteratePK(tb *database.Table, v document.Value, pkType document.ValueType, fn func(d document.Document) error) error {
-	v, err := v.ConvertTo(pkType)
+	v, err := v.CastAs(pkType)
 	if err != nil {
 		return err
 	}
@@ -473,12 +473,7 @@ func (op inOp) Eval(ctx EvalStack) (document.Value, error) {
 		return falseLitteral, nil
 	}
 
-	arr, err := b.ConvertToArray()
-	if err != nil {
-		return nullLitteral, err
-	}
-
-	ok, err := document.ArrayContains(arr, a)
+	ok, err := document.ArrayContains(b.V.(document.Array), a)
 	if err != nil {
 		return nullLitteral, err
 	}
@@ -494,13 +489,8 @@ func (op inOp) IterateIndex(idx index.Index, tb *database.Table, v document.Valu
 		return errors.New("IN operator takes an array")
 	}
 
-	a, err := v.ConvertToArray()
-	if err != nil {
-		return err
-	}
-
 	var eq eqOp
-	return a.Iterate(func(i int, value document.Value) error {
+	return v.V.(document.Array).Iterate(func(i int, value document.Value) error {
 		return eq.IterateIndex(idx, tb, value, fn)
 	})
 }
@@ -513,13 +503,9 @@ func (op inOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 	}
 
 	var d msgpack.EncodedDocument
-	arr, err := v.ConvertToArray()
-	if err != nil {
-		return err
-	}
 
-	return arr.Iterate(func(i int, value document.Value) error {
-		val, err := value.ConvertTo(pkType)
+	return v.V.(document.Array).Iterate(func(i int, value document.Value) error {
+		val, err := value.CastAs(pkType)
 		if err != nil {
 			return nil
 		}
diff --git a/sql/query/expr/dot_notation.go b/sql/query/expr/dot_notation.go
index 61437b45..0bf1461c 100644
--- a/sql/query/expr/dot_notation.go
+++ b/sql/query/expr/dot_notation.go
@@ -54,9 +54,9 @@ func (f FieldSelector) Eval(stack EvalStack) (document.Value, error) {
 
 		switch v.Type {
 		case document.DocumentValue:
-			stack.Document, err = v.ConvertToDocument()
+			stack.Document = v.V.(document.Document)
 		case document.ArrayValue:
-			a, err = v.ConvertToArray()
+			a = v.V.(document.Array)
 		default:
 			return nullLitteral, nil
 		}
diff --git a/sql/query/insert.go b/sql/query/insert.go
index 7da0bef9..24001136 100644
--- a/sql/query/insert.go
+++ b/sql/query/insert.go
@@ -2,6 +2,7 @@ package query
 
 import (
 	"errors"
+	"fmt"
 
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
@@ -59,12 +60,11 @@ func (stmt InsertStmt) insertDocuments(t *database.Table, stack expr.EvalStack)
 			return res, err
 		}
 
-		d, err := v.ConvertToDocument()
-		if err != nil {
-			return res, err
+		if v.Type != document.DocumentValue {
+			return res, fmt.Errorf("expected document, got %s", v.Type)
 		}
 
-		res.LastInsertKey, err = t.Insert(d)
+		res.LastInsertKey, err = t.Insert(v.V.(document.Document))
 		if err != nil {
 			return res, err
 		}
@@ -89,13 +89,12 @@ func (stmt InsertStmt) insertExprList(t *database.Table, stack expr.EvalStack) (
 
 		// each document must be a list of expressions
 		// (e1, e2, e3, ...) or [e1, e2, e2, ....]
-		vlist, err := v.ConvertToArray()
-		if err != nil {
-			return res, err
+		if v.Type != document.ArrayValue {
+			return res, fmt.Errorf("expected array, got %s", v.Type)
 		}
 
 		// iterate over each value
-		vlist.Iterate(func(i int, v document.Value) error {
+		v.V.(document.Array).Iterate(func(i int, v document.Value) error {
 			// get the field name
 			fieldName := stmt.FieldNames[i]
 
diff --git a/sql/query/plan.go b/sql/query/plan.go
deleted file mode 100644
index 472110e5..00000000
--- a/sql/query/plan.go
+++ /dev/null
@@ -1,507 +0,0 @@
-package query
-
-import (
-	"bytes"
-	"container/heap"
-	"errors"
-
-	"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/sql/query/expr"
-	"github.com/genjidb/genji/sql/scanner"
-)
-
-type queryPlan struct {
-	scanTable bool
-	field     *queryPlanField
-	sorted    bool
-}
-
-type queryPlanField struct {
-	indexedField expr.FieldSelector
-	op           expr.Operator
-	e            expr.Expr
-	uniqueIndex  bool
-	isPrimaryKey bool
-}
-
-func newQueryOptimizer(tx *database.Transaction, tableName string) (qo queryOptimizer, err error) {
-	t, err := tx.GetTable(tableName)
-	if err != nil {
-		return
-	}
-
-	indexes, err := t.Indexes()
-	if err != nil {
-		return
-	}
-
-	info, err := t.Info()
-	if err != nil {
-		return
-	}
-
-	return queryOptimizer{
-		tx:        tx,
-		t:         t,
-		tableName: tableName,
-		info:      info,
-		indexes:   indexes,
-	}, nil
-}
-
-// queryOptimizer is a really dumb query optimizer. gotta start somewhere. please don't be mad at me.
-type queryOptimizer struct {
-	tx               *database.Transaction
-	t                *database.Table
-	tableName        string
-	whereExpr        expr.Expr
-	args             []expr.Param
-	info             *database.TableInfo
-	indexes          map[string]database.Index
-	orderBy          expr.FieldSelector
-	orderByDirection scanner.Token
-	limit            int
-	offset           int
-}
-
-func (qo *queryOptimizer) optimizeQuery() (st document.Stream, err error) {
-	qp := qo.buildQueryPlan()
-
-	switch {
-	case qp.scanTable:
-		st = document.NewStream(qo.t)
-	case qp.field.isPrimaryKey:
-		if qp.field.e == nil {
-			pkit := pkIterator{
-				tx:               qo.tx,
-				tb:               qo.t,
-				info:             qo.info,
-				args:             qo.args,
-				e:                qp.field.e,
-				orderByDirection: qo.orderByDirection,
-			}
-
-			if qp.field.op != nil {
-				pkit.pkop, _ = qp.field.op.(pkIteratorOperator)
-			}
-
-			st = document.NewStream(pkit)
-			break
-		}
-
-		var v document.Value
-		v, err = qp.field.e.Eval(expr.EvalStack{
-			Tx:     qo.tx,
-			Params: qo.args,
-		})
-		if err != nil {
-			return
-		}
-
-		// for all operators except IN, we require the exact same type
-		// otherwise we operate a scan table.
-		if !expr.IsInOperator(qp.field.op) {
-			v, err = v.ConvertTo(qo.info.GetPrimaryKey().Type)
-			if err != nil {
-				err = nil
-				st = document.NewStream(qo.t)
-				break
-			}
-		}
-
-		pkit := pkIterator{
-			tx:               qo.tx,
-			tb:               qo.t,
-			info:             qo.info,
-			args:             qo.args,
-			e:                qp.field.e,
-			orderByDirection: qo.orderByDirection,
-			evalValue:        v,
-		}
-
-		if qp.field.op != nil {
-			pkit.pkop, _ = qp.field.op.(pkIteratorOperator)
-		}
-
-		st = document.NewStream(pkit)
-	default:
-		idxit := indexIterator{
-			tx:               qo.tx,
-			tb:               qo.t,
-			args:             qo.args,
-			e:                qp.field.e,
-			index:            qo.indexes[qp.field.indexedField.Name()],
-			orderByDirection: qo.orderByDirection,
-		}
-
-		if qp.field.op != nil {
-			idxit.iop, _ = qp.field.op.(indexIteratorOperator)
-		}
-
-		st = document.NewStream(idxit)
-	}
-
-	st = st.Filter(whereClause(qo.whereExpr, expr.EvalStack{
-		Tx:     qo.tx,
-		Params: qo.args,
-	}))
-
-	if len(qo.orderBy) != 0 && !qp.sorted {
-		st, err = qo.sortIterator(st)
-	}
-
-	return
-}
-
-func (qo *queryOptimizer) buildQueryPlan() queryPlan {
-	var qp queryPlan
-
-	qp.field = qo.analyseExpr(qo.whereExpr)
-	if qp.field == nil {
-		if len(qo.orderBy) != 0 {
-			_, ok := qo.indexes[qo.orderBy.Name()]
-			pk := qo.info.GetPrimaryKey()
-			if ok || (pk != nil && pk.Path.String() == qo.orderBy.Name()) {
-				qp.field = &queryPlanField{
-					indexedField: qo.orderBy,
-					isPrimaryKey: pk != nil && pk.Path.String() == qo.orderBy.Name(),
-				}
-				qp.sorted = true
-
-				return qp
-			}
-		}
-
-		qp.scanTable = true
-	}
-
-	return qp
-}
-
-type indexIteratorOperator interface {
-	IterateIndex(idx index.Index, tb *database.Table, v document.Value, fn func(d document.Document) error) error
-}
-
-// analyseExpr is a recursive function that scans each node the e Expr tree.
-// If it contains a comparison operator, it checks if this operator and its operands
-// can benefit from using an index. This check is done in the cmpOpCanUseIndex function.
-// If it contains an AND operator it checks if one of the operands can use an index.
-func (qo *queryOptimizer) analyseExpr(e expr.Expr) *queryPlanField {
-	op, ok := e.(expr.Operator)
-	if !ok {
-		return nil
-	}
-
-	if _, ok := op.(indexIteratorOperator); ok {
-		ok, fs, e := cmpOpCanUseIndex(op)
-		if !ok {
-			return nil
-		}
-		ok, e = evaluatesToScalarOrParam(e)
-		if !ok {
-			return nil
-		}
-
-		idx, ok := qo.indexes[fs.Name()]
-		if ok {
-			return &queryPlanField{
-				indexedField: fs,
-				op:           op,
-				e:            e,
-				uniqueIndex:  idx.Opts.Unique,
-			}
-		}
-
-		pk := qo.info.GetPrimaryKey()
-		if pk != nil && pk.Path.String() == fs.Name() {
-			return &queryPlanField{
-				indexedField: fs,
-				op:           op,
-				e:            e,
-				uniqueIndex:  true,
-				isPrimaryKey: true,
-			}
-		}
-
-		return nil
-	}
-
-	if expr.IsAndOperator(op) {
-		nodeL := qo.analyseExpr(op.LeftHand())
-		nodeR := qo.analyseExpr(op.LeftHand())
-
-		if nodeL == nil && nodeR == nil {
-			return nil
-		}
-
-		if nodeL != nil && nodeL.uniqueIndex {
-			return nodeL
-		}
-
-		if nodeR != nil && nodeR.uniqueIndex {
-			return nodeR
-		}
-
-		return nodeL
-	}
-
-	return nil
-}
-
-func cmpOpCanUseIndex(op expr.Operator) (bool, expr.FieldSelector, expr.Expr) {
-	lf, leftIsField := op.LeftHand().(expr.FieldSelector)
-	rf, rightIsField := op.RightHand().(expr.FieldSelector)
-
-	// field OP expr
-	if leftIsField && !rightIsField {
-		return true, lf, op.RightHand()
-	}
-
-	// expr OP field
-	if rightIsField && !leftIsField {
-		return true, rf, op.LeftHand()
-	}
-
-	return false, nil, nil
-}
-
-func evaluatesToScalarOrParam(e expr.Expr) (ok bool, newExpr expr.Expr) {
-	switch e.(type) {
-	case expr.LiteralValue:
-		return true, e
-	case expr.NamedParam, expr.PositionalParam:
-		return true, e
-	case expr.LiteralExprList:
-		v, err := e.Eval(expr.EvalStack{})
-		if err != nil {
-			return false, e
-		}
-
-		a, err := v.ConvertToArray()
-		if err != nil {
-			return false, e
-		}
-
-		return true, expr.ArrayValue(a)
-	}
-
-	return false, e
-}
-
-type indexIterator struct {
-	tx               *database.Transaction
-	tb               *database.Table
-	args             []expr.Param
-	index            index.Index
-	iop              indexIteratorOperator
-	e                expr.Expr
-	orderByDirection scanner.Token
-}
-
-var errStop = errors.New("stop")
-
-func (it indexIterator) Iterate(fn func(d document.Document) error) error {
-	if it.e == nil {
-		var err error
-
-		if it.orderByDirection == scanner.DESC {
-			err = it.index.DescendLessOrEqual(nil, func(val document.Value, key []byte) error {
-				r, err := it.tb.GetDocument(key)
-				if err != nil {
-					return err
-				}
-
-				return fn(r)
-			})
-		} else {
-			err = it.index.AscendGreaterOrEqual(nil, func(val document.Value, key []byte) error {
-				r, err := it.tb.GetDocument(key)
-				if err != nil {
-					return err
-				}
-
-				return fn(r)
-			})
-		}
-
-		return err
-	}
-
-	v, err := it.e.Eval(expr.EvalStack{
-		Tx:     it.tx,
-		Params: it.args,
-	})
-	if err != nil {
-		return err
-	}
-
-	if v.Type.IsNumber() {
-		v, err = v.ConvertTo(document.DoubleValue)
-		if err != nil {
-			return err
-		}
-	}
-
-	return it.iop.IterateIndex(it.index, it.tb, v, fn)
-}
-
-type pkIteratorOperator interface {
-	IteratePK(tb *database.Table, v document.Value, pkType document.ValueType, fn func(d document.Document) error) error
-}
-
-type pkIterator struct {
-	tx               *database.Transaction
-	tb               *database.Table
-	info             *database.TableInfo
-	args             []expr.Param
-	e                expr.Expr
-	pkop             pkIteratorOperator
-	orderByDirection scanner.Token
-	evalValue        document.Value
-}
-
-func (it pkIterator) Iterate(fn func(d document.Document) error) error {
-	if it.e == nil {
-		var err error
-
-		var d msgpack.EncodedDocument
-		it := it.tb.Store.NewIterator(engine.IteratorConfig{Reverse: it.orderByDirection == scanner.DESC})
-		defer func() {
-			it.Close()
-		}()
-
-		for it.Seek(nil); it.Valid(); it.Next() {
-			d, err = it.Item().ValueCopy(d)
-			if err != nil {
-				return err
-			}
-			err = fn(&d)
-			if err != nil {
-				return err
-			}
-		}
-
-		return nil
-	}
-
-	return it.pkop.IteratePK(it.tb, it.evalValue, it.info.GetPrimaryKey().Type, fn)
-}
-
-// sortIterator operates a partial sort on the iterator using a heap.
-// This ensures a O(n+k log n) time complexity
-// with k being the limit of the query, or the sum of the limit + offset, when both offset and limit are used.
-// if there are no limit or offsets, k = n, the number of elements in the table.
-// If the sorting is in ascending order, a min-heap will be used
-// otherwise a max-heap will be used instead.
-// Once the heap is filled entirely with the content of the table a stream is returned.
-// During iteration, the stream will pop the k-smallest or k-largest elements, depending on
-// the chosen sorting order (ASC or DESC).
-// This function is not memory efficient as it's loading the entire table in memory before
-// returning the k-smallest or k-largest elements.
-func (qo *queryOptimizer) sortIterator(it document.Iterator) (st document.Stream, err error) {
-	k := 0
-	if qo.limit != -1 {
-		k += qo.limit
-		if qo.offset != -1 {
-			k += qo.offset
-		}
-	}
-
-	path := document.ValuePath(qo.orderBy)
-
-	var h heap.Interface
-	if qo.orderByDirection == scanner.ASC {
-		h = new(minHeap)
-	} else {
-		h = new(maxHeap)
-	}
-
-	heap.Init(h)
-
-	err = it.Iterate(func(d document.Document) error {
-		v, err := path.GetValue(d)
-		if err != nil && err != document.ErrFieldNotFound {
-			return err
-		}
-		if err == document.ErrFieldNotFound {
-			v = document.NewNullValue()
-		}
-
-		value, err := index.EncodeFieldToIndexValue(v)
-		if err != nil {
-			return err
-		}
-
-		data, err := msgpack.EncodeDocument(d)
-		if err != nil {
-			return err
-		}
-
-		heap.Push(h, heapNode{
-			value: value,
-			data:  data,
-		})
-
-		return nil
-	})
-	if err != nil {
-		return
-	}
-
-	st = document.NewStream(&sortedIterator{h, k})
-
-	return
-}
-
-type sortedIterator struct {
-	h heap.Interface
-	k int
-}
-
-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(msgpack.EncodedDocument(heap.Pop(s.h).(heapNode).data))
-		if err != nil {
-			return err
-		}
-		i++
-	}
-
-	return nil
-}
-
-type heapNode struct {
-	value []byte
-	data  []byte
-}
-
-type minHeap []heapNode
-
-func (h minHeap) Len() int           { return len(h) }
-func (h minHeap) Less(i, j int) bool { return bytes.Compare(h[i].value, h[j].value) < 0 }
-func (h minHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
-
-func (h *minHeap) Push(x interface{}) {
-	*h = append(*h, x.(heapNode))
-}
-
-func (h *minHeap) Pop() interface{} {
-	old := *h
-	n := len(old)
-	x := old[n-1]
-	*h = old[0 : n-1]
-	return x
-}
-
-type maxHeap struct {
-	minHeap
-}
-
-func (h maxHeap) Less(i, j int) bool {
-	return bytes.Compare(h.minHeap[i].value, h.minHeap[j].value) > 0
-}


Test patch:
diff --git a/document/cast_test.go b/document/cast_test.go
index cb253bc5..91f397b3 100644
--- a/document/cast_test.go
+++ b/document/cast_test.go
@@ -61,7 +61,7 @@ func TestCastAs(t *testing.T) {
 			{boolV, NewIntegerValue(1), false},
 			{NewBoolValue(false), NewIntegerValue(0), false},
 			{integerV, integerV, false},
-			{durationV, Value{}, true},
+			{durationV, NewIntegerValue(int64(3 * time.Second)), false},
 			{doubleV, integerV, false},
 			{textV, Value{}, true},
 			{NewTextValue("10"), integerV, false},
diff --git a/document/compare_test.go b/document/compare_test.go
index e958e981..649cdcc3 100644
--- a/document/compare_test.go
+++ b/document/compare_test.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"testing"
-	"time"
 
 	"github.com/genjidb/genji/document"
 	"github.com/stretchr/testify/require"
@@ -16,7 +15,6 @@ var numericFuncs = []struct {
 }{
 	{"integer", func(x interface{}) document.Value { return document.NewIntegerValue(int64(x.(int))) }},
 	{"double", func(x interface{}) document.Value { return document.NewDoubleValue(float64(x.(int))) }},
-	{"duration", func(x interface{}) document.Value { return document.NewDurationValue(time.Duration(int64(x.(int)))) }},
 }
 
 var textFuncs = []struct {
diff --git a/document/document_test.go b/document/document_test.go
index 616490a6..f7abbada 100644
--- a/document/document_test.go
+++ b/document/document_test.go
@@ -448,16 +448,16 @@ func TestNewFromStruct(t *testing.T) {
 
 		v, err = doc.GetByField("o")
 		require.NoError(t, err)
-		d, err := v.ConvertToDocument()
-		require.NoError(t, err)
+		d, ok := v.V.(document.Document)
+		require.True(t, ok)
 		v, err = d.GetByField("a")
 		require.NoError(t, err)
 		require.EqualValues(t, 0, v.V.(int64))
 
 		v, err = doc.GetByField("t")
 		require.NoError(t, err)
-		a, err := v.ConvertToArray()
-		require.NoError(t, err)
+		a, ok := v.V.(document.Array)
+		require.True(t, ok)
 		var count int
 		err = a.Iterate(func(i int, v document.Value) error {
 			count++
diff --git a/document/value_test.go b/document/value_test.go
index 2cb45fe5..dbcfde07 100644
--- a/document/value_test.go
+++ b/document/value_test.go
@@ -1,7 +1,6 @@
 package document_test
 
 import (
-	"fmt"
 	"math"
 	"testing"
 	"time"
@@ -96,272 +95,6 @@ func TestNewValue(t *testing.T) {
 	}
 }
 
-func TestConvertToBlob(t *testing.T) {
-	tests := []struct {
-		name     string
-		v        document.Value
-		fails    bool
-		expected interface{}
-	}{
-		{"null", document.NewNullValue(), false, nil},
-		{"bytes", document.NewBlobValue([]byte("bar")), false, []byte("bar")},
-		{"text", document.NewTextValue("bar"), false, []byte("bar")},
-		{"bool", document.NewBoolValue(true), true, nil},
-		{"integer", document.NewIntegerValue(10), true, nil},
-		{"double", document.NewDoubleValue(10.1), true, nil},
-		{"document", document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewIntegerValue(10))), true, nil},
-		{"array", document.NewArrayValue(document.NewValueBuffer(document.NewIntegerValue(10))), true, nil},
-		{"duration", document.NewDurationValue(10 * time.Nanosecond), true, nil},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			res, err := test.v.ConvertTo(document.BlobValue)
-			if test.fails {
-				require.Error(t, err)
-			} else {
-				require.NoError(t, err)
-				require.Equal(t, test.expected, res.V)
-			}
-		})
-	}
-}
-
-func TestConvertToText(t *testing.T) {
-	tests := []struct {
-		name     string
-		v        document.Value
-		fails    bool
-		expected interface{}
-	}{
-		{"null", document.NewNullValue(), false, nil},
-		{"bytes", document.NewBlobValue([]byte("bar")), false, []byte("bar")},
-		{"text", document.NewTextValue("bar"), false, []byte("bar")},
-		{"bool", document.NewBoolValue(true), true, []byte{}},
-		{"integer", document.NewIntegerValue(10), true, []byte{}},
-		{"double", document.NewDoubleValue(10.1), true, []byte{}},
-		{"document", document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewIntegerValue(10))), true, []byte{}},
-		{"array", document.NewArrayValue(document.NewValueBuffer(document.NewIntegerValue(10))), true, []byte{}},
-		{"duration", document.NewDurationValue(10 * time.Nanosecond), true, []byte{}},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			res, err := test.v.ConvertTo(document.TextValue)
-			if test.fails {
-				require.Error(t, err)
-			} else {
-				require.NoError(t, err)
-				require.Equal(t, test.expected, res.V)
-			}
-		})
-	}
-}
-
-func TestConvertToBool(t *testing.T) {
-	tests := []struct {
-		name     string
-		v        document.Value
-		fails    bool
-		expected interface{}
-	}{
-		{"null", document.NewNullValue(), false, nil},
-		{"bytes", document.NewBlobValue([]byte("bar")), false, true},
-		{"zero bytes", document.NewBlobValue([]byte("")), false, false},
-		{"text", document.NewTextValue("bar"), false, true},
-		{"zero text", document.NewTextValue(""), false, false},
-		{"bool", document.NewBoolValue(true), false, true},
-		{"zero bool", document.NewBoolValue(false), false, false},
-		{"integer", document.NewIntegerValue(10), false, true},
-		{"zero integer", document.NewIntegerValue(0), false, false},
-		{"double", document.NewDoubleValue(10.1), false, true},
-		{"zero double", document.NewDoubleValue(0), false, false},
-		{"document", document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewBoolValue(true))), false, true},
-		{"zero document", document.NewDocumentValue(document.NewFieldBuffer()), false, false},
-		{"array", document.NewArrayValue(document.NewValueBuffer(document.NewIntegerValue(1))), false, true},
-		{"zero array", document.NewArrayValue(document.NewValueBuffer()), false, false},
-		{"duration", document.NewDurationValue(10 * time.Nanosecond), false, true},
-		{"zero duration", document.NewDurationValue(0), false, false},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			res, err := test.v.ConvertTo(document.BoolValue)
-			if test.fails {
-				require.Error(t, err)
-			} else {
-				require.NoError(t, err)
-				require.Equal(t, test.expected, res.V)
-			}
-		})
-	}
-}
-
-func TestConvertToNumber(t *testing.T) {
-	tests := []struct {
-		name     string
-		v        document.Value
-		fails    bool
-		expected interface{}
-	}{
-		{"null", document.NewNullValue(), false, nil},
-		{"bytes", document.NewBlobValue([]byte("bar")), true, 0},
-		{"text", document.NewTextValue("bar"), true, 0},
-		{"bool", document.NewBoolValue(true), false, 1},
-		{"integer", document.NewIntegerValue(10), false, 10},
-		{"double", document.NewDoubleValue(10), false, 10},
-		{"document", document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewIntegerValue(10))), true, 0},
-		{"array", document.NewArrayValue(document.NewValueBuffer(document.NewIntegerValue(10))), true, 0},
-		{"duration", document.NewDurationValue(10 * time.Nanosecond), false, 10},
-	}
-
-	check := func(t *testing.T, res document.Value, err error, fails bool, expected interface{}) {
-		if fails {
-			require.Error(t, err)
-		} else {
-			require.NoError(t, err)
-			require.Equal(t, expected, res.V)
-		}
-	}
-
-	for _, test := range tests {
-		t.Run(test.name+" to integer", func(t *testing.T) {
-			res, err := test.v.ConvertTo(document.IntegerValue)
-			expected := test.expected
-			if expected != nil {
-				expected = int64(expected.(int))
-			}
-			check(t, res, err, test.fails, expected)
-		})
-		t.Run(test.name+" to double", func(t *testing.T) {
-			res, err := test.v.ConvertTo(document.DoubleValue)
-			expected := test.expected
-			if expected != nil {
-				expected = float64(expected.(int))
-			}
-			check(t, res, err, test.fails, expected)
-		})
-	}
-
-	t.Run("double/precision loss", func(t *testing.T) {
-		_, err := document.NewDoubleValue(10.4).ConvertTo(document.IntegerValue)
-		require.Error(t, err)
-		_, err = document.NewDoubleValue(10.4).ConvertTo(document.IntegerValue)
-		require.Error(t, err)
-
-	})
-
-	t.Run("double/overflow", func(t *testing.T) {
-		_, err := document.NewDoubleValue(math.MaxFloat64).ConvertTo(document.IntegerValue)
-		require.Error(t, err)
-	})
-}
-
-func TestConvertToDuration(t *testing.T) {
-	tests := []struct {
-		name     string
-		v        document.Value
-		fails    bool
-		expected interface{}
-	}{
-		{"null", document.NewNullValue(), false, nil},
-		{"bytes", document.NewBlobValue([]byte("bar")), true, 0},
-		{"string", document.NewTextValue("1ms"), false, time.Millisecond},
-		{"bad string", document.NewTextValue("foo"), true, 0},
-		{"bool", document.NewBoolValue(true), false, time.Nanosecond},
-		{"int", document.NewIntegerValue(10), false, 10 * time.Nanosecond},
-		{"int8", document.NewIntegerValue(10), false, 10 * time.Nanosecond},
-		{"int16", document.NewIntegerValue(10), false, 10 * time.Nanosecond},
-		{"int32", document.NewIntegerValue(10), false, 10 * time.Nanosecond},
-		{"int64", document.NewIntegerValue(10), false, 10 * time.Nanosecond},
-		{"double", document.NewDoubleValue(10), false, 10 * time.Nanosecond},
-		{"document", document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewIntegerValue(10))), true, 0},
-		{"array", document.NewArrayValue(document.NewValueBuffer(document.NewIntegerValue(10))), true, 0},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			res, err := test.v.ConvertTo(document.DurationValue)
-			if test.fails {
-				require.Error(t, err)
-			} else {
-				require.NoError(t, err)
-				require.Equal(t, test.expected, res.V)
-			}
-		})
-	}
-}
-
-func TestConvertToDocument(t *testing.T) {
-	tests := []struct {
-		name     string
-		v        document.Value
-		fails    bool
-		expected document.Value
-	}{
-		{"null", document.NewNullValue(), false, document.NewNullValue()},
-		{"document", document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewIntegerValue(10))), false, document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewIntegerValue(10)))},
-		{"bytes", document.NewBlobValue([]byte("bar")), true, document.Value{}},
-		{"string", document.NewTextValue("bar"), true, document.Value{}},
-		{"bool", document.NewBoolValue(true), true, document.Value{}},
-		{"int", document.NewIntegerValue(10), true, document.Value{}},
-		{"int8", document.NewIntegerValue(10), true, document.Value{}},
-		{"int16", document.NewIntegerValue(10), true, document.Value{}},
-		{"int32", document.NewIntegerValue(10), true, document.Value{}},
-		{"int64", document.NewIntegerValue(10), true, document.Value{}},
-		{"double", document.NewDoubleValue(10), true, document.Value{}},
-		{"array", document.NewArrayValue(document.NewValueBuffer(document.NewIntegerValue(10))), true, document.Value{}},
-		{"duration", document.NewDurationValue(10 * time.Nanosecond), true, document.Value{}},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			res, err := test.v.ConvertTo(document.DocumentValue)
-			if test.fails {
-				require.Error(t, err)
-			} else {
-				require.NoError(t, err)
-				require.Equal(t, test.expected, res)
-			}
-		})
-	}
-}
-
-func TestConvertToArray(t *testing.T) {
-	tests := []struct {
-		name     string
-		v        document.Value
-		fails    bool
-		expected document.Array
-	}{
-		{"null", document.NewNullValue(), false, document.NewValueBuffer()},
-		{"array", document.NewArrayValue(document.NewValueBuffer().Append(document.NewIntegerValue(10))), false, document.NewValueBuffer().Append(document.NewIntegerValue(10))},
-		{"bytes", document.NewBlobValue([]byte("bar")), true, nil},
-		{"string", document.NewTextValue("bar"), true, nil},
-		{"bool", document.NewBoolValue(true), true, nil},
-		{"int", document.NewIntegerValue(10), true, nil},
-		{"int8", document.NewIntegerValue(10), true, nil},
-		{"int16", document.NewIntegerValue(10), true, nil},
-		{"int32", document.NewIntegerValue(10), true, nil},
-		{"int64", document.NewIntegerValue(10), true, nil},
-		{"double", document.NewDoubleValue(10), true, nil},
-		{"document", document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewIntegerValue(10))), true, nil},
-		{"duration", document.NewDurationValue(10 * time.Nanosecond), true, nil},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			res, err := test.v.ConvertToArray()
-			if test.fails {
-				require.Error(t, err)
-			} else {
-				require.NoError(t, err)
-				require.Equal(t, test.expected, res)
-			}
-		})
-	}
-}
-
 func TestValueAdd(t *testing.T) {
 	tests := []struct {
 		name           string
@@ -646,67 +379,3 @@ func TestValueBitwiseXor(t *testing.T) {
 		})
 	}
 }
-
-type CompareTest struct {
-	name     string
-	v, u     document.Value
-	expected int
-}
-
-func TestValueCompare(t *testing.T) {
-	tests := []CompareTest{
-		{"null,null", document.NewNullValue(), document.NewNullValue(), 0},
-		{"null,int8", document.NewNullValue(), document.NewIntegerValue(0), -1},
-		{"int8,null", document.NewIntegerValue(0), document.NewNullValue(), 1},
-	}
-
-	// cartesian computes a cartesian product, generating all possible combinations of the passed arrays
-	cartesian := func(vals ...[]document.Value) {
-		for _, x := range vals {
-			for _, y := range vals {
-				for i := 0; i < 2; i++ {
-					for j := 0; j < 2; j++ {
-						if j == 1 && i == 1 {
-							continue
-						}
-						v := x[i]
-						u := y[j]
-						signum := int((int64(i-j) >> 63) | int64(uint64(j-i)>>63))
-						tests = append(tests, CompareTest{
-							name:     fmt.Sprintf("%s(%s)%s%s(%s)", v.Type, v, []string{"<", "=", ">"}[signum+1], u.Type, u),
-							v:        v,
-							u:        u,
-							expected: signum,
-						})
-					}
-				}
-			}
-		}
-	}
-
-	// sample numeric values. Values at index [0] are known to be less than values at index [1]
-	int8s := []document.Value{document.NewIntegerValue(0), document.NewIntegerValue(1)}
-	int16s := []document.Value{document.NewIntegerValue(0), document.NewIntegerValue(1)}
-	int32s := []document.Value{document.NewIntegerValue(0), document.NewIntegerValue(1)}
-	int64s := []document.Value{document.NewIntegerValue(0), document.NewIntegerValue(1)}
-	float64s := []document.Value{document.NewDoubleValue(0), document.NewDoubleValue(1)}
-	bools := []document.Value{document.NewBoolValue(false), document.NewBoolValue(true)}
-
-	// generate a batch of tests mixing everything with everything
-	cartesian(int8s, int16s, int32s, int64s, float64s, bools)
-
-	// Sample blob and text values. Values at index [0] are known to be less than values at index [1]
-	texts := []document.Value{document.NewTextValue("ABC"), document.NewTextValue("CDE")}
-	blobs := []document.Value{document.NewBlobValue([]byte{65, 66, 67}), document.NewBlobValue([]byte{68, 69, 70})}
-
-	// generate another batch of tests mixing everything with everything
-	cartesian(texts, blobs)
-
-	// Run the tests
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			res := test.v.Compare(test.u)
-			require.Equal(t, test.expected, int((int64(res)>>63)|int64(uint64(-res)>>63)))
-		})
-	}
-}
diff --git a/example_test.go b/example_test.go
index fdf36b56..7f1118b7 100644
--- a/example_test.go
+++ b/example_test.go
@@ -111,8 +111,7 @@ func Example() {
 			if err != nil {
 				return false, err
 			}
-			id, err := v.ConvertToInt64()
-			return id%2 == 0, err
+			return v.V.(int64)%2 == 0, err
 		}).
 		// Enrich the documents with a new field
 		Map(func(d document.Document) (document.Document, error) {
diff --git a/sql/query/insert_test.go b/sql/query/insert_test.go
index 9475a18f..62575d52 100644
--- a/sql/query/insert_test.go
+++ b/sql/query/insert_test.go
@@ -147,7 +147,7 @@ func TestInsertStmt(t *testing.T) {
 			INSERT INTO test
 			VALUES {
 				i: 10000000000, db: 21.21, b: true,
-				du: 127ns, bb: "blobValue", byt: "bytesValue",
+				du: 127ns, bb: "YmxvYlZhbHVlCg==", byt: "Ynl0ZXNWYWx1ZQ==",
 				t: "text", a: [1, "foo", true], d: {"foo": "bar"}
 			}`)
 		require.NoError(t, err)
@@ -164,7 +164,7 @@ func TestInsertStmt(t *testing.T) {
 			"db": 21.21,
 			"b": true,
 			"du": "127ns",
-			"bb": "YmxvYlZhbHVl",
+			"bb": "YmxvYlZhbHVlCg==",
 			"byt": "Ynl0ZXNWYWx1ZQ==",
 			"t": "text",
 			"a": [1, "foo", true],
@@ -181,39 +181,36 @@ func TestInsertStmt(t *testing.T) {
 		}{
 			{"not null without type constraint", "NOT NULL", `{}`, `field "a" is required and must be not null`},
 
-			{"array", "ARRAY", `{a: "[1,2,3]"}`, `cannot convert "text" to "array"`},
 			{"array / not null with type constraint", "ARRAY NOT NULL", `{}`, `field "a" is required and must be not null`},
-			{"array / not null with non-respected type constraint ", "ARRAY NOT NULL", `{a: 42}`, `cannot convert "integer" to "array"`},
+			{"array / not null with non-respected type constraint ", "ARRAY NOT NULL", `{a: 42}`, `cannot cast integer as array`},
 
-			{"blob", "BLOB", `{a: true}`, `cannot convert "bool" to "bytes"`},
+			{"blob", "BLOB", `{a: true}`, `cannot cast bool as blob`},
 			{"blob / not null with type constraint", "BLOB NOT NULL", `{}`, `field "a" is required and must be not null`},
-			{"blob / not null with non-respected type constraint ", "BLOB NOT NULL", `{a: 42}`, `cannot convert "integer" to "bytes"`},
+			{"blob / not null with non-respected type constraint ", "BLOB NOT NULL", `{a: 42}`, `cannot cast integer as blob`},
 
 			{"bool / not null with type constraint", "BOOL NOT NULL", `{}`, `field "a" is required and must be not null`},
 
-			{"bytes", "BYTES", `{a: [1,2,3]}`, `cannot convert "array" to "bytes"`},
+			{"bytes", "BYTES", `{a: [1,2,3]}`, `cannot cast array as blob`},
 			{"bytes / not null with type constraint", "BYTES NOT NULL", `{}`, `field "a" is required and must be not null`},
-			{"bytes / not null with non-respected type constraint ", "BYTES NOT NULL", `{a: 42}`, `cannot convert "integer" to "bytes"`},
+			{"bytes / not null with non-respected type constraint ", "BYTES NOT NULL", `{a: 42}`, `cannot cast integer as blob`},
 
-			{"document", "DOCUMENT", `{a: "foo"}`, `cannot convert "text" to "document"`},
+			{"document", "DOCUMENT", `{"a": "foo"}`, `cannot cast "foo" as document: found "\x00", expected '{'`},
 			{"document / not null with type constraint", "DOCUMENT NOT NULL", `{}`, `field "a" is required and must be not null`},
-			{"document / not null with non-respected type constraint ", "DOCUMENT NOT NULL", `{a: false}`, `cannot convert "bool" to "document"`},
+			{"document / not null with non-respected type constraint ", "DOCUMENT NOT NULL", `{a: false}`, `cannot cast bool as document`},
 
-			{"duration", "DURATION", `{a: "foo"}`, `cannot convert "foo" to "duration": time: invalid duration foo`},
+			{"duration", "DURATION", `{a: "foo"}`, `cannot cast "foo" as duration: time: invalid duration foo`},
 			{"duration / not null with type constraint", "DURATION NOT NULL", `{}`, `field "a" is required and must be not null`},
-			{"duration / not null with non-respected type constraint ", "DURATION NOT NULL", `{a: [1,2,3]}`, `type "array" incompatible with "integer"`},
+			{"duration / not null with non-respected type constraint ", "DURATION NOT NULL", `{a: [1,2,3]}`, `cannot cast array as duration`},
 
-			{"double", "DOUBLE", `{a: "foo"}`, `cannot convert "text" to "double"`},
+			{"double", "DOUBLE", `{a: "foo"}`, `cannot cast "foo" as double: strconv.ParseFloat: parsing "foo": invalid syntax`},
 			{"double / not null with type constraint", "DOUBLE NOT NULL", `{}`, `field "a" is required and must be not null`},
-			{"double / not null with non-respected type constraint ", "DOUBLE NOT NULL", `{a: [1,2,3]}`, `cannot convert "array" to "double"`},
+			{"double / not null with non-respected type constraint ", "DOUBLE NOT NULL", `{a: [1,2,3]}`, `cannot cast array as double`},
 
-			{"integer", "INTEGER", `{a: "foo"}`, `cannot convert "text" to "integer": type "text" incompatible with "integer"`},
+			{"integer", "INTEGER", `{a: "foo"}`, `cannot cast "foo" as integer: strconv.ParseFloat: parsing "foo": invalid syntax`},
 			{"integer / not null with type constraint", "INTEGER NOT NULL", `{}`, `field "a" is required and must be not null`},
-			{"integer / not null with non-respected type constraint ", "INTEGER NOT NULL", `{a: [1,2,3]}`, `cannot convert "array" to "integer": type "array" incompatible with "integer"`},
+			{"integer / not null with non-respected type constraint ", "INTEGER NOT NULL", `{a: [1,2,3]}`, `cannot cast array as integer`},
 
-			{"text", "TEXT", `{a: [1,2,3]}`, `cannot convert "array" to "string"`},
 			{"text / not null with type constraint", "TEXT NOT NULL", `{}`, `field "a" is required and must be not null`},
-			{"text / not null with non-respected type constraint ", "TEXT NOT NULL", `{a: 42}`, `cannot convert "integer" to "string"`},
 		}
 
 		db, err := genji.Open(":memory:")