Repository: genjidb/genji (go)

Issue:
Panic on SELECT with LIMIT min(0)/max(0)
`parser.ParseQuery` panics with the following input and stack trace

```
SELECT * FROM e LIMIT min(0)
SELECT * FROM t OFFSET x(*)
```

<details>
<summary>Stack trace</summary>

```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x50ec01]

goroutine 1 [running]:
github.com/genjidb/genji/sql/query/expr.(*MinFunc).Eval(0xc0000d7da0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
	/home/runner/work/genji/genji/genji/sql/query/expr/function.go:242 +0x51
github.com/genjidb/genji/sql/parser.selectConfig.ToTree(0x69fb05, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
	/home/runner/work/genji/genji/genji/sql/parser/select.go:261 +0x4ed
github.com/genjidb/genji/sql/parser.(*Parser).parseSelectStatement(0xc0001200f0, 0x55, 0x0, 0x0)
	/home/runner/work/genji/genji/genji/sql/parser/select.go:63 +0x44d
github.com/genjidb/genji/sql/parser.(*Parser).ParseStatement(0xc0001200f0, 0x55, 0x0, 0x0, 0x0)
	/home/runner/work/genji/genji/genji/sql/parser/parser.go:91 +0x8a2
github.com/genjidb/genji/sql/parser.(*Parser).ParseQuery(0xc0001200f0, 0x5bd440, 0xc00001c0a8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xc00011e080)
	/home/runner/work/genji/genji/genji/sql/parser/parser.go:70 +0x214
github.com/genjidb/genji/sql/parser.ParseQuery(0x5bd440, 0xc00001c0a8, 0xc00011e080, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc000043e70, ...)
	/home/runner/work/genji/genji/genji/sql/parser/parser.go:41 +0x117
github.com/genjidb/genji/fuzz/sqlparser.Fuzz(0x7f58e832d000, 0x1a, 0x1a, 0x3)
	/home/runner/work/genji/genji/genji/fuzz/sqlparser/fuzz.go:13 +0xe9
go-fuzz-dep.Main(0xc000043f70, 0x1, 0x1)
	go-fuzz-dep/main.go:36 +0x1ad
main.main()
	github.com/genjidb/genji/fuzz/sqlparser/go.fuzz.main/main.go:15 +0x52
```
</details>


Pull request description:
Fix panic on aggregation functions MIN() and COUNT()
This fixes panic when running aggregator functions: MIN() and COUNT().

Fixes #257 

Patch:
diff --git a/cmd/genji/go.mod b/cmd/genji/go.mod
index 73fdfa77..72dd9068 100644
--- a/cmd/genji/go.mod
+++ b/cmd/genji/go.mod
@@ -10,6 +10,8 @@ require (
 	github.com/genjidb/genji/engine/badgerengine v0.9.0
 	github.com/stretchr/testify v1.6.1
 	github.com/urfave/cli/v2 v2.2.0
+	go.uber.org/multierr v1.6.0
+	golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520
 )
 
 replace (
diff --git a/cmd/genji/go.sum b/cmd/genji/go.sum
index 9984a0db..c66df2f7 100644
--- a/cmd/genji/go.sum
+++ b/cmd/genji/go.sum
@@ -24,10 +24,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgraph-io/badger/v2 v2.0.3/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM=
 github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k=
 github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
-github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
 github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA=
 github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
@@ -37,10 +35,6 @@ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/genjidb/genji v0.8.0 h1:yDxDF8rfwDMVSI1OPZUQzUl8+9QflTym87sUXr9hw3Y=
-github.com/genjidb/genji v0.8.0/go.mod h1:7MhLPBD74B2Z9+L0xwoKbfkS6Dba/PHcZp+vpjoZiyY=
-github.com/genjidb/genji/engine/badgerengine v0.8.0 h1:cYNTxOUTRQo5cRpLVrA8yS/OLv958ahsQatXdvjG9dk=
-github.com/genjidb/genji/engine/badgerengine v0.8.0/go.mod h1:1Rs9U2ahauFJu2ByWDlR34Pa+urEElGzT1hbVo8FEaA=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
@@ -95,6 +89,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
@@ -110,13 +105,20 @@ github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgq
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
 go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 h1:Bx6FllMpG4NWDOfhMBz1VR2QYNp/SAOHPIAsaVmxfPo=
+golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/cmd/genji/main.go b/cmd/genji/main.go
index bd8edde2..c251ead8 100644
--- a/cmd/genji/main.go
+++ b/cmd/genji/main.go
@@ -137,7 +137,7 @@ $ curl https://api.github.com/repos/genjidb/genji/issues | genji insert --db my.
 			engine = "badger"
 		}
 
