Repository: genjidb/genji
Pull request #264 (2020-10-15 21:35:11)

Add SELECT DISTINCT queries support
Adds new `DISTINCT` keyword 
Uses hash set to make projection result unique.

codecov-io: # [Codecov](https://codecov.io/gh/genjidb/genji/pull/264?src=pr&el=h1) Report
> Merging [#264](https://codecov.io/gh/genjidb/genji/pull/264?src=pr&el=desc) into [master](https://codecov.io/gh/genjidb/genji/commit/f5afd65c91591246d9a5386bf80332649f3c42f9?el=desc) will **increase** coverage by `0.16%`.
> The diff coverage is `40.74%`.

[![Impacted file tree graph](https://codecov.io/gh/genjidb/genji/pull/264/graphs/tree.svg?width=650&height=150&src=pr&token=nwR1GpvtyH)](https://codecov.io/gh/genjidb/genji/pull/264?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #264      +/-   ##
==========================================
+ Coverage   61.32%   61.48%   +0.16%     
==========================================
  Files          68       71       +3     
  Lines        6345     6577     +232     
==========================================
+ Hits         3891     4044     +153     
- Misses       1938     1999      +61     
- Partials      516      534      +18     
```


| [Impacted Files](https://codecov.io/gh/genjidb/genji/pull/264?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sql/planner/hash\_set.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-c3FsL3BsYW5uZXIvaGFzaF9zZXQuZ28=) | `0.00% <0.00%> (ø)` | |
| [sql/planner/tree.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-c3FsL3BsYW5uZXIvdHJlZS5nbw==) | `37.57% <ø> (ø)` | |
| [sql/scanner/token.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-c3FsL3NjYW5uZXIvdG9rZW4uZ28=) | `22.22% <ø> (ø)` | |
| [sql/parser/select.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-c3FsL3BhcnNlci9zZWxlY3QuZ28=) | `62.59% <44.44%> (-1.34%)` | :arrow_down: |
| [sql/planner/distinct.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-c3FsL3BsYW5uZXIvZGlzdGluY3QuZ28=) | `62.50% <62.50%> (ø)` | |
| [sql/planner/optimizer.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-c3FsL3BsYW5uZXIvb3B0aW1pemVyLmdv) | `80.90% <64.86%> (-3.25%)` | :arrow_down: |
| [sql/query/alter.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-c3FsL3F1ZXJ5L2FsdGVyLmdv) | `33.33% <0.00%> (-26.67%)` | :arrow_down: |
| [database/config.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-ZGF0YWJhc2UvY29uZmlnLmdv) | `67.85% <0.00%> (-0.74%)` | :arrow_down: |
| [sql/parser/expr.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-c3FsL3BhcnNlci9leHByLmdv) | `77.12% <0.00%> (-0.69%)` | :arrow_down: |
| [database/table.go](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree#diff-ZGF0YWJhc2UvdGFibGUuZ28=) | `56.70% <0.00%> (-0.55%)` | :arrow_down: |
| ... and [14 more](https://codecov.io/gh/genjidb/genji/pull/264/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/genjidb/genji/pull/264?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/genjidb/genji/pull/264?src=pr&el=footer). Last update [f5afd65...c29ffcb](https://codecov.io/gh/genjidb/genji/pull/264?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

tie: @tdakkota thanks! Can you please mark the discussions as resolved? It looks like only PR author can do that in GitHub…


Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100644
index 00000000..b0d2566e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,47 @@
+---
+name: Bug report
+about: Use this template for reporting bugs.
+labels: bug
+---
+
+<!-- Please answer these questions before submitting your issue. Thanks! -->
+
+### What version of Genji are you using?
+
+<pre>
+$ genji version
+
+</pre>
+
+### Does this issue reproduce with the latest release?
+
+<!-- Yes/no -->
+
+### What did you do?
+
+<!--
+If possible, provide a recipe for reproducing the error.
+A complete runnable program is good.
+A link on play.golang.org or goplay.space is best. You may use
+https://play.golang.org/p/_pr_rOXsAH3 as a starting point.
+-->
+
+### What did you expect to see?
+
+<!-- The expected behavior -->
+
+### What did you see instead?
+
+<!-- The observed behavior -->
+
+### What Go version and environment are you using?
+
+<pre>
+$ go version
+
+</pre>
+
+<details><summary><code>go env</code> Output</summary><br><pre>
+$ go env
+
+</pre></details>
diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100644
index 00000000..170496c3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.md
@@ -0,0 +1,21 @@
+---
+name: Feature request
+about: Use this template for proposing features.
+labels: enhancement
+---
+
+<!-- Answering these questions may help with writing the initial proposal body. Thanks! -->
+
+### Proposal
+
+<!--
+What is the current status of the proposed behavior? E.g. “Currently, it’s not possible to do X in Genji”.
+What is the proposed solution? E.g. “I propose adding SQL syntax Y that does X”.
+-->
+
+### Motivation
+
+<!--
+What is the motivation / use case for this feature?
+Does this feature exist in other databases, e.g. SQLite, PostgreSQL, MySQL?
+-->
diff --git a/README.md b/README.md
index ccfabb1b..214396e4 100644
--- a/README.md
+++ b/README.md
@@ -65,11 +65,12 @@ There are two ways of using Genji, either by using Genji's API or by using the [
 package main
 
 import (
-	"fmt"
-	"log"
+    "context"
+    "fmt"
+    "log"
 
-	"github.com/genjidb/genji"
-	"github.com/genjidb/genji/document"
+    "github.com/genjidb/genji"
+    "github.com/genjidb/genji/document"
 )
 
 func main() {
@@ -81,19 +82,20 @@ func main() {
     // Don't forget to close the database when you're done
     defer db.Close()
 
-    ctx := context.Background()
+    // Attach context, e.g. (*http.Request).Context().
+    db = db.WithContext(context.Background())
 
     // Create a table. Schemas are optional, you don't need to specify one if not needed
-    err = db.Exec(ctx, "CREATE TABLE user")
+    err = db.Exec("CREATE TABLE user")
 
     // Create an index
-    err = db.Exec(ctx, "CREATE INDEX idx_user_name ON test (name)")
+    err = db.Exec("CREATE INDEX idx_user_name ON test (name)")
 
     // Insert some data
-    err = db.Exec(ctx, "INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "Foo1", 15)
+    err = db.Exec("INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "Foo1", 15)
 
     // Supported values can go from simple integers to richer data types like lists or documents
-    err = db.Exec(ctx, `
+    err = db.Exec(`
     INSERT INTO user (id, name, age, address, friends)
     VALUES (
         11,
@@ -123,10 +125,10 @@ func main() {
     u.Address.City = "Lyon"
     u.Address.ZipCode = "69001"
 
-    err = db.Exec(ctx, `INSERT INTO user VALUES ?`, &u)
+    err = db.Exec(`INSERT INTO user VALUES ?`, &u)
 
     // Query some documents
-    res, err := db.Query(ctx, "SELECT id, name, age, address FROM user WHERE age >= ?", 18)
+    res, err := db.Query("SELECT id, name, age, address FROM user WHERE age >= ?", 18)
     // always close the result when you're done with it
     defer res.Close()
 
@@ -239,6 +241,7 @@ go get github.com/genjidb/genji/engine/badgerengine
 
 ```go
 import (
+    "context"
     "log"
 
     "github.com/genjidb/genji"
@@ -254,7 +257,7 @@ func main() {
     }
 
     // Pass it to genji
-    db, err := genji.New(ng)
+    db, err := genji.New(context.Background(), ng)
     if err != nil {
         log.Fatal(err)
     }
diff --git a/cmd/genji/insert.go b/cmd/genji/insert.go
index bc7ae693..d70a819f 100644
--- a/cmd/genji/insert.go
+++ b/cmd/genji/insert.go
@@ -38,7 +38,7 @@ func skipSpaces(r *bufio.Reader) (byte, error) {
 	return c, nil
 }
 
-func executeInsertCommand(ctx context.Context, db *genji.DB, table string, r io.Reader) error {
+func executeInsertCommand(db *genji.DB, table string, r io.Reader) error {
 	q := fmt.Sprintf("INSERT INTO %s VALUES ?", table)
 	rd := bufio.NewReader(r)
 	var c byte
@@ -66,7 +66,7 @@ func executeInsertCommand(ctx context.Context, db *genji.DB, table string, r io.
 				return err
 			}
 
-			if err := db.Exec(ctx, q, &fb); err != nil {
+			if err := db.Exec(q, &fb); err != nil {
 				return err
 			}
 		}
@@ -89,7 +89,7 @@ func executeInsertCommand(ctx context.Context, db *genji.DB, table string, r io.
 				return err
 			}
 
-			if err := db.Exec(ctx, q, &fb); err != nil {
+			if err := db.Exec(q, &fb); err != nil {
 				return err
 			}
 		}
@@ -137,14 +137,14 @@ func runInsertCommand(ctx context.Context, e, dbPath, table string, auto bool, a
 		return err
 	}
 
-	db, err := genji.New(ng)
+	db, err := genji.New(ctx, ng)
 	if err != nil {
 		return err
 	}
 	defer db.Close()
 
 	if createTable {
-		err := db.Exec(ctx, "CREATE TABLE "+table)
+		err := db.Exec("CREATE TABLE " + table)
 		if err != nil {
 			return err
 		}
@@ -153,7 +153,7 @@ func runInsertCommand(ctx context.Context, e, dbPath, table string, auto bool, a
 	fi, _ := os.Stdin.Stat()
 	m := fi.Mode()
 	if (m & os.ModeNamedPipe) != 0 {
-		return executeInsertCommand(ctx, db, table, os.Stdin)
+		return executeInsertCommand(db, table, os.Stdin)
 	}
 
 	if len(args) == 0 {
@@ -161,7 +161,7 @@ func runInsertCommand(ctx context.Context, e, dbPath, table string, auto bool, a
 	}
 
 	for _, arg := range args {
-		if err := executeInsertCommand(ctx, db, table, strings.NewReader(arg)); err != nil {
+		if err := executeInsertCommand(db, table, strings.NewReader(arg)); err != nil {
 			return err
 		}
 	}
diff --git a/cmd/genji/shell/command.go b/cmd/genji/shell/command.go
index dd7ced25..ce57db34 100644
--- a/cmd/genji/shell/command.go
+++ b/cmd/genji/shell/command.go
@@ -2,7 +2,6 @@ package shell
 
 import (
 	"bytes"
-	"context"
 	"errors"
 	"fmt"
 	"io"
@@ -58,9 +57,7 @@ func runTablesCmd(db *genji.DB, cmd []string) error {
 		return fmt.Errorf("usage: .tables")
 	}
 
-	ctx := context.Background()
-
-	res, err := db.Query(ctx, "SELECT table_name FROM __genji_tables")
+	res, err := db.Query("SELECT table_name FROM __genji_tables")
 	if err != nil {
 		return err
 	}
@@ -80,17 +77,15 @@ func runTablesCmd(db *genji.DB, cmd []string) error {
 // displayTableIndex prints all indexes that the given table contains.
 func displayTableIndex(db *genji.DB, tableName string) error {
 	return db.View(func(tx *genji.Tx) error {
-		ctx := context.Background()
-		_, err := tx.QueryDocument(ctx, "SELECT table_name FROM __genji_tables WHERE table_name = ?", tableName)
+		_, err := tx.QueryDocument("SELECT table_name FROM __genji_tables WHERE table_name = ?", tableName)
 		if err != nil {
 			if err == database.ErrDocumentNotFound {
 				return fmt.Errorf("%w: %q", database.ErrTableNotFound, tableName)
 			}
-
 			return err
 		}
 
-		res, err := tx.Query(ctx, "SELECT * FROM __genji_indexes WHERE table_name = ?", tableName)
+		res, err := tx.Query("SELECT * FROM __genji_indexes WHERE table_name = ?", tableName)
 		if err != nil {
 			return err
 		}
@@ -112,9 +107,7 @@ func displayTableIndex(db *genji.DB, tableName string) error {
 
 // displayAllIndexes shows all indexes that the database contains.
 func displayAllIndexes(db *genji.DB) error {
-	ctx := context.Background()
-
-	res, err := db.Query(ctx, "SELECT * FROM __genji_indexes")
+	res, err := db.Query("SELECT * FROM __genji_indexes")
 	if err != nil {
 		return err
 	}
@@ -272,7 +265,7 @@ func dumpTable(tx *genji.Tx, tableName string, w io.Writer) error {
 	}
 
 	q := fmt.Sprintf("SELECT * FROM %s", t.Name())
-	res, err := tx.Query(context.Background(), q)
+	res, err := tx.Query(q)
 	if err != nil {
 		return err
 	}
@@ -340,7 +333,7 @@ func runDumpCmd(db *genji.DB, tables []string, w io.Writer) error {
 
 	// tables slice argument is empty.
 	// Dump database content.
-	res, err := tx.Query(context.Background(), "SELECT table_name FROM __genji_tables")
+	res, err := tx.Query("SELECT table_name FROM __genji_tables")
 	if err != nil {
 		_, err = fmt.Fprintln(w, "ROLLBACK;")
 		return err
diff --git a/cmd/genji/shell/shell.go b/cmd/genji/shell/shell.go
index 6bce276f..9bee1e0b 100644
--- a/cmd/genji/shell/shell.go
+++ b/cmd/genji/shell/shell.go
@@ -99,12 +99,12 @@ func stdinFromTerminal() bool {
 }
 
 // Run a shell.
-func Run(ctx context.Context, opts *Options) (err error) {
+func Run(ctx context.Context, opts *Options) error {
 	if opts == nil {
 		opts = new(Options)
 	}
 
-	err = opts.validate()
+	err := opts.validate()
 	if err != nil {
 		return err
 	}
@@ -288,7 +288,11 @@ func (sh *Shell) runPrompt(ctx context.Context, execCh chan (string)) error {
 		}
 	}
 
-	pt := prompt.New(func(in string) {}, sh.completer, append(promptOpts, prompt.OptionHistory(history))...)
+	pt := prompt.New(
+		func(in string) {},
+		sh.completer,
+		promptOpts...,
+	)
 
 	for {
 		// Input() captures ctrl D and ctrl C.
@@ -429,7 +433,6 @@ func (sh *Shell) executeInput(ctx context.Context, in string) error {
 	// it must not be in the middle of a multi line query though
 	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
@@ -460,7 +463,7 @@ func (sh *Shell) runCommand(ctx context.Context, in string) error {
 	case ".help", "help":
 		return runHelpCmd()
 	case ".tables":
-		db, err := sh.getDB()
+		db, err := sh.getDB(ctx)
 		if err != nil {
 			return err
 		}
@@ -473,13 +476,13 @@ func (sh *Shell) runCommand(ctx context.Context, in string) error {
 
 		return errExitCommand
 	case ".indexes":
-		db, err := sh.getDB()
+		db, err := sh.getDB(ctx)
 		if err != nil {
 			return err
 		}
 		return runIndexesCmd(db, cmd)
 	case ".dump":
-		db, err := sh.getDB()
+		db, err := sh.getDB(ctx)
 		if err != nil {
 			return err
 		}
@@ -491,12 +494,12 @@ func (sh *Shell) runCommand(ctx context.Context, in string) error {
 }
 
 func (sh *Shell) runQuery(ctx context.Context, q string) error {
-	db, err := sh.getDB()
+	db, err := sh.getDB(ctx)
 	if err != nil {
 		return err
 	}
 
-	res, err := db.Query(ctx, q)
+	res, err := db.Query(q)
 	if err != nil {
 		return err
 	}
@@ -517,9 +520,9 @@ func (sh *Shell) runQuery(ctx context.Context, q string) error {
 	})
 }
 
-func (sh *Shell) getDB() (*genji.DB, error) {
+func (sh *Shell) getDB(ctx context.Context) (*genji.DB, error) {
 	if sh.db != nil {
-		return sh.db, nil
+		return sh.db.WithContext(ctx), nil
 	}
 
 	var ng engine.Engine
@@ -537,12 +540,12 @@ func (sh *Shell) getDB() (*genji.DB, error) {
 		return nil, err
 	}
 
-	sh.db, err = genji.New(ng)
+	sh.db, err = genji.New(ctx, ng)
 	if err != nil {
 		return nil, err
 	}
 
-	return sh.db, nil
+	return sh.db.WithContext(ctx), nil
 }
 
 func (sh *Shell) runPipedInput(ctx context.Context) (ran bool, err error) {
@@ -569,8 +572,8 @@ func (sh *Shell) changelivePrefix() (string, bool) {
 	return sh.livePrefix, sh.multiLine
 }
 
-func (sh *Shell) getAllIndexes() ([]string, error) {
-	db, err := sh.getDB()
+func (sh *Shell) getAllIndexes(ctx context.Context) ([]string, error) {
+	db, err := sh.getDB(ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -597,10 +600,10 @@ func (sh *Shell) getAllIndexes() ([]string, error) {
 }
 
 // getTables returns all the tables of the database
-func (sh *Shell) getAllTables() ([]string, error) {
+func (sh *Shell) getAllTables(ctx context.Context) ([]string, error) {
 	var tables []string
-	db, _ := sh.getDB()
-	res, err := db.Query(context.Background(), "SELECT table_name FROM __genji_tables")
+	db, _ := sh.getDB(ctx)
+	res, err := db.Query("SELECT table_name FROM __genji_tables")
 	if err != nil {
 		return nil, err
 	}
@@ -627,7 +630,7 @@ func (sh *Shell) getAllTables() ([]string, error) {
 func (sh *Shell) completer(in prompt.Document) []prompt.Suggest {
 	suggestions := prompt.FilterHasPrefix(sh.cmdSuggestions, in.Text, true)
 
-	_, err := parser.NewParser(strings.NewReader(in.Text)).ParseQuery(context.Background())
+	_, err := parser.ParseQuery(in.Text)
 	if err != nil {
 		e, ok := err.(*parser.ParseError)
 		if !ok || len(e.Expected) < 1 {
@@ -636,12 +639,12 @@ func (sh *Shell) completer(in prompt.Document) []prompt.Suggest {
 		expected := e.Expected
 		switch expected[0] {
 		case "table_name":
-			expected, err = sh.getAllTables()
+			expected, err = sh.getAllTables(context.Background())
 			if err != nil {
 				return suggestions
 			}
 		case "index_name":
-			expected, err = sh.getAllIndexes()
+			expected, err = sh.getAllIndexes(context.Background())
 			if err != nil {
 				return suggestions
 			}
diff --git a/database/config.go b/database/config.go
index 02adaa7b..8638f43b 100644
--- a/database/config.go
+++ b/database/config.go
@@ -340,7 +340,7 @@ func (t *tableInfoStore) loadAllTableInfo(tx engine.Transaction) error {
 		return err
 	}
 
-	it := st.NewIterator(engine.IteratorConfig{})
+	it := st.Iterator(engine.IteratorOptions{})
 	defer it.Close()
 
 	t.tableInfos = make(map[string]TableInfo)
@@ -360,6 +360,9 @@ func (t *tableInfoStore) loadAllTableInfo(tx engine.Transaction) error {
 
 		t.tableInfos[string(itm.Key())] = ti
 	}
+	if err := it.Err(); err != nil {
+		return err
+	}
 
 	t.tableInfos[tableInfoStoreName] = TableInfo{
 		storeName: []byte(tableInfoStoreName),
@@ -572,30 +575,28 @@ func (t *indexStore) Delete(indexName string) error {
 }
 
 func (t *indexStore) ListAll() ([]*IndexConfig, error) {
-	var idxList []*IndexConfig
-	it := t.st.NewIterator(engine.IteratorConfig{})
+	it := t.st.Iterator(engine.IteratorOptions{})
+	defer it.Close()
 
+	var idxList []*IndexConfig
 	var buf []byte
 	var err error
 	for it.Seek(nil); it.Valid(); it.Next() {
 		item := it.Item()
-		var opts IndexConfig
 		buf, err = item.ValueCopy(buf)
 		if err != nil {
-			it.Close()
 			return nil, err
 		}
 
+		var opts IndexConfig
 		err = opts.ScanDocument(t.db.Codec.NewDocument(buf))
 		if err != nil {
-			it.Close()
 			return nil, err
 		}
 
 		idxList = append(idxList, &opts)
 	}
-	err = it.Close()
-	if err != nil {
+	if err := it.Err(); err != nil {
 		return nil, err
 	}
 
diff --git a/database/database.go b/database/database.go
index 919fa31f..c9f3838d 100644
--- a/database/database.go
+++ b/database/database.go
@@ -2,6 +2,7 @@
 package database
 
 import (
+	"context"
 	"errors"
 	"sync"
 	"sync/atomic"
@@ -38,7 +39,7 @@ type Options struct {
 }
 
 // New initializes the DB using the given engine.
-func New(ng engine.Engine, opts Options) (*Database, error) {
+func New(ctx context.Context, ng engine.Engine, opts Options) (*Database, error) {
 	if opts.Codec == nil {
 		return nil, errors.New("missing codec")
 	}
@@ -48,7 +49,9 @@ func New(ng engine.Engine, opts Options) (*Database, error) {
 		Codec: opts.Codec,
 	}
 
-	ntx, err := db.ng.Begin(true)
+	ntx, err := db.ng.Begin(ctx, engine.TxOptions{
+		Writable: true,
+	})
 	if err != nil {
 		return nil, err
 	}
@@ -93,10 +96,10 @@ func (db *Database) Close() error {
 	return db.ng.Close()
 }
 
-// Begin starts a new transaction.
+// Begin starts a new transaction with default options.
 // The returned transaction must be closed either by calling Rollback or Commit.
 func (db *Database) Begin(writable bool) (*Transaction, error) {
-	return db.BeginTx(&TxOptions{
+	return db.BeginTx(context.Background(), &TxOptions{
 		ReadOnly: !writable,
 	})
 }
@@ -104,8 +107,10 @@ func (db *Database) Begin(writable bool) (*Transaction, error) {
 // BeginTx starts a new transaction with the given options.
 // If opts is empty, it will use the default options.
 // The returned transaction must be closed either by calling Rollback or Commit.
-// If the Global option is passed, it opens a database level transaction.
-func (db *Database) BeginTx(opts *TxOptions) (*Transaction, error) {
+// If the Attached option is passed, it opens a database level transaction, which gets
+// attached to the database and prevents any other transaction to be opened afterwards
+// until it gets rolled back or commited.
+func (db *Database) BeginTx(ctx context.Context, opts *TxOptions) (*Transaction, error) {
 	if opts == nil {
 		opts = new(TxOptions)
 	}
@@ -117,7 +122,9 @@ func (db *Database) BeginTx(opts *TxOptions) (*Transaction, error) {
 		return nil, errors.New("cannot open a transaction within a transaction")
 	}
 
-	ntx, err := db.ng.Begin(!opts.ReadOnly)
+	ntx, err := db.ng.Begin(ctx, engine.TxOptions{
+		Writable: !opts.ReadOnly,
+	})
 	if err != nil {
 		return nil, err
 	}
@@ -127,6 +134,7 @@ func (db *Database) BeginTx(opts *TxOptions) (*Transaction, error) {
 		db:             db,
 		tx:             ntx,
 		writable:       !opts.ReadOnly,
+		attached:       opts.Attached,
 		tableInfoStore: db.tableInfoStore,
 	}
 
diff --git a/database/table.go b/database/table.go
index 3076d1d3..bd6f8db3 100644
--- a/database/table.go
+++ b/database/table.go
@@ -330,7 +330,7 @@ func (t *Table) Iterate(fn func(d document.Document) error) error {
 		codec: t.tx.db.Codec,
 	}
 
-	it := t.Store.NewIterator(engine.IteratorConfig{})
+	it := t.Store.Iterator(engine.IteratorOptions{})
 	defer it.Close()
 
 	var err error
@@ -345,6 +345,9 @@ func (t *Table) Iterate(fn func(d document.Document) error) error {
 			return err
 		}
 	}
+	if err := it.Err(); err != nil {
+		return err
+	}
 
 	return nil
 }
diff --git a/database/transaction.go b/database/transaction.go
index e77bb79f..4c1863a4 100644
--- a/database/transaction.go
+++ b/database/transaction.go
@@ -25,6 +25,8 @@ type Transaction struct {
 	db       *Database
 	tx       engine.Transaction
 	writable bool
+	// if set to true, this transaction is attached to the database
+	attached bool
 
 	tableInfoStore *tableInfoStore
 	indexStore     *indexStore
@@ -37,9 +39,6 @@ func (tx *Transaction) DB() *Database {
 
 // Rollback the transaction. Can be used safely after commit.
 func (tx *Transaction) Rollback() error {
-	tx.db.attachedTxMu.Lock()
-	defer tx.db.attachedTxMu.Unlock()
-
 	if tx.writable {
 		tx.tableInfoStore.rollback(tx)
 	}
@@ -49,8 +48,13 @@ func (tx *Transaction) Rollback() error {
 		return err
 	}
 
-	if tx.db.attachedTransaction != nil {
-		tx.db.attachedTransaction = nil
+	if tx.attached {
+		tx.db.attachedTxMu.Lock()
+		defer tx.db.attachedTxMu.Unlock()
+
+		if tx.db.attachedTransaction != nil {
+			tx.db.attachedTransaction = nil
+		}
 	}
 
 	return nil
@@ -58,9 +62,6 @@ func (tx *Transaction) Rollback() error {
 
 // Commit the transaction.
 func (tx *Transaction) Commit() error {
-	tx.db.attachedTxMu.Lock()
-	defer tx.db.attachedTxMu.Unlock()
-
 	if tx.writable {
 		tx.tableInfoStore.commit(tx)
 	}
@@ -70,8 +71,13 @@ func (tx *Transaction) Commit() error {
 		return err
 	}
 
-	if tx.db.attachedTransaction != nil {
-		tx.db.attachedTransaction = nil
+	if tx.attached {
+		tx.db.attachedTxMu.Lock()
+		defer tx.db.attachedTxMu.Unlock()
+
+		if tx.db.attachedTransaction != nil {
+			tx.db.attachedTransaction = nil
+		}
 	}
 
 	return nil
@@ -196,21 +202,20 @@ func (tx *Transaction) DropTable(name string) error {
 		return errors.New("cannot write to read-only table")
 	}
 
-	it := tx.indexStore.st.NewIterator(engine.IteratorConfig{})
+	it := tx.indexStore.st.Iterator(engine.IteratorOptions{})
+	defer it.Close()
 
 	var buf []byte
 	for it.Seek(nil); it.Valid(); it.Next() {
 		item := it.Item()
-		var opts IndexConfig
 		buf, err = item.ValueCopy(buf)
 		if err != nil {
-			it.Close()
 			return err
 		}
 
+		var opts IndexConfig
 		err = opts.ScanDocument(tx.db.Codec.NewDocument(buf))
 		if err != nil {
-			it.Close()
 			return err
 		}
 
@@ -221,12 +226,10 @@ func (tx *Transaction) DropTable(name string) error {
 
 		err = tx.DropIndex(opts.IndexName)
 		if err != nil {
-			it.Close()
 			return err
 		}
 	}
-	err = it.Close()
-	if err != nil {
+	if err := it.Err(); err != nil {
 		return err
 	}
 
@@ -338,21 +341,31 @@ func (tx *Transaction) ReIndex(indexName string) error {
 	})
 }
 
-// ReIndexAll truncates and recreates all indexes of the database from scratch.
-func (tx *Transaction) ReIndexAll() error {
-	var indexes []string
+// allIndexNames returns a list of all index names in index store.
+func (tx *Transaction) allIndexNames() ([]string, error) {
+	it := tx.indexStore.st.Iterator(engine.IteratorOptions{})
+	defer it.Close()
 
-	it := tx.indexStore.st.NewIterator(engine.IteratorConfig{})
+	var indexes []string
 	for it.Seek(nil); it.Valid(); it.Next() {
 		indexes = append(indexes, string(it.Item().Key()))
 	}
-	err := it.Close()
+	if err := it.Err(); err != nil {
+		return nil, err
+	}
+
+	return indexes, nil
+}
+
+// ReIndexAll truncates and recreates all indexes of the database from scratch.
+func (tx *Transaction) ReIndexAll() error {
+	indexes, err := tx.allIndexNames()
 	if err != nil {
 		return err
 	}
 
 	for _, indexName := range indexes {
-		err = tx.ReIndex(indexName)
+		err := tx.ReIndex(indexName)
 		if err != nil {
 			return err
 		}
diff --git a/db.go b/db.go
index a44a7d7a..5af7e692 100644
--- a/db.go
+++ b/db.go
@@ -12,6 +12,15 @@ import (
 // DB represents a collection of tables stored in the underlying engine.
 type DB struct {
 	DB *database.Database
+
+	Context context.Context
+}
+
+func (db *DB) WithContext(ctx context.Context) *DB {
+	return &DB{
+		DB:      db.DB,
+		Context: ctx,
+	}
 }
 
 // Close the database.
@@ -22,7 +31,9 @@ func (db *DB) Close() error {
 // Begin starts a new transaction.
 // The returned transaction must be closed either by calling Rollback or Commit.
 func (db *DB) Begin(writable bool) (*Tx, error) {
-	tx, err := db.DB.Begin(writable)
+	tx, err := db.DB.BeginTx(db.Context, &database.TxOptions{
+		ReadOnly: !writable,
+	})
 	if err != nil {
 		return nil, err
 	}
@@ -60,8 +71,8 @@ func (db *DB) Update(fn func(tx *Tx) error) error {
 }
 
 // Exec a query against the database without returning the result.
-func (db *DB) Exec(ctx context.Context, q string, args ...interface{}) error {
-	res, err := db.Query(ctx, q, args...)
+func (db *DB) Exec(q string, args ...interface{}) error {
+	res, err := db.Query(q, args...)
 	if err != nil {
 		return err
 	}
@@ -71,19 +82,19 @@ func (db *DB) Exec(ctx context.Context, q string, args ...interface{}) error {
 
 // Query the database and return the result.
 // The returned result must always be closed after usage.
-func (db *DB) Query(ctx context.Context, q string, args ...interface{}) (*query.Result, error) {
-	pq, err := parser.ParseQuery(ctx, q)
+func (db *DB) Query(q string, args ...interface{}) (*query.Result, error) {
+	pq, err := parser.ParseQuery(q)
 	if err != nil {
 		return nil, err
 	}
 
-	return pq.Run(ctx, db.DB, argsToParams(args))
+	return pq.Run(db.Context, db.DB, argsToParams(args))
 }
 
 // QueryDocument runs the query and returns the first document.
 // If the query returns no error, QueryDocument returns database.ErrDocumentNotFound.
-func (db *DB) QueryDocument(ctx context.Context, q string, args ...interface{}) (document.Document, error) {
-	res, err := db.Query(ctx, q, args...)
+func (db *DB) QueryDocument(q string, args ...interface{}) (document.Document, error) {
+	res, err := db.Query(q, args...)
 	if err != nil {
 		return nil, err
 	}
@@ -117,19 +128,19 @@ type Tx struct {
 
 // Query the database withing the transaction and returns the result.
 // Closing the returned result after usage is not mandatory.
-func (tx *Tx) Query(ctx context.Context, q string, args ...interface{}) (*query.Result, error) {
-	pq, err := parser.ParseQuery(ctx, q)
+func (tx *Tx) Query(q string, args ...interface{}) (*query.Result, error) {
+	pq, err := parser.ParseQuery(q)
 	if err != nil {
 		return nil, err
 	}
 
-	return pq.Exec(ctx, tx.Transaction, argsToParams(args))
+	return pq.Exec(tx.Transaction, argsToParams(args))
 }
 
 // QueryDocument runs the query and returns the first document.
 // If the query returns no error, QueryDocument returns database.ErrDocumentNotFound.
-func (tx *Tx) QueryDocument(ctx context.Context, q string, args ...interface{}) (document.Document, error) {
-	res, err := tx.Query(ctx, q, args...)
+func (tx *Tx) QueryDocument(q string, args ...interface{}) (document.Document, error) {
+	res, err := tx.Query(q, args...)
 	if err != nil {
 		return nil, err
 	}
@@ -147,8 +158,8 @@ func (tx *Tx) QueryDocument(ctx context.Context, q string, args ...interface{})
 }
 
 // Exec a query against the database within tx and without returning the result.
-func (tx *Tx) Exec(ctx context.Context, q string, args ...interface{}) error {
-	res, err := tx.Query(ctx, q, args...)
+func (tx *Tx) Exec(q string, args ...interface{}) error {
+	res, err := tx.Query(q, args...)
 	if err != nil {
 		return err
 	}
diff --git a/document/iterator.go b/document/iterator.go
index c23afe04..98a7153e 100644
--- a/document/iterator.go
+++ b/document/iterator.go
@@ -13,7 +13,7 @@ var ErrStreamClosed = errors.New("stream closed")
 type Iterator interface {
 	// Iterate goes through all the documents and calls the given function by passing each one of them.
 	// If the given function returns an error, the iteration stops.
-	Iterate(func(d Document) error) error
+	Iterate(fn func(d Document) error) error
 }
 
 // NewIterator creates an iterator that iterates over documents.
@@ -37,8 +37,8 @@ func (rr documentsIterator) Iterate(fn func(d Document) error) error {
 }
 
 // The IteratorFunc type is an adapter to allow the use of ordinary functions as Iterators.
-// If f is a function with the appropriate signature, HandlerFunc(f) is a Handler that calls f.
-type IteratorFunc func(func(d Document) error) error
+// If f is a function with the appropriate signature, IteratorFunc(f) is an Iterator that calls f.
+type IteratorFunc func(fn func(d Document) error) error
 
 // Iterate calls f(fn).
 func (f IteratorFunc) Iterate(fn func(d Document) error) error {
diff --git a/engine/badgerengine/engine.go b/engine/badgerengine/engine.go
index 8dc141df..f4321f8a 100644
--- a/engine/badgerengine/engine.go
+++ b/engine/badgerengine/engine.go
@@ -3,6 +3,7 @@ package badgerengine
 
 import (
 	"bytes"
+	"context"
 
 	"github.com/dgraph-io/badger/v2"
 	"github.com/genjidb/genji/engine"
@@ -32,13 +33,13 @@ func NewEngine(opt badger.Options) (*Engine, error) {
 }
 
 // Begin creates a transaction using Badger's transaction API.
-func (e *Engine) Begin(writable bool) (engine.Transaction, error) {
-	tx := e.DB.NewTransaction(writable)
+func (e *Engine) Begin(ctx context.Context, opts engine.TxOptions) (engine.Transaction, error) {
+	tx := e.DB.NewTransaction(opts.Writable)
 
 	return &Transaction{
 		ng:       e,
 		tx:       tx,
-		writable: writable,
+		writable: opts.Writable,
 	}, nil
 }
 
diff --git a/engine/badgerengine/store.go b/engine/badgerengine/store.go
index 5733d70f..e3d6f2ea 100644
--- a/engine/badgerengine/store.go
+++ b/engine/badgerengine/store.go
@@ -127,21 +127,21 @@ func (s *Store) NextSequence() (uint64, error) {
 	return nb + 1, nil
 }
 
-// NewIterator uses a Badger iterator with default options.
+// Iterator uses a Badger iterator with default options.
 // Only one iterator is allowed per read-write transaction.
-func (s *Store) NewIterator(cfg engine.IteratorConfig) engine.Iterator {
+func (s *Store) Iterator(opts engine.IteratorOptions) engine.Iterator {
 	prefix := buildKey(s.prefix, nil)
 
 	opt := badger.DefaultIteratorOptions
 	opt.Prefix = prefix
-	opt.Reverse = cfg.Reverse
+	opt.Reverse = opts.Reverse
 	it := s.tx.NewIterator(opt)
 
 	return &iterator{
 		storePrefix: s.prefix,
 		prefix:      prefix,
 		it:          it,
-		reverse:     cfg.Reverse,
+		reverse:     opts.Reverse,
 		item:        badgerItem{prefix: prefix},
 	}
 }
@@ -182,6 +182,10 @@ func (it *iterator) Next() {
 	it.it.Next()
 }
 
+func (it *iterator) Err() error {
+	return nil
+}
+
 func (it *iterator) Item() engine.Item {
 	it.item.item = it.it.Item()
 
diff --git a/engine/boltengine/engine.go b/engine/boltengine/engine.go
index 59b54c34..ad013dce 100644
--- a/engine/boltengine/engine.go
+++ b/engine/boltengine/engine.go
@@ -2,6 +2,7 @@
 package boltengine
 
 import (
+	"context"
 	"os"
 
 	"github.com/genjidb/genji/engine"
@@ -30,15 +31,15 @@ func NewEngine(path string, mode os.FileMode, opts *bolt.Options) (*Engine, erro
 }
 
 // Begin creates a transaction using Bolt's transaction API.
-func (e *Engine) Begin(writable bool) (engine.Transaction, error) {
-	tx, err := e.DB.Begin(writable)
+func (e *Engine) Begin(ctx context.Context, opts engine.TxOptions) (engine.Transaction, error) {
+	tx, err := e.DB.Begin(opts.Writable)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Transaction{
 		tx:       tx,
-		writable: writable,
+		writable: opts.Writable,
 	}, nil
 }
 
diff --git a/engine/boltengine/store.go b/engine/boltengine/store.go
index b49eddd3..2ed80498 100644
--- a/engine/boltengine/store.go
+++ b/engine/boltengine/store.go
@@ -71,11 +71,11 @@ func (s *Store) NextSequence() (uint64, error) {
 	return s.bucket.NextSequence()
 }
 
-// NewIterator uses the bucket cursor.
-func (s *Store) NewIterator(cfg engine.IteratorConfig) engine.Iterator {
+// Iterator uses the Bolt bucket cursor.
+func (s *Store) Iterator(opts engine.IteratorOptions) engine.Iterator {
 	return &iterator{
 		c:       s.bucket.Cursor(),
-		reverse: cfg.Reverse,
+		reverse: opts.Reverse,
 	}
 }
 
@@ -116,6 +116,10 @@ func (it *iterator) Next() {
 	}
 }
 
+func (it *iterator) Err() error {
+	return nil
+}
+
 func (it *iterator) Item() engine.Item {
 	if it.item.k == nil {
 		return nil
diff --git a/engine/engine.go b/engine/engine.go
index 0f9cfb2d..5531015c 100644
--- a/engine/engine.go
+++ b/engine/engine.go
@@ -2,6 +2,7 @@
 package engine
 
 import (
+	"context"
 	"errors"
 )
 
@@ -29,11 +30,16 @@ type Engine interface {
 	// Begin returns a read-only or read/write transaction depending on whether writable is set to false
 	// or true, respectively.
 	// The behaviour of opening a transaction when another one is already opened depends on the implementation.
-	Begin(writable bool) (Transaction, error)
+	Begin(ctx context.Context, opts TxOptions) (Transaction, error)
 	// Close the engine after ensuring all the transactions have completed.
 	Close() error
 }
 
+// TxOptions is used to configure a transaction upon creation.
+type TxOptions struct {
+	Writable bool
+}
+
 // A Transaction provides methods for managing the collection of stores and the transaction itself.
 // The transaction is either read-only or read/write. Read-only transactions can be used to read stores
 // and read/write ones can be used to read, create, delete and modify stores.
@@ -65,14 +71,15 @@ type Store interface {
 	Delete(k []byte) error
 	// Truncate deletes all the key value pairs from the store.
 	Truncate() error
-	// NewIterator creates an iterator with the given config.
-	NewIterator(IteratorConfig) Iterator
+	// Iterator creates an iterator with the given options.
+	// The initial position depends on the implementation.
+	Iterator(opts IteratorOptions) Iterator
 	// NextSequence returns a monotonically increasing integer.
 	NextSequence() (uint64, error)
 }
 
-// IteratorConfig is used to configure an iterator upon creation.
-type IteratorConfig struct {
+// IteratorOptions is used to configure an iterator upon creation.
+type IteratorOptions struct {
 	Reverse bool
 }
 
@@ -83,6 +90,9 @@ type Iterator interface {
 	Seek(k []byte)
 	// Next moves the iterator to the next item.
 	Next()
+	// Err returns an error that invalidated iterator.
+	// If Err is not nil then Valid must return false.
+	Err() error
 	// Valid returns whether the iterator is positioned on a valid item or not.
 	Valid() bool
 	// Item returns the current item.
diff --git a/engine/memoryengine/engine.go b/engine/memoryengine/engine.go
index 028b6d66..2b01152d 100644
--- a/engine/memoryengine/engine.go
+++ b/engine/memoryengine/engine.go
@@ -1,6 +1,7 @@
 package memoryengine
 
 import (
+	"context"
 	"errors"
 	"sync"
 
@@ -32,8 +33,8 @@ func NewEngine() *Engine {
 }
 
 // Begin creates a transaction.
-func (ng *Engine) Begin(writable bool) (engine.Transaction, error) {
-	if writable {
+func (ng *Engine) Begin(ctx context.Context, opts engine.TxOptions) (engine.Transaction, error) {
+	if opts.Writable {
 		ng.mu.Lock()
 	} else {
 		ng.mu.RLock()
@@ -43,7 +44,7 @@ func (ng *Engine) Begin(writable bool) (engine.Transaction, error) {
 		return nil, errors.New("engine closed")
 	}
 
-	return &transaction{ng: ng, writable: writable}, nil
+	return &transaction{ng: ng, writable: opts.Writable}, nil
 }
 
 // Close the engine.
diff --git a/engine/memoryengine/store.go b/engine/memoryengine/store.go
index 88921486..1fde8f6a 100644
--- a/engine/memoryengine/store.go
+++ b/engine/memoryengine/store.go
@@ -171,11 +171,12 @@ func (s *storeTx) NextSequence() (uint64, error) {
 	return s.tx.ng.sequences[s.name], nil
 }
 
-func (s *storeTx) NewIterator(cfg engine.IteratorConfig) engine.Iterator {
+// Iterator creates an iterator with the given options.
+func (s *storeTx) Iterator(opts engine.IteratorOptions) engine.Iterator {
 	return &iterator{
 		tx:      s.tx,
 		tr:      s.tr,
-		reverse: cfg.Reverse,
+		reverse: opts.Reverse,
 		ch:      make(chan *item),
 		closed:  make(chan struct{}),
 	}
@@ -266,6 +267,10 @@ func (it *iterator) Next() {
 	it.item = <-it.ch
 }
 
+func (it *iterator) Err() error {
+	return nil
+}
+
 func (it *iterator) Item() engine.Item {
 	return it.item
 }
diff --git a/fuzz/sqlparser.go b/fuzz/sqlparser.go
index 989907dd..d8efeb06 100644
--- a/fuzz/sqlparser.go
+++ b/fuzz/sqlparser.go
@@ -1,7 +1,6 @@
 package fuzz
 
 import (
-	"context"
 	"strings"
 
 	"github.com/genjidb/genji/sql/parser"
@@ -10,7 +9,7 @@ import (
 func FuzzParseQuery(data []byte) int {
 	var b strings.Builder
 	b.Write(data)
-	q, err := parser.ParseQuery(context.Background(), b.String())
+	q, err := parser.ParseQuery(b.String())
 	if err != nil {
 		return 0
 	}
diff --git a/index/index.go b/index/index.go
index d9dd07f3..f950f1e4 100644
--- a/index/index.go
+++ b/index/index.go
@@ -282,7 +282,7 @@ func (idx *Index) iterate(st engine.Store, pivot document.Value, reverse bool, f
 		}
 	}
 
-	it := st.NewIterator(engine.IteratorConfig{Reverse: reverse})
+	it := st.Iterator(engine.IteratorOptions{Reverse: reverse})
 	defer it.Close()
 
 	for it.Seek(seek); it.Valid(); it.Next() {
@@ -297,6 +297,9 @@ func (idx *Index) iterate(st engine.Store, pivot document.Value, reverse bool, f
 			return err
 		}
 	}
+	if err := it.Err(); err != nil {
+		return err
+	}
 
 	return nil
 }
diff --git a/new.go b/new.go
index e87d7738..d691c284 100644
--- a/new.go
+++ b/new.go
@@ -3,19 +3,22 @@
 package genji
 
 import (
+	"context"
+
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 )
 
 // New initializes the DB using the given engine.
-func New(ng engine.Engine) (*DB, error) {
-	db, err := database.New(ng, database.Options{Codec: msgpack.NewCodec()})
+func New(ctx context.Context, ng engine.Engine) (*DB, error) {
+	db, err := database.New(ctx, ng, database.Options{Codec: msgpack.NewCodec()})
 	if err != nil {
 		return nil, err
 	}
 
 	return &DB{
-		DB: db,
+		DB:      db,
+		Context: context.Background(),
 	}, nil
 }
diff --git a/new_wasm.go b/new_wasm.go
index 4513a209..a8b43b24 100644
--- a/new_wasm.go
+++ b/new_wasm.go
@@ -3,19 +3,22 @@
 package genji
 
 import (
+	"context"
+
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document/encoding/custom"
 	"github.com/genjidb/genji/engine"
 )
 
 // New initializes the DB using the given engine.
-func New(ng engine.Engine) (*DB, error) {
-	db, err := database.New(ng, database.Options{Codec: custom.NewCodec()})
+func New(ctx context.Context, ng engine.Engine) (*DB, error) {
+	db, err := database.New(ctx, ng, database.Options{Codec: custom.NewCodec()})
 	if err != nil {
 		return nil, err
 	}
 
 	return &DB{
-		DB: db,
+		DB:      db,
+		Context: context.Background(),
 	}, nil
 }
diff --git a/open.go b/open.go
index 8e963d84..055766b9 100644
--- a/open.go
+++ b/open.go
@@ -3,6 +3,8 @@
 package genji
 
 import (
+	"context"
+
 	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/engine/boltengine"
 	"github.com/genjidb/genji/engine/memoryengine"
@@ -25,5 +27,6 @@ func Open(path string) (*DB, error) {
 		return nil, err
 	}
 
-	return New(ng)
+	ctx := context.Background()
+	return New(ctx, ng)
 }
diff --git a/sql/driver/driver.go b/sql/driver/driver.go
index 5f752972..6ade010d 100644
--- a/sql/driver/driver.go
+++ b/sql/driver/driver.go
@@ -92,7 +92,7 @@ func (c *conn) Prepare(q string) (driver.Stmt, error) {
 
 // PrepareContext returns a prepared statement, bound to this connection.
 func (c *conn) PrepareContext(ctx context.Context, q string) (driver.Stmt, error) {
-	pq, err := parser.ParseQuery(ctx, q)
+	pq, err := parser.ParseQuery(q)
 	if err != nil {
 		return nil, err
 	}
@@ -126,15 +126,12 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
 		return nil, errors.New("isolation levels are not supported")
 	}
 
-	var err error
+	db := c.db.WithContext(ctx)
 
 	// if the ReadOnly flag is explicitly specified, create a read-only transaction,
 	// otherwise create a read/write transaction.
-	if opts.ReadOnly {
-		c.tx, err = c.db.Begin(false)
-	} else {
-		c.tx, err = c.db.Begin(true)
-	}
+	var err error
+	c.tx, err = db.Begin(!opts.ReadOnly)
 
 	return c, err
 }
@@ -205,7 +202,7 @@ func (s stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver
 	// if calling ExecContext within a transaction, use it,
 	// otherwise use DB.
 	if s.tx != nil {
-		res, err = s.q.Exec(ctx, s.tx.Transaction, driverNamedValueToParams(args))
+		res, err = s.q.Exec(s.tx.Transaction, driverNamedValueToParams(args))
 	} else {
 		res, err = s.q.Run(ctx, s.db.DB, driverNamedValueToParams(args))
 	}
@@ -255,7 +252,7 @@ func (s stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (drive
 	// if calling QueryContext within a transaction, use it,
 	// otherwise use DB.
 	if s.tx != nil {
-		res, err = s.q.Exec(ctx, s.tx.Transaction, driverNamedValueToParams(args))
+		res, err = s.q.Exec(s.tx.Transaction, driverNamedValueToParams(args))
 	} else {
 		res, err = s.q.Run(ctx, s.db.DB, driverNamedValueToParams(args))
 	}
diff --git a/sql/parser/parser.go b/sql/parser/parser.go
index 31c16f68..734884ab 100644
--- a/sql/parser/parser.go
+++ b/sql/parser/parser.go
@@ -2,7 +2,6 @@ package parser
 
 import (
 	"bytes"
-	"context"
 	"fmt"
 	"io"
 	"strings"
@@ -37,8 +36,8 @@ func NewParserWithOptions(r io.Reader, opts *Options) *Parser {
 }
 
 // ParseQuery parses a query string and returns its AST representation.
-func ParseQuery(ctx context.Context, s string) (query.Query, error) {
-	return NewParser(strings.NewReader(s)).ParseQuery(ctx)
+func ParseQuery(s string) (query.Query, error) {
+	return NewParser(strings.NewReader(s)).ParseQuery()
 }
 
 // ParsePath parses a path to a value in a document.
@@ -47,17 +46,11 @@ func ParsePath(s string) (document.Path, error) {
 }
 
 // ParseQuery parses a Genji SQL string and returns a Query.
-func (p *Parser) ParseQuery(ctx context.Context) (query.Query, error) {
+func (p *Parser) ParseQuery() (query.Query, error) {
 	var statements []query.Statement
 	semi := true
 
 	for {
-		select {
-		case <-ctx.Done():
-			return query.Query{}, ctx.Err()
-		default:
-		}
-
 		if tok, pos, lit := p.ScanIgnoreWhitespace(); tok == scanner.EOF {
 			return query.New(statements...), nil
 		} else if tok == scanner.SEMICOLON {
diff --git a/sql/parser/select.go b/sql/parser/select.go
index 6a760fc3..3315f13a 100644
--- a/sql/parser/select.go
+++ b/sql/parser/select.go
@@ -14,6 +14,11 @@ func (p *Parser) parseSelectStatement() (*planner.Tree, error) {
 	var cfg selectConfig
 	var err error
 
+	cfg.Distinct, err = p.parseDistinct()
+	if err != nil {
+		return nil, err
+	}
+
 	// Parse path list or query.Wildcard
 	cfg.ProjectionExprs, err = p.parseResultFields()
 	if err != nil {
@@ -122,6 +127,15 @@ func (p *Parser) parseResultField() (planner.ProjectedField, error) {
 	return rf, nil
 }
 
+func (p *Parser) parseDistinct() (bool, error) {
+	if tok, _, _ := p.ScanIgnoreWhitespace(); tok != scanner.DISTINCT {
+		p.Unscan()
+		return false, nil
+	}
+
+	return true, nil
+}
+
 func (p *Parser) parseFrom() (string, bool, error) {
 	if tok, _, _ := p.ScanIgnoreWhitespace(); tok != scanner.FROM {
 		p.Unscan()
@@ -208,6 +222,7 @@ func (p *Parser) parseOffset() (expr.Expr, error) {
 // SelectConfig holds SELECT configuration.
 type selectConfig struct {
 	TableName        string
+	Distinct         bool
 	WhereExpr        expr.Expr
 	GroupByExpr      expr.Expr
 	OrderBy          expr.Path
@@ -235,6 +250,10 @@ func (cfg selectConfig) ToTree() (*planner.Tree, error) {
 
 	n = planner.NewProjectionNode(n, cfg.ProjectionExprs, cfg.TableName)
 
+	if cfg.Distinct {
+		n = planner.NewDedupNode(n, cfg.TableName)
+	}
+
 	if cfg.OrderBy != nil {
 		n = planner.NewSortNode(n, cfg.OrderBy, cfg.OrderByDirection)
 	}
diff --git a/sql/planner/distinct.go b/sql/planner/distinct.go
new file mode 100644
index 00000000..3d4b8b18
--- /dev/null
+++ b/sql/planner/distinct.go
@@ -0,0 +1,43 @@
+package planner
+
+import (
+	"github.com/genjidb/genji/database"
+	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/sql/query/expr"
+)
+
+type dedupNode struct {
+	node
+
+	tableName string
+	indexes   map[string]database.Index
+}
+
+func NewDedupNode(n Node, tableName string) Node {
+	return &dedupNode{
+		node: node{
+			op:   Dedup,
+			left: n,
+		},
+		tableName: tableName,
+	}
+}
+
+func (n *dedupNode) Bind(tx *database.Transaction, params []expr.Param) (err error) {
+	table, err := tx.GetTable(n.tableName)
+	if err != nil {
+		return
+	}
+
+	n.indexes, err = table.Indexes()
+	return
+}
+
+func (n *dedupNode) toStream(st document.Stream) (document.Stream, error) {
+	set := newDocumentHashSet(nil) // use default hashing algorithm
+	return st.Filter(set.Filter), nil
+}
+
+func (n *dedupNode) String() string {
+	return "Dedup()"
+}
diff --git a/sql/planner/explain.go b/sql/planner/explain.go
index 615212e7..e783370b 100644
--- a/sql/planner/explain.go
+++ b/sql/planner/explain.go
@@ -1,7 +1,6 @@
 package planner
 
 import (
-	"context"
 	"errors"
 
 	"github.com/genjidb/genji/database"
@@ -21,7 +20,7 @@ type ExplainStmt struct {
 // If the statement is a tree, Bind and Optimize will be called prior to
 // displaying all the operations.
 // Explain currently only works on SELECT, UPDATE and DELETE statements.
-func (s *ExplainStmt) Run(ctx context.Context, tx *database.Transaction, params []expr.Param) (query.Result, error) {
+func (s *ExplainStmt) Run(tx *database.Transaction, params []expr.Param) (query.Result, error) {
 	switch t := s.Statement.(type) {
 	case *Tree:
 		err := Bind(t, tx, params)
diff --git a/sql/planner/hash_set.go b/sql/planner/hash_set.go
new file mode 100644
index 00000000..ccf133fb
--- /dev/null
+++ b/sql/planner/hash_set.go
@@ -0,0 +1,68 @@
+package planner
+
+import (
+	"hash"
+	"hash/maphash"
+
+	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/key"
+)
+
+type documentHashSet struct {
+	hash hash.Hash64
+	set  map[uint64]struct{}
+}
+
+func newDocumentHashSet(hash hash.Hash64) *documentHashSet {
+	if hash == nil {
+		hash = &maphash.Hash{}
+	}
+
+	return &documentHashSet{
+		hash: hash,
+		set:  map[uint64]struct{}{},
+	}
+}
+
+func (s documentHashSet) generateKey(d document.Document) (uint64, error) {
+	defer s.hash.Reset()
+
+	fields, err := document.Fields(d)
+	if err != nil {
+		return 0, err
+	}
+
+	for _, field := range fields {
+		value, err := d.GetByField(field)
+		if err != nil {
+			return 0, err
+		}
+
+		buf, err := key.AppendValue(nil, value)
+		if err != nil {
+			return 0, err
+		}
+
+		_, err = s.hash.Write(buf)
+		if err != nil {
+			return 0, err
+		}
+	}
+
+	return s.hash.Sum64(), nil
+}
+
+func (s documentHashSet) Filter(d document.Document) (bool, error) {
+	k, err := s.generateKey(d)
+	if err != nil {
+		return false, err
+	}
+
+	_, ok := s.set[k]
+	if ok {
+		return false, nil
+	}
+
+	s.set[k] = struct{}{}
+	return true, nil
+}
diff --git a/sql/planner/optimizer.go b/sql/planner/optimizer.go
index 799abe69..45ba776c 100644
--- a/sql/planner/optimizer.go
+++ b/sql/planner/optimizer.go
@@ -11,6 +11,7 @@ var optimizerRules = []func(t *Tree) (*Tree, error){
 	SplitANDConditionRule,
 	PrecalculateExprRule,
 	RemoveUnnecessarySelectionNodesRule,
+	RemoveUnnecessaryDedupNodeRule,
 	UseIndexBasedOnSelectionNodeRule,
 }
 
@@ -248,6 +249,68 @@ func RemoveUnnecessarySelectionNodesRule(t *Tree) (*Tree, error) {
 	return t, nil
 }
 
+// RemoveUnnecessaryDedupNodeRule removes any Dedup nodes
+// where projection is already unique.
+func RemoveUnnecessaryDedupNodeRule(t *Tree) (*Tree, error) {
+	n := t.Root
+	var prev Node
+
+	for n != nil {
+		if n.Operation() == Dedup {
+			d, ok := n.(*dedupNode)
+			if !ok {
+				continue
+			}
+
+			pn, ok := d.left.(*ProjectionNode)
+			if !ok {
+				continue
+			}
+
+			// if the projection is unique, we remove the node from the tree
+			if isProjectionUnique(d.indexes, pn) {
+				if prev != nil {
+					prev.SetLeft(n.Left())
+				} else {
+					t.Root = n.Left()
+				}
+			}
+		}
+
+		prev = n
+		n = n.Left()
+	}
+
+	return t, nil
+}
+
+func isProjectionUnique(indexes map[string]database.Index, pn *ProjectionNode) bool {
+	pk := pn.info.GetPrimaryKey()
+	for _, field := range pn.Expressions {
+		e, ok := field.(ProjectedExpr)
+		if !ok {
+			return false
+		}
+
+		switch v := e.Expr.(type) {
+		case expr.Path:
+			if pk != nil && pk.Path.IsEqual(document.Path(v)) {
+				continue
+			}
+
+			if idx, ok := indexes[v.String()]; ok && idx.Unique {
+				continue
+			}
+		case expr.PKFunc:
+			continue
+		}
+
+		return false // if one field is not unique, so projection is not unique too.
+	}
+
+	return true
+}
+
 // UseIndexBasedOnSelectionNodeRule scans the tree for the first selection node whose condition is an
 // operator that satisfies the following criterias:
 // - implements the indexIteratorOperator interface
diff --git a/sql/planner/projection.go b/sql/planner/projection.go
index 77ef8741..ecede40a 100644
--- a/sql/planner/projection.go
+++ b/sql/planner/projection.go
@@ -126,14 +126,35 @@ type documentMask struct {
 
 var _ document.Document = documentMask{}
 
-func (r documentMask) GetByField(field string) (document.Value, error) {
+func (r documentMask) GetByField(field string) (v document.Value, err error) {
 	for _, rf := range r.resultFields {
 		if rf.Name() == field || rf.Name() == "*" {
-			return r.d.GetByField(field)
+			v, err = r.d.GetByField(field)
+			if err != document.ErrFieldNotFound {
+				return
+			}
+
+			stack := expr.EvalStack{
+				Document: r.d,
+				Info:     r.info,
+			}
+			var found bool
+			err = rf.Iterate(stack, func(f string, value document.Value) error {
+				if f == field {
+					v = value
+					found = true
+				}
+				return nil
+			})
+
+			if found || err != nil {
+				return
+			}
 		}
 	}
 
-	return document.Value{}, document.ErrFieldNotFound
+	err = document.ErrFieldNotFound
+	return
 }
 
 func (r documentMask) Iterate(fn func(field string, value document.Value) error) error {
diff --git a/sql/planner/replacement.go b/sql/planner/replacement.go
index 89c53d14..63c9887e 100644
--- a/sql/planner/replacement.go
+++ b/sql/planner/replacement.go
@@ -117,7 +117,7 @@ func (u *resumableIterator) Iterate(fn func(d document.Document) error) error {
 	var d encodedDocumentWithKey
 	var err error
 
-	it := u.store.NewIterator(engine.IteratorConfig{})
+	it := u.store.Iterator(engine.IteratorOptions{})
 	defer it.Close()
 
 	var buf []byte
@@ -136,6 +136,9 @@ func (u *resumableIterator) Iterate(fn func(d document.Document) error) error {
 			return err
 		}
 	}
+	if err := it.Err(); err != nil {
+		return err
+	}
 
 	return nil
 }
diff --git a/sql/planner/tree.go b/sql/planner/tree.go
index 79764f68..dbe1eb11 100644
--- a/sql/planner/tree.go
+++ b/sql/planner/tree.go
@@ -6,7 +6,6 @@
 package planner
 
 import (
-	"context"
 	"fmt"
 
 	"github.com/genjidb/genji/database"
@@ -43,6 +42,9 @@ const (
 	// 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.
+	_
+	// Dedup is an operation that removes duplicate documents from a stream
+	Dedup
 )
 
 // A Tree describes the flow of a stream of documents.
@@ -58,7 +60,7 @@ func NewTree(n Node) *Tree {
 
 // Run implements the query.Statement interface.
 // It binds the tree to the database resources and executes it.
-func (t *Tree) Run(ctx context.Context, tx *database.Transaction, params []expr.Param) (query.Result, error) {
+func (t *Tree) Run(tx *database.Transaction, params []expr.Param) (query.Result, error) {
 	err := Bind(t, tx, params)
 	if err != nil {
 		return query.Result{}, err
diff --git a/sql/query/alter.go b/sql/query/alter.go
index f53ccca2..eb48c673 100644
--- a/sql/query/alter.go
+++ b/sql/query/alter.go
@@ -1,7 +1,6 @@
 package query
 
 import (
-	"context"
 	"errors"
 
 	"github.com/genjidb/genji/database"
@@ -21,7 +20,7 @@ func (stmt AlterStmt) IsReadOnly() bool {
 
 // Run runs the ALTER TABLE statement in the given transaction.
 // It implements the Statement interface.
-func (stmt AlterStmt) Run(ctx context.Context, tx *database.Transaction, _ []expr.Param) (Result, error) {
+func (stmt AlterStmt) Run(tx *database.Transaction, _ []expr.Param) (Result, error) {
 	var res Result
 
 	if stmt.TableName == "" {
@@ -52,7 +51,7 @@ func (stmt AlterTableAddField) IsReadOnly() bool {
 
 // Run runs the ALTER TABLE ADD FIELD statement in the given transaction.
 // It implements the Statement interface.
-func (stmt AlterTableAddField) Run(ctx context.Context, tx *database.Transaction, _ []expr.Param) (Result, error) {
+func (stmt AlterTableAddField) Run(tx *database.Transaction, _ []expr.Param) (Result, error) {
 	var res Result
 
 	if stmt.TableName == "" {
diff --git a/sql/query/create.go b/sql/query/create.go
index fbc3fc13..3eb1da9e 100644
--- a/sql/query/create.go
+++ b/sql/query/create.go
@@ -1,7 +1,6 @@
 package query
 
 import (
-	"context"
 	"errors"
 
 	"github.com/genjidb/genji/database"
@@ -23,7 +22,7 @@ func (stmt CreateTableStmt) IsReadOnly() bool {
 
 // Run runs the Create table statement in the given transaction.
 // It implements the Statement interface.
-func (stmt CreateTableStmt) Run(ctx context.Context, tx *database.Transaction, args []expr.Param) (Result, error) {
+func (stmt CreateTableStmt) Run(tx *database.Transaction, args []expr.Param) (Result, error) {
 	var res Result
 
 	if stmt.TableName == "" {
@@ -60,7 +59,7 @@ func (stmt CreateIndexStmt) IsReadOnly() bool {
 
 // Run runs the Create index statement in the given transaction.
 // It implements the Statement interface.
-func (stmt CreateIndexStmt) Run(ctx context.Context, tx *database.Transaction, args []expr.Param) (Result, error) {
+func (stmt CreateIndexStmt) Run(tx *database.Transaction, args []expr.Param) (Result, error) {
 	var res Result
 
 	if stmt.TableName == "" {
diff --git a/sql/query/drop.go b/sql/query/drop.go
index 536d1393..d88babb0 100644
--- a/sql/query/drop.go
+++ b/sql/query/drop.go
@@ -1,7 +1,6 @@
 package query
 
 import (
-	"context"
 	"errors"
 
 	"github.com/genjidb/genji/database"
@@ -21,7 +20,7 @@ func (stmt DropTableStmt) IsReadOnly() bool {
 
 // Run runs the DropTable statement in the given transaction.
 // It implements the Statement interface.
-func (stmt DropTableStmt) Run(ctx context.Context, tx *database.Transaction, args []expr.Param) (Result, error) {
+func (stmt DropTableStmt) Run(tx *database.Transaction, args []expr.Param) (Result, error) {
 	var res Result
 
 	if stmt.TableName == "" {
@@ -49,7 +48,7 @@ func (stmt DropIndexStmt) IsReadOnly() bool {
 
 // Run runs the DropIndex statement in the given transaction.
 // It implements the Statement interface.
-func (stmt DropIndexStmt) Run(ctx context.Context, tx *database.Transaction, args []expr.Param) (Result, error) {
+func (stmt DropIndexStmt) Run(tx *database.Transaction, args []expr.Param) (Result, error) {
 	var res Result
 
 	if stmt.IndexName == "" {
diff --git a/sql/query/expr/comparison.go b/sql/query/expr/comparison.go
index 92423ac8..6684bdf3 100644
--- a/sql/query/expr/comparison.go
+++ b/sql/query/expr/comparison.go
@@ -134,7 +134,7 @@ func (op gtOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 		return err
 	}
 
-	it := tb.Store.NewIterator(engine.IteratorConfig{})
+	it := tb.Store.Iterator(engine.IteratorOptions{})
 	defer it.Close()
 
 	var buf []byte
@@ -152,6 +152,9 @@ func (op gtOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 			return err
 		}
 	}
+	if err := it.Err(); err != nil {
+		return err
+	}
 
 	return nil
 }
@@ -197,7 +200,7 @@ func (op gteOp) IteratePK(tb *database.Table, v document.Value, pkType document.
 		return err
 	}
 
-	it := tb.Store.NewIterator(engine.IteratorConfig{})
+	it := tb.Store.Iterator(engine.IteratorOptions{})
 	defer it.Close()
 
 	var buf []byte
@@ -212,6 +215,9 @@ func (op gteOp) IteratePK(tb *database.Table, v document.Value, pkType document.
 			return err
 		}
 	}
+	if err := it.Err(); err != nil {
+		return err
+	}
 
 	return nil
 }
@@ -274,7 +280,7 @@ func (op ltOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 		return err
 	}
 
-	it := tb.Store.NewIterator(engine.IteratorConfig{})
+	it := tb.Store.Iterator(engine.IteratorOptions{})
 	defer it.Close()
 
 	var buf []byte
@@ -292,6 +298,9 @@ func (op ltOp) IteratePK(tb *database.Table, v document.Value, pkType document.V
 			return err
 		}
 	}
+	if err := it.Err(); err != nil {
+		return err
+	}
 
 	return nil
 }
@@ -355,7 +364,7 @@ func (op lteOp) IteratePK(tb *database.Table, v document.Value, pkType document.
 		return err
 	}
 
-	it := tb.Store.NewIterator(engine.IteratorConfig{})
+	it := tb.Store.Iterator(engine.IteratorOptions{})
 	defer it.Close()
 
 	var buf []byte
@@ -373,6 +382,9 @@ func (op lteOp) IteratePK(tb *database.Table, v document.Value, pkType document.
 			return err
 		}
 	}
+	if err := it.Err(); err != nil {
+		return err
+	}
 
 	return nil
 }
diff --git a/sql/query/insert.go b/sql/query/insert.go
index cd8a84e9..24001136 100644
--- a/sql/query/insert.go
+++ b/sql/query/insert.go
@@ -1,7 +1,6 @@
 package query
 
 import (
-	"context"
 	"errors"
 	"fmt"
 
@@ -24,7 +23,7 @@ func (stmt InsertStmt) IsReadOnly() bool {
 
 // Run the Insert statement in the given transaction.
 // It implements the Statement interface.
-func (stmt InsertStmt) Run(ctx context.Context, tx *database.Transaction, args []expr.Param) (Result, error) {
+func (stmt InsertStmt) Run(tx *database.Transaction, args []expr.Param) (Result, error) {
 	var res Result
 
 	if stmt.TableName == "" {
diff --git a/sql/query/query.go b/sql/query/query.go
index 8ec774d4..32ef5c71 100644
--- a/sql/query/query.go
+++ b/sql/query/query.go
@@ -32,7 +32,7 @@ func (q Query) Run(ctx context.Context, db *database.Database, args []expr.Param
 	}
 
 	type queryAlterer interface {
-		alterQuery(db *database.Database, q *Query) error
+		alterQuery(ctx context.Context, db *database.Database, q *Query) error
 	}
 
 	for i, stmt := range q.Statements {
@@ -43,7 +43,7 @@ func (q Query) Run(ctx context.Context, db *database.Database, args []expr.Param
 		}
 
 		if qa, ok := stmt.(queryAlterer); ok {
-			err = qa.alterQuery(db, &q)
+			err = qa.alterQuery(ctx, db, &q)
 			if err != nil {
 				if tx := db.GetAttachedTx(); tx != nil {
 					tx.Rollback()
@@ -55,13 +55,15 @@ func (q Query) Run(ctx context.Context, db *database.Database, args []expr.Param
 		}
 
 		if q.tx == nil {
-			q.tx, err = db.Begin(!stmt.IsReadOnly())
+			q.tx, err = db.BeginTx(ctx, &database.TxOptions{
+				ReadOnly: stmt.IsReadOnly(),
+			})
 			if err != nil {
 				return nil, err
 			}
 		}
 
-		res, err = stmt.Run(ctx, q.tx, args)
+		res, err = stmt.Run(q.tx, args)
 		if err != nil {
 			if q.autoCommit {
 				q.tx.Rollback()
@@ -98,18 +100,12 @@ func (q Query) Run(ctx context.Context, db *database.Database, args []expr.Param
 }
 
 // Exec the query within the given transaction.
-func (q Query) Exec(ctx context.Context, tx *database.Transaction, args []expr.Param) (*Result, error) {
+func (q Query) Exec(tx *database.Transaction, args []expr.Param) (*Result, error) {
 	var res Result
 	var err error
 
 	for _, stmt := range q.Statements {
-		select {
-		case <-ctx.Done():
-			return nil, ctx.Err()
-		default:
-		}
-
-		res, err = stmt.Run(ctx, tx, args)
+		res, err = stmt.Run(tx, args)
 		if err != nil {
 			return nil, err
 		}
@@ -125,7 +121,7 @@ func New(statements ...Statement) Query {
 
 // A Statement represents a unique action that can be executed against the database.
 type Statement interface {
-	Run(context.Context, *database.Transaction, []expr.Param) (Result, error)
+	Run(*database.Transaction, []expr.Param) (Result, error)
 	IsReadOnly() bool
 }
 
diff --git a/sql/query/reindex.go b/sql/query/reindex.go
index aeee50ce..3bec4136 100644
--- a/sql/query/reindex.go
+++ b/sql/query/reindex.go
@@ -1,7 +1,6 @@
 package query
 
 import (
-	"context"
 	"errors"
 	
 	"github.com/genjidb/genji/database"
@@ -20,7 +19,7 @@ func (stmt ReIndexStmt) IsReadOnly() bool {
 
 // Run runs the Reindex statement in the given transaction.
 // It implements the Statement interface.
-func (stmt ReIndexStmt) Run(ctx context.Context, tx *database.Transaction, args []expr.Param) (Result, error) {
+func (stmt ReIndexStmt) Run(tx *database.Transaction, args []expr.Param) (Result, error) {
 	var res Result
 
 	if stmt.TableOrIndexName == "" {
diff --git a/sql/query/transaction.go b/sql/query/transaction.go
index fe1a7c48..5ec093fd 100644
--- a/sql/query/transaction.go
+++ b/sql/query/transaction.go
@@ -13,13 +13,13 @@ type BeginStmt struct {
 	Writable bool
 }
 
-func (stmt BeginStmt) alterQuery(db *database.Database, q *Query) error {
+func (stmt BeginStmt) alterQuery(ctx context.Context, db *database.Database, q *Query) error {
 	if q.tx != nil {
 		return errors.New("cannot begin a transaction within a transaction")
 	}
 
 	var err error
-	q.tx, err = db.BeginTx(&database.TxOptions{
+	q.tx, err = db.BeginTx(ctx, &database.TxOptions{
 		ReadOnly: !stmt.Writable,
 		Attached: true,
 	})
@@ -31,14 +31,14 @@ func (stmt BeginStmt) IsReadOnly() bool {
 	return !stmt.Writable
 }
 
-func (stmt BeginStmt) Run(ctx context.Context, tx *database.Transaction, args []expr.Param) (Result, error) {
+func (stmt BeginStmt) Run(tx *database.Transaction, args []expr.Param) (Result, error) {
 	return Result{}, errors.New("cannot begin a transaction within a transaction")
 }
 
 // RollbackStmt is a statement that rollbacks the current active transaction.
 type RollbackStmt struct{}
 
-func (stmt RollbackStmt) alterQuery(db *database.Database, q *Query) error {
+func (stmt RollbackStmt) alterQuery(ctx context.Context, db *database.Database, q *Query) error {
 	if q.tx == nil || q.autoCommit == true {
 		return errors.New("cannot rollback with no active transaction")
 	}
@@ -55,14 +55,14 @@ func (stmt RollbackStmt) IsReadOnly() bool {
 	return false
 }
 
-func (stmt RollbackStmt) Run(ctx context.Context, tx *database.Transaction, args []expr.Param) (Result, error) {
+func (stmt RollbackStmt) Run(tx *database.Transaction, args []expr.Param) (Result, error) {
 	return Result{}, errors.New("cannot rollback with no active transaction")
 }
 
 // CommitStmt is a statement that commits the current active transaction.
 type CommitStmt struct{}
 
-func (stmt CommitStmt) alterQuery(db *database.Database, q *Query) error {
+func (stmt CommitStmt) alterQuery(ctx context.Context, db *database.Database, q *Query) error {
 	if q.tx == nil || q.autoCommit == true {
 		return errors.New("cannot commit with no active transaction")
 	}
@@ -79,6 +79,6 @@ func (stmt CommitStmt) IsReadOnly() bool {
 	return false
 }
 
-func (stmt CommitStmt) Run(ctx context.Context, tx *database.Transaction, args []expr.Param) (Result, error) {
+func (stmt CommitStmt) Run(tx *database.Transaction, args []expr.Param) (Result, error) {
 	return Result{}, errors.New("cannot commit with no active transaction")
 }
diff --git a/sql/scanner/token.go b/sql/scanner/token.go
index 695733d1..dee2cac7 100644
--- a/sql/scanner/token.go
+++ b/sql/scanner/token.go
@@ -85,6 +85,7 @@ const (
 	DEFAULT
 	DELETE
 	DESC
+	DISTINCT
 	DROP
 	EXISTS
 	EXPLAIN
@@ -208,6 +209,7 @@ var tokens = [...]string{
 	DEFAULT:     "DEFAULT",
 	DELETE:      "DELETE",
 	DESC:        "DESC",
+	DISTINCT:    "DISTINCT",
 	DROP:        "DROP",
 	EXISTS:      "EXISTS",
 	EXPLAIN:     "EXPLAIN",


Test patch:
diff --git a/cmd/genji/insert_test.go b/cmd/genji/insert_test.go
index 8670059e..a4fc2746 100644
--- a/cmd/genji/insert_test.go
+++ b/cmd/genji/insert_test.go
@@ -2,7 +2,6 @@ package main
 
 import (
 	"bytes"
-	"context"
 	"strings"
 	"testing"
 
@@ -12,8 +11,6 @@ import (
 )
 
 func TestExecuteInsertCommand(t *testing.T) {
-	ctx := context.Background()
-
 	tests := []struct {
 		name  string
 		data  string
@@ -35,16 +32,16 @@ func TestExecuteInsertCommand(t *testing.T) {
 			require.NoError(t, err)
 			defer db.Close()
 
-			err = db.Exec(ctx, `CREATE TABLE foo`)
+			err = db.Exec(`CREATE TABLE foo`)
 			require.NoError(t, err)
-			err = executeInsertCommand(context.Background(), db, "foo", strings.NewReader(tt.data))
+			err = executeInsertCommand(db, "foo", strings.NewReader(tt.data))
 			if tt.fails {
 				require.Error(t, err)
 				return
 			}
 
 			require.NoError(t, err)
-			res, err := db.Query(ctx, "SELECT * FROM foo")
+			res, err := db.Query("SELECT * FROM foo")
 			defer res.Close()
 			require.NoError(t, err)
 
@@ -75,11 +72,11 @@ func TestExecuteInsertCommand(t *testing.T) {
 		require.NoError(t, err)
 		defer db.Close()
 
-		err = db.Exec(ctx, `CREATE TABLE foo`)
+		err = db.Exec(`CREATE TABLE foo`)
 		require.NoError(t, err)
-		err = executeInsertCommand(context.Background(), db, "foo", strings.NewReader(jsonArray))
+		err = executeInsertCommand(db, "foo", strings.NewReader(jsonArray))
 		require.NoError(t, err)
-		res, err := db.Query(ctx, "SELECT * FROM foo")
+		res, err := db.Query("SELECT * FROM foo")
 		defer res.Close()
 		require.NoError(t, err)
 
@@ -110,13 +107,13 @@ func TestExecuteInsertCommand(t *testing.T) {
 		defer db.Close()
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, `CREATE TABLE foo`)
+		err = db.Exec(`CREATE TABLE foo`)
 		require.NoError(t, err)
 
-		err = executeInsertCommand(context.Background(), db, "foo", strings.NewReader(jsonStream))
+		err = executeInsertCommand(db, "foo", strings.NewReader(jsonStream))
 		require.NoError(t, err)
 
-		res, err := db.Query(ctx, "SELECT * FROM foo")
+		res, err := db.Query("SELECT * FROM foo")
 		defer res.Close()
 		require.NoError(t, err)
 
diff --git a/cmd/genji/shell/command_test.go b/cmd/genji/shell/command_test.go
index b69279e6..bcc2677e 100644
--- a/cmd/genji/shell/command_test.go
+++ b/cmd/genji/shell/command_test.go
@@ -2,7 +2,6 @@ package shell
 
 import (
 	"bytes"
-	"context"
 	"fmt"
 	"strings"
 	"testing"
@@ -70,11 +69,9 @@ func TestIndexesCmd(t *testing.T) {
 			require.NoError(t, err)
 			defer db.Close()
 
-			ctx := context.Background()
-
-			err = db.Exec(ctx, "CREATE TABLE test")
+			err = db.Exec("CREATE TABLE test")
 			require.NoError(t, err)
-			err = db.Exec(ctx, `
+			err = db.Exec(`
 						CREATE INDEX idx_a ON test (a);
 						CREATE INDEX idx_b ON test (b);
 						CREATE INDEX idx_c ON test (c);
@@ -101,8 +98,6 @@ func TestRunDumpCmd(t *testing.T) {
 		{"text / pk and not null with type constraint", `INSERT INTO test (a, b, c) VALUES ('a', 'b', 'c')`, `TEXT PRIMARY KEY NOT NULL`, `INSERT INTO test VALUES {"a": "a", "b": "b", "c": "c"};`, false, nil},
 	}
 
-	ctx := context.Background()
-
 	for _, tt := range tests {
 
 		testFn := func(withIndexes, withConstraints bool) func(t *testing.T) {
@@ -118,19 +113,19 @@ func TestRunDumpCmd(t *testing.T) {
 				ci := "COMMIT;\n"
 				if withConstraints {
 					q := fmt.Sprintf("CREATE TABLE test (\n  a %s\n);\n", tt.fieldConstraint)
-					err := db.Exec(ctx, q)
+					err := db.Exec(q)
 					require.NoError(t, err)
 					bwant.WriteString(q)
 				} else {
 					q := `CREATE TABLE test;`
-					err = db.Exec(ctx, q)
+					err = db.Exec(q)
 					require.NoError(t, err)
 					q = fmt.Sprintf("%s\n", q)
 					bwant.WriteString(q)
 				}
 
 				if withIndexes {
-					err = db.Exec(ctx, `
+					err = db.Exec(`
 						CREATE INDEX idx_a ON test (a);
 					`)
 					require.NoError(t, err)
@@ -149,7 +144,7 @@ func TestRunDumpCmd(t *testing.T) {
 					require.NoError(t, err)
 
 				}
-				err = db.Exec(context.Background(), tt.query, tt.params...)
+				err = db.Exec(tt.query, tt.params...)
 				if tt.fails {
 					require.Error(t, err)
 					return
diff --git a/database/config_test.go b/database/config_test.go
index e1674beb..e232f790 100644
--- a/database/config_test.go
+++ b/database/config_test.go
@@ -1,11 +1,13 @@
 package database
 
 import (
+	"context"
 	"errors"
 	"testing"
 
 	"github.com/genjidb/genji/document"
 	"github.com/genjidb/genji/document/encoding/msgpack"
+	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/engine/memoryengine"
 	"github.com/stretchr/testify/require"
 )
@@ -37,7 +39,9 @@ func TestTableInfoStore(t *testing.T) {
 		ng := memoryengine.NewEngine()
 		defer ng.Close()
 
-		db, err := New(ng, Options{Codec: msgpack.NewCodec()})
+		db, err := New(context.Background(), ng, Options{
+			Codec: msgpack.NewCodec(),
+		})
 		require.NoError(t, err)
 		defer db.Close()
 
@@ -84,7 +88,9 @@ func TestTableInfoStore(t *testing.T) {
 		ng := memoryengine.NewEngine()
 		defer ng.Close()
 
-		db, err := New(ng, Options{Codec: msgpack.NewCodec()})
+		db, err := New(context.Background(), ng, Options{
+			Codec: msgpack.NewCodec(),
+		})
 		require.NoError(t, err)
 		defer db.Close()
 
@@ -111,7 +117,9 @@ func TestTableInfoStore(t *testing.T) {
 		ng := memoryengine.NewEngine()
 		defer ng.Close()
 
-		db, err := New(ng, Options{Codec: msgpack.NewCodec()})
+		db, err := New(context.Background(), ng, Options{
+			Codec: msgpack.NewCodec(),
+		})
 		require.NoError(t, err)
 		defer db.Close()
 
@@ -142,7 +150,9 @@ func TestIndexStore(t *testing.T) {
 	ng := memoryengine.NewEngine()
 	defer ng.Close()
 
-	tx, err := ng.Begin(true)
+	tx, err := ng.Begin(context.Background(), engine.TxOptions{
+		Writable: true,
+	})
 	require.NoError(t, err)
 	defer tx.Rollback()
 
diff --git a/database/database_test.go b/database/database_test.go
new file mode 100644
index 00000000..882546e3
--- /dev/null
+++ b/database/database_test.go
@@ -0,0 +1,69 @@
+package database_test
+
+import (
+	"testing"
+	"time"
+
+	"github.com/genjidb/genji"
+	"github.com/stretchr/testify/require"
+)
+
+// See issue https://github.com/genjidb/genji/issues/298
+func TestConcurrentTransactionManagement(t *testing.T) {
+	db, err := genji.Open(":memory:")
+	require.NoError(t, err)
+	defer func() {
+		require.NoError(t, db.Close())
+	}()
+
+	ch := make(chan struct{})
+	done := make(chan struct{})
+
+	go func() {
+		// 1. Start transaction T1.
+		tx, err := db.Begin(true)
+		require.NoError(t, err)
+
+		// Start transaction T2.
+		ch <- struct{}{}
+		// Wait in case goroutine gets rescheduled.
+		time.Sleep(time.Millisecond)
+
+		// 3. Commit or rollback T1.
+		require.NoError(t, tx.Rollback())
+
+		// Wait for T2 to finish and return.
+		<-ch
+		done <- struct{}{}
+	}()
+
+	go func() {
+		<-ch // wait for T1 to start.
+
+		// 2. Attempt to start transaction T2.
+		// Waits for T1 to finish.
+		tx, err := db.Begin(true)
+		require.NoError(t, err)
+		require.NoError(t, tx.Rollback())
+
+		ch <- struct{}{}
+	}()
+
+	r := make(chan bool)
+	go func() {
+		t := time.NewTimer(time.Second)
+		select {
+		case <-t.C:
+			r <- false
+		case <-done:
+			r <- true
+		}
+		if !t.Stop() {
+			<-t.C
+		}
+	}()
+
+	if ok := <-r; !ok {
+		t.Fatal("deadlock")
+	}
+}
diff --git a/database/table_test.go b/database/table_test.go
index 4c6ed7a0..a0202ae1 100644
--- a/database/table_test.go
+++ b/database/table_test.go
@@ -1,6 +1,7 @@
 package database_test
 
 import (
+	"context"
 	"encoding/binary"
 	"errors"
 	"fmt"
@@ -152,7 +153,9 @@ func TestTableInsert(t *testing.T) {
 	t.Run("Should generate the right docid on existing databases", func(t *testing.T) {
 		ng := memoryengine.NewEngine()
 
-		db, err := database.New(ng, database.Options{Codec: msgpack.NewCodec()})
+		db, err := database.New(context.Background(), ng, database.Options{
+			Codec: msgpack.NewCodec(),
+		})
 		require.NoError(t, err)
 
 		insertDoc := func(db *database.Database) []byte {
@@ -179,7 +182,9 @@ func TestTableInsert(t *testing.T) {
 		require.NoError(t, err)
 
 		// create new database object
-		db, err = database.New(ng, database.Options{Codec: msgpack.NewCodec()})
+		db, err = database.New(context.Background(), ng, database.Options{
+			Codec: msgpack.NewCodec(),
+		})
 		require.NoError(t, err)
 
 		key2 := insertDoc(db)
diff --git a/database/transaction_test.go b/database/transaction_test.go
index 4b13d7cb..98e0e59a 100644
--- a/database/transaction_test.go
+++ b/database/transaction_test.go
@@ -1,6 +1,7 @@
 package database_test
 
 import (
+	"context"
 	"errors"
 	"testing"
 
@@ -13,7 +14,9 @@ import (
 )
 
 func newTestDB(t testing.TB) (*database.Transaction, func()) {
-	db, err := database.New(memoryengine.NewEngine(), database.Options{Codec: msgpack.NewCodec()})
+	db, err := database.New(context.Background(), memoryengine.NewEngine(), database.Options{
+		Codec: msgpack.NewCodec(),
+	})
 	require.NoError(t, err)
 
 	tx, err := db.Begin(true)
@@ -48,7 +51,9 @@ func TestTxTable(t *testing.T) {
 	})
 
 	t.Run("Create and rollback", func(t *testing.T) {
-		db, err := database.New(memoryengine.NewEngine(), database.Options{Codec: msgpack.NewCodec()})
+		db, err := database.New(context.Background(), memoryengine.NewEngine(), database.Options{
+			Codec: msgpack.NewCodec(),
+		})
 		require.NoError(t, err)
 		defer db.Close()
 
diff --git a/db_test.go b/db_test.go
index 42d04b1e..eba49b22 100644
--- a/db_test.go
+++ b/db_test.go
@@ -1,7 +1,6 @@
 package genji_test
 
 import (
-	"context"
 	"fmt"
 	"log"
 	"testing"
@@ -25,19 +24,17 @@ func ExampleTx() {
 	}
 	defer tx.Rollback()
 
-	ctx := context.Background()
-
-	err = tx.Exec(ctx, "CREATE TABLE IF NOT EXISTS user")
+	err = tx.Exec("CREATE TABLE IF NOT EXISTS user")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	err = tx.Exec(ctx, "INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "foo", 15)
+	err = tx.Exec("INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "foo", 15)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	d, err := tx.QueryDocument(ctx, "SELECT id, name, age FROM user WHERE name = ?", "foo")
+	d, err := tx.QueryDocument("SELECT id, name, age FROM user WHERE name = ?", "foo")
 	if err != nil {
 		panic(err)
 	}
@@ -77,9 +74,7 @@ func TestQueryDocument(t *testing.T) {
 	tx, err := db.Begin(true)
 	require.NoError(t, err)
 
-	ctx := context.Background()
-
-	err = tx.Exec(ctx, `
+	err = tx.Exec(`
 			CREATE TABLE test;
 			INSERT INTO test (a, b) VALUES (1, 'foo'), (2, 'bar')
 		`)
@@ -90,7 +85,7 @@ func TestQueryDocument(t *testing.T) {
 		var a int
 		var b string
 
-		r, err := db.QueryDocument(ctx, "SELECT * FROM test")
+		r, err := db.QueryDocument("SELECT * FROM test")
 		err = document.Scan(r, &a, &b)
 		require.NoError(t, err)
 		require.Equal(t, 1, a)
@@ -100,7 +95,7 @@ func TestQueryDocument(t *testing.T) {
 		require.NoError(t, err)
 		defer tx.Rollback()
 
-		r, err = tx.QueryDocument(ctx, "SELECT * FROM test")
+		r, err = tx.QueryDocument("SELECT * FROM test")
 		require.NoError(t, err)
 		err = document.Scan(r, &a, &b)
 		require.NoError(t, err)
@@ -109,14 +104,14 @@ func TestQueryDocument(t *testing.T) {
 	})
 
 	t.Run("Should return an error if no document", func(t *testing.T) {
-		r, err := db.QueryDocument(ctx, "SELECT * FROM test WHERE a > 100")
+		r, err := db.QueryDocument("SELECT * FROM test WHERE a > 100")
 		require.Equal(t, database.ErrDocumentNotFound, err)
 		require.Nil(t, r)
 
 		tx, err := db.Begin(false)
 		require.NoError(t, err)
 		defer tx.Rollback()
-		r, err = tx.QueryDocument(ctx, "SELECT * FROM test WHERE a > 100")
+		r, err = tx.QueryDocument("SELECT * FROM test WHERE a > 100")
 		require.Equal(t, database.ErrDocumentNotFound, err)
 		require.Nil(t, r)
 	})
diff --git a/document/iterator_test.go b/document/iterator_test.go
index f9721834..dad203a2 100644
--- a/document/iterator_test.go
+++ b/document/iterator_test.go
@@ -2,7 +2,6 @@ package document_test
 
 import (
 	"bytes"
-	"context"
 	"encoding/json"
 	"fmt"
 	"log"
@@ -20,19 +19,17 @@ func ExampleStream_First() {
 	}
 	defer db.Close()
 
-	ctx := context.Background()
-
-	err = db.Exec(ctx, "CREATE TABLE user")
+	err = db.Exec("CREATE TABLE user")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	err = db.Exec(ctx, "INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "foo", 15)
+	err = db.Exec("INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "foo", 15)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	result, err := db.Query(ctx, "SELECT id, name, age FROM user WHERE name = ?", "foo")
+	result, err := db.Query("SELECT id, name, age FROM user WHERE name = ?", "foo")
 	if err != nil {
 		panic(err)
 	}
@@ -75,15 +72,13 @@ func ExampleStream_Iterate() {
 	}
 	defer db.Close()
 
-	ctx := context.Background()
-
-	err = db.Exec(ctx, "CREATE TABLE IF NOT EXISTS user")
+	err = db.Exec("CREATE TABLE IF NOT EXISTS user")
 	if err != nil {
 		log.Fatal(err)
 	}
 
 	for i := 1; i <= 10; i++ {
-		err = db.Exec(ctx, "INSERT INTO user VALUES ?", &User{
+		err = db.Exec("INSERT INTO user VALUES ?", &User{
 			ID:   int64(i),
 			Name: fmt.Sprintf("foo%d", i),
 			Age:  uint32(i * 10),
@@ -100,7 +95,7 @@ func ExampleStream_Iterate() {
 		}
 	}
 
-	result, err := db.Query(ctx, `SELECT id, name, age, address FROM user WHERE age >= 18`)
+	result, err := db.Query(`SELECT id, name, age, address FROM user WHERE age >= 18`)
 	if err != nil {
 		panic(err)
 	}
diff --git a/engine/badgerengine/example_test.go b/engine/badgerengine/example_test.go
index e639bf73..8386ea3d 100644
--- a/engine/badgerengine/example_test.go
+++ b/engine/badgerengine/example_test.go
@@ -1,6 +1,7 @@
 package badgerengine_test
 
 import (
+	"context"
 	"io/ioutil"
 	"log"
 	"os"
@@ -23,7 +24,7 @@ func Example() {
 		log.Fatal(err)
 	}
 
-	db, err := genji.New(ng)
+	db, err := genji.New(context.Background(), ng)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/engine/boltengine/example_test.go b/engine/boltengine/example_test.go
index fe9d4d70..2078148b 100644
--- a/engine/boltengine/example_test.go
+++ b/engine/boltengine/example_test.go
@@ -1,6 +1,7 @@
 package boltengine_test
 
 import (
+	"context"
 	"io/ioutil"
 	"log"
 	"os"
@@ -36,7 +37,7 @@ func ExampleNewEngine() {
 		log.Fatal(err)
 	}
 
-	db, err := genji.New(ng)
+	db, err := genji.New(context.Background(), ng)
 	defer db.Close()
 	if err != nil {
 		log.Fatal(err)
diff --git a/engine/enginetest/benchmark.go b/engine/enginetest/benchmark.go
index 7bd661a6..15ecf508 100644
--- a/engine/enginetest/benchmark.go
+++ b/engine/enginetest/benchmark.go
@@ -47,11 +47,13 @@ func BenchmarkStoreScan(b *testing.B, builder Builder) {
 
 			b.ResetTimer()
 			for i := 0; i < b.N; i++ {
-				it := st.NewIterator(engine.IteratorConfig{})
+				it := st.Iterator(engine.IteratorOptions{})
 				for it.Seek(nil); it.Valid(); it.Next() {
 				}
-				err := it.Close()
-				if err != nil {
+				if err := it.Err(); err != nil {
+					require.NoError(b, err)
+				}
+				if err := it.Close(); err != nil {
 					require.NoError(b, err)
 				}
 			}
diff --git a/engine/enginetest/testing.go b/engine/enginetest/testing.go
index 474cef44..edc71cde 100644
--- a/engine/enginetest/testing.go
+++ b/engine/enginetest/testing.go
@@ -71,7 +71,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 	}()
 
 	t.Run("Commit on read-only transaction should fail", func(t *testing.T) {
-		tx, err := ng.Begin(false)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: false,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -80,7 +82,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 	})
 
 	t.Run("Commit after rollback should fail", func(t *testing.T) {
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -92,7 +96,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 	})
 
 	t.Run("Rollback after commit should not fail", func(t *testing.T) {
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -104,7 +110,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 	})
 
 	t.Run("Commit after commit should fail", func(t *testing.T) {
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -116,7 +124,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 	})
 
 	t.Run("Rollback after rollback should not fail", func(t *testing.T) {
-		tx, err := ng.Begin(false)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: false,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -128,7 +138,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 	})
 
 	t.Run("Read-Only write attempts", func(t *testing.T) {
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 
 		// create store for testing store methods
@@ -139,7 +151,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 		require.NoError(t, err)
 
 		// create a new read-only transaction
-		tx, err = ng.Begin(false)
+		tx, err = ng.Begin(context.Background(), engine.TxOptions{
+			Writable: false,
+		})
 		defer tx.Rollback()
 
 		// fetch the store and the index
@@ -214,7 +228,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 
 				if test.initFn != nil {
 					func() {
-						tx, err := ng.Begin(true)
+						tx, err := ng.Begin(context.Background(), engine.TxOptions{
+							Writable: true,
+						})
 						require.NoError(t, err)
 						defer tx.Rollback()
 
@@ -225,7 +241,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 					}()
 				}
 
-				tx, err := ng.Begin(true)
+				tx, err := ng.Begin(context.Background(), engine.TxOptions{
+					Writable: true,
+				})
 				require.NoError(t, err)
 				defer tx.Rollback()
 
@@ -235,7 +253,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 				err = tx.Rollback()
 				require.NoError(t, err)
 
-				tx, err = ng.Begin(true)
+				tx, err = ng.Begin(context.Background(), engine.TxOptions{
+					Writable: true,
+				})
 				require.NoError(t, err)
 				defer tx.Rollback()
 
@@ -254,7 +274,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 			t.Run(test.name+"/commit", func(t *testing.T) {
 				if test.initFn != nil {
 					func() {
-						tx, err := ng.Begin(true)
+						tx, err := ng.Begin(context.Background(), engine.TxOptions{
+							Writable: true,
+						})
 						require.NoError(t, err)
 						defer tx.Rollback()
 
@@ -265,7 +287,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 					}()
 				}
 
-				tx, err := ng.Begin(true)
+				tx, err := ng.Begin(context.Background(), engine.TxOptions{
+					Writable: true,
+				})
 				require.NoError(t, err)
 				defer tx.Rollback()
 
@@ -275,7 +299,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 				err = tx.Commit()
 				require.NoError(t, err)
 
-				tx, err = ng.Begin(true)
+				tx, err = ng.Begin(context.Background(), engine.TxOptions{
+					Writable: true,
+				})
 				require.NoError(t, err)
 				defer tx.Rollback()
 
@@ -306,7 +332,9 @@ func TestTransactionCommitRollback(t *testing.T, builder Builder) {
 					require.NoError(t, ng.Close())
 				}()
 
-				tx, err := ng.Begin(true)
+				tx, err := ng.Begin(context.Background(), engine.TxOptions{
+					Writable: true,
+				})
 				require.NoError(t, err)
 				defer tx.Rollback()
 
@@ -329,7 +357,9 @@ func TestTransactionCreateStore(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -348,7 +378,9 @@ func TestTransactionCreateStore(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -368,7 +400,9 @@ func TestTransactionStore(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		tx, err := ng.Begin(false)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: false,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -383,7 +417,9 @@ func TestTransactionStore(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -426,7 +462,9 @@ func TestTransactionDropStore(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -447,7 +485,9 @@ func TestTransactionDropStore(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -458,7 +498,9 @@ func TestTransactionDropStore(t *testing.T, builder Builder) {
 
 func storeBuilder(t testing.TB, builder Builder) (engine.Store, func()) {
 	ng, cleanup := builder()
-	tx, err := ng.Begin(true)
+	tx, err := ng.Begin(context.Background(), engine.TxOptions{
+		Writable: true,
+	})
 	require.NoError(t, err)
 	err = tx.CreateStore([]byte("test"))
 	require.NoError(t, err)
@@ -480,13 +522,14 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 			st, cleanup := storeBuilder(t, builder)
 			defer cleanup()
 
-			it := st.NewIterator(engine.IteratorConfig{Reverse: reverse})
+			it := st.Iterator(engine.IteratorOptions{Reverse: reverse})
 			defer it.Close()
 			i := 0
 
 			for it.Seek(nil); it.Valid(); it.Next() {
 				i++
 			}
+			require.NoError(t, it.Err())
 			require.Zero(t, i)
 		}
 		t.Run("Reverse: false", func(t *testing.T) {
@@ -508,7 +551,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 
 		var i uint8 = 1
 		var count int
-		it := st.NewIterator(engine.IteratorConfig{})
+		it := st.Iterator(engine.IteratorOptions{})
 		defer it.Close()
 
 		for it.Seek(nil); it.Valid(); it.Next() {
@@ -520,6 +563,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 			i++
 			count++
 		}
+		require.NoError(t, it.Err())
 
 		require.Equal(t, count, 10)
 	})
@@ -535,7 +579,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 
 		var i uint8 = 10
 		var count int
-		it := st.NewIterator(engine.IteratorConfig{Reverse: true})
+		it := st.Iterator(engine.IteratorOptions{Reverse: true})
 		defer it.Close()
 
 		for it.Seek(nil); it.Valid(); it.Next() {
@@ -547,6 +591,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 			i--
 			count++
 		}
+		require.NoError(t, it.Err())
 		require.Equal(t, 10, count)
 	})
 
@@ -561,7 +606,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 
 		var i uint8 = 4
 		var count int
-		it := st.NewIterator(engine.IteratorConfig{})
+		it := st.Iterator(engine.IteratorOptions{})
 		defer it.Close()
 
 		for it.Seek([]byte{i}); it.Valid(); it.Next() {
@@ -573,6 +618,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 			i++
 			count++
 		}
+		require.NoError(t, it.Err())
 		require.Equal(t, 7, count)
 	})
 
@@ -587,7 +633,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 
 		var i uint8 = 4
 		var count int
-		it := st.NewIterator(engine.IteratorConfig{Reverse: true})
+		it := st.Iterator(engine.IteratorOptions{Reverse: true})
 		defer it.Close()
 
 		for it.Seek([]byte{i}); it.Valid(); it.Next() {
@@ -599,6 +645,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 			i--
 			count++
 		}
+		require.NoError(t, it.Err())
 		require.Equal(t, 4, count)
 	})
 
@@ -613,7 +660,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 		require.NoError(t, err)
 
 		called := false
-		it := st.NewIterator(engine.IteratorConfig{})
+		it := st.Iterator(engine.IteratorOptions{})
 		defer it.Close()
 
 		for it.Seek([]byte{2}); it.Valid(); it.Next() {
@@ -624,6 +671,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 			require.Equal(t, []byte{3}, v)
 			called = true
 		}
+		require.NoError(t, it.Err())
 
 		require.True(t, called)
 	})
@@ -639,7 +687,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 		require.NoError(t, err)
 
 		called := false
-		it := st.NewIterator(engine.IteratorConfig{Reverse: true})
+		it := st.Iterator(engine.IteratorOptions{Reverse: true})
 		defer it.Close()
 
 		for it.Seek([]byte{2}); it.Valid(); it.Next() {
@@ -650,6 +698,7 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 			require.Equal(t, []byte{1}, v)
 			called = true
 		}
+		require.NoError(t, it.Err())
 		require.True(t, called)
 	})
 
@@ -661,11 +710,12 @@ func TestStoreIterator(t *testing.T, builder Builder) {
 		err := st.Put(k, []byte{1})
 		require.NoError(t, err)
 
-		it := st.NewIterator(engine.IteratorConfig{Reverse: true})
+		it := st.Iterator(engine.IteratorOptions{Reverse: true})
 		defer it.Close()
 
 		it.Seek(nil)
 
+		require.NoError(t, it.Err())
 		require.True(t, it.Valid())
 		require.Equal(t, it.Item().Key(), k)
 	})
@@ -813,9 +863,10 @@ func TestStoreTruncate(t *testing.T, builder Builder) {
 		err = st.Truncate()
 		require.NoError(t, err)
 
-		it := st.NewIterator(engine.IteratorConfig{})
+		it := st.Iterator(engine.IteratorOptions{})
 		defer it.Close()
 		it.Seek(nil)
+		require.NoError(t, it.Err())
 		require.False(t, it.Valid())
 	})
 }
@@ -829,14 +880,19 @@ func TestStoreNextSequence(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		tx, err := ng.Begin(true)
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
+
 		require.NoError(t, err)
 		err = tx.CreateStore([]byte("test"))
 		require.NoError(t, err)
 		err = tx.Commit()
 		require.NoError(t, err)
 
-		tx, err = ng.Begin(false)
+		tx, err = ng.Begin(context.Background(), engine.TxOptions{
+			Writable: false,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -864,7 +920,10 @@ func TestStoreNextSequence(t *testing.T, builder Builder) {
 		defer func() {
 			require.NoError(t, ng.Close())
 		}()
-		tx, err := ng.Begin(true)
+
+		tx, err := ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 
 		require.NoError(t, err)
 		err = tx.CreateStore([]byte("test"))
@@ -878,7 +937,9 @@ func TestStoreNextSequence(t *testing.T, builder Builder) {
 		err = tx.Commit()
 		require.NoError(t, err)
 
-		tx, err = ng.Begin(true)
+		tx, err = ng.Begin(context.Background(), engine.TxOptions{
+			Writable: true,
+		})
 		require.NoError(t, err)
 		defer tx.Rollback()
 
@@ -892,8 +953,6 @@ func TestStoreNextSequence(t *testing.T, builder Builder) {
 
 // TestQueries test simple queries against the engine.
 func TestQueries(t *testing.T, builder Builder) {
-	ctx := context.Background()
-
 	t.Run("SELECT", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
@@ -901,12 +960,10 @@ func TestQueries(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		db, err := genji.New(ng)
+		db, err := genji.New(context.Background(), ng)
 		require.NoError(t, err)
 
-		ctx := context.Background()
-
-		st, err := db.Query(ctx, `
+		st, err := db.Query(`
 			CREATE TABLE test;
 			INSERT INTO test (a) VALUES (1), (2), (3), (4);
 			SELECT * FROM test;
@@ -919,7 +976,7 @@ func TestQueries(t *testing.T, builder Builder) {
 		require.NoError(t, err)
 
 		t.Run("ORDER BY", func(t *testing.T) {
-			st, err := db.Query(ctx, "SELECT * FROM test ORDER BY a DESC")
+			st, err := db.Query("SELECT * FROM test ORDER BY a DESC")
 			require.NoError(t, err)
 			defer st.Close()
 
@@ -943,10 +1000,10 @@ func TestQueries(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		db, err := genji.New(ng)
+		db, err := genji.New(context.Background(), ng)
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, `
+		err = db.Exec(`
 			CREATE TABLE test;
 			INSERT INTO test (a) VALUES (1), (2), (3), (4);
 		`)
@@ -960,10 +1017,10 @@ func TestQueries(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		db, err := genji.New(ng)
+		db, err := genji.New(context.Background(), ng)
 		require.NoError(t, err)
 
-		st, err := db.Query(ctx, `
+		st, err := db.Query(`
 				CREATE TABLE test;
 				INSERT INTO test (a) VALUES (1), (2), (3), (4);
 				UPDATE test SET a = 5;
@@ -984,22 +1041,22 @@ func TestQueries(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		db, err := genji.New(ng)
+		db, err := genji.New(context.Background(), ng)
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, "CREATE TABLE test")
+		err = db.Exec("CREATE TABLE test")
 		require.NoError(t, err)
 
 		err = db.Update(func(tx *genji.Tx) error {
 			for i := 1; i < 200; i++ {
-				err = tx.Exec(ctx, "INSERT INTO test (a) VALUES (?)", i)
+				err = tx.Exec("INSERT INTO test (a) VALUES (?)", i)
 				require.NoError(t, err)
 			}
 			return nil
 		})
 		require.NoError(t, err)
 
-		st, err := db.Query(ctx, `
+		st, err := db.Query(`
 			DELETE FROM test WHERE a > 2;
 			SELECT * FROM test;
 		`)
@@ -1013,8 +1070,6 @@ func TestQueries(t *testing.T, builder Builder) {
 
 // TestQueriesSameTransaction test simple queries in the same transaction.
 func TestQueriesSameTransaction(t *testing.T, builder Builder) {
-	ctx := context.Background()
-
 	t.Run("SELECT", func(t *testing.T) {
 		ng, cleanup := builder()
 		defer cleanup()
@@ -1022,11 +1077,11 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		db, err := genji.New(ng)
+		db, err := genji.New(context.Background(), ng)
 		require.NoError(t, err)
 
 		err = db.Update(func(tx *genji.Tx) error {
-			st, err := tx.Query(ctx, `
+			st, err := tx.Query(`
 				CREATE TABLE test;
 				INSERT INTO test (a) VALUES (1), (2), (3), (4);
 				SELECT * FROM test;
@@ -1048,11 +1103,11 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		db, err := genji.New(ng)
+		db, err := genji.New(context.Background(), ng)
 		require.NoError(t, err)
 
 		err = db.Update(func(tx *genji.Tx) error {
-			err = tx.Exec(ctx, `
+			err = tx.Exec(`
 			CREATE TABLE test;
 			INSERT INTO test (a) VALUES (1), (2), (3), (4);
 		`)
@@ -1069,11 +1124,11 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		db, err := genji.New(ng)
+		db, err := genji.New(context.Background(), ng)
 		require.NoError(t, err)
 
 		err = db.Update(func(tx *genji.Tx) error {
-			st, err := tx.Query(ctx, `
+			st, err := tx.Query(`
 				CREATE TABLE test;
 				INSERT INTO test (a) VALUES (1), (2), (3), (4);
 				UPDATE test SET a = 5;
@@ -1097,11 +1152,11 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
 			require.NoError(t, ng.Close())
 		}()
 
-		db, err := genji.New(ng)
+		db, err := genji.New(context.Background(), ng)
 		require.NoError(t, err)
 
 		err = db.Update(func(tx *genji.Tx) error {
-			st, err := tx.Query(ctx, `
+			st, err := tx.Query(`
 			CREATE TABLE test;
 			INSERT INTO test (a) VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
 			DELETE FROM test WHERE a > 2;
diff --git a/example_test.go b/example_test.go
index cf32df37..7f1118b7 100644
--- a/example_test.go
+++ b/example_test.go
@@ -1,7 +1,6 @@
 package genji_test
 
 import (
-	"context"
 	"encoding/json"
 	"fmt"
 	"os"
@@ -28,40 +27,38 @@ func Example() {
 	}
 	defer db.Close()
 
-	ctx := context.Background()
-
 	// Create a table. Genji tables are schemaless by default, you don't need to specify a schema.
-	err = db.Exec(ctx, "CREATE TABLE user")
+	err = db.Exec("CREATE TABLE user")
 	if err != nil {
 		panic(err)
 	}
 
 	// Create an index.
-	err = db.Exec(ctx, "CREATE INDEX idx_user_name ON user (name)")
+	err = db.Exec("CREATE INDEX idx_user_name ON user (name)")
 	if err != nil {
 		panic(err)
 	}
 
 	// Insert some data
-	err = db.Exec(ctx, "INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "foo", 15)
+	err = db.Exec("INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "foo", 15)
 	if err != nil {
 		panic(err)
 	}
 
 	// Insert some data using document notation
-	err = db.Exec(ctx, `INSERT INTO user VALUES {id: 12, "name": "bar", age: ?, address: {city: "Lyon", zipcode: "69001"}}`, 16)
+	err = db.Exec(`INSERT INTO user VALUES {id: 12, "name": "bar", age: ?, address: {city: "Lyon", zipcode: "69001"}}`, 16)
 	if err != nil {
 		panic(err)
 	}
 
 	// Structs can be used to describe a document
-	err = db.Exec(ctx, "INSERT INTO user VALUES ?, ?", &User{ID: 1, Name: "baz", Age: 100}, &User{ID: 2, Name: "bat"})
+	err = db.Exec("INSERT INTO user VALUES ?, ?", &User{ID: 1, Name: "baz", Age: 100}, &User{ID: 2, Name: "bat"})
 	if err != nil {
 		panic(err)
 	}
 
 	// Query some documents
-	stream, err := db.Query(ctx, "SELECT * FROM user WHERE id > ?", 1)
+	stream, err := db.Query("SELECT * FROM user WHERE id > ?", 1)
 	if err != nil {
 		panic(err)
 	}
diff --git a/index/index_test.go b/index/index_test.go
index 19cfd49d..f7849a8c 100644
--- a/index/index_test.go
+++ b/index/index_test.go
@@ -2,12 +2,14 @@
 package index_test
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"strconv"
 	"testing"
 
 	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/engine/memoryengine"
 	"github.com/genjidb/genji/index"
 	"github.com/genjidb/genji/key"
@@ -16,7 +18,9 @@ import (
 
 func getIndex(t testing.TB, unique bool) (*index.Index, func()) {
 	ng := memoryengine.NewEngine()
-	tx, err := ng.Begin(true)
+	tx, err := ng.Begin(context.Background(), engine.TxOptions{
+		Writable: true,
+	})
 	require.NoError(t, err)
 
 	idx := index.NewIndex(tx, "foo", index.Options{Unique: unique})
diff --git a/sql/parser/alter_test.go b/sql/parser/alter_test.go
index 6be2f6de..b6c05987 100644
--- a/sql/parser/alter_test.go
+++ b/sql/parser/alter_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/database"
@@ -25,7 +24,7 @@ func TestParserAlterTable(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.errored {
 				require.Error(t, err)
 				return
@@ -76,7 +75,7 @@ func TestParserAlterTableAddColumn(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.errored {
 				require.Error(t, err)
 				return
diff --git a/sql/parser/create_test.go b/sql/parser/create_test.go
index ebc8ab20..8e68dd6d 100644
--- a/sql/parser/create_test.go
+++ b/sql/parser/create_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/database"
@@ -183,7 +182,7 @@ func TestParserCreateTable(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.errored {
 				require.Error(t, err)
 				return
@@ -211,7 +210,7 @@ func TestParserCreateIndex(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.errored {
 				require.Error(t, err)
 				return
diff --git a/sql/parser/delete_test.go b/sql/parser/delete_test.go
index b9c17afb..b203e2ad 100644
--- a/sql/parser/delete_test.go
+++ b/sql/parser/delete_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/sql/planner"
@@ -29,7 +28,7 @@ func TestParserDelete(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			require.NoError(t, err)
 			require.Len(t, q.Statements, 1)
 			require.EqualValues(t, test.expected, q.Statements[0])
diff --git a/sql/parser/drop_test.go b/sql/parser/drop_test.go
index f609b8cb..e7de9baf 100644
--- a/sql/parser/drop_test.go
+++ b/sql/parser/drop_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/sql/query"
@@ -23,7 +22,7 @@ func TestParserDrop(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.errored {
 				require.Error(t, err)
 				return
diff --git a/sql/parser/explain_test.go b/sql/parser/explain_test.go
index 9942b790..2efcea51 100644
--- a/sql/parser/explain_test.go
+++ b/sql/parser/explain_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/sql/planner"
@@ -22,7 +21,7 @@ func TestParserExplain(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.errored {
 				require.Error(t, err)
 				return
diff --git a/sql/parser/insert_test.go b/sql/parser/insert_test.go
index 061aaeff..06660ab0 100644
--- a/sql/parser/insert_test.go
+++ b/sql/parser/insert_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/sql/query"
@@ -80,7 +79,7 @@ func TestParserInsert(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.fails {
 				require.Error(t, err)
 				return
diff --git a/sql/parser/parser_test.go b/sql/parser/parser_test.go
index e7a151e7..0f7909be 100644
--- a/sql/parser/parser_test.go
+++ b/sql/parser/parser_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -38,7 +37,7 @@ func TestParserMultiStatement(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			require.NoError(t, err)
 			require.EqualValues(t, test.expected, q.Statements)
 		})
@@ -48,6 +47,6 @@ func TestParserMultiStatement(t *testing.T) {
 func TestParserDivideByZero(t *testing.T) {
 	// See https://github.com/genjidb/genji/issues/268
 	require.NotPanics(t, func() {
-		_, _ = ParseQuery(context.Background(), "SELECT * FROM t LIMIT 0 % .5")
+		_, _ = ParseQuery("SELECT * FROM t LIMIT 0 % .5")
 	})
 }
diff --git a/sql/parser/reindex_test.go b/sql/parser/reindex_test.go
index 61927155..4bbd29ed 100644
--- a/sql/parser/reindex_test.go
+++ b/sql/parser/reindex_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/sql/query"
@@ -22,7 +21,7 @@ func TestParserReIndex(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.errored {
 				require.Error(t, err)
 				return
diff --git a/sql/parser/select_test.go b/sql/parser/select_test.go
index cd65b125..4576f90c 100644
--- a/sql/parser/select_test.go
+++ b/sql/parser/select_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/sql/planner"
@@ -231,7 +230,7 @@ func TestParserSelect(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if !test.mustFail {
 				require.NoError(t, err)
 				require.Len(t, q.Statements, 1)
diff --git a/sql/parser/transaction_test.go b/sql/parser/transaction_test.go
index 67c8c2f3..3c247572 100644
--- a/sql/parser/transaction_test.go
+++ b/sql/parser/transaction_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/sql/query"
@@ -28,7 +27,7 @@ func TestParserTransactions(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.s, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.errored {
 				require.Error(t, err)
 				return
diff --git a/sql/parser/update_test.go b/sql/parser/update_test.go
index 43d23227..89e74ec7 100644
--- a/sql/parser/update_test.go
+++ b/sql/parser/update_test.go
@@ -1,7 +1,6 @@
 package parser
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji/sql/planner"
@@ -127,7 +126,7 @@ func TestParserUpdate(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			q, err := ParseQuery(context.Background(), test.s)
+			q, err := ParseQuery(test.s)
 			if test.errored {
 				require.Error(t, err)
 				return
diff --git a/sql/planner/explain_test.go b/sql/planner/explain_test.go
index 9a5c640f..749daf26 100644
--- a/sql/planner/explain_test.go
+++ b/sql/planner/explain_test.go
@@ -1,7 +1,6 @@
 package planner_test
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji"
@@ -40,17 +39,15 @@ func TestExplainStmt(t *testing.T) {
 			require.NoError(t, err)
 			defer db.Close()
 
-			ctx := context.Background()
-
-			err = db.Exec(ctx, "CREATE TABLE test (k INTEGER PRIMARY KEY)")
+			err = db.Exec("CREATE TABLE test (k INTEGER PRIMARY KEY)")
 			require.NoError(t, err)
-			err = db.Exec(ctx, `
+			err = db.Exec(`
 						CREATE INDEX idx_a ON test (a);
 						CREATE UNIQUE INDEX idx_b ON test (b);
 					`)
 			require.NoError(t, err)
 
-			d, err := db.QueryDocument(ctx, test.query)
+			d, err := db.QueryDocument(test.query)
 			if test.fails {
 				require.Error(t, err)
 				return
diff --git a/sql/planner/optimizer_test.go b/sql/planner/optimizer_test.go
index b0d1001d..0be44b31 100644
--- a/sql/planner/optimizer_test.go
+++ b/sql/planner/optimizer_test.go
@@ -1,7 +1,6 @@
 package planner_test
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji"
@@ -206,6 +205,120 @@ func TestRemoveUnnecessarySelectionNodesRule(t *testing.T) {
 	}
 }
 
+func TestRemoveUnnecessaryDedupNodeRule(t *testing.T) {
+	tests := []struct {
+		name           string
+		root, expected planner.Node
+	}{
+		{
+			"non-unique key",
+			planner.NewDedupNode(
+				planner.NewProjectionNode(
+					planner.NewTableInputNode("foo"),
+					[]planner.ProjectedField{planner.ProjectedExpr{
+						Expr:     expr.Path{document.PathFragment{FieldName: "b"}},
+						ExprName: "b",
+					}},
+					"foo",
+				), "foo"),
+			nil,
+		},
+		{
+			"primary key",
+			planner.NewDedupNode(
+				planner.NewProjectionNode(
+					planner.NewTableInputNode("foo"),
+					[]planner.ProjectedField{planner.ProjectedExpr{
+						Expr:     expr.Path{document.PathFragment{FieldName: "a"}},
+						ExprName: "a",
+					}},
+					"foo",
+				), "foo"),
+			planner.NewProjectionNode(
+				planner.NewTableInputNode("foo"),
+				[]planner.ProjectedField{planner.ProjectedExpr{
+					Expr:     expr.Path{document.PathFragment{FieldName: "a"}},
+					ExprName: "a",
+				}},
+				"foo",
+			),
+		},
+		{
+			"unique index",
+			planner.NewDedupNode(
+				planner.NewProjectionNode(
+					planner.NewTableInputNode("foo"),
+					[]planner.ProjectedField{planner.ProjectedExpr{
+						Expr:     expr.Path{document.PathFragment{FieldName: "c"}},
+						ExprName: "c",
+					}},
+					"foo",
+				), "foo"),
+			planner.NewProjectionNode(
+				planner.NewTableInputNode("foo"),
+				[]planner.ProjectedField{planner.ProjectedExpr{
+					Expr:     expr.Path{document.PathFragment{FieldName: "c"}},
+					ExprName: "c",
+				}},
+				"foo",
+			),
+		},
+		{
+			"pk() function",
+			planner.NewDedupNode(
+				planner.NewProjectionNode(
+					planner.NewTableInputNode("foo"),
+					[]planner.ProjectedField{planner.ProjectedExpr{
+						Expr:     expr.PKFunc{},
+						ExprName: "pk()",
+					}},
+					"foo",
+				), "foo"),
+			planner.NewProjectionNode(
+				planner.NewTableInputNode("foo"),
+				[]planner.ProjectedField{planner.ProjectedExpr{
+					Expr:     expr.PKFunc{},
+					ExprName: "pk()",
+				}},
+				"foo",
+			),
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			db, err := genji.Open(":memory:")
+			require.NoError(t, err)
+			defer db.Close()
+
+			tx, err := db.Begin(true)
+			require.NoError(t, err)
+			defer tx.Rollback()
+
+			err = tx.Exec(context.Background(), `
+				CREATE TABLE foo(a integer PRIMARY KEY, b integer, c integer);
+				CREATE UNIQUE INDEX idx_foo_idx ON foo(c);
+				INSERT INTO foo (a, b, c) VALUES
+					(1, 1, 1),
+					(2, 2, 2),
+					(3, 3, 3)
+			`)
+			require.NoError(t, err)
+
+			err = planner.Bind(planner.NewTree(test.root), tx.Transaction, nil)
+			require.NoError(t, err)
+
+			res, err := planner.RemoveUnnecessaryDedupNodeRule(planner.NewTree(test.root))
+			require.NoError(t, err)
+			if test.expected != nil {
+				require.Equal(t, planner.NewTree(test.expected).String(), res.String())
+			} else {
+				require.Equal(t, test.root, res.Root)
+			}
+		})
+	}
+}
+
 func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 	tests := []struct {
 		name           string
@@ -347,7 +460,7 @@ func TestUseIndexBasedOnSelectionNodeRule(t *testing.T) {
 			require.NoError(t, err)
 			defer tx.Rollback()
 
-			err = tx.Exec(context.Background(), `
+			err = tx.Exec(`
 				CREATE TABLE foo;
 				CREATE INDEX idx_foo_a ON foo(a);
 				CREATE INDEX idx_foo_b ON foo(b);
diff --git a/sql/query/alter_test.go b/sql/query/alter_test.go
index d3d85be9..ca5dcb5c 100644
--- a/sql/query/alter_test.go
+++ b/sql/query/alter_test.go
@@ -1,7 +1,6 @@
 package query_test
 
 import (
-	"context"
 	"errors"
 	"testing"
 
@@ -12,38 +11,36 @@ import (
 )
 
 func TestAlterTable(t *testing.T) {
-	ctx := context.Background()
-
 	db, err := genji.Open(":memory:")
 	require.NoError(t, err)
 	defer db.Close()
 
-	err = db.Exec(ctx, "CREATE TABLE foo")
+	err = db.Exec("CREATE TABLE foo")
 	require.NoError(t, err)
 
 	// Insert some data into foo
-	err = db.Exec(ctx, `INSERT INTO foo VALUES {name: "John Doe", age: 99}`)
+	err = db.Exec(`INSERT INTO foo VALUES {name: "John Doe", age: 99}`)
 	require.NoError(t, err)
 
 	// Renaming the table to the same name should fail.
-	err = db.Exec(ctx, "ALTER TABLE foo RENAME TO foo")
+	err = db.Exec("ALTER TABLE foo RENAME TO foo")
 	require.EqualError(t, err, database.ErrTableAlreadyExists.Error())
 
-	err = db.Exec(ctx, "ALTER TABLE foo RENAME TO bar")
+	err = db.Exec("ALTER TABLE foo RENAME TO bar")
 	require.NoError(t, err)
 
 	// Selecting from the old name should fail.
-	err = db.Exec(ctx, "SELECT * FROM foo")
+	err = db.Exec("SELECT * FROM foo")
 	if !errors.Is(err, database.ErrTableNotFound) {
 		require.Equal(t, err, database.ErrTableNotFound)
 	}
 
-	d, err := db.QueryDocument(ctx, "SELECT * FROM bar")
+	d, err := db.QueryDocument("SELECT * FROM bar")
 	data, err := document.MarshalJSON(d)
 	require.NoError(t, err)
 	require.JSONEq(t, `{"name": "John Doe", "age": 99}`, string(data))
 
 	// Renaming a read-only table should fail
-	err = db.Exec(ctx, "ALTER TABLE __genji_tables RENAME TO bar")
+	err = db.Exec("ALTER TABLE __genji_tables RENAME TO bar")
 	require.Error(t, err)
 }
diff --git a/sql/query/create_test.go b/sql/query/create_test.go
index 0729fd5d..0e8689bd 100644
--- a/sql/query/create_test.go
+++ b/sql/query/create_test.go
@@ -1,7 +1,6 @@
 package query_test
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji"
@@ -18,8 +17,6 @@ func parsePath(t testing.TB, str string) document.Path {
 }
 
 func TestCreateTable(t *testing.T) {
-	ctx := context.Background()
-
 	tests := []struct {
 		name  string
 		query string
@@ -50,7 +47,7 @@ func TestCreateTable(t *testing.T) {
 			require.NoError(t, err)
 			defer db.Close()
 
-			err = db.Exec(ctx, test.query)
+			err = db.Exec(test.query)
 			if test.fails {
 				require.Error(t, err)
 				return
@@ -71,7 +68,7 @@ func TestCreateTable(t *testing.T) {
 		defer db.Close()
 
 		t.Run("with fixed size data types", func(t *testing.T) {
-			err = db.Exec(ctx, `CREATE TABLE test(d double, b bool)`)
+			err = db.Exec(`CREATE TABLE test(d double, b bool)`)
 			require.NoError(t, err)
 
 			err = db.View(func(tx *genji.Tx) error {
@@ -96,7 +93,7 @@ func TestCreateTable(t *testing.T) {
 		})
 
 		t.Run("with variable size data types", func(t *testing.T) {
-			err = db.Exec(ctx, `
+			err = db.Exec(`
 				CREATE TABLE test1(
 					foo.bar[1].hello bytes PRIMARY KEY, foo.a[1][2] TEXT NOT NULL, bar[4][0].bat integer, b blob, t text, a array, d document
 				)
@@ -129,9 +126,7 @@ func TestCreateTable(t *testing.T) {
 		})
 
 		t.Run("with variable aliases data types", func(t *testing.T) {
-			ctx := context.Background()
-
-			err = db.Exec(ctx, `
+			err = db.Exec(`
 				CREATE TABLE test2(
 					foo.bar[1].hello bytes PRIMARY KEY, foo.a[1][2] VARCHAR(255) NOT NULL, bar[4][0].bat tinyint,
 				 	dp double precision, r real, b bigint, m mediumint, eight int8, ii int2, c character(64)
@@ -188,12 +183,10 @@ func TestCreateIndex(t *testing.T) {
 			require.NoError(t, err)
 			defer db.Close()
 
-			ctx := context.Background()
-
-			err = db.Exec(ctx, "CREATE TABLE test")
+			err = db.Exec("CREATE TABLE test")
 			require.NoError(t, err)
 
-			err = db.Exec(ctx, test.query)
+			err = db.Exec(test.query)
 			if test.fails {
 				require.Error(t, err)
 				return
diff --git a/sql/query/delete_test.go b/sql/query/delete_test.go
index 7c6da93d..3ba0a12e 100644
--- a/sql/query/delete_test.go
+++ b/sql/query/delete_test.go
@@ -2,7 +2,6 @@ package query_test
 
 import (
 	"bytes"
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji"
@@ -11,8 +10,6 @@ import (
 )
 
 func TestDeleteStmt(t *testing.T) {
-	ctx := context.Background()
-
 	tests := []struct {
 		name     string
 		query    string
@@ -32,23 +29,23 @@ func TestDeleteStmt(t *testing.T) {
 			require.NoError(t, err)
 			defer db.Close()
 
-			err = db.Exec(ctx, "CREATE TABLE test")
+			err = db.Exec("CREATE TABLE test")
 			require.NoError(t, err)
-			err = db.Exec(ctx, "INSERT INTO test (a, b, c) VALUES ('foo1', 'bar1', 'baz1')")
+			err = db.Exec("INSERT INTO test (a, b, c) VALUES ('foo1', 'bar1', 'baz1')")
 			require.NoError(t, err)
-			err = db.Exec(ctx, "INSERT INTO test (a, b) VALUES ('foo2', 'bar1')")
+			err = db.Exec("INSERT INTO test (a, b) VALUES ('foo2', 'bar1')")
 			require.NoError(t, err)
-			err = db.Exec(ctx, "INSERT INTO test (d, b, e) VALUES ('foo3', 'bar2', 'bar3')")
+			err = db.Exec("INSERT INTO test (d, b, e) VALUES ('foo3', 'bar2', 'bar3')")
 			require.NoError(t, err)
 
-			err = db.Exec(ctx, test.query, test.params...)
+			err = db.Exec(test.query, test.params...)
 			if test.fails {
 				require.Error(t, err)
 				return
 			}
 			require.NoError(t, err)
 
-			st, err := db.Query(ctx, "SELECT * FROM test")
+			st, err := db.Query("SELECT * FROM test")
 			require.NoError(t, err)
 			defer st.Close()
 
diff --git a/sql/query/drop_test.go b/sql/query/drop_test.go
index deb106ae..81dfd5b9 100644
--- a/sql/query/drop_test.go
+++ b/sql/query/drop_test.go
@@ -1,7 +1,6 @@
 package query_test
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji"
@@ -15,24 +14,22 @@ func TestDropTable(t *testing.T) {
 	require.NoError(t, err)
 	defer db.Close()
 
-	ctx := context.Background()
-
-	err = db.Exec(ctx, "CREATE TABLE test1; CREATE TABLE test2; CREATE TABLE test3")
+	err = db.Exec("CREATE TABLE test1; CREATE TABLE test2; CREATE TABLE test3")
 	require.NoError(t, err)
 
-	err = db.Exec(ctx, "DROP TABLE test1")
+	err = db.Exec("DROP TABLE test1")
 	require.NoError(t, err)
 
-	err = db.Exec(ctx, "DROP TABLE IF EXISTS test1")
+	err = db.Exec("DROP TABLE IF EXISTS test1")
 	require.NoError(t, err)
 
 	// Dropping a table that doesn't exist without "IF EXISTS"
 	// should return an error.
-	err = db.Exec(ctx, "DROP TABLE test1")
+	err = db.Exec("DROP TABLE test1")
 	require.Error(t, err)
 
 	// Assert that only the table `test1` has been dropped.
-	res, err := db.Query(ctx, "SELECT table_name FROM __genji_tables")
+	res, err := db.Query("SELECT table_name FROM __genji_tables")
 	require.NoError(t, err)
 	var tables []string
 	err = res.Iterate(func(d document.Document) error {
@@ -49,7 +46,7 @@ func TestDropTable(t *testing.T) {
 	require.Len(t, tables, 2)
 
 	// Dropping a read-only table should fail.
-	err = db.Exec(ctx, "DROP TABLE __genji_tables")
+	err = db.Exec("DROP TABLE __genji_tables")
 	require.Error(t, err)
 }
 
@@ -58,15 +55,13 @@ func TestDropIndex(t *testing.T) {
 	require.NoError(t, err)
 	defer db.Close()
 
-	ctx := context.Background()
-
-	err = db.Exec(ctx, `
+	err = db.Exec(`
 		CREATE TABLE test1(foo text); CREATE INDEX idx_test1_foo ON test1(foo);
 		CREATE TABLE test2(bar text); CREATE INDEX idx_test2_bar ON test2(bar);
 	`)
 	require.NoError(t, err)
 
-	err = db.Exec(ctx, "DROP INDEX idx_test2_bar")
+	err = db.Exec("DROP INDEX idx_test2_bar")
 	require.NoError(t, err)
 
 	// Assert that the good index has been dropped.
diff --git a/sql/query/insert_test.go b/sql/query/insert_test.go
index 3a196334..705d618a 100644
--- a/sql/query/insert_test.go
+++ b/sql/query/insert_test.go
@@ -2,7 +2,6 @@ package query_test
 
 import (
 	"bytes"
-	"context"
 	"database/sql"
 	"fmt"
 	"testing"
@@ -14,8 +13,6 @@ import (
 )
 
 func TestInsertStmt(t *testing.T) {
-	ctx := context.Background()
-
 	tests := []struct {
 		name     string
 		query    string
@@ -50,26 +47,24 @@ func TestInsertStmt(t *testing.T) {
 				require.NoError(t, err)
 				defer db.Close()
 
-				ctx := context.Background()
-
-				err = db.Exec(ctx, "CREATE TABLE test")
+				err = db.Exec("CREATE TABLE test")
 				require.NoError(t, err)
 				if withIndexes {
-					err = db.Exec(ctx, `
+					err = db.Exec(`
 						CREATE INDEX idx_a ON test (a);
 						CREATE INDEX idx_b ON test (b);
 						CREATE INDEX idx_c ON test (c);
 					`)
 					require.NoError(t, err)
 				}
-				err = db.Exec(ctx, test.query, test.params...)
+				err = db.Exec(test.query, test.params...)
 				if test.fails {
 					require.Error(t, err)
 					return
 				}
 				require.NoError(t, err)
 
-				st, err := db.Query(ctx, "SELECT pk(), * FROM test")
+				st, err := db.Query("SELECT pk(), * FROM test")
 				require.NoError(t, err)
 				defer st.Close()
 
@@ -89,15 +84,15 @@ func TestInsertStmt(t *testing.T) {
 		require.NoError(t, err)
 		defer db.Close()
 
-		err = db.Exec(ctx, "CREATE TABLE test (foo INTEGER PRIMARY KEY)")
+		err = db.Exec("CREATE TABLE test (foo INTEGER PRIMARY KEY)")
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, `INSERT INTO test (bar) VALUES (1)`)
+		err = db.Exec(`INSERT INTO test (bar) VALUES (1)`)
 		require.Error(t, err)
-		err = db.Exec(ctx, `INSERT INTO test (bar, foo) VALUES (1, 2)`)
+		err = db.Exec(`INSERT INTO test (bar, foo) VALUES (1, 2)`)
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, `INSERT INTO test (bar, foo) VALUES (1, 2)`)
+		err = db.Exec(`INSERT INTO test (bar, foo) VALUES (1, 2)`)
 		require.Equal(t, err, database.ErrDuplicateDocument)
 	})
 
@@ -106,10 +101,10 @@ func TestInsertStmt(t *testing.T) {
 		require.NoError(t, err)
 		defer db.Close()
 
-		err = db.Exec(ctx, "CREATE TABLE test")
+		err = db.Exec("CREATE TABLE test")
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, "INSERT INTO test (`pk()`, `key`) VALUES (1, 2)")
+		err = db.Exec("INSERT INTO test (`pk()`, `key`) VALUES (1, 2)")
 		require.NoError(t, err)
 	})
 
@@ -118,7 +113,7 @@ func TestInsertStmt(t *testing.T) {
 		require.NoError(t, err)
 		defer db.Close()
 
-		err = db.Exec(ctx, "CREATE TABLE test")
+		err = db.Exec("CREATE TABLE test")
 		require.NoError(t, err)
 
 		type foo struct {
@@ -126,9 +121,9 @@ func TestInsertStmt(t *testing.T) {
 			B string `genji:"b-b"`
 		}
 
-		err = db.Exec(ctx, "INSERT INTO test VALUES ?", &foo{A: "a", B: "b"})
+		err = db.Exec("INSERT INTO test VALUES ?", &foo{A: "a", B: "b"})
 		require.NoError(t, err)
-		res, err := db.Query(ctx, "SELECT * FROM test")
+		res, err := db.Query("SELECT * FROM test")
 		defer res.Close()
 
 		require.NoError(t, err)
@@ -144,14 +139,14 @@ func TestInsertStmt(t *testing.T) {
 		require.NoError(t, err)
 		defer db.Close()
 
-		err = db.Exec(ctx, `CREATE TABLE test(
+		err = db.Exec(`CREATE TABLE test(
 			b bool, db double,
 			i integer, bb blob, byt bytes,
 			t text, a array, d document
 		)`)
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, `
+		err = db.Exec(`
 			INSERT INTO test
 			VALUES {
 				i: 10000000000, db: 21.21, b: true,
@@ -160,7 +155,7 @@ func TestInsertStmt(t *testing.T) {
 			}`)
 		require.NoError(t, err)
 
-		res, err := db.Query(ctx, "SELECT * FROM test")
+		res, err := db.Query("SELECT * FROM test")
 		defer res.Close()
 		require.NoError(t, err)
 
@@ -249,18 +244,18 @@ func TestInsertStmt(t *testing.T) {
 			{"character / not null with type constraint", "CHARACTER(64) NOT NULL", `{}`},
 		}
 
-		db, err := genji.Open(":memory:")
-		require.NoError(t, err)
-		defer db.Close()
-
-		for i, test := range tests {
+		for _, test := range tests {
 			t.Run(test.name, func(t *testing.T) {
-				q := fmt.Sprintf("CREATE TABLE test%d (a %s)", i, test.fieldConstraint)
-				err := db.Exec(ctx, q)
+				db, err := genji.Open(":memory:")
+				require.NoError(t, err)
+				defer db.Close()
+
+				q := fmt.Sprintf("CREATE TABLE test (a %s)", test.fieldConstraint)
+				err = db.Exec(q)
 				require.NoError(t, err)
 
-				q = fmt.Sprintf("INSERT INTO test%d VALUES %s", i, test.value)
-				err = db.Exec(ctx, q)
+				q = fmt.Sprintf("INSERT INTO test VALUES %s", test.value)
+				err = db.Exec(q)
 				require.Error(t, err)
 			})
 		}
diff --git a/sql/query/reindex_test.go b/sql/query/reindex_test.go
index 4473c4ce..008828fa 100644
--- a/sql/query/reindex_test.go
+++ b/sql/query/reindex_test.go
@@ -1,7 +1,6 @@
 package query_test
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji"
@@ -10,8 +9,6 @@ import (
 )
 
 func TestReIndex(t *testing.T) {
-	ctx := context.Background()
-
 	tests := []struct {
 		name            string
 		query           string
@@ -31,7 +28,7 @@ func TestReIndex(t *testing.T) {
 			require.NoError(t, err)
 			defer db.Close()
 
-			err = db.Exec(ctx, `
+			err = db.Exec(`
 				CREATE TABLE test1;
 				CREATE TABLE test2;
 
@@ -45,7 +42,7 @@ func TestReIndex(t *testing.T) {
 			`)
 			require.NoError(t, err)
 
-			err = db.Exec(ctx, test.query)
+			err = db.Exec(test.query)
 			if test.fails {
 				require.Error(t, err)
 				return
diff --git a/sql/query/select_test.go b/sql/query/select_test.go
index 993e904a..ddd9ed99 100644
--- a/sql/query/select_test.go
+++ b/sql/query/select_test.go
@@ -2,8 +2,8 @@ package query_test
 
 import (
 	"bytes"
-	"context"
 	"database/sql"
+	"strconv"
 	"testing"
 
 	"github.com/genjidb/genji"
@@ -12,8 +12,6 @@ import (
 )
 
 func TestSelectStmt(t *testing.T) {
-	ctx := context.Background()
-
 	tests := []struct {
 		name     string
 		query    string
@@ -33,6 +31,8 @@ func TestSelectStmt(t *testing.T) {
 		{"No table, wildcard", "SELECT *", true, ``, nil},
 		{"No table, document", "SELECT {a: 1, b: 2 + 1}", false, `[{"{a: 1, b: 2 + 1}":{"a":1,"b":3}}]`, nil},
 		{"No cond", "SELECT * FROM test", false, `[{"k":1,"color":"red","size":10,"shape":"square"},{"k":2,"color":"blue","size":10,"weight":100},{"k":3,"height":100,"weight":200}]`, nil},
+		{"With DISTINCT", "SELECT DISTINCT * FROM test", false, `[{"k":1,"color":"red","size":10,"shape":"square"},{"k":2,"color":"blue","size":10,"weight":100},{"k":3,"height":100,"weight":200}]`, nil},
+		{"With DISTINCT and expr", "SELECT DISTINCT 'a' FROM test", false, `[{"'a'":"a"}]`, nil},
 		{"Multiple wildcards cond", "SELECT *, *, color FROM test", false, `[{"k":1,"color":"red","size":10,"shape":"square","k":1,"color":"red","size":10,"shape":"square","color":"red"},{"k":2,"color":"blue","size":10,"weight":100,"k":2,"color":"blue","size":10,"weight":100,"color":"blue"},{"k":3,"height":100,"weight":200,"k":3,"height":100,"weight":200,"color":null}]`, nil},
 		{"With fields", "SELECT color, shape FROM test", false, `[{"color":"red","shape":"square"},{"color":"blue","shape":null},{"color":null,"shape":null}]`, nil},
 		{"With expr fields", "SELECT color, color != 'red' AS notred FROM test", false, `[{"color":"red","notred":false},{"color":"blue","notred":true},{"color":null,"notred":null}]`, nil},
@@ -97,10 +97,10 @@ func TestSelectStmt(t *testing.T) {
 				require.NoError(t, err)
 				defer db.Close()
 
-				err = db.Exec(ctx, "CREATE TABLE test (k INTEGER PRIMARY KEY)")
+				err = db.Exec("CREATE TABLE test (k INTEGER PRIMARY KEY)")
 				require.NoError(t, err)
 				if withIndexes {
-					err = db.Exec(ctx, `
+					err = db.Exec(`
 						CREATE INDEX idx_color ON test (color);
 						CREATE INDEX idx_size ON test (size);
 						CREATE INDEX idx_shape ON test (shape);
@@ -110,14 +110,14 @@ func TestSelectStmt(t *testing.T) {
 					require.NoError(t, err)
 				}
 
-				err = db.Exec(ctx, "INSERT INTO test (k, color, size, shape) VALUES (1, 'red', 10, 'square')")
+				err = db.Exec("INSERT INTO test (k, color, size, shape) VALUES (1, 'red', 10, 'square')")
 				require.NoError(t, err)
-				err = db.Exec(ctx, "INSERT INTO test (k, color, size, weight) VALUES (2, 'blue', 10, 100)")
+				err = db.Exec("INSERT INTO test (k, color, size, weight) VALUES (2, 'blue', 10, 100)")
 				require.NoError(t, err)
-				err = db.Exec(ctx, "INSERT INTO test (k, height, weight) VALUES (3, 100, 200)")
+				err = db.Exec("INSERT INTO test (k, height, weight) VALUES (3, 100, 200)")
 				require.NoError(t, err)
 
-				st, err := db.Query(ctx, test.query, test.params...)
+				st, err := db.Query(test.query, test.params...)
 				defer st.Close()
 				if test.fails {
 					require.Error(t, err)
@@ -140,16 +140,16 @@ func TestSelectStmt(t *testing.T) {
 		require.NoError(t, err)
 		defer db.Close()
 
-		err = db.Exec(ctx, "CREATE TABLE test (foo INTEGER PRIMARY KEY)")
+		err = db.Exec("CREATE TABLE test (foo INTEGER PRIMARY KEY)")
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, `INSERT INTO test (foo, bar) VALUES (1, 'a')`)
-		err = db.Exec(ctx, `INSERT INTO test (foo, bar) VALUES (2, 'b')`)
-		err = db.Exec(ctx, `INSERT INTO test (foo, bar) VALUES (3, 'c')`)
-		err = db.Exec(ctx, `INSERT INTO test (foo, bar) VALUES (4, 'd')`)
+		err = db.Exec(`INSERT INTO test (foo, bar) VALUES (1, 'a')`)
+		err = db.Exec(`INSERT INTO test (foo, bar) VALUES (2, 'b')`)
+		err = db.Exec(`INSERT INTO test (foo, bar) VALUES (3, 'c')`)
+		err = db.Exec(`INSERT INTO test (foo, bar) VALUES (4, 'd')`)
 		require.NoError(t, err)
 
-		st, err := db.Query(ctx, "SELECT * FROM test WHERE foo < 400 AND foo >= 2")
+		st, err := db.Query("SELECT * FROM test WHERE foo < 400 AND foo >= 2")
 		require.NoError(t, err)
 		defer st.Close()
 
@@ -164,14 +164,14 @@ func TestSelectStmt(t *testing.T) {
 		require.NoError(t, err)
 		defer db.Close()
 
-		err = db.Exec(ctx, "CREATE TABLE test")
+		err = db.Exec("CREATE TABLE test")
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, `INSERT INTO test VALUES {a: {b: 1}}, {a: 1}, {a: [1, 2, [8,9]]}`)
+		err = db.Exec(`INSERT INTO test VALUES {a: {b: 1}}, {a: 1}, {a: [1, 2, [8,9]]}`)
 		require.NoError(t, err)
 
 		call := func(q string, res ...string) {
-			st, err := db.Query(ctx, q)
+			st, err := db.Query(q)
 			require.NoError(t, err)
 			defer st.Close()
 
@@ -197,7 +197,7 @@ func TestSelectStmt(t *testing.T) {
 		require.NoError(t, err)
 		defer db.Close()
 
-		err = db.Exec(ctx, "SELECT * FROM foo")
+		err = db.Exec("SELECT * FROM foo")
 		require.Error(t, err)
 	})
 
@@ -206,13 +206,13 @@ func TestSelectStmt(t *testing.T) {
 		require.NoError(t, err)
 		defer db.Close()
 
-		err = db.Exec(ctx, "CREATE TABLE test; CREATE INDEX idx_foo ON test(foo);")
+		err = db.Exec("CREATE TABLE test; CREATE INDEX idx_foo ON test(foo);")
 		require.NoError(t, err)
 
-		err = db.Exec(ctx, `INSERT INTO test (foo) VALUES (1), ('hello'), (2), (true)`)
+		err = db.Exec(`INSERT INTO test (foo) VALUES (1), ('hello'), (2), (true)`)
 		require.NoError(t, err)
 
-		st, err := db.Query(ctx, "SELECT * FROM test ORDER BY foo")
+		st, err := db.Query("SELECT * FROM test ORDER BY foo")
 		require.NoError(t, err)
 		defer st.Close()
 
@@ -222,3 +222,80 @@ func TestSelectStmt(t *testing.T) {
 		require.JSONEq(t, `[{"foo": true},{"foo": 1}, {"foo": 2},{"foo": "hello"}]`, buf.String())
 	})
 }
+
+func TestDistinct(t *testing.T) {
+	ctx := context.Background()
+
+	types := []struct {
+		name          string
+		generateValue func(i, notUniqueCount int) (unique interface{}, notunique interface{})
+	}{
+		{`integer`, func(i, notUniqueCount int) (unique interface{}, notunique interface{}) {
+			return i, i % notUniqueCount
+		}},
+		{`double`, func(i, notUniqueCount int) (unique interface{}, notunique interface{}) {
+			return float64(i), float64(i % notUniqueCount)
+		}},
+		{`text`, func(i, notUniqueCount int) (unique interface{}, notunique interface{}) {
+			return strconv.Itoa(i), strconv.Itoa(i % notUniqueCount)
+		}},
+		{`array`, func(i, notUniqueCount int) (unique interface{}, notunique interface{}) {
+			return []interface{}{i}, []interface{}{i % notUniqueCount}
+		}},
+	}
+
+	for _, typ := range types {
+		total := 100
+		notUnique := total / 10
+
+		t.Run(typ.name, func(t *testing.T) {
+			db, err := genji.Open(":memory:")
+			require.NoError(t, err)
+			defer db.Close()
+
+			tx, err := db.Begin(true)
+			require.NoError(t, err)
+			defer tx.Rollback()
+
+			err = tx.Exec(ctx, "CREATE TABLE test(a "+typ.name+" PRIMARY KEY, b "+typ.name+", doc DOCUMENT, nullable "+typ.name+");")
+			require.NoError(t, err)
+
+			err = tx.Exec(ctx, "CREATE UNIQUE INDEX test_doc_index ON test(doc);")
+			require.NoError(t, err)
+
+			for i := 0; i < total; i++ {
+				unique, nonunique := typ.generateValue(i, notUnique)
+				err = tx.Exec(ctx, `INSERT INTO test VALUES {a: ?, b: ?, doc: {a: ?, b: ?}, nullable: null}`, unique, nonunique, unique, nonunique)
+				require.NoError(t, err)
+			}
+			err = tx.Commit()
+			require.NoError(t, err)
+
+			tests := []struct {
+				name          string
+				query         string
+				expectedCount int
+			}{
+				{`unique`, `SELECT DISTINCT a FROM test`, total},
+				{`non-unique`, `SELECT DISTINCT b FROM test`, notUnique},
+				{`documents`, `SELECT DISTINCT doc FROM test`, total},
+				{`null`, `SELECT DISTINCT nullable FROM test`, 1},
+				{`wildcard`, `SELECT DISTINCT * FROM test`, total},
+				{`literal`, `SELECT DISTINCT 'a' FROM test`, 1},
+				{`pk()`, `SELECT DISTINCT pk() FROM test`, total},
+			}
+
+			for _, test := range tests {
+				t.Run(test.name, func(t *testing.T) {
+					q, err := db.Query(ctx, test.query)
+					require.NoError(t, err)
+					defer q.Close()
+
+					c, err := q.Count()
+					require.NoError(t, err)
+					require.Equal(t, test.expectedCount, c)
+				})
+			}
+		})
+	}
+}
diff --git a/sql/query/transaction_test.go b/sql/query/transaction_test.go
index fa9d92ed..153bd569 100644
--- a/sql/query/transaction_test.go
+++ b/sql/query/transaction_test.go
@@ -1,7 +1,6 @@
 package query_test
 
 import (
-	"context"
 	"testing"
 
 	"github.com/genjidb/genji"
@@ -28,15 +27,13 @@ func TestTransactionRun(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			ctx := context.Background()
-
 			db, err := genji.Open(":memory:")
 			require.NoError(t, err)
 			defer db.Close()
-			defer db.Exec(ctx, "ROLLBACK")
+			defer db.Exec("ROLLBACK")
 
 			for _, q := range test.queries {
-				err = db.Exec(ctx, q)
+				err = db.Exec(q)
 				if err != nil {
 					break
 				}
diff --git a/sql/query/update_test.go b/sql/query/update_test.go
index 8a979042..52590448 100644
--- a/sql/query/update_test.go
+++ b/sql/query/update_test.go
@@ -2,7 +2,6 @@ package query_test
 
 import (
 	"bytes"
-	"context"
 	"database/sql"
 	"testing"
 
@@ -12,8 +11,6 @@ import (
 )
 
 func TestUpdateStmt(t *testing.T) {
-	ctx := context.Background()
-
 	tests := []struct {
 		name     string
 		query    string
@@ -51,23 +48,23 @@ func TestUpdateStmt(t *testing.T) {
 			require.NoError(t, err)
 			defer db.Close()
 
-			err = db.Exec(ctx, "CREATE TABLE test (a text not null)")
+			err = db.Exec("CREATE TABLE test (a text not null)")
 			require.NoError(t, err)
-			err = db.Exec(ctx, "INSERT INTO test (a, b, c) VALUES ('foo1', 'bar1', 'baz1')")
+			err = db.Exec("INSERT INTO test (a, b, c) VALUES ('foo1', 'bar1', 'baz1')")
 			require.NoError(t, err)
-			err = db.Exec(ctx, "INSERT INTO test (a, b) VALUES ('foo2', 'bar2')")
+			err = db.Exec("INSERT INTO test (a, b) VALUES ('foo2', 'bar2')")
 			require.NoError(t, err)
-			err = db.Exec(ctx, "INSERT INTO test (a, d, e) VALUES ('foo3', 'bar3', 'baz3')")
+			err = db.Exec("INSERT INTO test (a, d, e) VALUES ('foo3', 'bar3', 'baz3')")
 			require.NoError(t, err)
 
-			err = db.Exec(ctx, test.query, test.params...)
+			err = db.Exec(test.query, test.params...)
 			if test.fails {
 				require.Error(t, err)
 				return
 			}
 			require.NoError(t, err)
 
-			st, err := db.Query(ctx, "SELECT * FROM test")
+			st, err := db.Query("SELECT * FROM test")
 			require.NoError(t, err)
 			defer st.Close()
 
@@ -103,19 +100,19 @@ func TestUpdateStmt(t *testing.T) {
 			require.NoError(t, err)
 			defer db.Close()
 
-			err = db.Exec(ctx, `CREATE TABLE foo;`)
+			err = db.Exec(`CREATE TABLE foo;`)
 			require.NoError(t, err)
-			err = db.Exec(ctx, `INSERT INTO foo (a) VALUES ([1, 0, 0]), ([2, 0]);`)
+			err = db.Exec(`INSERT INTO foo (a) VALUES ([1, 0, 0]), ([2, 0]);`)
 			require.NoError(t, err)
 
-			err = db.Exec(ctx, tt.query, tt.params...)
+			err = db.Exec(tt.query, tt.params...)
 			if tt.fails {
 				require.Error(t, err)
 				continue
 			}
 
 			require.NoError(t, err)
-			st, err := db.Query(ctx, "SELECT * FROM foo")
+			st, err := db.Query("SELECT * FROM foo")
 			require.NoError(t, err)
 			defer st.Close()
 
diff --git a/sql/scanner/scanner_test.go b/sql/scanner/scanner_test.go
index 5e661cfe..18eadacf 100644
--- a/sql/scanner/scanner_test.go
+++ b/sql/scanner/scanner_test.go
@@ -130,6 +130,7 @@ func TestScanner_Scan(t *testing.T) {
 		{s: `DEFAULT`, tok: scanner.DEFAULT, raw: `DEFAULT`},
 		{s: `DELETE`, tok: scanner.DELETE, raw: `DELETE`},
 		{s: `DESC`, tok: scanner.DESC, raw: `DESC`},
+		{s: `DISTINCT`, tok: scanner.DISTINCT, raw: `DISTINCT`},
 		{s: `DROP`, tok: scanner.DROP, raw: `DROP`},
 		{s: `FIELD`, tok: scanner.FIELD, raw: `FIELD`},
 		{s: `FROM`, tok: scanner.FROM, raw: `FROM`},