Repository: genjidb/genji
Pull request #294 (2020-10-25 18:17:49)

Normalize naming of path related types and functions
Most important changes are:
- `document.ValuePath` renamed to `document.Path`
- `expr.FieldSelector` renamed to `expr.Path`

The rest is essentially renaming constructors, tests, etc.


Patch:
diff --git a/README.md b/README.md
index bd997f0c..ccfabb1b 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,6 @@
 [![Build Status](https://travis-ci.org/genjidb/genji.svg)](https://travis-ci.org/genjidb/genji)
 [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/genjidb/genji)
 [![Slack channel](https://img.shields.io/badge/slack-join%20chat-green.svg)](https://gophers.slack.com/messages/CKPCYQFE0)
-
 ![Fuzz](https://github.com/genjidb/genji/workflows/Fuzz/badge.svg)
 
 Genji is a schemaless database that allows running SQL queries on documents.
diff --git a/cmd/genji/shell/command.go b/cmd/genji/shell/command.go
index cce41386..dd7ced25 100644
--- a/cmd/genji/shell/command.go
+++ b/cmd/genji/shell/command.go
@@ -322,7 +322,7 @@ func runDumpCmd(db *genji.DB, tables []string, w io.Writer) error {
 			_, err = fmt.Fprintln(w, "COMMIT;")
 			return err
 		}
-		
+
 		// Blank separation between tables.
 		if i > 0 {
 			if _, err := fmt.Fprintln(w, ""); err != nil {
diff --git a/database/config.go b/database/config.go
index 54fb025c..02adaa7b 100644
--- a/database/config.go
+++ b/database/config.go
@@ -15,7 +15,7 @@ const storePrefix = 't'
 
 // FieldConstraint describes constraints on a particular field.
 type FieldConstraint struct {
-	Path         document.ValuePath
+	Path         document.Path
 	Type         document.ValueType
 	IsPrimaryKey bool
 	IsNotNull    bool
@@ -30,7 +30,7 @@ func (f *FieldConstraint) HasDefaultValue() bool {
 func (f *FieldConstraint) ToDocument() document.Document {
 	buf := document.NewFieldBuffer()
 
-	buf.Add("path", document.NewArrayValue(valuePathToArray(f.Path)))
+	buf.Add("path", document.NewArrayValue(pathToArray(f.Path)))
 	buf.Add("type", document.NewIntegerValue(int64(f.Type)))
 	buf.Add("is_primary_key", document.NewBoolValue(f.IsPrimaryKey))
 	buf.Add("is_not_null", document.NewBoolValue(f.IsNotNull))
@@ -46,7 +46,7 @@ func (f *FieldConstraint) ScanDocument(d document.Document) error {
 	if err != nil {
 		return err
 	}
-	f.Path, err = arrayToValuePath(v)
+	f.Path, err = arrayToPath(v.V.(document.Array))
 	if err != nil {
 		return err
 	}
@@ -366,8 +366,8 @@ func (t *tableInfoStore) loadAllTableInfo(tx engine.Transaction) error {
 		readOnly:  true,
 		FieldConstraints: []FieldConstraint{
 			{
-				Path: document.ValuePath{
-					document.ValuePathFragment{
+				Path: document.Path{
+					document.PathFragment{
 						FieldName: "table_name",
 					},
 				},
@@ -381,8 +381,8 @@ func (t *tableInfoStore) loadAllTableInfo(tx engine.Transaction) error {
 		readOnly:  true,
 		FieldConstraints: []FieldConstraint{
 			{
-				Path: document.ValuePath{
-					document.ValuePathFragment{
+				Path: document.Path{
+					document.PathFragment{
 						FieldName: "index_name",
 					},
 				},
@@ -439,7 +439,7 @@ func (t *tableInfoStore) GetTableInfo() map[string]TableInfo {
 type IndexConfig struct {
 	TableName string
 	IndexName string
-	Path      document.ValuePath
+	Path      document.Path
 
 	// If set to true, values will be associated with at most one key. False by default.
 	Unique bool
@@ -455,7 +455,7 @@ func (i *IndexConfig) ToDocument() document.Document {
 	buf.Add("unique", document.NewBoolValue(i.Unique))
 	buf.Add("index_name", document.NewTextValue(i.IndexName))
 	buf.Add("table_name", document.NewTextValue(i.TableName))
-	buf.Add("path", document.NewArrayValue(valuePathToArray(i.Path)))
+	buf.Add("path", document.NewArrayValue(pathToArray(i.Path)))
 	if i.Type != 0 {
 		buf.Add("type", document.NewIntegerValue(int64(i.Type)))
 	}
@@ -486,7 +486,7 @@ func (i *IndexConfig) ScanDocument(d document.Document) error {
 	if err != nil {
 		return err
 	}
-	i.Path, err = arrayToValuePath(v)
+	i.Path, err = arrayToPath(v.V.(document.Array))
 	if err != nil {
 		return err
 	}
@@ -602,14 +602,14 @@ func (t *indexStore) ListAll() ([]*IndexConfig, error) {
 	return idxList, nil
 }
 
-func arrayToValuePath(v document.Value) (document.ValuePath, error) {
-	var path document.ValuePath
+func arrayToPath(a document.Array) (document.Path, error) {
+	var path document.Path
 
-	err := v.V.(document.Array).Iterate(func(_ int, value document.Value) error {
+	err := a.Iterate(func(_ int, value document.Value) error {
 		if value.Type == document.TextValue {
-			path = append(path, document.ValuePathFragment{FieldName: value.V.(string)})
+			path = append(path, document.PathFragment{FieldName: value.V.(string)})
 		} else {
-			path = append(path, document.ValuePathFragment{ArrayIndex: int(value.V.(int64))})
+			path = append(path, document.PathFragment{ArrayIndex: int(value.V.(int64))})
 		}
 		return nil
 	})
@@ -617,7 +617,7 @@ func arrayToValuePath(v document.Value) (document.ValuePath, error) {
 	return path, err
 }
 
-func valuePathToArray(path document.ValuePath) document.Array {
+func pathToArray(path document.Path) document.Array {
 	abuf := document.NewValueBuffer()
 	for _, p := range path {
 		if p.FieldName != "" {
diff --git a/database/table.go b/database/table.go
index 2552d0a9..3076d1d3 100644
--- a/database/table.go
+++ b/database/table.go
@@ -559,7 +559,7 @@ func validateConstraint(d document.Document, c *FieldConstraint) error {
 	return nil
 }
 
-func getParentValue(d document.Document, p document.ValuePath) (document.Value, error) {
+func getParentValue(d document.Document, p document.Path) (document.Value, error) {
 	if len(p) == 0 {
 		return document.Value{}, errors.New("empty path")
 	}
diff --git a/document/document.go b/document/document.go
index 7b9c930e..60ab2646 100644
--- a/document/document.go
+++ b/document/document.go
@@ -153,9 +153,8 @@ func (fb *FieldBuffer) setFieldValue(field string, reqValue Value) error {
 	return err
 }
 
-// setValueAtPath deep replaces or creates a field
-// at the given path
-func setValueAtPath(v Value, p ValuePath, newValue Value) (Value, error) {
+// setValueAtPath deep replaces or creates a field at the given path
+func setValueAtPath(v Value, p Path, newValue Value) (Value, error) {
 	switch v.Type {
 	case DocumentValue:
 		var buf FieldBuffer
@@ -207,7 +206,7 @@ func setValueAtPath(v Value, p ValuePath, newValue Value) (Value, error) {
 }
 
 // Set replaces a field if it already exists or creates one if not.
-func (fb *FieldBuffer) Set(path ValuePath, v Value) error {
+func (fb *FieldBuffer) Set(path Path, v Value) error {
 	if len(path) == 1 {
 		return fb.setFieldValue(path[0].FieldName, v)
 	}
@@ -324,19 +323,19 @@ func (fb *FieldBuffer) Fields() []string {
 	return fields
 }
 
-// A ValuePath represents the path to a particular value within a document.
-type ValuePath []ValuePathFragment
+// A Path represents the path to a particular value within a document.
+type Path []PathFragment
 
-// ValuePathFragment is a fragment of a path representing either a field name or
+// PathFragment is a fragment of a path representing either a field name or
 // the index of an array.
-type ValuePathFragment struct {
+type PathFragment struct {
 	FieldName  string
 	ArrayIndex int
 }
 
 // String representation of all the fragments of the path.
 // It implements the Stringer interface.
-func (p ValuePath) String() string {
+func (p Path) String() string {
 	var b strings.Builder
 
 	for i := range p {
@@ -353,7 +352,7 @@ func (p ValuePath) String() string {
 }
 
 // IsEqual returns whether other is equal to p.
-func (p ValuePath) IsEqual(other ValuePath) bool {
+func (p Path) IsEqual(other Path) bool {
 	if len(other) != len(p) {
 		return false
 	}
@@ -368,11 +367,11 @@ func (p ValuePath) IsEqual(other ValuePath) bool {
 }
 
 // GetValue from a document.
-func (p ValuePath) GetValue(d Document) (Value, error) {
+func (p Path) GetValue(d Document) (Value, error) {
 	return p.getValueFromDocument(d)
 }
 
-func (p ValuePath) getValueFromDocument(d Document) (Value, error) {
+func (p Path) getValueFromDocument(d Document) (Value, error) {
 	if len(p) == 0 {
 		return Value{}, ErrFieldNotFound
 	}
@@ -392,7 +391,7 @@ func (p ValuePath) getValueFromDocument(d Document) (Value, error) {
 	return p[1:].getValueFromValue(v)
 }
 
-func (p ValuePath) getValueFromArray(a Array) (Value, error) {
+func (p Path) getValueFromArray(a Array) (Value, error) {
 	if len(p) == 0 {
 		return Value{}, ErrFieldNotFound
 	}
@@ -416,7 +415,7 @@ func (p ValuePath) getValueFromArray(a Array) (Value, error) {
 	return p[1:].getValueFromValue(v)
 }
 
-func (p ValuePath) getValueFromValue(v Value) (Value, error) {
+func (p Path) getValueFromValue(v Value) (Value, error) {
 	switch v.Type {
 	case DocumentValue:
 		return p.getValueFromDocument(v.V.(Document))
diff --git a/sql/parser/expr.go b/sql/parser/expr.go
index 39cbad44..02e0f691 100644
--- a/sql/parser/expr.go
+++ b/sql/parser/expr.go
@@ -168,7 +168,7 @@ func (p *Parser) parseUnaryExpr() (expr.Expr, error) {
 		if err != nil {
 			return nil, err
 		}
-		fs := expr.FieldSelector(field)
+		fs := expr.Path(field)
 		return fs, nil
 	case scanner.NAMEDPARAM:
 		if len(lit) == 1 {
@@ -322,7 +322,7 @@ func (p *Parser) parseType() (document.ValueType, error) {
 		p.Unscan()
 		return document.DoubleValue, nil
 	case scanner.TYPEINTEGER, scanner.TYPEINT, scanner.TYPEINT2, scanner.TYPEINT8, scanner.TYPETINYINT,
-		 scanner.TYPEBIGINT, scanner.TYPEMEDIUMINT, scanner.TYPESMALLINT:
+		scanner.TYPEBIGINT, scanner.TYPEMEDIUMINT, scanner.TYPESMALLINT:
 		return document.IntegerValue, nil
 	case scanner.TYPETEXT:
 		return document.TextValue, nil
@@ -330,7 +330,7 @@ func (p *Parser) parseType() (document.ValueType, error) {
 		if tok, pos, lit := p.ScanIgnoreWhitespace(); tok != scanner.LPAREN {
 			return 0, newParseError(scanner.Tokstr(tok, lit), []string{"("}, pos)
 		}
-		
+
 		// The value between parentheses is not used.
 		if tok, pos, lit := p.ScanIgnoreWhitespace(); tok != scanner.INTEGER {
 			return 0, newParseError(scanner.Tokstr(tok, lit), []string{"integer"}, pos)
@@ -410,14 +410,14 @@ func (p *Parser) parseKV() (expr.KVPair, error) {
 }
 
 // parsePath parses a path to a specific value.
-func (p *Parser) parsePath() (document.ValuePath, error) {
-	var vPath document.ValuePath
+func (p *Parser) parsePath() (document.Path, error) {
+	var path document.Path
 	// parse first mandatory ident
 	chunk, err := p.parseIdent()
 	if err != nil {
 		return nil, err
 	}
-	vPath = append(vPath, document.ValuePathFragment{
+	path = append(path, document.PathFragment{
 		FieldName: chunk,
 	})
 
@@ -434,7 +434,7 @@ LOOP:
 			if tok != scanner.IDENT {
 				return nil, newParseError(lit, []string{"identifier"}, pos)
 			}
-			vPath = append(vPath, document.ValuePathFragment{
+			path = append(path, document.PathFragment{
 				FieldName: lit,
 			})
 		case scanner.LSBRACKET:
@@ -447,7 +447,7 @@ LOOP:
 			if err != nil {
 				return nil, newParseError(lit, []string{"integer"}, pos)
 			}
-			vPath = append(vPath, document.ValuePathFragment{
+			path = append(path, document.PathFragment{
 				ArrayIndex: idx,
 			})
 			// scan the next token for a closing left bracket
@@ -461,7 +461,7 @@ LOOP:
 		}
 	}
 
-	return vPath, nil
+	return path, nil
 }
 
 func (p *Parser) parseExprListUntil(rightToken scanner.Token) (expr.LiteralExprList, error) {
diff --git a/sql/parser/parser.go b/sql/parser/parser.go
index c6c84e67..31c16f68 100644
--- a/sql/parser/parser.go
+++ b/sql/parser/parser.go
@@ -41,8 +41,8 @@ func ParseQuery(ctx context.Context, s string) (query.Query, error) {
 	return NewParser(strings.NewReader(s)).ParseQuery(ctx)
 }
 
-// ParsePath parses the path of a value in a document.
-func ParsePath(s string) (document.ValuePath, error) {
+// ParsePath parses a path to a value in a document.
+func ParsePath(s string) (document.Path, error) {
 	return NewParser(strings.NewReader(s)).parsePath()
 }
 
@@ -130,22 +130,22 @@ func (p *Parser) parseCondition() (expr.Expr, error) {
 }
 
 // parsePathList parses a list of paths in the form: (path, path, ...), if exists
-func (p *Parser) parsePathList() ([]document.ValuePath, error) {
+func (p *Parser) parsePathList() ([]document.Path, error) {
 	// Parse ( token.
 	if tok, _, _ := p.ScanIgnoreWhitespace(); tok != scanner.LPAREN {
 		p.Unscan()
 		return nil, nil
 	}
 
-	var paths []document.ValuePath
+	var paths []document.Path
 	var err error
-	var vp document.ValuePath
+	var path document.Path
 	// Parse first (required) path.
-	if vp, err = p.parsePath(); err != nil {
+	if path, err = p.parsePath(); err != nil {
 		return nil, err
 	}
 
-	paths = append(paths, vp)
+	paths = append(paths, path)
 
 	// Parse remaining (optional) paths.
 	for {
diff --git a/sql/parser/select.go b/sql/parser/select.go
index b3838175..6a760fc3 100644
--- a/sql/parser/select.go
+++ b/sql/parser/select.go
@@ -100,9 +100,9 @@ func (p *Parser) parseResultField() (planner.ProjectedField, error) {
 		return nil, err
 	}
 
-	// FieldSelectors may be quoted, we make sure we name the result path
+	// Paths may be quoted, we make sure we name the result path
 	// with the unquoted name instead.
-	if fs, ok := e.(expr.FieldSelector); ok {
+	if fs, ok := e.(expr.Path); ok {
 		lit = fs.String()
 	}
 
@@ -156,7 +156,7 @@ func (p *Parser) parseGroupBy() (expr.Expr, error) {
 	return e, err
 }
 
-func (p *Parser) parseOrderBy() (expr.FieldSelector, scanner.Token, error) {
+func (p *Parser) parseOrderBy() (expr.Path, scanner.Token, error) {
 	// parse ORDER token
 	if tok, _, _ := p.ScanIgnoreWhitespace(); tok != scanner.ORDER {
 		p.Unscan()
@@ -169,18 +169,18 @@ func (p *Parser) parseOrderBy() (expr.FieldSelector, scanner.Token, error) {
 	}
 
 	// parse path
-	ref, err := p.parsePath()
+	path, err := p.parsePath()
 	if err != nil {
 		return nil, 0, err
 	}
 
 	// parse optional ASC or DESC
 	if tok, _, _ := p.ScanIgnoreWhitespace(); tok == scanner.ASC || tok == scanner.DESC {
-		return expr.FieldSelector(ref), tok, nil
+		return expr.Path(path), tok, nil
 	}
 	p.Unscan()
 
-	return expr.FieldSelector(ref), 0, nil
+	return expr.Path(path), 0, nil
 }
 
 func (p *Parser) parseLimit() (expr.Expr, error) {
@@ -210,7 +210,7 @@ type selectConfig struct {
 	TableName        string
 	WhereExpr        expr.Expr
 	GroupByExpr      expr.Expr
-	OrderBy          expr.FieldSelector
+	OrderBy          expr.Path
 	OrderByDirection scanner.Token
 	OffsetExpr       expr.Expr
 	LimitExpr        expr.Expr
diff --git a/sql/parser/update.go b/sql/parser/update.go
index 4befd7de..88a34d15 100644
--- a/sql/parser/update.go
+++ b/sql/parser/update.go
@@ -128,7 +128,7 @@ type updateConfig struct {
 }
 
 type updateSetPair struct {
-	path document.ValuePath
+	path document.Path
 	e    expr.Expr
 }
 
diff --git a/sql/planner/optimizer.go b/sql/planner/optimizer.go
index afcab60c..799abe69 100644
--- a/sql/planner/optimizer.go
+++ b/sql/planner/optimizer.go
@@ -251,7 +251,7 @@ func RemoveUnnecessarySelectionNodesRule(t *Tree) (*Tree, error) {
 // UseIndexBasedOnSelectionNodeRule scans the tree for the first selection node whose condition is an
 // operator that satisfies the following criterias:
 // - implements the indexIteratorOperator interface
-// - one of its operands is path selector that is indexed
+// - one of its operands is a path expression that is indexed
 // - the other operand is a literal value or a parameter
 // If found, it will replace the input node by an indexInputNode using this index.
 func UseIndexBasedOnSelectionNodeRule(t *Tree) (*Tree, error) {
@@ -392,7 +392,7 @@ func selectionNodeValidForIndex(sn *selectionNode, tableName string, indexes map
 	}
 
 	// now, we look if an index exists for that path
-	idx, ok := indexes[field.Name()]
+	idx, ok := indexes[field.String()]
 	if !ok {
 		return nil
 	}
@@ -403,9 +403,9 @@ func selectionNodeValidForIndex(sn *selectionNode, tableName string, indexes map
 	return in
 }
 
-func opCanUseIndex(op expr.Operator) (bool, expr.FieldSelector, expr.Expr) {
-	lf, leftIsField := op.LeftHand().(expr.FieldSelector)
-	rf, rightIsField := op.RightHand().(expr.FieldSelector)
+func opCanUseIndex(op expr.Operator) (bool, expr.Path, expr.Expr) {
+	lf, leftIsField := op.LeftHand().(expr.Path)
+	rf, rightIsField := op.RightHand().(expr.Path)
 
 	// path OP expr
 	if leftIsField && !rightIsField {
diff --git a/sql/planner/sort.go b/sql/planner/sort.go
index 7f10eacb..0adfdaaa 100644
--- a/sql/planner/sort.go
+++ b/sql/planner/sort.go
@@ -15,7 +15,7 @@ import (
 type sortNode struct {
 	node
 
-	sortField expr.FieldSelector
+	sortField expr.Path
 	direction scanner.Token
 }
 
@@ -23,7 +23,7 @@ var _ operationNode = (*sortNode)(nil)
 
 // NewSortNode creates a node that sorts a stream according to a given
 // document path and a sort direction.
-func NewSortNode(n Node, sortField expr.FieldSelector, direction scanner.Token) Node {
+func NewSortNode(n Node, sortField expr.Path, direction scanner.Token) Node {
 	if direction == 0 {
 		direction = scanner.ASC
 	}
@@ -61,7 +61,7 @@ func (n *sortNode) String() string {
 
 type sortIterator struct {
 	st        document.Stream
-	sortField expr.FieldSelector
+	sortField expr.Path
 	direction scanner.Token
 }
 
@@ -93,7 +93,7 @@ func (it *sortIterator) Iterate(fn func(d document.Document) error) error {
 // This function is not memory efficient as it's loading the entire stream in memory before
 // returning the k-smallest or k-largest elements.
 func (it *sortIterator) sortStream(st document.Stream) (heap.Interface, error) {
-	path := document.ValuePath(it.sortField)
+	path := document.Path(it.sortField)
 
 	var h heap.Interface
 	if it.direction == scanner.ASC {
diff --git a/sql/planner/tree.go b/sql/planner/tree.go
index 20120e7d..79764f68 100644
--- a/sql/planner/tree.go
+++ b/sql/planner/tree.go
@@ -38,9 +38,9 @@ const (
 	Skip
 	// Sort is an operation that sorts a stream of document according to a given path and a direction.
 	Sort
-	// Set is an operation that adds or replaces a path for every document of the stream.
+	// Set is an operation that adds a value or replaces at a given path for every document of the stream.
 	Set
-	// Unset is an operation that removes a path from every document of a stream
+	// Unset is an operation that removes a value at a given path from every document of a stream
 	Unset
 	// Group is an operation that groups documents based on a given path.
 )
@@ -320,7 +320,7 @@ func (n *offsetNode) toStream(st document.Stream) (document.Stream, error) {
 type setNode struct {
 	node
 
-	path document.ValuePath
+	path document.Path
 	e    expr.Expr
 
 	tx     *database.Transaction
@@ -329,8 +329,8 @@ type setNode struct {
 
 var _ operationNode = (*setNode)(nil)
 
-// NewSetNode creates a node that adds or replaces a path for every document of the stream.
-func NewSetNode(n Node, path document.ValuePath, e expr.Expr) Node {
+// NewSetNode creates a node that adds or replaces a value at the given path for every document of the stream.
+func NewSetNode(n Node, path document.Path, e expr.Expr) Node {
 	return &setNode{
 		node: node{
 			op:   Set,
@@ -390,7 +390,7 @@ type unsetNode struct {
 
 var _ operationNode = (*unsetNode)(nil)
 
-// NewUnsetNode creates a node that adds or replaces a path for every document of the stream.
+// NewUnsetNode creates a node that removes a value at a given path for every document of the stream.
 func NewUnsetNode(n Node, field string) Node {
 	return &unsetNode{
 		node: node{
@@ -438,7 +438,7 @@ func (n *unsetNode) String() string {
 	return fmt.Sprintf("Unset(%s)", n.field)
 }
 
-// A GroupingNode is a node that groups documents by a given path.
+// A GroupingNode is a node that groups documents by value.
 type GroupingNode struct {
 	node
 
diff --git a/sql/query/constraint.go b/sql/query/constraint.go
index 04e1a683..cec86396 100644
--- a/sql/query/constraint.go
+++ b/sql/query/constraint.go
@@ -2,20 +2,20 @@ package query
 
 import (
 	"fmt"
-	"github.com/genjidb/genji/database"
 
+	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
 )
 
 // constraintNode is a tree node which stores a type of document field
 type constraintNode struct {
-	frag   document.ValuePathFragment
+	frag   document.PathFragment
 	typ    document.ValueType
 	parent *constraintNode
 	sub    []*constraintNode
 }
 
-func createConstraintNode(parent *constraintNode, path document.ValuePath, typ document.ValueType) *constraintNode {
+func createConstraintNode(parent *constraintNode, path document.Path, typ document.ValueType) *constraintNode {
 	node := &constraintNode{
 		frag:   path[0],
 		parent: parent,
@@ -39,15 +39,15 @@ func createConstraintNode(parent *constraintNode, path document.ValuePath, typ d
 	return node
 }
 
-func (n *constraintNode) getPath() document.ValuePath {
+func (n *constraintNode) getPath() document.Path {
 	if n.parent == nil {
-		return document.ValuePath{n.frag}
+		return document.Path{n.frag}
 	}
 
 	return append(n.parent.getPath(), n.frag)
 }
 
-func (n *constraintNode) search(path document.ValuePath) *constraintNode {
+func (n *constraintNode) search(path document.Path) *constraintNode {
 	if n.frag != path[0] {
 		return nil
 	}
@@ -66,7 +66,7 @@ func (n *constraintNode) search(path document.ValuePath) *constraintNode {
 	return nil
 }
 
-func (n *constraintNode) insert(path document.ValuePath, typ document.ValueType) error {
+func (n *constraintNode) insert(path document.Path, typ document.ValueType) error {
 	switch {
 	case len(path) == 1:
 		return fmt.Errorf("%q already exists as type %s", n.getPath().String(), n.typ.String())
@@ -109,7 +109,7 @@ type constraintTree struct {
 	roots []*constraintNode
 }
 
-func (tree *constraintTree) insert(path document.ValuePath, typ document.ValueType) error {
+func (tree *constraintTree) insert(path document.Path, typ document.ValueType) error {
 	for _, sub := range tree.roots {
 		if sub.frag == path[0] {
 			return sub.insert(path, typ)
@@ -120,7 +120,7 @@ func (tree *constraintTree) insert(path document.ValuePath, typ document.ValueTy
 	return nil
 }
 
-func (tree *constraintTree) search(path document.ValuePath) *constraintNode {
+func (tree *constraintTree) search(path document.Path) *constraintNode {
 	for _, sub := range tree.roots {
 		if sub.frag == path[0] {
 			return sub.search(path)
diff --git a/sql/query/create.go b/sql/query/create.go
index 9110a605..fbc3fc13 100644
--- a/sql/query/create.go
+++ b/sql/query/create.go
@@ -48,7 +48,7 @@ func (stmt CreateTableStmt) Run(ctx context.Context, tx *database.Transaction, a
 type CreateIndexStmt struct {
 	IndexName   string
 	TableName   string
-	Path        document.ValuePath
+	Path        document.Path
 	IfNotExists bool
 	Unique      bool
 }
diff --git a/sql/query/expr/path.go b/sql/query/expr/path.go
index 1c3c5b16..13ad63ed 100644
--- a/sql/query/expr/path.go
+++ b/sql/query/expr/path.go
@@ -4,22 +4,17 @@ import (
 	"github.com/genjidb/genji/document"
 )
 
-// A FieldSelector is a ResultField that extracts a field from a document at a given path.
-type FieldSelector document.ValuePath
+// A Path is an expression that extracts a value from a document at a given path.
+type Path document.Path
 
-// Name joins the chunks of the fields selector with the . separator.
-func (f FieldSelector) Name() string {
-	return f.String()
-}
-
-// Eval extracts the document from the context and selects the right field.
+// Eval extracts the document from the context and selects the right value.
 // It implements the Expr interface.
-func (f FieldSelector) Eval(stack EvalStack) (document.Value, error) {
+func (p Path) Eval(stack EvalStack) (document.Value, error) {
 	if stack.Document == nil {
 		return nullLitteral, document.ErrFieldNotFound
 	}
 
-	v, err := document.ValuePath(f).GetValue(stack.Document)
+	v, err := document.Path(p).GetValue(stack.Document)
 	if err == document.ErrFieldNotFound || err == document.ErrValueNotFound {
 		return nullLitteral, nil
 	}
@@ -29,22 +24,22 @@ func (f FieldSelector) Eval(stack EvalStack) (document.Value, error) {
 
 // IsEqual compares this expression with the other expression and returns
 // true if they are equal.
-func (f FieldSelector) IsEqual(other Expr) bool {
+func (p Path) IsEqual(other Expr) bool {
 	if other == nil {
 		return false
 	}
 
-	o, ok := other.(FieldSelector)
+	o, ok := other.(Path)
 	if !ok {
 		return false
 	}
 
-	if len(f) != len(o) {
+	if len(p) != len(o) {
 		return false
 	}
 
-	for i := range f {
-		if f[i] != o[i] {
+	for i := range p {
+		if p[i] != o[i] {
 			return false
 		}
 	}
@@ -52,6 +47,6 @@ func (f FieldSelector) IsEqual(other Expr) bool {
 	return true
 }
 
-func (f FieldSelector) String() string {
-	return document.ValuePath(f).String()
+func (p Path) String() string {
+	return document.Path(p).String()
 }


Test patch:
diff --git a/database/config_test.go b/database/config_test.go
index b0319fc6..e1674beb 100644
--- a/database/config_test.go
+++ b/database/config_test.go
@@ -10,9 +10,9 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func newValuePath(s string) document.ValuePath {
-	return document.ValuePath{
-		document.ValuePathFragment{
+func newPath(s string) document.Path {
+	return document.Path{
+		document.PathFragment{
 			FieldName: "k",
 		},
 	}
@@ -21,7 +21,7 @@ func newValuePath(s string) document.ValuePath {
 func TestTableInfo(t *testing.T) {
 	info := &TableInfo{
 		FieldConstraints: []FieldConstraint{
-			{Path: newValuePath("k"), Type: document.DoubleValue, IsPrimaryKey: true},
+			{Path: newPath("k"), Type: document.DoubleValue, IsPrimaryKey: true},
 		},
 	}
 
@@ -47,7 +47,7 @@ func TestTableInfoStore(t *testing.T) {
 
 		info := &TableInfo{
 			FieldConstraints: []FieldConstraint{
-				{Path: newValuePath("k"), Type: document.DoubleValue, IsPrimaryKey: true},
+				{Path: newPath("k"), Type: document.DoubleValue, IsPrimaryKey: true},
 			},
 		}
 
@@ -90,7 +90,7 @@ func TestTableInfoStore(t *testing.T) {
 
 		info := &TableInfo{
 			FieldConstraints: []FieldConstraint{
-				{Path: newValuePath("k"), Type: document.DoubleValue, IsPrimaryKey: true},
+				{Path: newPath("k"), Type: document.DoubleValue, IsPrimaryKey: true},
 			},
 		}
 
@@ -117,7 +117,7 @@ func TestTableInfoStore(t *testing.T) {
 
 		info := &TableInfo{
 			FieldConstraints: []FieldConstraint{
-				{Path: newValuePath("k"), Type: document.DoubleValue, IsPrimaryKey: true},
+				{Path: newPath("k"), Type: document.DoubleValue, IsPrimaryKey: true},
 			},
 		}
 
diff --git a/database/table_test.go b/database/table_test.go
index 2e17c66e..4c6ed7a0 100644
--- a/database/table_test.go
+++ b/database/table_test.go
@@ -15,7 +15,7 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func parsePath(t testing.TB, str string) document.ValuePath {
+func parsePath(t testing.TB, str string) document.Path {
 	vp, err := parser.ParsePath(str)
 	require.NoError(t, err)
 	return vp
diff --git a/database/transaction_test.go b/database/transaction_test.go
index a8b6294f..4b13d7cb 100644
--- a/database/transaction_test.go
+++ b/database/transaction_test.go
@@ -3,6 +3,7 @@ package database_test
 import (
 	"errors"
 	"testing"
+
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
 	"github.com/genjidb/genji/document/encoding/msgpack"
diff --git a/document/document_test.go b/document/document_test.go
index a95c3a9b..fd6b098f 100644
--- a/document/document_test.go
+++ b/document/document_test.go
@@ -501,7 +501,7 @@ func (f *foo) GetByField(field string) (document.Value, error) {
 	return document.Value{}, errors.New("unknown field")
 }
 
-func TestValuePath(t *testing.T) {
+func TestPath(t *testing.T) {
 	tests := []struct {
 		name   string
 		data   string
diff --git a/sql/parser/delete_test.go b/sql/parser/delete_test.go
index 3febd1df..b9c17afb 100644
--- a/sql/parser/delete_test.go
+++ b/sql/parser/delete_test.go
@@ -23,7 +23,7 @@ func TestParserDelete(t *testing.T) {
 			planner.NewTree(planner.NewDeletionNode(
 				planner.NewSelectionNode(
 					planner.NewTableInputNode("test"),
-					expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10))),
+					expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10))),
 				"test"))},
 	}
 
diff --git a/sql/parser/expr_test.go b/sql/parser/expr_test.go
index a80fa052..9f68be16 100644
--- a/sql/parser/expr_test.go
+++ b/sql/parser/expr_test.go
@@ -9,10 +9,10 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func parsePath(t testing.TB, ref string) document.ValuePath {
+func parsePath(t testing.TB, p string) document.Path {
 	t.Helper()
 
-	vp, err := ParsePath(ref)
+	vp, err := ParsePath(p)
 	require.NoError(t, err)
 	return vp
 }
@@ -57,7 +57,7 @@ func TestParserExpr(t *testing.T) {
 				expr.KVPair{K: "f", V: expr.KVPairs{
 					expr.KVPair{K: "foo", V: expr.TextValue("bar")},
 				}},
-				expr.KVPair{K: "g", V: expr.FieldSelector(parsePath(t, "h.i.j"))},
+				expr.KVPair{K: "g", V: expr.Path(parsePath(t, "h.i.j"))},
 				expr.KVPair{K: "k", V: expr.LiteralExprList{expr.IntegerValue(1), expr.IntegerValue(2), expr.IntegerValue(3)}},
 			},
 			false},
@@ -108,28 +108,28 @@ func TestParserExpr(t *testing.T) {
 				expr.IntegerValue(1),
 				expr.BoolValue(true),
 				expr.KVPairs{expr.KVPair{K: "a", V: expr.IntegerValue(1)}},
-				expr.FieldSelector(parsePath(t, "a.b.c")),
+				expr.Path(parsePath(t, "a.b.c")),
 				expr.Parentheses{E: expr.IntegerValue(-1)},
 				expr.LiteralExprList{expr.IntegerValue(-1)},
 			}, false},
 		{"list with brackets: missing bracket", `[1, true, {a: 1}, a.b.c, (-1), [-1]`, nil, true},
 
 		// operators
-		{"=", "age = 10", expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"!=", "age != 10", expr.Neq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{">", "age > 10", expr.Gt(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{">=", "age >= 10", expr.Gte(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"<", "age < 10", expr.Lt(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"<=", "age <= 10", expr.Lte(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"+", "age + 10", expr.Add(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"-", "age - 10", expr.Sub(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"*", "age * 10", expr.Mul(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"/", "age / 10", expr.Div(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"%", "age % 10", expr.Mod(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"&", "age & 10", expr.BitwiseAnd(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)), false},
-		{"IN", "age IN ages", expr.In(expr.FieldSelector(parsePath(t, "age")), expr.FieldSelector(parsePath(t, "ages"))), false},
-		{"IS", "age IS NULL", expr.Is(expr.FieldSelector(parsePath(t, "age")), expr.NullValue()), false},
-		{"IS NOT", "age IS NOT NULL", expr.IsNot(expr.FieldSelector(parsePath(t, "age")), expr.NullValue()), false},
+		{"=", "age = 10", expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"!=", "age != 10", expr.Neq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{">", "age > 10", expr.Gt(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{">=", "age >= 10", expr.Gte(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"<", "age < 10", expr.Lt(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"<=", "age <= 10", expr.Lte(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"+", "age + 10", expr.Add(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"-", "age - 10", expr.Sub(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"*", "age * 10", expr.Mul(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"/", "age / 10", expr.Div(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"%", "age % 10", expr.Mod(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"&", "age & 10", expr.BitwiseAnd(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)), false},
+		{"IN", "age IN ages", expr.In(expr.Path(parsePath(t, "age")), expr.Path(parsePath(t, "ages"))), false},
+		{"IS", "age IS NULL", expr.Is(expr.Path(parsePath(t, "age")), expr.NullValue()), false},
+		{"IS NOT", "age IS NOT NULL", expr.IsNot(expr.Path(parsePath(t, "age")), expr.NullValue()), false},
 		{"precedence", "4 > 1 + 2", expr.Gt(
 			expr.IntegerValue(4),
 			expr.Add(
@@ -139,27 +139,27 @@ func TestParserExpr(t *testing.T) {
 		), false},
 		{"AND", "age = 10 AND age <= 11",
 			expr.And(
-				expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
-				expr.Lte(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(11)),
+				expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
+				expr.Lte(expr.Path(parsePath(t, "age")), expr.IntegerValue(11)),
 			), false},
 		{"OR", "age = 10 OR age = 11",
 			expr.Or(
-				expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
-				expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(11)),
+				expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
+				expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(11)),
 			), false},
 		{"AND then OR", "age >= 10 AND age > $age OR age < 10.4",
 			expr.Or(
 				expr.And(
-					expr.Gte(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
-					expr.Gt(expr.FieldSelector(parsePath(t, "age")), expr.NamedParam("age")),
+					expr.Gte(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
+					expr.Gt(expr.Path(parsePath(t, "age")), expr.NamedParam("age")),
 				),
-				expr.Lt(expr.FieldSelector(parsePath(t, "age")), expr.DoubleValue(10.4)),
+				expr.Lt(expr.Path(parsePath(t, "age")), expr.DoubleValue(10.4)),
 			), false},
-		{"with NULL", "age > NULL", expr.Gt(expr.FieldSelector(parsePath(t, "age")), expr.NullValue()), false},
+		{"with NULL", "age > NULL", expr.Gt(expr.Path(parsePath(t, "age")), expr.NullValue()), false},
 		{"pk() function", "pk()", &expr.PKFunc{}, false},
-		{"count(expr) function", "count(a)", &expr.CountFunc{Expr: expr.FieldSelector(parsePath(t, "a"))}, false},
+		{"count(expr) function", "count(a)", &expr.CountFunc{Expr: expr.Path(parsePath(t, "a"))}, false},
 		{"count(*) function", "count(*)", &expr.CountFunc{Wildcard: true}, false},
-		{"CAST", "CAST(a.b[1][0] AS TEXT)", expr.CastFunc{Expr: expr.FieldSelector(parsePath(t, "a.b[1][0]")), CastAs: document.TextValue}, false},
+		{"CAST", "CAST(a.b[1][0] AS TEXT)", expr.CastFunc{Expr: expr.Path(parsePath(t, "a.b[1][0]")), CastAs: document.TextValue}, false},
 	}
 
 	for _, test := range tests {
@@ -176,32 +176,32 @@ func TestParserExpr(t *testing.T) {
 	}
 }
 
-func TestParserPath(t *testing.T) {
+func TestParsePath(t *testing.T) {
 	tests := []struct {
 		name     string
 		s        string
-		expected document.ValuePath
+		expected document.Path
 		fails    bool
 	}{
-		{"one fragment", `a`, document.ValuePath{
-			document.ValuePathFragment{FieldName: "a"},
+		{"one fragment", `a`, document.Path{
+			document.PathFragment{FieldName: "a"},
 		}, false},
-		{"one fragment with quotes", "`    \"a\"`", document.ValuePath{
-			document.ValuePathFragment{FieldName: "    \"a\""},
+		{"one fragment with quotes", "`    \"a\"`", document.Path{
+			document.PathFragment{FieldName: "    \"a\""},
 		}, false},
-		{"multiple fragments", `a.b[100].c[1][2]`, document.ValuePath{
-			document.ValuePathFragment{FieldName: "a"},
-			document.ValuePathFragment{FieldName: "b"},
-			document.ValuePathFragment{ArrayIndex: 100},
-			document.ValuePathFragment{FieldName: "c"},
-			document.ValuePathFragment{ArrayIndex: 1},
-			document.ValuePathFragment{ArrayIndex: 2},
+		{"multiple fragments", `a.b[100].c[1][2]`, document.Path{
+			document.PathFragment{FieldName: "a"},
+			document.PathFragment{FieldName: "b"},
+			document.PathFragment{ArrayIndex: 100},
+			document.PathFragment{FieldName: "c"},
+			document.PathFragment{ArrayIndex: 1},
+			document.PathFragment{ArrayIndex: 2},
 		}, false},
-		{"with quotes", "`some ident`.` with`[5].`  \"quotes`", document.ValuePath{
-			document.ValuePathFragment{FieldName: "some ident"},
-			document.ValuePathFragment{FieldName: " with"},
-			document.ValuePathFragment{ArrayIndex: 5},
-			document.ValuePathFragment{FieldName: "  \"quotes"},
+		{"with quotes", "`some ident`.` with`[5].`  \"quotes`", document.Path{
+			document.PathFragment{FieldName: "some ident"},
+			document.PathFragment{FieldName: " with"},
+			document.PathFragment{ArrayIndex: 5},
+			document.PathFragment{FieldName: "  \"quotes"},
 		}, false},
 		{"negative index", `a.b[-100].c`, nil, true},
 		{"with spaces", `a.  b[100].  c`, nil, true},
@@ -228,17 +228,17 @@ func TestParserParams(t *testing.T) {
 		expected expr.Expr
 		errored  bool
 	}{
-		{"one positional", "age = ?", expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.PositionalParam(1)), false},
+		{"one positional", "age = ?", expr.Eq(expr.Path(parsePath(t, "age")), expr.PositionalParam(1)), false},
 		{"multiple positional", "age = ? AND age <= ?",
 			expr.And(
-				expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.PositionalParam(1)),
-				expr.Lte(expr.FieldSelector(parsePath(t, "age")), expr.PositionalParam(2)),
+				expr.Eq(expr.Path(parsePath(t, "age")), expr.PositionalParam(1)),
+				expr.Lte(expr.Path(parsePath(t, "age")), expr.PositionalParam(2)),
 			), false},
-		{"one named", "age = $age", expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.NamedParam("age")), false},
+		{"one named", "age = $age", expr.Eq(expr.Path(parsePath(t, "age")), expr.NamedParam("age")), false},
 		{"multiple named", "age = $foo OR age = $bar",
 			expr.Or(
-				expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.NamedParam("foo")),
-				expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.NamedParam("bar")),
+				expr.Eq(expr.Path(parsePath(t, "age")), expr.NamedParam("foo")),
+				expr.Eq(expr.Path(parsePath(t, "age")), expr.NamedParam("bar")),
 			), false},
 		{"mixed", "age >= ? AND age > $foo OR age < ?", nil, true},
 	}
diff --git a/sql/parser/select_test.go b/sql/parser/select_test.go
index 619595d6..cd65b125 100644
--- a/sql/parser/select_test.go
+++ b/sql/parser/select_test.go
@@ -70,7 +70,7 @@ func TestParserSelect(t *testing.T) {
 			planner.NewTree(
 				planner.NewProjectionNode(
 					planner.NewTableInputNode("test"),
-					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.FieldSelector(parsePath(t, "a")), ExprName: "a"}, planner.ProjectedExpr{Expr: expr.FieldSelector(parsePath(t, "b")), ExprName: "b"}},
+					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.Path(parsePath(t, "a")), ExprName: "a"}, planner.ProjectedExpr{Expr: expr.Path(parsePath(t, "b")), ExprName: "b"}},
 					"test",
 				)),
 			false},
@@ -78,7 +78,7 @@ func TestParserSelect(t *testing.T) {
 			planner.NewTree(
 				planner.NewProjectionNode(
 					planner.NewTableInputNode("test"),
-					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.FieldSelector(parsePath(t, "`long \"path\"`")), ExprName: "long \"path\""}},
+					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.Path(parsePath(t, "`long \"path\"`")), ExprName: "long \"path\""}},
 					"test",
 				)),
 			false},
@@ -86,7 +86,7 @@ func TestParserSelect(t *testing.T) {
 			planner.NewTree(
 				planner.NewProjectionNode(
 					planner.NewTableInputNode("test"),
-					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.FieldSelector(parsePath(t, "a")), ExprName: "A"}, planner.ProjectedExpr{Expr: expr.FieldSelector(parsePath(t, "b")), ExprName: "b"}},
+					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.Path(parsePath(t, "a")), ExprName: "A"}, planner.ProjectedExpr{Expr: expr.Path(parsePath(t, "b")), ExprName: "b"}},
 					"test",
 				)),
 			false},
@@ -94,7 +94,7 @@ func TestParserSelect(t *testing.T) {
 			planner.NewTree(
 				planner.NewProjectionNode(
 					planner.NewTableInputNode("test"),
-					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.FieldSelector(parsePath(t, "a")), ExprName: "a"}, planner.ProjectedExpr{Expr: expr.FieldSelector(parsePath(t, "b")), ExprName: "b"}, planner.Wildcard{}},
+					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.Path(parsePath(t, "a")), ExprName: "a"}, planner.ProjectedExpr{Expr: expr.Path(parsePath(t, "b")), ExprName: "b"}, planner.Wildcard{}},
 					"test",
 				)),
 			false},
@@ -102,7 +102,7 @@ func TestParserSelect(t *testing.T) {
 			planner.NewTree(
 				planner.NewProjectionNode(
 					planner.NewTableInputNode("test"),
-					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.Gt(expr.FieldSelector(parsePath(t, "a")), expr.IntegerValue(1)), ExprName: "a    > 1"}},
+					[]planner.ProjectedField{planner.ProjectedExpr{Expr: expr.Gt(expr.Path(parsePath(t, "a")), expr.IntegerValue(1)), ExprName: "a    > 1"}},
 					"test",
 				)),
 			false},
@@ -111,7 +111,7 @@ func TestParserSelect(t *testing.T) {
 				planner.NewProjectionNode(
 					planner.NewSelectionNode(
 						planner.NewTableInputNode("test"),
-						expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+						expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 					),
 					[]planner.ProjectedField{planner.Wildcard{}},
 					"test",
@@ -123,9 +123,9 @@ func TestParserSelect(t *testing.T) {
 					planner.NewGroupingNode(
 						planner.NewSelectionNode(
 							planner.NewTableInputNode("test"),
-							expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+							expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 						),
-						expr.FieldSelector(parsePath(t, "a.b.c")),
+						expr.Path(parsePath(t, "a.b.c")),
 					),
 					[]planner.ProjectedField{planner.Wildcard{}},
 					"test",
@@ -137,12 +137,12 @@ func TestParserSelect(t *testing.T) {
 					planner.NewProjectionNode(
 						planner.NewSelectionNode(
 							planner.NewTableInputNode("test"),
-							expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+							expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 						),
 						[]planner.ProjectedField{planner.Wildcard{}},
 						"test",
 					),
-					expr.FieldSelector(parsePath(t, "a.b.c")),
+					expr.Path(parsePath(t, "a.b.c")),
 					scanner.ASC,
 				)),
 			false},
@@ -152,12 +152,12 @@ func TestParserSelect(t *testing.T) {
 					planner.NewProjectionNode(
 						planner.NewSelectionNode(
 							planner.NewTableInputNode("test"),
-							expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+							expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 						),
 						[]planner.ProjectedField{planner.Wildcard{}},
 						"test",
 					),
-					expr.FieldSelector(parsePath(t, "a.b.c")),
+					expr.Path(parsePath(t, "a.b.c")),
 					scanner.ASC,
 				)),
 			false},
@@ -167,12 +167,12 @@ func TestParserSelect(t *testing.T) {
 					planner.NewProjectionNode(
 						planner.NewSelectionNode(
 							planner.NewTableInputNode("test"),
-							expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+							expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 						),
 						[]planner.ProjectedField{planner.Wildcard{}},
 						"test",
 					),
-					expr.FieldSelector(parsePath(t, "a.b.c")),
+					expr.Path(parsePath(t, "a.b.c")),
 					scanner.DESC,
 				)),
 			false},
@@ -182,7 +182,7 @@ func TestParserSelect(t *testing.T) {
 					planner.NewProjectionNode(
 						planner.NewSelectionNode(
 							planner.NewTableInputNode("test"),
-							expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+							expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 						),
 						[]planner.ProjectedField{planner.Wildcard{}},
 						"test",
@@ -196,7 +196,7 @@ func TestParserSelect(t *testing.T) {
 					planner.NewProjectionNode(
 						planner.NewSelectionNode(
 							planner.NewTableInputNode("test"),
-							expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+							expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 						),
 						[]planner.ProjectedField{planner.Wildcard{}},
 						"test",
@@ -211,7 +211,7 @@ func TestParserSelect(t *testing.T) {
 						planner.NewProjectionNode(
 							planner.NewSelectionNode(
 								planner.NewTableInputNode("test"),
-								expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+								expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 							),
 							[]planner.ProjectedField{planner.Wildcard{}},
 							"test",
diff --git a/sql/parser/update_test.go b/sql/parser/update_test.go
index a042bf3e..43d23227 100644
--- a/sql/parser/update_test.go
+++ b/sql/parser/update_test.go
@@ -33,7 +33,7 @@ func TestParserUpdate(t *testing.T) {
 						planner.NewSetNode(
 							planner.NewSelectionNode(
 								planner.NewTableInputNode("test"),
-								expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+								expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 							),
 							parsePath(t, "a"), expr.IntegerValue(1),
 						),
@@ -109,7 +109,7 @@ func TestParserUpdate(t *testing.T) {
 						planner.NewUnsetNode(
 							planner.NewSelectionNode(
 								planner.NewTableInputNode("test"),
-								expr.Eq(expr.FieldSelector(parsePath(t, "age")), expr.IntegerValue(10)),
+								expr.Eq(expr.Path(parsePath(t, "age")), expr.IntegerValue(10)),
 							),
 							"a",
 						),
diff --git a/sql/planner/optimizer_test.go b/sql/planner/optimizer_test.go
index 8c5bea91..b0d1001d 100644
--- a/sql/planner/optimizer_test.go
+++ b/sql/planner/optimizer_test.go
@@ -114,17 +114,17 @@ func TestPrecalculateExprRule(t *testing.T) {
 		},
 		{
 			"constant sub-expr: a > 1 - 40 -> a > -39",
-			expr.Gt(expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}}, expr.Sub(expr.IntegerValue(1), expr.DoubleValue(40))),
-			expr.Gt(expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}}, expr.DoubleValue(-39)),
+			expr.Gt(expr.Path{document.PathFragment{FieldName: "a"}}, expr.Sub(expr.IntegerValue(1), expr.DoubleValue(40))),
+			expr.Gt(expr.Path{document.PathFragment{FieldName: "a"}}, expr.DoubleValue(-39)),
 		},
 		{
 			"non-constant expr list: [a, 1 - 40] -> [a, -39]",
 			expr.LiteralExprList{
-				expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}},
+				expr.Path{document.PathFragment{FieldName: "a"}},
 				expr.Sub(expr.IntegerValue(1), expr.DoubleValue(40)),
 			},
 			expr.LiteralExprList{
-				expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}},
+				expr.Path{document.PathFragment{FieldName: "a"}},
 				expr.DoubleValue(-39),
 			},
 		},
@@ -141,11 +141,11 @@ func TestPrecalculateExprRule(t *testing.T) {
 		{
 			`non-constant kvpair: {"a": d, "b": 1 - 40} -> {"a": 3, "b": -39}`,
 			expr.KVPairs{
-				{K: "a", V: expr.FieldSelector{document.ValuePathFragment{FieldName: "d"}}},
+				{K: "a", V: expr.Path{document.PathFragment{FieldName: "d"}}},
 				{K: "b", V: expr.Sub(expr.IntegerValue(1), expr.DoubleValue(40))},
 			},
 			expr.KVPairs{
-				{K: "a", V: expr.FieldSelector{document.ValuePathFragment{FieldName: "d"}}},
+				{K: "a", V: expr.Path{document.PathFragment{FieldName: "d"}}},
 				{K: "b", V: expr.DoubleValue(-39)},
 			},
 		},
@@ -178,8 +178,8 @@ func TestRemoveUnnecessarySelectionNodesRule(t *testing.T) {
 	}{
 		{
 			"non-constant expr",
-			planner.NewSelectionNode(planner.NewTableInputNode("foo"), expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}}),
-			planner.NewSelectionNode(planner.NewTableInputNode("foo"), expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}}),
+			planner.NewSelectionNode(planner.NewTableInputNode("foo"), expr.Path{document.PathFragment{FieldName: "a"}}),
+			planner.NewSelectionNode(planner.NewTableInputNode("foo"), expr.Path{document.PathFragment{FieldName: "a"}}),
 		},
 		{
 			"truthy constant expr",
@@ -215,7 +215,7 @@ func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 			"non-indexed path",
 			planner.NewSelectionNode(planner.NewTableInputNode("foo"),
 				expr.Eq(
-					expr.FieldSelector{document.ValuePathFragment{FieldName: "d"}},
+					expr.Path{document.PathFragment{FieldName: "d"}},
 					expr.IntegerValue(1),
 				)),
 			nil,
@@ -224,7 +224,7 @@ func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 			"FROM foo WHERE a = 1",
 			planner.NewSelectionNode(planner.NewTableInputNode("foo"),
 				expr.Eq(
-					expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}},
+					expr.Path{document.PathFragment{FieldName: "a"}},
 					expr.IntegerValue(1),
 				)),
 			planner.NewIndexInputNode(
@@ -240,12 +240,12 @@ func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 			planner.NewSelectionNode(
 				planner.NewSelectionNode(planner.NewTableInputNode("foo"),
 					expr.Eq(
-						expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}},
+						expr.Path{document.PathFragment{FieldName: "a"}},
 						expr.IntegerValue(1),
 					),
 				),
 				expr.Eq(
-					expr.FieldSelector{document.ValuePathFragment{FieldName: "b"}},
+					expr.Path{document.PathFragment{FieldName: "b"}},
 					expr.IntegerValue(2),
 				),
 			),
@@ -258,7 +258,7 @@ func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 					scanner.ASC,
 				),
 				expr.Eq(
-					expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}},
+					expr.Path{document.PathFragment{FieldName: "a"}},
 					expr.IntegerValue(1),
 				),
 			),
@@ -268,12 +268,12 @@ func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 			planner.NewSelectionNode(
 				planner.NewSelectionNode(planner.NewTableInputNode("foo"),
 					expr.Eq(
-						expr.FieldSelector{document.ValuePathFragment{FieldName: "c"}},
+						expr.Path{document.PathFragment{FieldName: "c"}},
 						expr.IntegerValue(3),
 					),
 				),
 				expr.Eq(
-					expr.FieldSelector{document.ValuePathFragment{FieldName: "b"}},
+					expr.Path{document.PathFragment{FieldName: "b"}},
 					expr.IntegerValue(2),
 				),
 			),
@@ -286,7 +286,7 @@ func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 					scanner.ASC,
 				),
 				expr.Eq(
-					expr.FieldSelector{document.ValuePathFragment{FieldName: "b"}},
+					expr.Path{document.PathFragment{FieldName: "b"}},
 					expr.IntegerValue(2),
 				),
 			),
@@ -297,18 +297,18 @@ func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 				planner.NewSelectionNode(
 					planner.NewSelectionNode(planner.NewTableInputNode("foo"),
 						expr.Eq(
-							expr.FieldSelector{document.ValuePathFragment{FieldName: "c"}},
+							expr.Path{document.PathFragment{FieldName: "c"}},
 							expr.IntegerValue(3),
 						),
 					),
 					expr.Eq(
-						expr.FieldSelector{document.ValuePathFragment{FieldName: "b"}},
+						expr.Path{document.PathFragment{FieldName: "b"}},
 						expr.IntegerValue(2),
 					),
 				),
 				[]planner.ProjectedField{
 					planner.ProjectedExpr{
-						Expr: expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}},
+						Expr: expr.Path{document.PathFragment{FieldName: "a"}},
 					},
 				},
 				"foo",
@@ -323,13 +323,13 @@ func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 						scanner.ASC,
 					),
 					expr.Eq(
-						expr.FieldSelector{document.ValuePathFragment{FieldName: "b"}},
+						expr.Path{document.PathFragment{FieldName: "b"}},
 						expr.IntegerValue(2),
 					),
 				),
 				[]planner.ProjectedField{
 					planner.ProjectedExpr{
-						Expr: expr.FieldSelector{document.ValuePathFragment{FieldName: "a"}},
+						Expr: expr.Path{document.PathFragment{FieldName: "a"}},
 					},
 				},
 				"foo",
diff --git a/sql/query/create_test.go b/sql/query/create_test.go
index c605b763..0729fd5d 100644
--- a/sql/query/create_test.go
+++ b/sql/query/create_test.go
@@ -11,7 +11,7 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func parsePath(t testing.TB, str string) document.ValuePath {
+func parsePath(t testing.TB, str string) document.Path {
 	vp, err := parser.ParsePath(str)
 	require.NoError(t, err)
 	return vp
diff --git a/sql/query/expr/expr_test.go b/sql/query/expr/expr_test.go
index bf12df81..b0c638aa 100644
--- a/sql/query/expr/expr_test.go
+++ b/sql/query/expr/expr_test.go
@@ -26,8 +26,8 @@ var stackWithDoc = expr.EvalStack{
 
 var fakeTableInfo = &database.TableInfo{
 	FieldConstraints: []database.FieldConstraint{
-		{Path: document.ValuePath{document.ValuePathFragment{FieldName: "c"}, document.ValuePathFragment{ArrayIndex: 0}}, IsPrimaryKey: true},
-		{Path: document.ValuePath{document.ValuePathFragment{FieldName: "c"}, document.ValuePathFragment{ArrayIndex: 1}}},
+		{Path: document.Path{document.PathFragment{FieldName: "c"}, document.PathFragment{ArrayIndex: 0}}, IsPrimaryKey: true},
+		{Path: document.Path{document.PathFragment{FieldName: "c"}, document.PathFragment{ArrayIndex: 1}}},
 	},
 }
 var stackWithDocAndInfo = expr.EvalStack{