package store import ( "encoding/json" "io/ioutil" "os" "path/filepath" "testing" "time" ) 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) { s := mustNewStore(true) defer os.RemoveAll(s.Path()) if err := s.Open(true); err != nil { t.Fatalf("failed to open single-node store: %s", err.Error()) } } func Test_OpenStoreCloseSingleNode(t *testing.T) { s := mustNewStore(true) defer os.RemoveAll(s.Path()) if err := s.Open(true); err != nil { t.Fatalf("failed to open single-node store: %s", err.Error()) } if err := s.Close(true); err != nil { t.Fatalf("failed to close single-node store: %s", err.Error()) } } func Test_SingleNodeInMemExecuteQuery(t *testing.T) { s := mustNewStore(true) 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()) } r, err := s.Query([]string{`SELECT * FROM foo`}, false, false, None) if err != nil { t.Fatalf("failed to query single node: %s", err.Error()) } r, err = s.Query([]string{`SELECT * FROM foo`}, false, false, None) if err != nil { t.Fatalf("failed to query single node: %s", err.Error()) } 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 Test_SingleNodeFileExecuteQuery(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()) } r, err := s.Query([]string{`SELECT * FROM foo`}, false, false, None) if err != nil { t.Fatalf("failed to query single node: %s", err.Error()) } r, err = s.Query([]string{`SELECT * FROM foo`}, false, false, None) if err != nil { t.Fatalf("failed to query single node: %s", err.Error()) } 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 Test_SingleNodeExecuteQueryTx(t *testing.T) { s := mustNewStore(true) 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, true) if err != nil { t.Fatalf("failed to execute on single node: %s", err.Error()) } r, err := s.Query([]string{`SELECT * FROM foo`}, false, true, None) if err != nil { t.Fatalf("failed to query single node: %s", err.Error()) } r, err = s.Query([]string{`SELECT * FROM foo`}, false, true, Weak) if err != nil { t.Fatalf("failed to query single node: %s", err.Error()) } r, err = s.Query([]string{`SELECT * FROM foo`}, false, true, Strong) 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) } _, err = s.Execute(queries, false, true) if err != nil { t.Fatalf("failed to execute on single node: %s", err.Error()) } } func Test_MultiNodeExecuteQuery(t *testing.T) { s0 := mustNewStore(true) defer os.RemoveAll(s0.Path()) if err := s0.Open(true); err != nil { t.Fatalf("failed to open node for multi-node test: %s", err.Error()) } defer s0.Close(true) s0.WaitForLeader(10 * time.Second) s1 := mustNewStore(true) defer os.RemoveAll(s1.Path()) if err := s1.Open(false); err != nil { t.Fatalf("failed to open node for multi-node test: %s", err.Error()) } defer s1.Close(true) // Join the second node to the first. if err := s0.Join(s1.Addr().String()); err != nil { t.Fatalf("failed to join to node at %s: %s", s0.Addr().String(), err.Error()) } queries := []string{ `CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`, `INSERT INTO foo(id, name) VALUES(1, "fiona")`, } _, err := s0.Execute(queries, false, false) if err != nil { t.Fatalf("failed to execute on single node: %s", err.Error()) } r, err := s0.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) } // Wait until the 3 log entries have been applied to the follower, // and then query. if err := s1.WaitForAppliedIndex(3, 5*time.Second); err != nil { t.Fatalf("error waiting for follower to apply index: %s:", err.Error()) } r, err = s1.Query([]string{`SELECT * FROM foo`}, false, false, Weak) if err == nil { t.Fatalf("successfully queried non-leader node") } r, err = s1.Query([]string{`SELECT * FROM foo`}, false, false, Strong) if err == nil { t.Fatalf("successfully queried non-leader node") } r, err = s1.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 Test_SingleNodeSnapshotOnDisk(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 Test_SingleNodeSnapshotInMem(t *testing.T) { s := mustNewStore(true) 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() defer os.RemoveAll(path) cfg := NewDBConfig("", inmem) s := New(cfg, path, "localhost:0") if s == nil { panic("failed to create new store") } return s } func mustTempDir() string { var err error path, err := ioutil.TempDir("", "rqlilte-test-") if err != nil { panic("failed to create temp dir") } return path } func asJSON(v interface{}) string { b, err := json.Marshal(v) if err != nil { panic("failed to JSON marshal value") } return string(b) }