|
|
@ -4,14 +4,27 @@ import (
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/json"
|
|
|
|
"io/ioutil"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
sql "github.com/otoolep/rqlite/db"
|
|
|
|
sql "github.com/otoolep/rqlite/db"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type mockSnapshotSink struct {
|
|
|
|
|
|
|
|
*os.File
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (m *mockSnapshotSink) ID() string {
|
|
|
|
|
|
|
|
return "1"
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (m *mockSnapshotSink) Cancel() error {
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Test_OpenStoreSingleNode(t *testing.T) {
|
|
|
|
func Test_OpenStoreSingleNode(t *testing.T) {
|
|
|
|
s := mustNewStore()
|
|
|
|
s := mustNewStore(true)
|
|
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.Open(true); err != nil {
|
|
|
|
if err := s.Open(true); err != nil {
|
|
|
@ -20,7 +33,7 @@ func Test_OpenStoreSingleNode(t *testing.T) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Test_OpenStoreCloseSingleNode(t *testing.T) {
|
|
|
|
func Test_OpenStoreCloseSingleNode(t *testing.T) {
|
|
|
|
s := mustNewStore()
|
|
|
|
s := mustNewStore(true)
|
|
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.Open(true); err != nil {
|
|
|
|
if err := s.Open(true); err != nil {
|
|
|
@ -32,7 +45,7 @@ func Test_OpenStoreCloseSingleNode(t *testing.T) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Test_SingleNodeExecuteQuery(t *testing.T) {
|
|
|
|
func Test_SingleNodeExecuteQuery(t *testing.T) {
|
|
|
|
s := mustNewStore()
|
|
|
|
s := mustNewStore(true)
|
|
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.Open(true); err != nil {
|
|
|
|
if err := s.Open(true); err != nil {
|
|
|
@ -70,7 +83,7 @@ func Test_SingleNodeExecuteQuery(t *testing.T) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Test_SingleNodeExecuteQueryTx(t *testing.T) {
|
|
|
|
func Test_SingleNodeExecuteQueryTx(t *testing.T) {
|
|
|
|
s := mustNewStore()
|
|
|
|
s := mustNewStore(true)
|
|
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.Open(true); err != nil {
|
|
|
|
if err := s.Open(true); err != nil {
|
|
|
@ -112,7 +125,7 @@ func Test_SingleNodeExecuteQueryTx(t *testing.T) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Test_MultiNodeExecuteQuery(t *testing.T) {
|
|
|
|
func Test_MultiNodeExecuteQuery(t *testing.T) {
|
|
|
|
s0 := mustNewStore()
|
|
|
|
s0 := mustNewStore(true)
|
|
|
|
defer os.RemoveAll(s0.Path())
|
|
|
|
defer os.RemoveAll(s0.Path())
|
|
|
|
if err := s0.Open(true); err != nil {
|
|
|
|
if err := s0.Open(true); err != nil {
|
|
|
|
t.Fatalf("failed to open node for multi-node test: %s", err.Error())
|
|
|
|
t.Fatalf("failed to open node for multi-node test: %s", err.Error())
|
|
|
@ -120,7 +133,7 @@ func Test_MultiNodeExecuteQuery(t *testing.T) {
|
|
|
|
defer s0.Close(true)
|
|
|
|
defer s0.Close(true)
|
|
|
|
s0.WaitForLeader(10 * time.Second)
|
|
|
|
s0.WaitForLeader(10 * time.Second)
|
|
|
|
|
|
|
|
|
|
|
|
s1 := mustNewStore()
|
|
|
|
s1 := mustNewStore(true)
|
|
|
|
defer os.RemoveAll(s1.Path())
|
|
|
|
defer os.RemoveAll(s1.Path())
|
|
|
|
if err := s1.Open(false); err != nil {
|
|
|
|
if err := s1.Open(false); err != nil {
|
|
|
|
t.Fatalf("failed to open node for multi-node test: %s", err.Error())
|
|
|
|
t.Fatalf("failed to open node for multi-node test: %s", err.Error())
|
|
|
@ -176,11 +189,75 @@ func Test_MultiNodeExecuteQuery(t *testing.T) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func mustNewStore() *Store {
|
|
|
|
func Test_SingleNodeSnapshot(t *testing.T) {
|
|
|
|
|
|
|
|
s := mustNewStore(false)
|
|
|
|
|
|
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.Open(true); err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer s.Close(true)
|
|
|
|
|
|
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
queries := []string{
|
|
|
|
|
|
|
|
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
|
|
|
|
|
|
|
|
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := s.Execute(queries, false, false)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("failed to execute on single node: %s", err.Error())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.Query([]string{`SELECT * FROM foo`}, false, false, None)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Snap the node and write to disk.
|
|
|
|
|
|
|
|
f, err := s.Snapshot()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("failed to snapshot node: %s", err.Error())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
snapDir := mustTempDir()
|
|
|
|
|
|
|
|
defer os.RemoveAll(snapDir)
|
|
|
|
|
|
|
|
snapFile, err := os.Create(filepath.Join(snapDir, "snapshot"))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("failed to create snapshot file: %s", err.Error())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
sink := &mockSnapshotSink{snapFile}
|
|
|
|
|
|
|
|
if err := f.Persist(sink); err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("failed to persist snapshot to disk: %s", err.Error())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check restoration.
|
|
|
|
|
|
|
|
snapFile, err = os.Open(filepath.Join(snapDir, "snapshot"))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("failed to open snapshot file: %s", err.Error())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.Restore(snapFile); err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("failed to restore snapshot from disk: %s", err.Error())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Ensure database is back in the correct state.
|
|
|
|
|
|
|
|
r, err := s.Query([]string{`SELECT * FROM foo`}, false, false, None)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got {
|
|
|
|
|
|
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got {
|
|
|
|
|
|
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func mustNewStore(inmem bool) *Store {
|
|
|
|
path := mustTempDir()
|
|
|
|
path := mustTempDir()
|
|
|
|
defer os.RemoveAll(path)
|
|
|
|
defer os.RemoveAll(path)
|
|
|
|
|
|
|
|
|
|
|
|
s := New(newInMemoryConfig(), path, "localhost:0")
|
|
|
|
cfg := sql.NewConfig()
|
|
|
|
|
|
|
|
cfg.Memory = inmem
|
|
|
|
|
|
|
|
s := New(cfg, path, "localhost:0")
|
|
|
|
if s == nil {
|
|
|
|
if s == nil {
|
|
|
|
panic("failed to create new store")
|
|
|
|
panic("failed to create new store")
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -196,12 +273,6 @@ func mustTempDir() string {
|
|
|
|
return path
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func newInMemoryConfig() *sql.Config {
|
|
|
|
|
|
|
|
c := sql.NewConfig()
|
|
|
|
|
|
|
|
c.Memory = true
|
|
|
|
|
|
|
|
return c
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func asJSON(v interface{}) string {
|
|
|
|
func asJSON(v interface{}) string {
|
|
|
|
b, err := json.Marshal(v)
|
|
|
|
b, err := json.Marshal(v)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|