mirror of
				https://github.com/chaisql/chai.git
				synced 2025-11-01 03:12:38 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			876 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			876 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package genji_test
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/asdine/genji"
 | |
| 	"github.com/asdine/genji/engine/memory"
 | |
| 	"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 ExampleOpen() {
 | |
| 	db, err := genji.Open(memory.NewEngine())
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 	defer db.Close()
 | |
| 
 | |
| 	_, err = db.Exec("CREATE TABLE IF NOT EXISTS user")
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_user_Name ON user (Name)")
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	_, err = db.Exec("INSERT INTO user (ID, Name, Age) VALUES (?, ?, ?)", 10, "foo", 15)
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	_, err = db.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 := db.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(memory.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(memory.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 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 generate a key even if a field with the default key name is present", func(t *testing.T) {
 | |
| 		tb, cleanup := newTestTable(t)
 | |
| 		defer cleanup()
 | |
| 
 | |
| 		rec := newRecord()
 | |
| 		rec.Add(record.NewStringField("_key", "foo"))
 | |
| 		key, err := tb.Insert(rec)
 | |
| 		require.NoError(t, err)
 | |
| 		require.Equal(t, value.NewInt64(1).Data, key)
 | |
| 	})
 | |
| 
 | |
| 	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{
 | |
| 			PrimaryKey: "foo",
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 		tb, err := tx.GetTable("test")
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		rec := record.NewFieldBuffer(
 | |
| 			record.NewIntField("foo", 1),
 | |
| 		)
 | |
| 
 | |
| 		// insert
 | |
| 		key, err := tb.Insert(rec)
 | |
| 		require.NoError(t, err)
 | |
| 		require.Equal(t, value.EncodeInt(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{
 | |
| 			PrimaryKey: "foo",
 | |
| 		})
 | |
| 		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)
 | |
| 
 | |
| 		rec := newRecord()
 | |
| 		foo := record.NewFloat32Field("foo", 10)
 | |
| 		rec = append(rec, foo)
 | |
| 
 | |
| 		key, err := tb.Insert(rec)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotEmpty(t, key)
 | |
| 
 | |
| 		f, err := foo.DecodeToFloat64()
 | |
| 		require.NoError(t, err)
 | |
| 		expected := value.NewFloat64(f)
 | |
| 		var count int
 | |
| 		err = idx.AscendGreaterOrEqual(index.EmptyPivot(value.Float64), func(val value.Value, k []byte) error {
 | |
| 			require.Equal(t, val, expected)
 | |
| 			require.Equal(t, key, k)
 | |
| 			count++
 | |
| 			return nil
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 		require.Equal(t, 1, 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)
 | |
| 	})
 | |
| }
 | 
