Repository: genjidb/genji
Pull request #142 (2020-07-25 19:53:53)

Store table information in memory
This PR makes `tableInfoStore` keep table information in memory. Table information is loaded at database startup and kept up to date every time a table is created, altered or dropped.
This change allows us to have access to table information without the need for a transaction, which is handy if we need to use this during parsing time.

It also fixes a bug where duplicate generated store id was overwritten.


Patch:
diff --git a/cmd/genji/shell/command.go b/cmd/genji/shell/command.go
index e4fd6a43..3c72b361 100644
--- a/cmd/genji/shell/command.go
+++ b/cmd/genji/shell/command.go
@@ -6,13 +6,15 @@ import (
 	"github.com/genjidb/genji"
 )
 
-func runTablesCmd(db *genji.DB) error {
+func runTablesCmd(db *genji.DB, cmd []string) error {
+	if len(cmd) > 1 {
+		return fmt.Errorf("usage: .tables")
+	}
+
 	var tables []string
 	err := db.View(func(tx *genji.Tx) error {
-		var err error
-
-		tables, err = tx.ListTables()
-		return err
+		tables = tx.ListTables()
+		return nil
 	})
 	if err != nil {
 		return err
@@ -24,3 +26,59 @@ func runTablesCmd(db *genji.DB) error {
 
 	return nil
 }
+
+// displayTableIndex prints all indexes that the given table contains.
+func displayTableIndex(db *genji.DB, tableName string) error {
+	err := db.View(func(tx *genji.Tx) error {
+		t, err := tx.GetTable(tableName)
+		if err != nil {
+			return err
+		}
+
+		indexes, err := t.Indexes()
+		if err != nil {
+			return err
+		}
+
+		for _, idx := range indexes {
+			fmt.Printf("%s on %s(%s)\n", idx.Opts.IndexName, idx.Opts.TableName, idx.Opts.Path)
+		}
+
+		return nil
+	})
+
+	return err
+}
+
+// displayAllIndexes shows all indexes that the database contains.
+func displayAllIndexes(db *genji.DB) error {
+	err := db.View(func(tx *genji.Tx) error {
+		indexes, err := tx.ListIndexes()
+		if err != nil {
+			return err
+		}
+
+		for _, idx := range indexes {
+			fmt.Printf("%s on %s(%s)\n", idx.IndexName, idx.TableName, idx.Path)
+		}
+
+		return nil
+	})
+
+	return err
+}
+
+// runIndexesCmd executes all indexes of the database or all indexes of the given table.
+func runIndexesCmd(db *genji.DB, in []string) error {
+	switch len(in) {
+	case 1:
+		// If the input is ".indexes"
+		return displayAllIndexes(db)
+	case 2:
+		// If the input is ".indexes <tableName>"
+		return displayTableIndex(db, in[1])
+
+	}
+
+	return fmt.Errorf("usage: .indexes [tablename]")
+}
diff --git a/cmd/genji/shell/shell.go b/cmd/genji/shell/shell.go
index 5757d1c4..6283da6b 100644
--- a/cmd/genji/shell/shell.go
+++ b/cmd/genji/shell/shell.go
@@ -189,7 +189,7 @@ func (sh *Shell) executeInput(in string) error {
 	switch {
 	// if it starts with a "." it's a command
 	// it must not be in the middle of a multi line query though
-	case strings.HasPrefix(in, ".") && sh.query == "":
+	case strings.HasPrefix(in, "."):
 		return sh.runCommand(in)
 	// If it ends with a ";" we can run a query
 	case strings.HasSuffix(in, ";"):
@@ -202,6 +202,7 @@ func (sh *Shell) executeInput(in string) error {
 	// If the input is empty we ignore it
 	case in == "":
 		return nil
+
 	// If we reach this case, it means the user is in the middle of a
 	// multi line query. We change the prompt and set the multiLine var to true.
 	default:
@@ -213,16 +214,29 @@ func (sh *Shell) executeInput(in string) error {
 	return nil
 }
 
-func (sh *Shell) runCommand(cmd string) error {
-	switch cmd {
+func (sh *Shell) runCommand(in string) error {
+	cmd := strings.Fields(in)
+	switch cmd[0] {
 	case ".tables":
 		db, err := sh.getDB()
 		if err != nil {
 			return err
 		}
-		return runTablesCmd(db)
+
+		return runTablesCmd(db, cmd)
 	case ".exit":
+		if len(cmd) > 1 {
+			return fmt.Errorf("usage: .exit")
+		}
+
 		os.Exit(0)
+	case ".indexes":
+		db, err := sh.getDB()
+		if err != nil {
+			return err
+		}
+
+		return runIndexesCmd(db, cmd)
 	}
 
 	return fmt.Errorf("unknown command %q", cmd)
diff --git a/database/config.go b/database/config.go
index 0d01f531..40297b37 100644
--- a/database/config.go
+++ b/database/config.go
@@ -4,6 +4,8 @@ import (
 	"crypto/rand"
 	"encoding/binary"
 	"fmt"
+	"sort"
+	"sync"
 	"time"
 
 	"github.com/genjidb/genji/document"
@@ -141,35 +143,52 @@ func (ti *TableInfo) ScanDocument(d document.Document) error {
 	})
 }
 
+// tableInfoStore manages table information.
+// It loads table information during database startup
+// and holds it in memory.
 type tableInfoStore struct {
-	st engine.Store
+	// tableInfos contains information about all the tables
+	tableInfos map[string]TableInfo
+
+	mu sync.RWMutex
+}
+
+func newTableInfoStore(tx engine.Transaction) (*tableInfoStore, error) {
+	var ts tableInfoStore
+
+	err := ts.loadAllTableInfo(tx)
+	if err != nil {
+		return nil, err
+	}
+
+	return &ts, nil
 }
 
 // Insert a new tableInfo for the given table name.
 // It automatically generates a unique storeID for that table.
-func (t *tableInfoStore) Insert(tableName string, info *TableInfo) ([]byte, error) {
-	key := []byte(tableName)
-	_, err := t.st.Get(key)
-	if err == nil {
+func (t *tableInfoStore) Insert(tx engine.Transaction, tableName string, info *TableInfo) ([]byte, error) {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+
+	_, ok := t.tableInfos[tableName]
+	if ok {
 		return nil, ErrTableAlreadyExists
 	}
-	if err != engine.ErrKeyNotFound {
-		return nil, err
-	}
 
+	var found bool = true
 	var id [6]byte
-	for {
+	for found {
 		id = generateStoreID()
-		_, err = t.st.Get(id[:])
-		if err == nil {
-			// A store with this id already exists.
-			// Let's generate a new one.
-			continue
-		}
-		if err != engine.ErrKeyNotFound {
-			return nil, err
+
+		found = false
+		for _, ti := range t.tableInfos {
+			if ti.storeID == id {
+				// A store with this id already exists.
+				// Let's generate a new one.
+				found = true
+				break
+			}
 		}
-		break
 	}
 	info.storeID = id
 
@@ -178,50 +197,104 @@ func (t *tableInfoStore) Insert(tableName string, info *TableInfo) ([]byte, erro
 		return nil, err
 	}
 
-	err = t.st.Put(key, v)
+	st, err := tx.GetStore([]byte(tableInfoStoreName))
+	if err != nil {
+		return nil, err
+	}
+
+	err = st.Put([]byte(tableName), v)
 	if err != nil {
 		return nil, err
 	}
+
+	t.tableInfos[tableName] = *info
 	return info.storeID[:], err
 }
 
 func (t *tableInfoStore) Get(tableName string) (*TableInfo, error) {
-	key := []byte(tableName)
-	v, err := t.st.Get(key)
-	if err == engine.ErrKeyNotFound {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+
+	info, ok := t.tableInfos[tableName]
+	if !ok {
 		return nil, ErrTableNotFound
 	}
-	if err != nil {
-		return nil, err
-	}
 
-	var ti TableInfo
-	err = ti.ScanDocument(msgpack.EncodedDocument(v))
+	return &info, nil
+}
+
+func (t *tableInfoStore) Delete(tx engine.Transaction, tableName string) error {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+
+	st, err := tx.GetStore([]byte(tableInfoStoreName))
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	return &ti, nil
-}
-
-func (t *tableInfoStore) Delete(tableName string) error {
 	key := []byte(tableName)
-	err := t.st.Delete(key)
+	err = st.Delete(key)
 	if err == engine.ErrKeyNotFound {
 		return ErrTableNotFound
 	}
-	return err
+	if err != nil {
+		return err
+	}
+
+	delete(t.tableInfos, tableName)
+
+	return nil
 }
 
-func (t *tableInfoStore) ListTables() ([]string, error) {
-	it := t.st.NewIterator(engine.IteratorConfig{})
+func (t *tableInfoStore) loadAllTableInfo(tx engine.Transaction) error {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+
+	st, err := tx.GetStore([]byte(tableInfoStoreName))
+	if err != nil {
+		return err
+	}
+
+	it := st.NewIterator(engine.IteratorConfig{})
+	defer it.Close()
 
-	var names []string
+	t.tableInfos = make(map[string]TableInfo)
+	var b []byte
 	for it.Seek(nil); it.Valid(); it.Next() {
-		k := it.Item().Key()
-		names = append(names, string(k))
+		itm := it.Item()
+		b, err = itm.ValueCopy(b)
+		if err != nil {
+			return err
+		}
+
+		var ti TableInfo
+		err = ti.ScanDocument(msgpack.EncodedDocument(b))
+		if err != nil {
+			return err
+		}
+
+		t.tableInfos[string(itm.Key())] = ti
 	}
-	return names, it.Close()
+
+	return nil
+}
+
+// ListTables lists all the tables.
+// The returned slice is lexicographically ordered.
+func (t *tableInfoStore) ListTables() []string {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+
+	names := make([]string, len(t.tableInfos))
+	var i int
+	for k := range t.tableInfos {
+		names[i] = k
+		i++
+	}
+
+	sort.Strings(names)
+
+	return names
 }
 
 func generateStoreID() [6]byte {
@@ -237,12 +310,12 @@ func generateStoreID() [6]byte {
 
 // IndexConfig holds the configuration of an index.
 type IndexConfig struct {
-	// If set to true, values will be associated with at most one key. False by default.
-	Unique bool
-
-	IndexName string
 	TableName string
+	IndexName string
 	Path      document.ValuePath
+
+	// If set to true, values will be associated with at most one key. False by default.
+	Unique bool
 }
 
 // ToDocument creates a document from an IndexConfig.
@@ -297,11 +370,7 @@ func (i *IndexConfig) ScanDocument(d document.Document) error {
 // the index configuration and provides methods to manipulate the index.
 type Index struct {
 	index.Index
-
-	IndexName string
-	TableName string
-	Path      document.ValuePath
-	Unique    bool
+	Opts IndexConfig
 }
 
 type indexStore struct {
diff --git a/database/database.go b/database/database.go
index d67c9ec6..54f77c8e 100644
--- a/database/database.go
+++ b/database/database.go
@@ -13,6 +13,9 @@ type Database struct {
 
 	mu sync.Mutex
 
+	// tableInfoStore manages information about all the tables
+	tableInfoStore *tableInfoStore
+
 	// tableDocids holds the latest docid for a table.
 	// it is cached in this map the first time a table is accessed
 	// and is used by every call to table#Insert to generate the
@@ -33,18 +36,12 @@ func New(ng engine.Engine) (*Database, error) {
 	}
 	defer ntx.Rollback()
 
-	_, err = ntx.GetStore([]byte(tableInfoStoreName))
-	if err == engine.ErrStoreNotFound {
-		err = ntx.CreateStore([]byte(tableInfoStoreName))
-	}
+	err = db.initInternalStores(ntx)
 	if err != nil {
 		return nil, err
 	}
 
-	_, err = ntx.GetStore([]byte(indexStoreName))
-	if err == engine.ErrStoreNotFound {
-		err = ntx.CreateStore([]byte(indexStoreName))
-	}
+	db.tableInfoStore, err = newTableInfoStore(ntx)
 	if err != nil {
 		return nil, err
 	}
@@ -57,6 +54,22 @@ func New(ng engine.Engine) (*Database, error) {
 	return &db, nil
 }
 
+func (db *Database) initInternalStores(tx engine.Transaction) error {
+	_, err := tx.GetStore([]byte(tableInfoStoreName))
+	if err == engine.ErrStoreNotFound {
+		err = tx.CreateStore([]byte(tableInfoStoreName))
+	}
+	if err != nil {
+		return err
+	}
+
+	_, err = tx.GetStore([]byte(indexStoreName))
+	if err == engine.ErrStoreNotFound {
+		err = tx.CreateStore([]byte(indexStoreName))
+	}
+	return err
+}
+
 // Close the underlying engine.
 func (db *Database) Close() error {
 	return db.ng.Close()
@@ -71,14 +84,10 @@ func (db *Database) Begin(writable bool) (*Transaction, error) {
 	}
 
 	tx := Transaction{
-		db:       db,
-		Tx:       ntx,
-		writable: writable,
-	}
-
-	tx.tableInfoStore, err = tx.getTableInfoStore()
-	if err != nil {
-		return nil, err
+		db:             db,
+		Tx:             ntx,
+		writable:       writable,
+		tableInfoStore: db.tableInfoStore,
 	}
 
 	tx.indexStore, err = tx.getIndexStore()
diff --git a/database/table.go b/database/table.go
index e8904285..db2e9a62 100644
--- a/database/table.go
+++ b/database/table.go
@@ -375,7 +375,7 @@ func (t *Table) Insert(d document.Document) ([]byte, error) {
 	}
 
 	for _, idx := range indexes {
-		v, err := idx.Path.GetValue(d)
+		v, err := idx.Opts.Path.GetValue(d)
 		if err != nil {
 			v = document.NewNullValue()
 		}
@@ -407,7 +407,7 @@ func (t *Table) Delete(key []byte) error {
 	}
 
 	for _, idx := range indexes {
-		v, err := idx.Path.GetValue(d)
+		v, err := idx.Opts.Path.GetValue(d)
 		if err != nil {
 			return err
 		}
@@ -447,7 +447,7 @@ func (t *Table) replace(indexes map[string]Index, key []byte, d document.Documen
 
 	// remove key from indexes
 	for _, idx := range indexes {
-		v, err := idx.Path.GetValue(old)
+		v, err := idx.Opts.Path.GetValue(old)
 		if err != nil {
 			return err
 		}
@@ -472,7 +472,7 @@ func (t *Table) replace(indexes map[string]Index, key []byte, d document.Documen
 
 	// update indexes
 	for _, idx := range indexes {
-		v, err := idx.Path.GetValue(d)
+		v, err := idx.Opts.Path.GetValue(d)
 		if err != nil {
 			continue
 		}
@@ -541,11 +541,8 @@ func (t *Table) Indexes() (map[string]Index, error) {
 			}
 
 			indexes[opts.Path.String()] = Index{
-				Index:     idx,
-				IndexName: opts.IndexName,
-				TableName: opts.TableName,
-				Path:      opts.Path,
-				Unique:    opts.Unique,
+				Index: idx,
+				Opts:  opts,
 			}
 
 			return nil
diff --git a/database/transaction.go b/database/transaction.go
index e684de46..4d551a0b 100644
--- a/database/transaction.go
+++ b/database/transaction.go
@@ -70,7 +70,7 @@ func (tx Transaction) CreateTable(name string, info *TableInfo) error {
 	if info == nil {
 		info = new(TableInfo)
 	}
-	sid, err := tx.tableInfoStore.Insert(name, info)
+	sid, err := tx.tableInfoStore.Insert(tx.Tx, name, info)
 	if err != nil {
 		return err
 	}
@@ -145,7 +145,7 @@ func (tx Transaction) DropTable(name string) error {
 		return err
 	}
 
-	err = tx.tableInfoStore.Delete(name)
+	err = tx.tableInfoStore.Delete(tx.Tx, name)
 	if err != nil {
 		return err
 	}
@@ -155,7 +155,7 @@ func (tx Transaction) DropTable(name string) error {
 
 // ListTables lists all the tables.
 // The returned slice is lexicographically ordered.
-func (tx Transaction) ListTables() ([]string, error) {
+func (tx Transaction) ListTables() []string {
 	return tx.tableInfoStore.ListTables()
 }
 
@@ -185,11 +185,8 @@ func (tx Transaction) GetIndex(name string) (*Index, error) {
 	}
 
 	return &Index{
-		Index:     idx,
-		IndexName: opts.IndexName,
-		TableName: opts.TableName,
-		Path:      opts.Path,
-		Unique:    opts.Unique,
+		Index: idx,
+		Opts:  *opts,
 	}, nil
 }
 
@@ -226,7 +223,7 @@ func (tx Transaction) ReIndex(indexName string) error {
 		return err
 	}
 
-	tb, err := tx.GetTable(idx.TableName)
+	tb, err := tx.GetTable(idx.Opts.TableName)
 	if err != nil {
 		return err
 	}
@@ -237,7 +234,7 @@ func (tx Transaction) ReIndex(indexName string) error {
 	}
 
 	return tb.Iterate(func(d document.Document) error {
-		v, err := idx.Path.GetValue(d)
+		v, err := idx.Opts.Path.GetValue(d)
 		if err != nil {
 			return err
 		}
@@ -269,16 +266,6 @@ func (tx Transaction) ReIndexAll() error {
 	return nil
 }
 
-func (tx *Transaction) getTableInfoStore() (*tableInfoStore, error) {
-	st, err := tx.Tx.GetStore([]byte(tableInfoStoreName))
-	if err != nil {
-		return nil, err
-	}
-	return &tableInfoStore{
-		st: st,
-	}, nil
-}
-
 func (tx *Transaction) getIndexStore() (*indexStore, error) {
 	st, err := tx.Tx.GetStore([]byte(indexStoreName))
 	if err != nil {
diff --git a/sql/planner/optimizer.go b/sql/planner/optimizer.go
index 7bedb861..a74c7ba7 100644
--- a/sql/planner/optimizer.go
+++ b/sql/planner/optimizer.go
@@ -398,7 +398,7 @@ func selectionNodeValidForIndex(sn *selectionNode, tableName string, indexes map
 		return nil
 	}
 
-	in := NewIndexInputNode(tableName, idx.IndexName, iop, e, scanner.ASC).(*indexInputNode)
+	in := NewIndexInputNode(tableName, idx.Opts.IndexName, iop, e, scanner.ASC).(*indexInputNode)
 	in.index = idx
 
 	return in
diff --git a/sql/query/plan.go b/sql/query/plan.go
index 6aa57462..472110e5 100644
--- a/sql/query/plan.go
+++ b/sql/query/plan.go
@@ -212,7 +212,7 @@ func (qo *queryOptimizer) analyseExpr(e expr.Expr) *queryPlanField {
 				indexedField: fs,
 				op:           op,
 				e:            e,
-				uniqueIndex:  idx.Unique,
+				uniqueIndex:  idx.Opts.Unique,
 			}
 		}
 


Test patch:
diff --git a/cmd/genji/shell/command_test.go b/cmd/genji/shell/command_test.go
new file mode 100644
index 00000000..d60cde67
--- /dev/null
+++ b/cmd/genji/shell/command_test.go
@@ -0,0 +1,83 @@
+package shell
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/genjidb/genji"
+	"github.com/stretchr/testify/require"
+)
+
+func TestRunTablesCmd(t *testing.T) {
+	tests := []struct {
+		name    string
+		in      string
+		wantErr bool
+	}{
+		{
+			"Table",
+			".tables",
+			false,
+		},
+		{
+			"Table with options",
+			".tables test",
+			true,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			db, err := genji.Open(":memory:")
+			require.NoError(t, err)
+			defer db.Close()
+
+			if err := displayTableIndex(db, test.in); (err != nil) != test.wantErr {
+				require.Errorf(t, err, "", test.wantErr)
+			}
+		})
+	}
+}
+
+func TestIndexesCmd(t *testing.T) {
+	tests := []struct {
+		name    string
+		in      []string
+		wantErr bool
+	}{
+		{
+			".Indexes",
+			strings.Fields(".indexes"),
+			false,
+		},
+		{
+			"Indexes with table name",
+			strings.Fields(".indexes test"),
+			false,
+		},
+		{
+			"Indexes with nonexistent table name",
+			strings.Fields(".indexes foo"),
+			false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			db, err := genji.Open(":memory:")
+			require.NoError(t, err)
+			defer db.Close()
+
+			err = db.Exec("CREATE TABLE test")
+			require.NoError(t, err)
+			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)
+			if err := runIndexesCmd(db, test.in); (err != nil) != test.wantErr {
+				require.Errorf(t, err, "", test.wantErr)
+			}
+		})
+	}
+}
diff --git a/database/config_test.go b/database/config_test.go
index d86a2578..d7d633be 100644
--- a/database/config_test.go
+++ b/database/config_test.go
@@ -12,16 +12,13 @@ func TestTableInfoStore(t *testing.T) {
 	ng := memoryengine.NewEngine()
 	defer ng.Close()
 
-	tx, err := ng.Begin(true)
+	db, err := New(ng)
 	require.NoError(t, err)
-	defer tx.Rollback()
+	defer db.Close()
 
-	err = tx.CreateStore([]byte("foo"))
+	tx, err := db.Begin(true)
 	require.NoError(t, err)
-	st, err := tx.GetStore([]byte("foo"))
-	require.NoError(t, err)
-
-	tcs := tableInfoStore{st}
+	defer tx.Rollback()
 
 	info := &TableInfo{
 		FieldConstraints: []FieldConstraint{
@@ -30,37 +27,29 @@ func TestTableInfoStore(t *testing.T) {
 	}
 
 	// Inserting one tableInfo should work.
-	sid, err := tcs.Insert("foo1", info)
+	sid, err := tx.tableInfoStore.Insert(tx.Tx, "foo1", info)
 	require.NoError(t, err)
 	require.NotNil(t, sid)
 
 	// Inserting an existing tableInfo should not work.
-	_, err = tcs.Insert("foo1", info)
+	_, err = tx.tableInfoStore.Insert(tx.Tx, "foo1", info)
 	require.Equal(t, err, ErrTableAlreadyExists)
 
-	// Listing all tables should return their name
-	// lexicographically ordered.
-	_, _ = tcs.Insert("foo3", info)
-	_, _ = tcs.Insert("foo2", info)
-	lt, err := tcs.ListTables()
-	require.NoError(t, err)
-	require.Equal(t, []string{"foo1", "foo2", "foo3"}, lt)
-
 	// Getting an existing tableInfo should work.
-	received, err := tcs.Get("foo1")
+	received, err := tx.tableInfoStore.Get("foo1")
 	require.NoError(t, err)
 	require.NotNil(t, received.storeID)
 
 	// Getting a non-existing tableInfo should not work.
-	_, err = tcs.Get("unknown")
+	_, err = tx.tableInfoStore.Get("unknown")
 	require.Equal(t, ErrTableNotFound, err)
 
 	// Deleting an existing tableInfo should work.
-	err = tcs.Delete("foo1")
+	err = tx.tableInfoStore.Delete(tx.Tx, "foo1")
 	require.NoError(t, err)
 
 	// Deleting a non-existing tableInfo should not work.
-	err = tcs.Delete("foo1")
+	err = tx.tableInfoStore.Delete(tx.Tx, "foo1")
 	require.Equal(t, ErrTableNotFound, err)
 }
 
diff --git a/database/transaction_test.go b/database/transaction_test.go
index 7c1536ed..d2151649 100644
--- a/database/transaction_test.go
+++ b/database/transaction_test.go
@@ -79,11 +79,10 @@ func TestTxTable(t *testing.T) {
 		tx, cleanup := newTestDB(t)
 		defer cleanup()
 
-		tables, err := tx.ListTables()
-		require.NoError(t, err)
+		tables := tx.ListTables()
 		require.Len(t, tables, 0)
 
-		err = tx.CreateTable("foo", nil)
+		err := tx.CreateTable("foo", nil)
 		require.NoError(t, err)
 
 		err = tx.CreateTable("bar", nil)
@@ -92,9 +91,7 @@ func TestTxTable(t *testing.T) {
 		err = tx.CreateTable("baz", nil)
 		require.NoError(t, err)
 
-		tables, err = tx.ListTables()
-		require.NoError(t, err)
-
+		tables = tx.ListTables()
 		// The returned slice should be lexicographically ordered.
 		exp := []string{"bar", "baz", "foo"}
 		require.Equal(t, exp, tables)
diff --git a/sql/query/drop_test.go b/sql/query/drop_test.go
index 42699013..64692a16 100644
--- a/sql/query/drop_test.go
+++ b/sql/query/drop_test.go
@@ -30,10 +30,8 @@ func TestDropTable(t *testing.T) {
 	// Assert that only the table `test1` has been dropped.
 	var tables []string
 	err = db.View(func(tx *genji.Tx) error {
-		var err error
-
-		tables, err = tx.ListTables()
-		return err
+		tables = tx.ListTables()
+		return nil
 	})
 	require.Len(t, tables, 2)
 }