-		return shell.Run(&shell.Options{
+		return shell.Run(c.Context, &shell.Options{
 			Engine: engine,
 			DBPath: dbpath,
 		})
diff --git a/cmd/genji/shell/command.go b/cmd/genji/shell/command.go
index 6d1aa3e5..cce41386 100644
--- a/cmd/genji/shell/command.go
+++ b/cmd/genji/shell/command.go
@@ -363,11 +363,7 @@ func runDumpCmd(db *genji.DB, tables []string, w io.Writer) error {
 			return err
 		}
 
-		if err := dumpTable(tx, tableName, w); err != nil {
-			return err
-		}
-
-		return nil
+		return dumpTable(tx, tableName, w)
 	})
 	if err != nil {
 		_, err = fmt.Fprintln(w, "ROLLBACK;")
diff --git a/cmd/genji/shell/shell.go b/cmd/genji/shell/shell.go
index 645b6207..6bce276f 100644
--- a/cmd/genji/shell/shell.go
+++ b/cmd/genji/shell/shell.go
@@ -4,11 +4,15 @@ import (
 	"bufio"
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"os"
+	"os/signal"
 	"path/filepath"
 	"strings"
+	"sync"
+	"syscall"
 
 	"github.com/c-bata/go-prompt"
 	"github.com/dgraph-io/badger/v2"
@@ -19,12 +23,23 @@ import (
 	"github.com/genjidb/genji/engine/boltengine"
 	"github.com/genjidb/genji/engine/memoryengine"
 	"github.com/genjidb/genji/sql/parser"
+	"go.uber.org/multierr"
+	"golang.org/x/sync/errgroup"
 )
 
 const (
 	historyFilename = ".genji_history"
 )
 
+var (
+	// error returned when the exit command is executed
+	errExitCommand = errors.New("exit command")
+	// error returned when the program received a termination signal
+	errExitSignal = errors.New("termination signal received")
+	// error returned when the prompt reads a ctrl d input
+	errExitCtrlD = errors.New("ctrl-d")
+)
+
 // A Shell manages a command line shell program for manipulating a Genji database.
 type Shell struct {
 	db   *genji.DB
@@ -37,6 +52,13 @@ type Shell struct {
 	history []string
 
 	cmdSuggestions []prompt.Suggest
+
+	// context used for execution cancelation,
+	// these must not be used manually.
+	// Use getExecContext and cancelExecContext instead.
+	execContext  context.Context
+	execCancelFn func()
+	mu           sync.Mutex
 }
 
 // Options of the shell.
@@ -77,12 +99,12 @@ func stdinFromTerminal() bool {
 }
 
 // Run a shell.
-func Run(opts *Options) error {
+func Run(ctx context.Context, opts *Options) (err error) {
 	if opts == nil {
 		opts = new(Options)
 	}
 
-	err := opts.validate()
+	err = opts.validate()
 	if err != nil {
 		return err
 	}
@@ -103,25 +125,144 @@ func Run(opts *Options) error {
 		fmt.Println("Enter \".help\" for usage hints.")
 	}
 
-	sh.loadCommandSuggestions()
-	history, err := sh.loadHistory()
+	ran, err := sh.runPipedInput(ctx)
 	if err != nil {
 		return err
 	}
+	if ran {
+		return nil
+	}
+
+	defer func() {
+		if sh.db != nil {
+			closeErr := sh.db.Close()
+			if closeErr != nil {
+				err = multierr.Append(err, closeErr)
+			}
+		}
+	}()
+
+	defer func() {
+		dumpErr := sh.dumpHistory()
+		if dumpErr != nil {
+			err = multierr.Append(err, dumpErr)
+		}
+	}()
+
+	promptExecCh := make(chan string)
+
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	g, ctx := errgroup.WithContext(ctx)
+
+	g.Go(func() error {
+		return sh.runSignalHandlers(ctx)
+	})
+
+	g.Go(func() error {
+		return sh.runExecutor(ctx, promptExecCh)
+	})
+
+	// Because go-prompt doesn't handle cancelation
+	// it is impossible to ask it to stop when the prompt.Input function
+	// is running.
+	// We run it in a non-managed goroutine with no graceful shutdown
+	// so that if the prompt.Input function is running when we want to quit the program
+	// we simply don't wait for this goroutine to end.
+	// This goroutine must not manage any resource.
+	go func() {
+		defer cancel()
+
+		err := sh.runPrompt(ctx, promptExecCh)
+		if err != nil && err != errExitCtrlD {
+			fmt.Fprintf(os.Stderr, err.Error())
+		}
+	}()
+
+	err = g.Wait()
+	if err == errExitCommand || err == errExitSignal || err == context.Canceled {
+		return nil
+	}
+
+	return err
+}
+
+// runSignalHandlers handles two different signals.
+// On SIGINT, it cancels any query execution using sh.cancelExecution.
+// On SIGTERM, it triggers graceful shutdown by returning errExitSignal.
+func (sh *Shell) runSignalHandlers(ctx context.Context) error {
+	interruptC := make(chan os.Signal, 1)
+	termC := make(chan os.Signal, 1)
+	signal.Notify(interruptC, os.Interrupt)
+	signal.Notify(termC, syscall.SIGTERM)
+
+	for {
+		select {
+		case <-interruptC:
+			sh.cancelExecution()
+		case <-termC:
+			fmt.Fprintf(os.Stderr, "\nTermination signal received. Quitting...\n")
+			return errExitSignal
+		case <-ctx.Done():
+			return ctx.Err()
+		}
+	}
+}
+
+// runExecutor manages execution. It reads user input from promptExecCh, executes any
+// command or query and writes back an empty string to that channel once it's done.
+func (sh *Shell) runExecutor(ctx context.Context, promptExecCh chan string) error {
+	for {
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case input := <-promptExecCh:
+			err := sh.executeInput(sh.getExecContext(ctx), input)
+			// if the context has been canceled
+			// there is no way to tell at this point
+			// if this is because of a user interruption
+			// or a termination signal.
+			// If it's the latter, it will be detected by the Select statement.
+			if err == context.Canceled {
+				// Print a newline for cleanliness
+				fmt.Println()
+				continue
+			}
+			if err == errExitCommand {
+				return err
+			}
+			if err != nil {
+				fmt.Println(err)
+			}
+		case promptExecCh <- "":
+		}
+	}
+}
 
-	ran, err := sh.runPipedInput()
+// runPrompt is a stateless function that displays a prompt to the user.
+// User input is sent to the execCh channel which must deal with parsing and error handling.
+// Once the execution of the user input is done by the reader of the channel, it must
+// send a string back to execCh so that this function will display another prompt.
+func (sh *Shell) runPrompt(ctx context.Context, execCh chan (string)) error {
+	sh.loadCommandSuggestions()
+	history, err := sh.loadHistory()
 	if err != nil {
 		return err
 	}
-	if ran {
-		return nil
-	}
+
+	// we store the last key stroke to
+	// determine if ctrl D was pressed by the user.
+	var lastKeyStroke prompt.Key
 
 	promptOpts := []prompt.Option{
 		prompt.OptionPrefix("genji> "),
-		prompt.OptionTitle("genji"),
+		prompt.OptionTitle("Genji"),
 		prompt.OptionLivePrefix(sh.changelivePrefix),
 		prompt.OptionHistory(history),
+		prompt.OptionBreakLineCallback(func(d *prompt.Document) {
+			lastKeyStroke = d.LastKeyStroke()
+		}),
 	}
 
 	// If NO_COLOR env var is present, disable color. See https://no-color.org
@@ -147,22 +288,60 @@ func Run(opts *Options) error {
 		}
 	}
 
-	e := prompt.New(
-		sh.execute,
-		sh.completer,
-		promptOpts...,
-	)
+	pt := prompt.New(func(in string) {}, sh.completer, append(promptOpts, prompt.OptionHistory(history))...)
 
-	e.Run()
+	for {
+		// Input() captures ctrl D and ctrl C.
+		// It never returns when ctrl C is pressed but does on CTRL D
+		// under specific conditions.
+		input := pt.Input()
 
-	if sh.db != nil {
-		err = sh.db.Close()
-		if err != nil {
-			return err
+		// go-prompt ignores ctrl D it if it was pressed while the line is not empty.
+		// However, it returns if the line is empty and sets lastKeyStroke to prompt.ControlD.
+		// if so, we must stop the program.
+		if lastKeyStroke == prompt.ControlD {
+			return errExitCtrlD
+		}
+
+		input = strings.TrimSpace(input)
+
+		if len(input) == 0 {
+			continue
 		}
+
+		// deleguate execution to the sh.runExecutor goroutine
+		execCh <- input
+		// and wait for it to finish to display another prompt.
+		<-execCh
+	}
+}
+
+// cancelExecution must be called to cancel any ongoing execution without
+// stopping the program.
+// Calling this function when there is no ongoing execution is a no-op.
+func (sh *Shell) cancelExecution() {
+	sh.mu.Lock()
+	defer sh.mu.Unlock()
+
+	if sh.execCancelFn != nil {
+		sh.execCancelFn()
+		sh.execContext = nil
+		sh.execCancelFn = nil
+	}
+}
+
+// getExecContext returns the current cancelable execution context
+// or creates one if needed.
+func (sh *Shell) getExecContext(ctx context.Context) context.Context {
+	sh.mu.Lock()
+	defer sh.mu.Unlock()
+
+	if sh.execContext != nil {
+		return sh.execContext
 	}
 
-	return sh.dumpHistory()
+	sh.execContext, sh.execCancelFn = context.WithCancel(ctx)
+	return sh.execContext
 }
 
 func (sh *Shell) loadCommandSuggestions() {
@@ -240,30 +419,23 @@ func (sh *Shell) dumpHistory() error {
 	return w.Flush()
 }
 
-func (sh *Shell) execute(in string) {
+// executeInput stores user input in the history and executes it.
+func (sh *Shell) executeInput(ctx context.Context, in string) error {
 	sh.history = append(sh.history, in)
 
-	err := sh.executeInput(in)
-	if err != nil {
-		fmt.Println(err)
-	}
-}
-
-func (sh *Shell) executeInput(in string) error {
-	in = strings.TrimSpace(in)
 	switch {
 	// if it starts with a "." it's a command
 	// if the input is "help" or "exit", then it's a command.
 	// it must not be in the middle of a multi line query though
-	case strings.HasPrefix(in, "."), in == "help", in == "exit":
-		return sh.runCommand(in)
+	case !sh.multiLine && strings.HasPrefix(in, "."), in == "help", in == "exit":
+		return sh.runCommand(ctx, in)
 
 	// If it ends with a ";" we can run a query
 	case strings.HasSuffix(in, ";"):
 		sh.query = sh.query + in
 		sh.multiLine = false
 		sh.livePrefix = in
-		err := sh.runQuery(sh.query)
+		err := sh.runQuery(ctx, sh.query)
 		sh.query = ""
 		return err
 	// If the input is empty we ignore it
@@ -281,7 +453,7 @@ func (sh *Shell) executeInput(in string) error {
 	return nil
 }
 
-func (sh *Shell) runCommand(in string) error {
+func (sh *Shell) runCommand(ctx context.Context, in string) error {
 	in = strings.TrimSuffix(in, ";")
 	cmd := strings.Fields(in)
 	switch cmd[0] {
@@ -299,7 +471,7 @@ func (sh *Shell) runCommand(in string) error {
 			return fmt.Errorf("usage: .exit")
 		}
 
-		sh.exit()
+		return errExitCommand
 	case ".indexes":
 		db, err := sh.getDB()
 		if err != nil {
@@ -316,17 +488,15 @@ func (sh *Shell) runCommand(in string) error {
 	default:
 		return displaySuggestions(in)
 	}
-
-	return fmt.Errorf("unknown command %q", cmd)
 }
 
-func (sh *Shell) runQuery(q string) error {
+func (sh *Shell) runQuery(ctx context.Context, q string) error {
 	db, err := sh.getDB()
 	if err != nil {
 		return err
 	}
 
-	res, err := db.Query(context.Background(), q)
+	res, err := db.Query(ctx, q)
 	if err != nil {
 		return err
 	}
@@ -337,24 +507,14 @@ func (sh *Shell) runQuery(q string) error {
 	enc.SetEscapeHTML(false)
 	enc.SetIndent("", "  ")
 	return res.Iterate(func(d document.Document) error {
-		return enc.Encode(d)
-	})
-}
-
-func (sh *Shell) exit() {
-	if sh.db != nil {
-		err := sh.db.Close()
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+		select {
+		case <-ctx.Done():
+			return errors.New("interrupted")
+		default:
 		}
-	}
-
-	err := sh.dumpHistory()
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
-	}
 
-	os.Exit(0)
+		return enc.Encode(d)
+	})
 }
 
 func (sh *Shell) getDB() (*genji.DB, error) {
@@ -374,20 +534,18 @@ func (sh *Shell) getDB() (*genji.DB, error) {
 		ng, err = badgerengine.NewEngine(badger.DefaultOptions(sh.opts.DBPath).WithLogger(nil))
 	}
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "%v\n", err)
-		os.Exit(2)
+		return nil, err
 	}
 
 	sh.db, err = genji.New(ng)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "%v\n", err)
-		os.Exit(2)
+		return nil, err
 	}
 
 	return sh.db, nil
 }
 
-func (sh *Shell) runPipedInput() (ran bool, err error) {
+func (sh *Shell) runPipedInput(ctx context.Context) (ran bool, err error) {
 	// Check if there is any input being piped in from the terminal
 	stat, _ := os.Stdin.Stat()
 	m := stat.Mode()
@@ -399,7 +557,7 @@ func (sh *Shell) runPipedInput() (ran bool, err error) {
 	if err != nil {
 		return true, fmt.Errorf("Unable to read piped input: %w", err)
 	}
-	err = sh.runQuery(string(data))
+	err = sh.runQuery(ctx, string(data))
 	if err != nil {
 		return true, fmt.Errorf("Unable to execute provided sql statements: %w", err)
 	}
diff --git a/database/table.go b/database/table.go
index 515a5ac0..2552d0a9 100644
--- a/database/table.go
+++ b/database/table.go
@@ -514,7 +514,7 @@ func validateConstraint(d document.Document, c *FieldConstraint) error {
 		}
 	case document.ArrayValue:
 		// if it's an array, we can assume it's a ValueBuffer
-		buf := parent.V.(*document.ValueBuffer)
+		buf := parent.V.(document.ValueBuffer)
 
 		frag := c.Path[len(c.Path)-1]
 		if frag.FieldName != "" {
diff --git a/document/array.go b/document/array.go
index 58bf0683..b1ee610b 100644
--- a/document/array.go
+++ b/document/array.go
@@ -68,6 +68,11 @@ type ValueBuffer []Value
 
 // NewValueBuffer creates a buffer of values.
 func NewValueBuffer(values ...Value) ValueBuffer {
+	if len(values) == 0 {
+		// If called with no values return a non-nil slice.
+		return ValueBuffer{}
+	}
+
 	return ValueBuffer(values)
 }
 
@@ -141,7 +146,7 @@ func (vb *ValueBuffer) Copy(a Array) error {
 				return err
 			}
 
-			err = vb.Replace(i, NewArrayValue(&buf))
+			err = vb.Replace(i, NewArrayValue(buf))
 			if err != nil {
 				return err
 			}
diff --git a/document/create.go b/document/create.go
index ce4014b9..07a3e1e5 100644
--- a/document/create.go
+++ b/document/create.go
@@ -9,16 +9,43 @@ import (
 	"reflect"
 	"strings"
 	"time"
+
+	"github.com/buger/jsonparser"
 )
 
-// NewFromJSON creates a document from a JSON object.
-func NewFromJSON(data []byte) (Document, error) {
-	var fb FieldBuffer
-	err := fb.UnmarshalJSON(data)
+// NewFromJSON creates a document from raw JSON data.
+// The returned document will lazily decode the data.
+// If data is not a valid json object, calls to Iterate or GetByField will
+// return an error.
+func NewFromJSON(data []byte) Document {
+	return &jsonEncodedDocument{data}
+}
+
+type jsonEncodedDocument struct {
+	data []byte
+}
+
+func (j jsonEncodedDocument) Iterate(fn func(field string, value Value) error) error {
+	return jsonparser.ObjectEach(j.data, func(key, value []byte, dataType jsonparser.ValueType, offset int) error {
+		v, err := parseJSONValue(dataType, value)
+		if err != nil {
+			return err
+		}
+
+		return fn(string(key), v)
+	})
+}
+
+func (j jsonEncodedDocument) GetByField(field string) (Value, error) {
+	v, dt, _, err := jsonparser.Get(j.data, field)
+	if dt == jsonparser.NotExist {
+		return Value{}, ErrFieldNotFound
+	}
 	if err != nil {
-		return nil, err
+		return Value{}, err
 	}
-	return &fb, nil
+
+	return parseJSONValue(dt, v)
 }
 
 // NewFromMap creates a document from a map.
diff --git a/document/document.go b/document/document.go
index 648a32d4..7b9c930e 100644
--- a/document/document.go
+++ b/document/document.go
@@ -290,7 +290,7 @@ func (fb *FieldBuffer) Copy(d Document) error {
 				return err
 			}
 
-			fb.fields[i].Value = NewArrayValue(&buf)
+			fb.fields[i].Value = NewArrayValue(buf)
 		}
 	}
 
diff --git a/document/value.go b/document/value.go
index c38c96c4..6d920468 100644
--- a/document/value.go
+++ b/document/value.go
@@ -258,7 +258,17 @@ func (v Value) MarshalJSON() ([]byte, error) {
 			}
 		}
 
-		return strconv.AppendFloat(nil, v.V.(float64), fmt, -1, 64), nil
+		// By default the precision is -1 to use the smallest number of digits.
+		// See https://pkg.go.dev/strconv#FormatFloat
+		prec := -1
+
+		// If fractional floating-point is 0, the precision should be 1 to add a trailing .0
+		_, frac := math.Modf(f)
+		if frac == 0 {
+			prec = 1
+		}
+
+		return strconv.AppendFloat(nil, v.V.(float64), fmt, prec, 64), nil
 	case TextValue:
 		return []byte(strconv.Quote(v.V.(string))), nil
 	case BlobValue:
diff --git a/sql/query/expr/function.go b/sql/query/expr/function.go
index d005c0b1..89b22b56 100644
--- a/sql/query/expr/function.go
+++ b/sql/query/expr/function.go
@@ -159,6 +159,9 @@ type CountFunc struct {
 }
 
 func (c *CountFunc) Eval(ctx EvalStack) (document.Value, error) {
+	if ctx.Document == nil {
+		return document.Value{}, errors.New("misuse of aggregation function COUNT()")
+	}
 	return ctx.Document.GetByField(c.String())
 }
 
@@ -239,6 +242,9 @@ type MinFunc struct {
 
 // Eval extracts the min value from the given document and returns it.
 func (m *MinFunc) Eval(ctx EvalStack) (document.Value, error) {
+	if ctx.Document == nil {
+		return document.Value{}, errors.New("misuse of aggregation function MIN()")
+	}
 	return ctx.Document.GetByField(m.String())
 }
 
@@ -336,6 +342,9 @@ type MaxFunc struct {
 
 // Eval extracts the max value from the given document and returns it.
 func (m *MaxFunc) Eval(ctx EvalStack) (document.Value, error) {
+	if ctx.Document == nil {
+		return document.Value{}, errors.New("misuse of aggregation function MAX()")
+	}
 	return ctx.Document.GetByField(m.String())
 }
 
@@ -433,6 +442,9 @@ type SumFunc struct {
 
 // Eval extracts the sum value from the given document and returns it.
 func (s *SumFunc) Eval(ctx EvalStack) (document.Value, error) {
+	if ctx.Document == nil {
+		return document.Value{}, errors.New("misuse of aggregation function SUM()")
+	}
 	return ctx.Document.GetByField(s.String())
 }
 
@@ -545,6 +557,9 @@ type AvgFunc struct {
 
 // Eval extracts the average value from the given document and returns it.
 func (s *AvgFunc) Eval(ctx EvalStack) (document.Value, error) {
+	if ctx.Document == nil {
+		return document.Value{}, errors.New("misuse of aggregation function AVG()")
+	}
 	return ctx.Document.GetByField(s.String())
 }
 
diff --git a/sql/query/expr/path.go b/sql/query/expr/path.go
index a0d7289e..1c3c5b16 100644
--- a/sql/query/expr/path.go
+++ b/sql/query/expr/path.go
@@ -24,7 +24,7 @@ func (f FieldSelector) Eval(stack EvalStack) (document.Value, error) {
 		return nullLitteral, nil
 	}
 
-	return v, nil
+	return v, err
 }
 
 // IsEqual compares this expression with the other expression and returns


Test patch:
diff --git a/document/create_test.go b/document/create_test.go
new file mode 100644
index 00000000..b85ae5d0
--- /dev/null
+++ b/document/create_test.go
@@ -0,0 +1,143 @@
+package document_test
+
+import (
+	"testing"
+
+	"github.com/genjidb/genji/document"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNewFromJSON(t *testing.T) {
+	tests := []struct {
+		name     string
+		data     string
+		expected *document.FieldBuffer
+		fails    bool
+	}{
+		{"empty object", "{}", document.NewFieldBuffer(), false},
+		{"empty object, missing closing bracket", "{", nil, true},
+		{"classic object", `{"a": 1, "b": true, "c": "hello", "d": [1, 2, 3], "e": {"f": "g"}}`,
+			document.NewFieldBuffer().
+				Add("a", document.NewIntegerValue(1)).
+				Add("b", document.NewBoolValue(true)).
+				Add("c", document.NewTextValue("hello")).
+				Add("d", document.NewArrayValue(document.NewValueBuffer().
+					Append(document.NewIntegerValue(1)).
+					Append(document.NewIntegerValue(2)).
+					Append(document.NewIntegerValue(3)))).
+				Add("e", document.NewDocumentValue(document.NewFieldBuffer().Add("f", document.NewTextValue("g")))),
+			false},
+		{"string values", `{"a": "hello ciao"}`, document.NewFieldBuffer().Add("a", document.NewTextValue("hello ciao")), false},
+		{"+integer values", `{"a": 1000}`, document.NewFieldBuffer().Add("a", document.NewIntegerValue(1000)), false},
+		{"-integer values", `{"a": -1000}`, document.NewFieldBuffer().Add("a", document.NewIntegerValue(-1000)), false},
+		{"+float values", `{"a": 10000000000.0}`, document.NewFieldBuffer().Add("a", document.NewDoubleValue(10000000000)), false},
+		{"-float values", `{"a": -10000000000.0}`, document.NewFieldBuffer().Add("a", document.NewDoubleValue(-10000000000)), false},
+		{"bool values", `{"a": true, "b": false}`, document.NewFieldBuffer().Add("a", document.NewBoolValue(true)).Add("b", document.NewBoolValue(false)), false},
+		{"empty arrays", `{"a": []}`, document.NewFieldBuffer().Add("a", document.NewArrayValue(document.NewValueBuffer())), false},
+		{"nested arrays", `{"a": [[1,  2]]}`, document.NewFieldBuffer().
+			Add("a", document.NewArrayValue(
+				document.NewValueBuffer().
+					Append(document.NewArrayValue(
+						document.NewValueBuffer().
+							Append(document.NewIntegerValue(1)).
+							Append(document.NewIntegerValue(2)))))), false},
+		{"missing comma", `{"a": 1 "b": 2}`, nil, true},
+		{"missing closing brackets", `{"a": 1, "b": 2`, nil, true},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			d := document.NewFromJSON([]byte(test.data))
+
+			fb := document.NewFieldBuffer()
+			err := fb.Copy(d)
+
+			if test.fails {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+				require.Equal(t, *test.expected, *fb)
+			}
+		})
+	}
+
+	t.Run("GetByField", func(t *testing.T) {
+		d := document.NewFromJSON([]byte(`{"a": 1000}`))
+
+		v, err := d.GetByField("a")
+		require.NoError(t, err)
+		require.Equal(t, document.NewIntegerValue(1000), v)
+
+		v, err = d.GetByField("b")
+		require.Equal(t, document.ErrFieldNotFound, err)
+	})
+}
+
+func TestNewFromMap(t *testing.T) {
+	m := map[string]interface{}{
+		"name":     "foo",
+		"age":      10,
+		"nilField": nil,
+	}
+
+	doc, err := document.NewFromMap(m)
+	require.NoError(t, err)
+
+	t.Run("Iterate", func(t *testing.T) {
+		counter := make(map[string]int)
+
+		err := doc.Iterate(func(f string, v document.Value) error {
+			counter[f]++
+			switch f {
+			case "name":
+				require.Equal(t, m[f], v.V.(string))
+			default:
+				require.EqualValues(t, m[f], v.V)
+			}
+			return nil
+		})
+		require.NoError(t, err)
+		require.Len(t, counter, 3)
+		require.Equal(t, counter["name"], 1)
+		require.Equal(t, counter["age"], 1)
+		require.Equal(t, counter["nilField"], 1)
+	})
+
+	t.Run("GetByField", func(t *testing.T) {
+		v, err := doc.GetByField("name")
+		require.NoError(t, err)
+		require.Equal(t, document.NewTextValue("foo"), v)
+
+		v, err = doc.GetByField("age")
+		require.NoError(t, err)
+		require.Equal(t, document.NewIntegerValue(10), v)
+
+		v, err = doc.GetByField("nilField")
+		require.NoError(t, err)
+		require.Equal(t, document.NewNullValue(), v)
+
+		_, err = doc.GetByField("bar")
+		require.Equal(t, document.ErrFieldNotFound, err)
+	})
+
+	t.Run("Invalid types", func(t *testing.T) {
+
+		// test NewFromMap rejects invalid types
+		_, err = document.NewFromMap(8)
+		require.Error(t, err, "Expected document.NewFromMap to return an error if the passed parameter is not a map")
+		_, err = document.NewFromMap(map[int]float64{2: 4.3})
+		require.Error(t, err, "Expected document.NewFromMap to return an error if the passed parameter is not a map with a string key type")
+	})
+}
+
+func BenchmarkJSONToDocument(b *testing.B) {
+	data := []byte(`{"_id":"5f8aefb8e443c6c13afdb305","index":0,"guid":"42c2719e-3371-4b2f-b855-d302a8b7eab0","isActive":true,"balance":"$1,064.79","picture":"http://placehold.it/32x32","age":40,"eyeColor":"blue","name":"Adele Webb","gender":"female","company":"EXTRAGEN","email":"adelewebb@extragen.com","phone":"+1 (964) 409-2397","address":"970 Charles Place, Watrous, Texas, 2522","about":"Amet non do ullamco duis velit sunt esse et cillum nisi mollit ea magna. Tempor ut occaecat proident laborum velit nisi et excepteur exercitation non est labore. Laboris pariatur enim proident et. Qui minim enim et incididunt incididunt adipisicing tempor. Occaecat adipisicing sint ex ut exercitation exercitation voluptate. Laboris adipisicing ut cillum eu cillum est sunt amet Lorem quis pariatur.\r\n","registered":"2016-05-25T10:36:44 -04:00","latitude":64.57112,"longitude":176.136138,"tags":["velit","minim","eiusmod","est","eu","voluptate","deserunt"],"friends":[{"id":0,"name":"Mathis Robertson"},{"id":1,"name":"Cecilia Donaldson"},{"id":2,"name":"Joann Goodwin"}],"greeting":"Hello, Adele Webb! You have 2 unread messages.","favoriteFruit":"apple"}`)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		d := document.NewFromJSON(data)
+		d.Iterate(func(string, document.Value) error {
+			return nil
+		})
+	}
+}
diff --git a/document/document_test.go b/document/document_test.go
index ec5784cd..a95c3a9b 100644
--- a/document/document_test.go
+++ b/document/document_test.go
@@ -83,7 +83,6 @@ func TestFieldBuffer(t *testing.T) {
 	})
 
 	t.Run("Set", func(t *testing.T) {
-
 		tests := []struct {
 			name  string
 			data  string
@@ -118,9 +117,8 @@ func TestFieldBuffer(t *testing.T) {
 			t.Run(tt.name, func(t *testing.T) {
 				var fb document.FieldBuffer
 
-				d, err := document.NewFromJSON([]byte(tt.data))
-				require.NoError(t, err)
-				err = fb.Copy(d)
+				d := document.NewFromJSON([]byte(tt.data))
+				err := fb.Copy(d)
 				require.NoError(t, err)
 				p, err := parser.ParsePath(tt.path)
 				require.NoError(t, err)
@@ -227,63 +225,6 @@ func TestFieldBuffer(t *testing.T) {
 	})
 }
 
-func TestNewFromMap(t *testing.T) {
-	m := map[string]interface{}{
-		"name":     "foo",
-		"age":      10,
-		"nilField": nil,
-	}
-
-	doc, err := document.NewFromMap(m)
-	require.NoError(t, err)
-
-	t.Run("Iterate", func(t *testing.T) {
-		counter := make(map[string]int)
-
-		err := doc.Iterate(func(f string, v document.Value) error {
-			counter[f]++
-			switch f {
-			case "name":
-				require.Equal(t, m[f], v.V.(string))
-			default:
-				require.EqualValues(t, m[f], v.V)
-			}
-			return nil
-		})
-		require.NoError(t, err)
-		require.Len(t, counter, 3)
-		require.Equal(t, counter["name"], 1)
-		require.Equal(t, counter["age"], 1)
-		require.Equal(t, counter["nilField"], 1)
-	})
-
-	t.Run("GetByField", func(t *testing.T) {
-		v, err := doc.GetByField("name")
-		require.NoError(t, err)
-		require.Equal(t, document.NewTextValue("foo"), v)
-
-		v, err = doc.GetByField("age")
-		require.NoError(t, err)
-		require.Equal(t, document.NewIntegerValue(10), v)
-
-		v, err = doc.GetByField("nilField")
-		require.NoError(t, err)
-		require.Equal(t, document.NewNullValue(), v)
-
-		_, err = doc.GetByField("bar")
-		require.Equal(t, document.ErrFieldNotFound, err)
-	})
-
-	t.Run("Invalid types", func(t *testing.T) {
-
-		// test NewFromMap rejects invalid types
-		_, err = document.NewFromMap(8)
-		require.Error(t, err, "Expected document.NewFromMap to return an error if the passed parameter is not a map")
-		_, err = document.NewFromMap(map[int]float64{2: 4.3})
-		require.Error(t, err, "Expected document.NewFromMap to return an error if the passed parameter is not a map with a string key type")
-	})
-}
-
 func TestNewFromStruct(t *testing.T) {
 	type group struct {
 		A int
diff --git a/document/value_test.go b/document/value_test.go
index b6750b4a..4b9a502f 100644
--- a/document/value_test.go
+++ b/document/value_test.go
@@ -21,6 +21,8 @@ func TestValueString(t *testing.T) {
 		{"bool", document.NewBoolValue(true), "true"},
 		{"int", document.NewIntegerValue(10), "10"},
 		{"double", document.NewDoubleValue(10.1), "10.1"},
+		{"double with trailing .0", document.NewDoubleValue(10), "10.0"},
+		{"big double with trailing .0", document.NewDoubleValue(1e21), "1.0e+21"},
 		{"document", document.NewDocumentValue(document.NewFieldBuffer().Add("a", document.NewIntegerValue(10))), "{\"a\": 10}"},
 		{"array", document.NewArrayValue(document.NewValueBuffer(document.NewIntegerValue(10))), "[10]"},
 	}
diff --git a/engine/badgerengine/engine_test.go b/engine/badgerengine/engine_test.go
index 2016cacd..43f22702 100644
--- a/engine/badgerengine/engine_test.go
+++ b/engine/badgerengine/engine_test.go
@@ -3,7 +3,7 @@ package badgerengine_test
 import (
 	"io/ioutil"
 	"os"
-	"path"
+	"path/filepath"
 	"testing"
 
 	"github.com/dgraph-io/badger/v2"
@@ -16,7 +16,7 @@ import (
 func builder(t testing.TB) func() (engine.Engine, func()) {
 	return func() (engine.Engine, func()) {
 		dir, cleanup := tempDir(t)
-		opts := badger.DefaultOptions(path.Join(dir, "badger"))
+		opts := badger.DefaultOptions(filepath.Join(dir, "badger"))
 		opts.Logger = nil
 
 		ng, err := badgerengine.NewEngine(opts)
diff --git a/engine/badgerengine/example_test.go b/engine/badgerengine/example_test.go
index 37171a41..e639bf73 100644
--- a/engine/badgerengine/example_test.go
+++ b/engine/badgerengine/example_test.go
@@ -4,7 +4,7 @@ import (
 	"io/ioutil"
 	"log"
 	"os"
-	"path"
+	"path/filepath"
 
 	"github.com/dgraph-io/badger/v2"
 	"github.com/genjidb/genji"
@@ -18,7 +18,7 @@ func Example() {
 	}
 	defer os.RemoveAll(dir)
 
-	ng, err := badgerengine.NewEngine(badger.DefaultOptions(path.Join(dir, "badger")))
+	ng, err := badgerengine.NewEngine(badger.DefaultOptions(filepath.Join(dir, "badger")))
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/engine/boltengine/engine_test.go b/engine/boltengine/engine_test.go
index 9f89ef6d..d31e520c 100644
--- a/engine/boltengine/engine_test.go
+++ b/engine/boltengine/engine_test.go
@@ -3,7 +3,7 @@ package boltengine_test
 import (
 	"io/ioutil"
 	"os"
-	"path"
+	"path/filepath"
 	"testing"
 
 	"github.com/genjidb/genji/engine"
@@ -15,7 +15,7 @@ import (
 func builder(t testing.TB) func() (engine.Engine, func()) {
 	return func() (engine.Engine, func()) {
 		dir, cleanup := tempDir(t)
-		ng, err := boltengine.NewEngine(path.Join(dir, "test.db"), 0600, nil)
+		ng, err := boltengine.NewEngine(filepath.Join(dir, "test.db"), 0o600, nil)
 		require.NoError(t, err)
 		return ng, cleanup
 	}
diff --git a/engine/boltengine/example_test.go b/engine/boltengine/example_test.go
index c4fa2731..fe9d4d70 100644
--- a/engine/boltengine/example_test.go
+++ b/engine/boltengine/example_test.go
@@ -4,7 +4,7 @@ import (
 	"io/ioutil"
 	"log"
 	"os"
-	"path"
+	"path/filepath"
 
 	"github.com/genjidb/genji"
 	"github.com/genjidb/genji/engine/boltengine"
@@ -17,7 +17,7 @@ func Example() {
 	}
 	defer os.RemoveAll(dir)
 
-	db, err := genji.Open(path.Join(dir, "my.db"))
+	db, err := genji.Open(filepath.Join(dir, "my.db"))
 	defer db.Close()
 	if err != nil {
 		log.Fatal(err)
@@ -31,7 +31,7 @@ func ExampleNewEngine() {
 	}
 	defer os.RemoveAll(dir)
 
-	ng, err := boltengine.NewEngine(path.Join(dir, "genji.db"), 0600, nil)
+	ng, err := boltengine.NewEngine(filepath.Join(dir, "genji.db"), 0o600, nil)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/engine/enginetest/benchmark.go b/engine/enginetest/benchmark.go
index 8b354aef..7bd661a6 100644
--- a/engine/enginetest/benchmark.go
+++ b/engine/enginetest/benchmark.go
@@ -17,13 +17,14 @@ func BenchmarkStorePut(b *testing.B, builder Builder) {
 		b.Run(fmt.Sprintf("%.05d", size), func(b *testing.B) {
 			for i := 0; i < b.N; i++ {
 				st, cleanup := storeBuilder(b, builder)
+				defer cleanup()
 
+				b.ResetTimer()
 				for j := 0; j < size; j++ {
 					k := []byte(fmt.Sprintf("k%d", j))
-
 					st.Put(k, v)
 				}
-				cleanup()
+				b.StopTimer()
 			}
 		})
 	}
diff --git a/engine/enginetest/testing.go b/engine/enginetest/testing.go
index e2fdc468..474cef44 100644
--- a/engine/enginetest/testing.go
+++ b/engine/enginetest/testing.go
@@ -14,7 +14,8 @@ import (
 )
 
 // Builder is a function that can create an engine on demand and that provides
-// a function to cleanup up and remove any created state.
+// a function to cleanup up and remove any created state. Note that the engine
+// is not closed on cleanup.
 // Tests will use the builder like this:
 //     ng, cleanup := builder()
 //     defer cleanup()
@@ -65,6 +66,9 @@ func TestEngine(t *testing.T, builder Builder) {
 func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 	ng, cleanup := builder()
 	defer cleanup()
+	defer func() {
+		require.NoError(t, ng.Close())
+	}()
 
 	t.Run("Commit on read-only transaction should fail", func(t *testing.T) {
 		tx, err := ng.Begin(false)
@@ -204,6 +208,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 			t.Run(test.name+"/rollback", func(t *testing.T) {
 				ng, cleanup := builder()
 				defer cleanup()
+				defer func() {
+					require.NoError(t, ng.Close())
+				}()
 
 				if test.initFn != nil {
 					func() {
@@ -240,6 +247,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 		for _, test := range tests {
 			ng, cleanup := builder()
 			defer cleanup()
+			defer func() {
+				require.NoError(t, ng.Close())
+			}()
 
 			t.Run(test.name+"/commit", func(t *testing.T) {
 				if test.initFn != nil {
@@ -292,6 +302,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 			t.Run(test.name, func(t *testing.T) {
 				ng, cleanup := builder()
 				defer cleanup()
+				defer func() {
+					require.NoError(t, ng.Close())
+				}()
 
 				tx, err := ng.Begin(true)
 				require.NoError(t, err)
@@ -312,6 +325,9 @@ func TestTransactionCreateStore(t *testing.T, builder Builder) {
 	t.Run("Should create a store", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		tx, err := ng.Begin(true)
 		require.NoError(t, err)
@@ -328,6 +344,9 @@ func TestTransactionCreateStore(t *testing.T, builder Builder) {
 	t.Run("Should fail if store already exists", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		tx, err := ng.Begin(true)
 		require.NoError(t, err)
@@ -345,6 +364,9 @@ func TestTransactionStore(t *testing.T, builder Builder) {
 	t.Run("Should fail if store not found", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		tx, err := ng.Begin(false)
 		require.NoError(t, err)
@@ -357,6 +379,9 @@ func TestTransactionStore(t *testing.T, builder Builder) {
 	t.Run("Should return the right store", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		tx, err := ng.Begin(true)
 		require.NoError(t, err)
@@ -397,6 +422,9 @@ func TestTransactionDropStore(t *testing.T, builder Builder) {
 	t.Run("Should drop a store", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		tx, err := ng.Begin(true)
 		require.NoError(t, err)
@@ -415,6 +443,9 @@ func TestTransactionDropStore(t *testing.T, builder Builder) {
 	t.Run("Should fail if store not found", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		tx, err := ng.Begin(true)
 		require.NoError(t, err)
@@ -434,8 +465,11 @@ func storeBuilder(t testing.TB, builder Builder) (engine.Store, func()) {
 	st, err := tx.GetStore([]byte("test"))
 	require.NoError(t, err)
 	return st, func() {
-		tx.Rollback()
-		cleanup()
+		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
+		defer tx.Rollback()
 	}
 }
 
@@ -791,6 +825,10 @@ func TestStoreNextSequence(t *testing.T, builder Builder) {
 	t.Run("Should fail if tx not writable", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
+
 		tx, err := ng.Begin(true)
 		require.NoError(t, err)
 		err = tx.CreateStore([]byte("test"))
@@ -823,7 +861,11 @@ func TestStoreNextSequence(t *testing.T, builder Builder) {
 	t.Run("Should store the last sequence", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 		tx, err := ng.Begin(true)
+
 		require.NoError(t, err)
 		err = tx.CreateStore([]byte("test"))
 		require.NoError(t, err)
@@ -855,10 +897,12 @@ func TestQueries(t *testing.T, builder Builder) {
 	t.Run("SELECT", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		db, err := genji.New(ng)
 		require.NoError(t, err)
-		defer db.Close()
 
 		ctx := context.Background()
 
@@ -895,10 +939,12 @@ func TestQueries(t *testing.T, builder Builder) {
 	t.Run("INSERT", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		db, err := genji.New(ng)
 		require.NoError(t, err)
-		defer db.Close()
 
 		err = db.Exec(ctx, `
 			CREATE TABLE test;
@@ -910,10 +956,12 @@ func TestQueries(t *testing.T, builder Builder) {
 	t.Run("UPDATE", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		db, err := genji.New(ng)
 		require.NoError(t, err)
-		defer db.Close()
 
 		st, err := db.Query(ctx, `
 				CREATE TABLE test;
@@ -932,10 +980,12 @@ func TestQueries(t *testing.T, builder Builder) {
 	t.Run("DELETE", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		db, err := genji.New(ng)
 		require.NoError(t, err)
-		defer db.Close()
 
 		err = db.Exec(ctx, "CREATE TABLE test")
 		require.NoError(t, err)
@@ -968,10 +1018,12 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
 	t.Run("SELECT", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		db, err := genji.New(ng)
 		require.NoError(t, err)
-		defer db.Close()
 
 		err = db.Update(func(tx *genji.Tx) error {
 			st, err := tx.Query(ctx, `
@@ -992,10 +1044,12 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
 	t.Run("INSERT", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		db, err := genji.New(ng)
 		require.NoError(t, err)
-		defer db.Close()
 
 		err = db.Update(func(tx *genji.Tx) error {
 			err = tx.Exec(ctx, `
@@ -1011,10 +1065,12 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
 	t.Run("UPDATE", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		db, err := genji.New(ng)
 		require.NoError(t, err)
-		defer db.Close()
 
 		err = db.Update(func(tx *genji.Tx) error {
 			st, err := tx.Query(ctx, `
@@ -1037,10 +1093,12 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
 	t.Run("DELETE", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
+		defer func() {
+			require.NoError(t, ng.Close())
+		}()
 
 		db, err := genji.New(ng)
 		require.NoError(t, err)
-		defer db.Close()
 
 		err = db.Update(func(tx *genji.Tx) error {
 			st, err := tx.Query(ctx, `
diff --git a/sql/parser/select_test.go b/sql/parser/select_test.go
index f3a54f98..619595d6 100644
--- a/sql/parser/select_test.go
+++ b/sql/parser/select_test.go
@@ -222,6 +222,11 @@ func TestParserSelect(t *testing.T) {
 				)),
 			false},
 		{"WithOffsetThenLimit", "SELECT * FROM test WHERE age = 10 OFFSET 20 LIMIT 10", nil, true},
+		{"Invalid use of MIN() aggregator", "SELECT * FROM test LIMIT min(0)", nil, true},
+		{"Invalid use of COUNT() aggregator", "SELECT * FROM test OFFSET x(*)", nil, true},
+		{"Invalid use of MAX() aggregator", "SELECT * FROM test LIMIT max(0)", nil, true},
+		{"Invalid use of SUM() aggregator", "SELECT * FROM test LIMIT sum(0)", nil, true},
+		{"Invalid use of AVG() aggregator", "SELECT * FROM test LIMIT avg(0)", nil, true},
 	}
 
 	for _, test := range tests {
diff --git a/sql/query/expr/expr_test.go b/sql/query/expr/expr_test.go
index cbd4da65..bf12df81 100644
--- a/sql/query/expr/expr_test.go
+++ b/sql/query/expr/expr_test.go
@@ -13,13 +13,11 @@ import (
 )
 
 var doc document.Document = func() document.Document {
-	d, _ := document.NewFromJSON([]byte(`{
+	return document.NewFromJSON([]byte(`{
 		"a": 1,
 		"b": {"foo bar": [1, 2]},
 		"c": [1, {"foo": "bar"}, [1, 2]]
 	}`))
-
-	return d
 }()
 
 var stackWithDoc = expr.EvalStack{
diff --git a/sql/query/expr/path_test.go b/sql/query/expr/path_test.go
index a52dfd4b..c8f3a267 100644
--- a/sql/query/expr/path_test.go
+++ b/sql/query/expr/path_test.go
@@ -1,6 +1,7 @@
 package expr_test
 
 import (
+	"encoding/json"
 	"testing"
 
 	"github.com/genjidb/genji/document"
@@ -16,8 +17,10 @@ func TestPathExpr(t *testing.T) {
 	}{
 		{"a", document.NewIntegerValue(1), false},
 		{"b", func() document.Value {
-			d, _ := document.NewFromJSON([]byte(`{"foo bar": [1, 2]}`))
-			return document.NewDocumentValue(d)
+			fb := document.NewFieldBuffer()
+			err := json.Unmarshal([]byte(`{"foo bar": [1, 2]}`), fb)
+			require.NoError(t, err)
+			return document.NewDocumentValue(fb)
 		}(),
 			false},
 		{"b.`foo bar`[0]", document.NewIntegerValue(1), false},
@@ -30,12 +33,11 @@ func TestPathExpr(t *testing.T) {
 		{"d", nullLitteral, false},
 	}
 
-	d, err := document.NewFromJSON([]byte(`{
+	d := document.NewFromJSON([]byte(`{
 		"a": 1,
 		"b": {"foo bar": [1, 2]},
 		"c": [1, {"foo": "bar"}, [1, 2]]
 	}`))
-	require.NoError(t, err)
 
 	for _, test := range tests {
 		t.Run(test.expr, func(t *testing.T) {