package store import ( "compress/gzip" "io" "os" "testing" "time" command "github.com/rqlite/rqlite/v8/command/proto" ) func test_SingleNodeProvide(t *testing.T, vaccuum, compress bool) { s0, ln := mustNewStore(t) defer ln.Close() if err := s0.Open(); err != nil { t.Fatalf("failed to open single-node store: %s", err.Error()) } if err := s0.Bootstrap(NewServer(s0.ID(), s0.Addr(), true)); err != nil { t.Fatalf("failed to bootstrap single-node store: %s", err.Error()) } defer s0.Close(true) if _, err := s0.WaitForLeader(10 * time.Second); err != nil { t.Fatalf("Error waiting for leader: %s", err) } er := executeRequestFromStrings([]string{ `CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`, `INSERT INTO foo(id, name) VALUES(1, "fiona")`, }, false, false) _, err := s0.Execute(er) if err != nil { t.Fatalf("failed to execute on single node: %s", err.Error()) } qr := queryRequestFromString("SELECT * FROM foo", false, false) qr.Level = command.QueryRequest_QUERY_REQUEST_LEVEL_NONE r, err := s0.Query(qr) if err != nil { t.Fatalf("failed to query leader 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) } tmpFd := mustCreateTempFD() defer os.Remove(tmpFd.Name()) defer tmpFd.Close() provider := NewProvider(s0, vaccuum, compress) if err := provider.Provide(tmpFd); err != nil { t.Fatalf("failed to provide SQLite data: %s", err.Error()) } // Load the provided data into a new store and check it. s1, ln := mustNewStore(t) defer ln.Close() if err := s1.Open(); err != nil { t.Fatalf("failed to open single-node store: %s", err.Error()) } if err := s1.Bootstrap(NewServer(s1.ID(), s1.Addr(), true)); err != nil { t.Fatalf("failed to bootstrap single-node store: %s", err.Error()) } defer s1.Close(true) if _, err := s1.WaitForLeader(10 * time.Second); err != nil { t.Fatalf("Error waiting for leader: %s", err) } s1file := tmpFd.Name() if compress { unCompressedFile, err := gunzip(tmpFd.Name()) if err != nil { t.Fatalf("failed to gunzip provided SQLite data: %s", err.Error()) } s1file = unCompressedFile } err = s1.Load(loadRequestFromFile(s1file)) if err != nil { t.Fatalf("failed to load provided SQLite data: %s", err.Error()) } qr = queryRequestFromString("SELECT * FROM foo", false, false) qr.Level = command.QueryRequest_QUERY_REQUEST_LEVEL_STRONG r, err = s1.Query(qr) if err != nil { t.Fatalf("failed to query leader 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) } } // Test_SingleNodeProvide tests that the Store correctly implements // the Provide method. func Test_SingleNodeProvide(t *testing.T) { t.Run("NoVacuumNoCompress", func(t *testing.T) { test_SingleNodeProvide(t, false, false) }) t.Run("VacuumNoCompress", func(t *testing.T) { test_SingleNodeProvide(t, true, false) }) t.Run("NoVacuumCompress", func(t *testing.T) { test_SingleNodeProvide(t, false, true) }) t.Run("VacuumCompress", func(t *testing.T) { test_SingleNodeProvide(t, true, true) }) } func Test_SingleNodeProvideLastIndex(t *testing.T) { s, ln := mustNewStore(t) defer ln.Close() if err := s.Open(); err != nil { t.Fatalf("failed to open single-node store: %s", err.Error()) } if err := s.Bootstrap(NewServer(s.ID(), s.Addr(), true)); err != nil { t.Fatalf("failed to bootstrap single-node store: %s", err.Error()) } defer s.Close(true) if _, err := s.WaitForLeader(10 * time.Second); err != nil { t.Fatalf("Error waiting for leader: %s", err) } tmpFile := mustCreateTempFile() defer os.Remove(tmpFile) provider := NewProvider(s, false, false) lm, err := provider.LastIndex() if err != nil { t.Fatalf("failed to get last modified: %s", err.Error()) } er := executeRequestFromStrings([]string{ `CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`, `INSERT INTO foo(id, name) VALUES(1, "fiona")`, }, false, false) _, err = s.Execute(er) if err != nil { t.Fatalf("failed to execute on single node: %s", err.Error()) } if _, err := s.WaitForAppliedFSM(2 * time.Second); err != nil { t.Fatalf("failed to wait for FSM to apply") } newLI, err := provider.LastIndex() if err != nil { t.Fatalf("failed to get last modified: %s", err.Error()) } if newLI <= lm { t.Fatalf("last index should have changed") } lm = newLI // Try various queries and commands which should not change the database. qr := queryRequestFromString("SELECT * FROM foo", false, false) qr.Level = command.QueryRequest_QUERY_REQUEST_LEVEL_STRONG _, err = s.Query(qr) if err != nil { t.Fatalf("failed to query leader node: %s", err.Error()) } if _, err := s.WaitForAppliedFSM(2 * time.Second); err != nil { t.Fatalf("failed to wait for FSM to apply") } newLI, err = provider.LastIndex() if err != nil { t.Fatalf("failed to get last modified: %s", err.Error()) } if newLI != lm { t.Fatalf("last index should not have changed") } lm = newLI if af, err := s.Noop("don't care"); err != nil || af.Error() != nil { t.Fatalf("failed to execute Noop") } newLI, err = provider.LastIndex() if err != nil { t.Fatalf("failed to get last modified: %s", err.Error()) } if newLI != lm { t.Fatalf("last index should not have changed") } lm = newLI // Right now any "execute" will be assumed to change the database. It's possible // that by checking the Error field of the response we could avoid this. er = executeRequestFromStrings([]string{ `INSERT INTO foo(id, name) VALUES(1, "fiona")`, // Constraint violation. }, false, false) _, err = s.Execute(er) if err != nil { t.Fatalf("failed to execute on single node: %s", err.Error()) } newLI, err = provider.LastIndex() if err != nil { t.Fatalf("failed to get last modified: %s", err.Error()) } if newLI == lm { t.Fatalf("last index should changed even with constraint violation") } lm = newLI // This should change the database. er = executeRequestFromStrings([]string{ `INSERT INTO foo(id, name) VALUES(2, "fiona")`, }, false, false) _, err = s.Execute(er) if err != nil { t.Fatalf("failed to execute on single node: %s", err.Error()) } if _, err := s.WaitForAppliedFSM(2 * time.Second); err != nil { t.Fatalf("failed to wait for FSM to apply") } newLI, err = provider.LastIndex() if err != nil { t.Fatalf("failed to get last modified: %s", err.Error()) } if newLI <= lm { t.Fatalf("last index should have changed") } } func gunzip(file string) (string, error) { f, err := os.Open(file) if err != nil { return "", err } defer f.Close() gz, err := gzip.NewReader(f) if err != nil { return "", err } defer gz.Close() tmpFd := mustCreateTempFD() defer tmpFd.Close() _, err = io.Copy(tmpFd, gz) if err != nil { return "", err } return tmpFd.Name(), nil }