Files
chaisql/db_test.go
2019-11-19 15:42:59 +01:00

927 lines
20 KiB
Go

package genji_test
import (
"fmt"
"log"
"testing"
"github.com/asdine/genji"
"github.com/asdine/genji/engine/memoryengine"
"github.com/asdine/genji/index"
"github.com/asdine/genji/record"
"github.com/asdine/genji/value"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
func ExampleDB_SQLDB() {
db, err := genji.New(memoryengine.NewEngine())
if err != nil {
log.Fatal(err)
}
dbx := db.SQLDB()
defer db.Close()
_, err = dbx.Exec("CREATE TABLE IF NOT EXISTS user")
if err != nil {
log.Fatal(err)
}
_, err = dbx.Exec("CREATE INDEX IF NOT EXISTS idx_user_name ON user (name)")
if err != nil {
log.Fatal(err)
}
_, err = dbx.Exec("INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "foo", 15)
if err != nil {
log.Fatal(err)
}
_, err = dbx.Exec("INSERT INTO user RECORDS ?, ?", &User{ID: 1, Name: "bar", Age: 100}, &User{ID: 2, Name: "baz"})
if err != nil {
log.Fatal(err)
}
rows, err := dbx.Query("SELECT * FROM user WHERE name = ?", "bar")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var u User
err = rows.Scan(&u)
if err != nil {
log.Fatal(err)
}
fmt.Println(u)
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
// Output: {1 bar 100}
}
func ExampleTx() {
db, err := genji.New(memoryengine.NewEngine())
if err != nil {
log.Fatal(err)
}
defer db.Close()
tx, err := db.Begin(false)
if err != nil {
panic(err)
}
defer tx.Rollback()
err = tx.Exec("CREATE TABLE IF NOT EXISTS user")
if err != nil {
log.Fatal(err)
}
err = tx.Exec("INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "foo", 15)
if err != nil {
log.Fatal(err)
}
result, err := tx.Query("SELECT id, name, age FROM user WHERE name = ?", "foo")
if err != nil {
panic(err)
}
defer result.Close()
var u User
r, err := result.First()
if err != nil {
panic(err)
}
err = u.ScanRecord(r)
if err != nil {
panic(err)
}
fmt.Println(u)
var id uint64
var name string
var age uint8
err = record.Scan(r, &id, &name, &age)
if err != nil {
panic(err)
}
fmt.Println(id, name, age)
err = tx.Commit()
if err != nil {
panic(err)
}
// Output: {10 foo 15}
// 10 foo 15
}
func newTestDB(t testing.TB) (*genji.Tx, func()) {
db, err := genji.New(memoryengine.NewEngine())
require.NoError(t, err)
tx, err := db.Begin(true)
require.NoError(t, err)
return tx, func() {
tx.Rollback()
}
}
func newTestTable(t testing.TB) (*genji.Table, func()) {
tx, fn := newTestDB(t)
err := tx.CreateTable("test", nil)
require.NoError(t, err)
tb, err := tx.GetTable("test")
require.NoError(t, err)
return tb, fn
}
func TestTxCreateIndex(t *testing.T) {
t.Run("Should create an index and return it", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateTable("test", nil)
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
IndexName: "idxFoo", TableName: "test", FieldName: "foo",
})
require.NoError(t, err)
idx, err := tx.GetIndex("idxFoo")
require.NoError(t, err)
require.NotNil(t, idx)
})
t.Run("Should fail if it already exists", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateTable("test", nil)
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
IndexName: "idxFoo", TableName: "test", FieldName: "foo",
})
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
IndexName: "idxFoo", TableName: "test", FieldName: "foo",
})
require.Equal(t, genji.ErrIndexAlreadyExists, err)
})
t.Run("Should fail if table doesn't exists", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateIndex(index.Options{
IndexName: "idxFoo", TableName: "test", FieldName: "foo",
})
require.Equal(t, genji.ErrTableNotFound, err)
})
}
func TestTxDropIndex(t *testing.T) {
t.Run("Should drop an index", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateTable("test", nil)
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
IndexName: "idxFoo", TableName: "test", FieldName: "foo",
})
require.NoError(t, err)
err = tx.DropIndex("idxFoo")
require.NoError(t, err)
_, err = tx.GetIndex("idxFoo")
require.Error(t, err)
})
t.Run("Should fail if it doesn't exist", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.DropIndex("idxFoo")
require.Equal(t, genji.ErrIndexNotFound, err)
})
}
func TestTxReIndex(t *testing.T) {
newTestTableFn := func(t *testing.T) (*genji.Tx, *genji.Table, func()) {
tx, cleanup := newTestDB(t)
err := tx.CreateTable("test", nil)
require.NoError(t, err)
tb, err := tx.GetTable("test")
require.NoError(t, err)
for i := 0; i < 10; i++ {
_, err = tb.Insert(record.NewFieldBuffer(
record.NewIntField("a", i),
record.NewIntField("b", i*10),
))
require.NoError(t, err)
}
err = tx.CreateIndex(index.Options{
IndexName: "a",
TableName: "test",
FieldName: "a",
})
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
IndexName: "b",
TableName: "test",
FieldName: "b",
})
require.NoError(t, err)
return tx, tb, cleanup
}
t.Run("Should fail if not found", func(t *testing.T) {
tx, _, cleanup := newTestTableFn(t)
defer cleanup()
err := tx.ReIndex("foo")
require.Equal(t, genji.ErrIndexNotFound, err)
})
t.Run("Should reindex the right index", func(t *testing.T) {
tx, _, cleanup := newTestTableFn(t)
defer cleanup()
err := tx.ReIndex("a")
require.NoError(t, err)
idx, err := tx.GetIndex("a")
require.NoError(t, err)
var i int
err = idx.AscendGreaterOrEqual(index.EmptyPivot(value.Int), func(val value.Value, key []byte) error {
require.Equal(t, value.NewFloat64(float64(i)), val)
i++
return nil
})
require.Equal(t, 10, i)
require.NoError(t, err)
idx, err = tx.GetIndex("b")
require.NoError(t, err)
i = 0
err = idx.AscendGreaterOrEqual(index.EmptyPivot(value.Int), func(val value.Value, key []byte) error {
i++
return nil
})
require.NoError(t, err)
require.Zero(t, i)
})
}
func TestQueryRecord(t *testing.T) {
db, err := genji.New(memoryengine.NewEngine())
require.NoError(t, err)
tx, err := db.Begin(true)
require.NoError(t, err)
err = tx.Exec(`
CREATE TABLE test;
INSERT INTO test (a, b) VALUES (1, 'foo'), (2, 'bar')
`)
require.NoError(t, err)
require.NoError(t, tx.Commit())
t.Run("Should return the first record", func(t *testing.T) {
var a int
var b string
r, err := db.QueryRecord("SELECT * FROM test")
err = record.Scan(r, &a, &b)
require.NoError(t, err)
require.Equal(t, 1, a)
require.Equal(t, "foo", b)
tx, err := db.Begin(false)
require.NoError(t, err)
defer tx.Rollback()
r, err = tx.QueryRecord("SELECT * FROM test")
require.NoError(t, err)
err = record.Scan(r, &a, &b)
require.NoError(t, err)
require.Equal(t, 1, a)
require.Equal(t, "foo", b)
})
t.Run("Should return an error if no record", func(t *testing.T) {
r, err := db.QueryRecord("SELECT * FROM test WHERE a > 100")
require.Equal(t, genji.ErrRecordNotFound, err)
require.Nil(t, r)
tx, err := db.Begin(false)
require.NoError(t, err)
defer tx.Rollback()
r, err = tx.QueryRecord("SELECT * FROM test WHERE a > 100")
require.Equal(t, genji.ErrRecordNotFound, err)
require.Nil(t, r)
})
}
func TestReIndexAll(t *testing.T) {
t.Run("Should succeed if not indexes", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.ReIndexAll()
require.NoError(t, err)
})
t.Run("Should reindex all indexes", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateTable("test1", nil)
require.NoError(t, err)
tb1, err := tx.GetTable("test1")
require.NoError(t, err)
err = tx.CreateTable("test2", nil)
require.NoError(t, err)
tb2, err := tx.GetTable("test2")
require.NoError(t, err)
for i := 0; i < 10; i++ {
_, err = tb1.Insert(record.NewFieldBuffer(
record.NewIntField("a", i),
record.NewIntField("b", i*10),
))
require.NoError(t, err)
_, err = tb2.Insert(record.NewFieldBuffer(
record.NewIntField("a", i),
record.NewIntField("b", i*10),
))
require.NoError(t, err)
}
err = tx.CreateIndex(index.Options{
IndexName: "t1a",
TableName: "test1",
FieldName: "a",
})
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
IndexName: "t2a",
TableName: "test2",
FieldName: "a",
})
require.NoError(t, err)
err = tx.ReIndexAll()
require.NoError(t, err)
idx, err := tx.GetIndex("t1a")
require.NoError(t, err)
var i int
err = idx.AscendGreaterOrEqual(index.EmptyPivot(value.Int), func(val value.Value, key []byte) error {
require.Equal(t, value.NewFloat64(float64(i)), val)
i++
return nil
})
require.Equal(t, 10, i)
require.NoError(t, err)
idx, err = tx.GetIndex("t2a")
require.NoError(t, err)
i = 0
err = idx.AscendGreaterOrEqual(index.EmptyPivot(value.Int), func(val value.Value, key []byte) error {
require.Equal(t, value.NewFloat64(float64(i)), val)
i++
return nil
})
require.Equal(t, 10, i)
require.NoError(t, err)
})
}
func newRecord() record.FieldBuffer {
return record.FieldBuffer([]record.Field{
record.NewStringField("fielda", "a"),
record.NewStringField("fieldb", "b"),
})
}
// TestTableIterate verifies Iterate behaviour.
func TestTableIterate(t *testing.T) {
t.Run("Should not fail with no records", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
i := 0
err := tb.Iterate(func(r record.Record) error {
i++
return nil
})
require.NoError(t, err)
require.Zero(t, i)
})
t.Run("Should iterate over all records", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
for i := 0; i < 10; i++ {
_, err := tb.Insert(newRecord())
require.NoError(t, err)
}
m := make(map[string]int)
err := tb.Iterate(func(r record.Record) error {
m[string(r.(record.Keyer).Key())]++
return nil
})
require.NoError(t, err)
require.Len(t, m, 10)
for _, c := range m {
require.Equal(t, 1, c)
}
})
t.Run("Should stop if fn returns error", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
for i := 0; i < 10; i++ {
_, err := tb.Insert(newRecord())
require.NoError(t, err)
}
i := 0
err := tb.Iterate(func(_ record.Record) error {
i++
if i >= 5 {
return errors.New("some error")
}
return nil
})
require.EqualError(t, err, "some error")
require.Equal(t, 5, i)
})
}
// TestTableRecord verifies Record behaviour.
func TestTableRecord(t *testing.T) {
t.Run("Should fail if not found", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
r, err := tb.GetRecord([]byte("id"))
require.Equal(t, genji.ErrRecordNotFound, err)
require.Nil(t, r)
})
t.Run("Should return the right record", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
// create two records, one with an additional field
rec1 := newRecord()
rec1.Add(record.NewInt64Field("fieldc", 40))
rec2 := newRecord()
key1, err := tb.Insert(rec1)
require.NoError(t, err)
_, err = tb.Insert(rec2)
require.NoError(t, err)
// fetch rec1 and make sure it returns the right one
res, err := tb.GetRecord(key1)
require.NoError(t, err)
fc, err := res.GetField("fieldc")
require.NoError(t, err)
require.Equal(t, rec1[2], fc)
})
}
// TestTableInsert verifies Insert behaviour.
func TestTableInsert(t *testing.T) {
t.Run("Should generate a key by default", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
rec := newRecord()
key1, err := tb.Insert(rec)
require.NoError(t, err)
require.NotEmpty(t, key1)
key2, err := tb.Insert(rec)
require.NoError(t, err)
require.NotEmpty(t, key2)
require.NotEqual(t, key1, key2)
})
t.Run("Should use the right field if key is specified", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateTable("test", &genji.TableConfig{
PrimaryKeyName: "foo",
PrimaryKeyType: value.Int32,
})
require.NoError(t, err)
tb, err := tx.GetTable("test")
require.NoError(t, err)
rec := record.NewFieldBuffer(
record.NewIntField("foo", 1),
record.NewStringField("bar", "baz"),
)
// insert
key, err := tb.Insert(rec)
require.NoError(t, err)
require.Equal(t, value.EncodeInt32(1), key)
// make sure the record is fetchable using the returned key
_, err = tb.GetRecord(key)
require.NoError(t, err)
// insert again
key, err = tb.Insert(rec)
require.Equal(t, genji.ErrDuplicateRecord, err)
})
t.Run("Should fail if Pk not found in record or empty", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateTable("test", &genji.TableConfig{
PrimaryKeyName: "foo",
PrimaryKeyType: value.Int,
})
require.NoError(t, err)
tb, err := tx.GetTable("test")
require.NoError(t, err)
tests := [][]byte{
nil,
[]byte{},
[]byte(nil),
}
for _, test := range tests {
t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) {
rec := record.NewFieldBuffer(
record.NewBytesField("foo", test),
)
_, err := tb.Insert(rec)
require.Error(t, err)
})
}
})
t.Run("Should update indexes if there are indexed fields", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateTable("test", nil)
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
IndexName: "idxFoo", TableName: "test", FieldName: "foo",
})
require.NoError(t, err)
idx, err := tx.GetIndex("idxFoo")
require.NoError(t, err)
tb, err := tx.GetTable("test")
require.NoError(t, err)
// create one record with the foo field
rec1 := newRecord()
foo := record.NewFloat64Field("foo", 10)
rec1 = append(rec1, foo)
// create one record without the foo field
rec2 := newRecord()
key1, err := tb.Insert(rec1)
require.NoError(t, err)
key2, err := tb.Insert(rec2)
require.NoError(t, err)
var count int
err = idx.AscendGreaterOrEqual(nil, func(val value.Value, k []byte) error {
switch count {
case 0:
// key2, which doesn't countain the field must appear first in the next,
// as null values are the smallest possible values
require.Equal(t, key2, k)
case 1:
require.Equal(t, key1, k)
}
count++
return nil
})
require.NoError(t, err)
require.Equal(t, 2, count)
})
}
// TestTableDelete verifies Delete behaviour.
func TestTableDelete(t *testing.T) {
t.Run("Should fail if not found", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
err := tb.Delete([]byte("id"))
require.Equal(t, genji.ErrRecordNotFound, err)
})
t.Run("Should delete the right record", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
// create two records, one with an additional field
rec1 := newRecord()
rec1.Add(record.NewInt64Field("fieldc", 40))
rec2 := newRecord()
key1, err := tb.Insert(rec1)
require.NoError(t, err)
key2, err := tb.Insert(rec2)
require.NoError(t, err)
// delete the record
err = tb.Delete([]byte(key1))
require.NoError(t, err)
// try again, should fail
err = tb.Delete([]byte(key1))
require.Equal(t, genji.ErrRecordNotFound, err)
// make sure it didn't also delete the other one
res, err := tb.GetRecord(key2)
require.NoError(t, err)
_, err = res.GetField("fieldc")
require.Error(t, err)
})
}
// TestTableReplace verifies Replace behaviour.
func TestTableReplace(t *testing.T) {
t.Run("Should fail if not found", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
err := tb.Replace([]byte("id"), newRecord())
require.Equal(t, genji.ErrRecordNotFound, err)
})
t.Run("Should replace the right record", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
// create two different records
rec1 := newRecord()
rec2 := record.FieldBuffer([]record.Field{
record.NewStringField("fielda", "c"),
record.NewStringField("fieldb", "d"),
})
key1, err := tb.Insert(rec1)
require.NoError(t, err)
key2, err := tb.Insert(rec2)
require.NoError(t, err)
// create a third record
rec3 := record.FieldBuffer([]record.Field{
record.NewStringField("fielda", "e"),
record.NewStringField("fieldb", "f"),
})
// replace rec1 with rec3
err = tb.Replace(key1, rec3)
require.NoError(t, err)
// make sure it replaced it correctly
res, err := tb.GetRecord(key1)
require.NoError(t, err)
f, err := res.GetField("fielda")
require.NoError(t, err)
require.Equal(t, "e", string(f.Data))
// make sure it didn't also replace the other one
res, err = tb.GetRecord(key2)
require.NoError(t, err)
f, err = res.GetField("fielda")
require.NoError(t, err)
require.Equal(t, "c", string(f.Data))
})
}
// TestTableTruncate verifies Truncate behaviour.
func TestTableTruncate(t *testing.T) {
t.Run("Should succeed if table empty", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
err := tb.Truncate()
require.NoError(t, err)
})
t.Run("Should truncate the table", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
// create two records
rec1 := newRecord()
rec2 := newRecord()
_, err := tb.Insert(rec1)
require.NoError(t, err)
_, err = tb.Insert(rec2)
require.NoError(t, err)
err = tb.Truncate()
require.NoError(t, err)
err = tb.Iterate(func(_ record.Record) error {
return errors.New("should not iterate")
})
require.NoError(t, err)
})
}
func TestTableIndexes(t *testing.T) {
t.Run("Should succeed if table has no indexes", func(t *testing.T) {
tb, cleanup := newTestTable(t)
defer cleanup()
m, err := tb.Indexes()
require.NoError(t, err)
require.Empty(t, m)
})
t.Run("Should return a map of all the indexes", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateTable("test1", nil)
require.NoError(t, err)
tb, err := tx.GetTable("test1")
require.NoError(t, err)
err = tx.CreateTable("test2", nil)
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
Unique: true,
IndexName: "idx1a",
TableName: "test1",
FieldName: "a",
})
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
Unique: false,
IndexName: "idx1b",
TableName: "test1",
FieldName: "b",
})
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
Unique: false,
IndexName: "ifx2a",
TableName: "test2",
FieldName: "a",
})
require.NoError(t, err)
m, err := tb.Indexes()
require.NoError(t, err)
require.Len(t, m, 2)
idx1a, ok := m["a"]
require.True(t, ok)
require.NotNil(t, idx1a)
idx1b, ok := m["b"]
require.True(t, ok)
require.NotNil(t, idx1b)
})
}
// BenchmarkTableInsert benchmarks the Insert method with 1, 10, 1000 and 10000 successive insertions.
func BenchmarkTableInsert(b *testing.B) {
for size := 1; size <= 10000; size *= 10 {
b.Run(fmt.Sprintf("%.05d", size), func(b *testing.B) {
var fields []record.Field
for i := int64(0); i < 10; i++ {
fields = append(fields, record.NewInt64Field(fmt.Sprintf("name-%d", i), i))
}
rec := record.FieldBuffer(fields)
b.ResetTimer()
b.StopTimer()
for i := 0; i < b.N; i++ {
tb, cleanup := newTestTable(b)
b.StartTimer()
for j := 0; j < size; j++ {
tb.Insert(rec)
}
b.StopTimer()
cleanup()
}
})
}
}
// BenchmarkTableScan benchmarks the Scan method with 1, 10, 1000 and 10000 successive insertions.
func BenchmarkTableScan(b *testing.B) {
for size := 1; size <= 10000; size *= 10 {
b.Run(fmt.Sprintf("%.05d", size), func(b *testing.B) {
tb, cleanup := newTestTable(b)
defer cleanup()
var fields []record.Field
for i := int64(0); i < 10; i++ {
fields = append(fields, record.NewInt64Field(fmt.Sprintf("name-%d", i), i))
}
rec := record.FieldBuffer(fields)
for i := 0; i < size; i++ {
_, err := tb.Insert(rec)
require.NoError(b, err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
tb.Iterate(func(record.Record) error {
return nil
})
}
b.StopTimer()
})
}
}
func TestTxListTables(t *testing.T) {
t.Run("Should succeed if not tables", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
list, err := tx.ListTables()
require.NoError(t, err)
require.Len(t, list, 0)
})
t.Run("Should return the right tables", func(t *testing.T) {
tx, cleanup := newTestDB(t)
defer cleanup()
err := tx.CreateTable("a", nil)
require.NoError(t, err)
err = tx.CreateTable("b", nil)
require.NoError(t, err)
err = tx.CreateIndex(index.Options{
IndexName: "idxa",
TableName: "a",
FieldName: "foo",
})
require.NoError(t, err)
list, err := tx.ListTables()
require.NoError(t, err)
require.Len(t, list, 2)
require.Equal(t, []string{"a", "b"}, list)
})
}